使用redis实现购物车
redis是什么
redis是一款开源的、功能强大、支持多种数据类型、高性能的键值对存储数据库。
redis支持的数据类型有:
- 字符串
- 哈希
- 列表
- 有序集合
- 散列
- 集合
为什么选择redis来实现购物车
将购物车数据存放到Redis中,可以加快购物车的读写性能,从而提高用户体验,缺点就是Redis数据是存放到内存,相对成本较高。但是这个成本,一般企业都可以接受。
一般情况下购物车功能都是使用session/cookie实现的,也就是将整个购物车数据都存储到session中。这样做的好处就是不用操作数据库就可以实现,同时用户可以不同登录就可以将商品加入到购物车中,缺点就是1. 导致session过于臃肿 2. session数据默认是存储到文件中的,所以操作session是相对比较慢的。
如何使用redis实现购物车功能
- 首先我们创建一个JedisClient类来操作redis
/*
Created by IntelliJ IDEA.
User: Kalvin
Date: 2020/5/13
Time: 14:21
*/
package com.lianwei.lssg.cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public class JedisClient {
@Autowired
private StringRedisTemplate redisTemplate;
// Key(键),简单的key-value操作
/**
* 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。
*
* @param key
* @return
*/
public long ttl(String key) {
return redisTemplate.getExpire(key);
}
/**
* 实现命令:expire 设置过期时间,单位秒
*
* @param key
* @return
*/
public void expire(String key, long timeout) {
redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 实现命令:INCR key,增加key一次
*
* @param key
* @return
*/
public long incr(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key
*/
public Set<String> keys(String pattern) {
return redisTemplate.keys(pattern);
}
/**
* 实现命令:DEL key,删除一个key
*
* @param key
*/
public void del(String key) {
redisTemplate.delete(key);
}
// String(字符串)
/**
* 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key)
*
* @param key
* @param value
*/
public void set(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 实现命令:SET key value EX seconds,设置key-value和超时时间(秒)
*
* @param key
* @param value
* @param timeout
* (以秒为单位)
*/
public void set(String key, String value, long timeout) {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
/**
* 实现命令:GET key,返回 key所关联的字符串值。
*
* @param key
* @return value
*/
public String get(String key) {
return (String)redisTemplate.opsForValue().get(key);
}
// Hash(哈希表)
/**
* 实现命令:HEXISTS key field,查找哈希表中是否包含指定键值对 key中给定域 field的值
* @param key
* @param field
* @return
*/
public Boolean hexists(String key, String field){
return redisTemplate.opsForHash().hasKey(key, field);
}
/**
* 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value
*
* @param key
* @param field
* @param value
*/
public void hset(String key, String field, Object value) {
redisTemplate.opsForHash().put(key, field, value);
}
/**
* 实现命令:HGET key field,返回哈希表 key中给定域 field的值
*
* @param key
* @param field
* @return
*/
public String hget(String key, String field) {
return (String) redisTemplate.opsForHash().get(key, field);
}
/**
* 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
*
* @param key
* @param fields
*/
public void hdel(String key, Object... fields) {
redisTemplate.opsForHash().delete(key, fields);
}
/**
* 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。
*
* @param key
* @return
*/
public Map<Object, Object> hgetall(String key) {
return redisTemplate.opsForHash().entries(key);
}
// List(列表)
/**
* 实现命令:LPUSH key value,将一个值 value插入到列表 key的表头
*
* @param key
* @param value
* @return 执行 LPUSH命令后,列表的长度。
*/
public long lpush(String key, String value) {
return redisTemplate.opsForList().leftPush(key, value);
}
/**
* 实现命令:LPOP key,移除并返回列表 key的头元素。
*
* @param key
* @return 列表key的头元素。
*/
public String lpop(String key) {
return (String)redisTemplate.opsForList().leftPop(key);
}
/**
* 实现命令:RPUSH key value,将一个值 value插入到列表 key的表尾(最右边)。
*
* @param key
* @param value
* @return 执行 LPUSH命令后,列表的长度。
*/
public long rpush(String key, String value) {
return redisTemplate.opsForList().rightPush(key, value);
}
}
在redis数据库中我们使用cart作为filed 以用户名作为key 购物车内容为value
/*
Created by IntelliJ IDEA.
User: Kalvin
Date: 2020/5/12
Time: 17:41
*/
package com.lianwei.lssg.controller.before;
import com.alibaba.fastjson.JSON;
import com.lianwei.lssg.cache.JedisClient;
import com.lianwei.lssg.entity.LssgCart;
import com.lianwei.lssg.entity.LssgCartItem;
import com.lianwei.lssg.entity.LssgProduct;
import com.lianwei.lssg.service.before.LssgProductService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@Controller
@RequestMapping("cart")
public class CartController {
@Resource
private JedisClient jedisClient;
@Resource
private LssgProductService lssgProductService;
private static final String REDIS_CART = "cart";
/**
* 商品加入到购物车
* */
@RequestMapping("/addProductToCart")
@ResponseBody
public LssgCart addProductToCart(@RequestParam("productId")Integer productId, @RequestParam(value = "nums",required = false)String nums){
//获取已登录的用户信息
Subject subject = SecurityUtils.getSubject();
String userLoginName = (String)subject.getPrincipal();
LssgProduct lssgProduct = lssgProductService.selectOneProductByProductId(productId);
/**
* 判断lssgProduct是否为空
* */
String productNumState;
if(lssgProduct.getProductNum()<=0){
productNumState = "无货";
}else {
productNumState = "有货";
}
/**
* 存在一个问题:当session超时后因该强制用户进行登入,只有登入成功才能做相应的操作
* */
if(("").equals(userLoginName)){
System.out.println("session超时了");
/*map.put("lssg",false);*/
return null;
}
Boolean hexists = jedisClient.hexists(REDIS_CART, userLoginName);
System.out.println("hexists---->"+hexists);
LssgCart lssgCart = null;
if(nums==null|| nums.equals("")){
nums = String.valueOf(1);
}
BigDecimal num =new BigDecimal(nums);
if(!hexists){
lssgCart = new LssgCart();
LssgCartItem lssgCartItem = new LssgCartItem(lssgProduct,num,lssgProduct.getProductMallPrice(),productNumState);
lssgCartItem.setSubtotalPrice((lssgProduct.getProductMallPrice()).multiply(lssgCartItem.getBuyNum()));
Map<String,LssgCartItem> lssgCartItemMap = new HashMap<String, LssgCartItem>();
lssgCartItemMap.put(String.valueOf(lssgProduct.getProductId()),lssgCartItem);
lssgCart.setCartItems(lssgCartItemMap);
lssgCart.setTotalPrice(lssgCartItem.getSubtotalPrice());
}else {
lssgCart = JSON.parseObject(jedisClient.hget(REDIS_CART,userLoginName),LssgCart.class);
if(lssgCart.getCartItems().containsKey(String.valueOf(lssgProduct.getProductId()))){
LssgCartItem lssgCartItem = lssgCart.getCartItems().get(String.valueOf(lssgProduct.getProductId()));
//数量
lssgCartItem.setBuyNum(lssgCartItem.getBuyNum().add(num));
//小计+商品的单价(由于是BigDecimal类型所以用他自己封装好的)
lssgCartItem.setSubtotalPrice((lssgProduct.getProductMallPrice()).multiply(lssgCartItem.getBuyNum()));
lssgCart.getCartItems().put(String.valueOf(lssgProduct.getProductId()),lssgCartItem);
}else{
//没买过,先创建新的购物项
LssgCartItem lssgCartItem = new LssgCartItem(lssgProduct,num,lssgProduct.getProductMallPrice(),productNumState);
lssgCartItem.setSubtotalPrice((lssgProduct.getProductMallPrice()).multiply(lssgCartItem.getBuyNum()));
//存入购物车的Map集合
lssgCart.getCartItems().put(String.valueOf(lssgProduct.getProductId()),lssgCartItem);
}
//计算总价格,并加入到购物车
BigDecimal sum = new BigDecimal("0.00");
Set<String> ks = lssgCart.getCartItems().keySet();
for(String key: ks){
LssgCartItem lssgCartItem = lssgCart.getCartItems().get(key);
sum = sum.add(((lssgCartItem.getLssgProduct().getProductMallPrice()).multiply(lssgCartItem.getBuyNum())));
}
lssgCart.setTotalPrice(sum);
}
jedisClient.hset(REDIS_CART,userLoginName, JSON.toJSONString(lssgCart));
System.out.println("添加成功!");
jedisClient.hget(REDIS_CART,userLoginName);
return JSON.parseObject(jedisClient.hget(REDIS_CART,userLoginName),LssgCart.class);
}
/**
*购物车信息展示到前台购物车页
* */
@RequestMapping("/fromCartShowProduct")
@ResponseBody
public LssgCart fromCartShowProduct(){
//获取已登录的用户信息
Subject subject = SecurityUtils.getSubject();
String userLoginName = (String) subject.getPrincipal();
/**
* 存在一个问题:当session超时后因该强制用户进行登入,只有登入成功才能做相应的操作
* */
if(("").equals(userLoginName)){
System.out.println("session超时了");
/*map.put("lssg",false);*/
return null;
}
Boolean hexists = jedisClient.hexists(REDIS_CART, userLoginName);
if(hexists){
return JSON.parseObject(jedisClient.hget(REDIS_CART,userLoginName),LssgCart.class);
}
return null;
}
/**
* 实现购物车页面通过+/-/输入 按钮来对商品的数量更新操作
* */
@RequestMapping("/updateCartProductNum")
@ResponseBody
public LssgCart decCartProductNum(@RequestParam("productId")Integer productId, @RequestParam("nums")String nums){
//获取已登录的用户信息
Subject subject = SecurityUtils.getSubject();
String userLoginName = (String) subject.getPrincipal();
LssgCart lssgCart = JSON.parseObject(jedisClient.hget(REDIS_CART,userLoginName),LssgCart.class);
Map<String,LssgCartItem> lssgCartItemMap = lssgCart.getCartItems();
LssgCartItem lssgCartItem = lssgCartItemMap.get(String.valueOf(productId));
//购物项(该商品)原来的小计
BigDecimal oldSubtotalPrice = lssgCartItem.getSubtotalPrice();
System.out.println("oldSubtotalPrice--->"+oldSubtotalPrice);
BigDecimal num =new BigDecimal(nums);
//更新购买数量
lssgCartItem.setBuyNum(num);
//更新小计
lssgCartItem.setSubtotalPrice((lssgCartItem.getBuyNum()).multiply(lssgCartItem.getLssgProduct().getProductMallPrice()));
//把购物项放到购物项(map)集合里
lssgCartItemMap.put(String.valueOf(productId),lssgCartItem);
lssgCart.setCartItems(lssgCartItemMap);
//更新购物车总价 新的购物车总价 = 原来购物车总价 - 原来购物项的小计 + 新的购物项的小计
lssgCart.setTotalPrice(lssgCart.getTotalPrice().subtract(oldSubtotalPrice).add(lssgCartItem.getSubtotalPrice()));
//更新购物车到redis
jedisClient.hset(REDIS_CART,userLoginName,JSON.toJSONString(lssgCart));
//从redis中取出购物车传到到前台
return JSON.parseObject(jedisClient.hget(REDIS_CART,userLoginName),LssgCart.class);
}
/**
* 删除购物车中某一个购物项
* */
@RequestMapping("/delCartProduct")
@ResponseBody
public LssgCart delCartProduct(@RequestParam("productId")Integer productId, @RequestParam("nums")String nums){
//获取已登录的用户信息
Subject subject = SecurityUtils.getSubject();
String userLoginName = (String) subject.getPrincipal();
LssgCart lssgCart = JSON.parseObject(jedisClient.hget(REDIS_CART,userLoginName),LssgCart.class);
Map<String,LssgCartItem> lssgCartItemMap = lssgCart.getCartItems();
LssgCartItem lssgCartItem = lssgCartItemMap.get(String.valueOf(productId));
BigDecimal num =new BigDecimal(nums);
System.out.println("num---->"+num);
//通过商品id删除该购物项
lssgCartItemMap.remove(String.valueOf(productId));
//更新总价 新的购物车总价 = 原来购物车总价 - 原来购物项的小计
lssgCart.setTotalPrice(lssgCart.getTotalPrice().subtract(((lssgCartItem.getLssgProduct().getProductMallPrice()).multiply(num))));
//更新购物车到redis
jedisClient.hset(REDIS_CART,userLoginName,JSON.toJSONString(lssgCart));
//从redis中取出购物车传到到前台
return JSON.parseObject(jedisClient.hget(REDIS_CART,userLoginName),LssgCart.class);
}
/**
* 清空购物车
* */
@RequestMapping("/emptyCart")
public void emptyCart(){
//获取已登录的用户信息
Subject subject = SecurityUtils.getSubject();
String userLoginName = (String) subject.getPrincipal();
jedisClient.hdel(REDIS_CART,userLoginName);
}
/**
*删除已结算的购物项
* */
@RequestMapping("/delProductByProductIds")
@ResponseBody
public LssgCart delProductByProductIds(@RequestParam(value="productIds[]")String[] productIds){
//获取已登录的用户信息
Subject subject = SecurityUtils.getSubject();
String userLoginName = (String) subject.getPrincipal();
LssgCart lssgCart = JSON.parseObject(jedisClient.hget(REDIS_CART,userLoginName),LssgCart.class);
BigDecimal delTotalPrice =new BigDecimal("0.00");
Map<String,LssgCartItem> lssgCartItemMap = lssgCart.getCartItems();
for(String productId : productIds){
LssgCartItem lssgCartItem = lssgCartItemMap.get(productId);
// MySQL数据库中商品加上相对应的库存量
delTotalPrice = delTotalPrice.add(lssgCartItem.getSubtotalPrice());
//通过商品id删除该购物项
lssgCartItemMap.remove(productId);
}
//更新总价 新的购物车总价 = 原来购物车总价 - 要结算购物项的小计
lssgCart.setTotalPrice(lssgCart.getTotalPrice().subtract(delTotalPrice));
//更新购物车到redis
jedisClient.hset(REDIS_CART,userLoginName,JSON.toJSONString(lssgCart));
//从redis中取出购物车传到到前台
return JSON.parseObject(jedisClient.hget(REDIS_CART,userLoginName),LssgCart.class);
}
/**
* 统计购物车中的购物项
* */
@RequestMapping("/countCartItme")
@ResponseBody
public Integer countCartItme(){
//获取已登录的用户信息
Subject subject = SecurityUtils.getSubject();
String userLoginName = (String) subject.getPrincipal();
LssgCart lssgCart = JSON.parseObject(jedisClient.hget(REDIS_CART,userLoginName),LssgCart.class);
if(lssgCart==null){
return 0;
}
return lssgCart.getCartItems().size();
}
}
在lssgCartItemMap.put(String.valueOf(lssgProduct.getProductId()),lssgCartItem);中我们把商品ID作为购物项集合lssgCartItemMap的key