Hash结构之购物车实战
我们平时在网上购物,一般都会有购物车,支持买多件商品,每个商品可以买不同数量。
购物车常见实现方式:
(1)、存储到数据库:性能存在瓶颈。
(2)、后端存储到缓存如redis:支持高性能处理,可以开启AOF持久化防止重启丢失。
购物车数据结构介绍:
购物车结构是一个双层Map,Map<String,Map<String,String>>,第一层Map,Key是用户id;第二层Map,Key是购物车中商品id,值是购物车数据。
所以今天我们使用redis的Hash结构来实现购物车,包括添加、查看和清空!
首先我们要准备相关类和数据准备。
VideoDO类:
public class VideoDO {
private int id;
private String title;
private String img;
private int price;
public VideoDO() {
}
public VideoDO(int id, String title, String img, int price) {
this.id = id;
this.title = title;
this.img = img;
this.price = price;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getImg() {
return img;
}
public void setImg(String img) {
this.img = img;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
CartItemVO类(购物项):
public class CartItemVO {
//商品id
private Integer productId;
//购买数量
private Integer buyNum;
//商品标题
private String productTitle;
//图片
private String productImg;
//商品单价
private int price ;
//总价格,单价+数量
private int totalPrice;
public int getProductId() {
return productId;
}
public void setProductId(int productId) {
this.productId = productId;
}
public Integer getBuyNum() {
return buyNum;
}
public void setBuyNum(Integer buyNum) {
this.buyNum = buyNum;
}
public String getProductTitle() {
return productTitle;
}
public void setProductTitle(String productTitle) {
this.productTitle = productTitle;
}
public String getProductImg() {
return productImg;
}
public void setProductImg(String productImg) {
this.productImg = productImg;
}
/**
* 商品单价 * 购买数量
* @return
*/
public int getTotalPrice() {
return this.price*this.buyNum;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public void setTotalPrice(int totalPrice) {
this.totalPrice = totalPrice;
}
}
CartVO类(购物车):
public class CartVO {
//购物项
private List<CartItemVO> cartItems;
//购物车总价格
private Integer totalAmount;
//总价格
public int getTotalAmount() {
return cartItems.stream().mapToInt(CartItemVO::getTotalPrice).sum();
}
public List<CartItemVO> getCartItems() {
return cartItems;
}
public void setCartItems(List<CartItemVO> cartItems) {
this.cartItems = cartItems;
}
}
数据源层(这里我们为了方便实现,我们省略service层):
@Repository
public class VideoDao {
private static Map<Integer, VideoDO> map = new HashMap<>();
static {
map.put(1,new VideoDO(1,"综合项目实战","hhhh",1000));
map.put(2,new VideoDO(2,"RabbitMQ实战","llll",479));
map.put(3,new VideoDO(3,"Java基础","xxxx",449));
map.put(4,new VideoDO(4,"玩转Nginx","yyyy",549));
map.put(5,new VideoDO(5,"ssm课程","oooo",649));
map.put(6,new VideoDO(6,"SpringCloud实战","zzzz",559));
}
// 模拟从数据库找
public VideoDO findDetailById(int videoId) {
return map.get(videoId);
}
}
json工具类:
public class JsonUtil {
// 定义jackson对象
private static final ObjectMapper MAPPER = new ObjectMapper();
/**
* 将对象转换成json字符串。
* @return
*/
public static String objectToJson(Object data) {
try {
String string = MAPPER.writeValueAsString(data);
return string;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 将json结果集转化为对象
* @param jsonData json数据
* @param beanType 对象中的object类型
* @return
* @param <T>
*/
public static <T> T jsonToPojo(String jsonData, Class<T> beanType) {
try {
T t = MAPPER.readValue(jsonData, beanType);
return t;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
准备工作做好啦,下面我们来实现添加购物车接口的开发:
@RestController
@RequestMapping("/api/v1/cart")
public class CartController {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private VideoDao videoDao;
@RequestMapping("add")
//一般我们添加购物车需要商品的id和购买的数量
public JsonData addCart(int videoId,int buyNum){
//获取购物车
BoundHashOperations<String, Object, Object> myCart = getMyCartOps();
//+“”是为了将videoId变成字符串
Object cacheObj = myCart.get(videoId+"");
//购物车没有这个商品
if(cacheObj == null){
CartItemVO cartItemVO = new CartItemVO();
VideoDO videoDO = videoDao.findDetailById(videoId);
cartItemVO.setBuyNum(buyNum);
cartItemVO.setProductId(videoId);
cartItemVO.setPrice(videoDO.getPrice());
cartItemVO.setProductImg(videoDO.getImg());
cartItemVO.setProductTitle(videoDO.getTitle());
myCart.put(videoId+"", JsonUtil.objectToJson(cartItemVO));
}else {
//购物车有这个商品,增加商品购买数量
String result = (String) cacheObj;
CartItemVO cartItemVO = JsonUtil.jsonToPojo(result, CartItemVO.class);
cartItemVO.setBuyNum(cartItemVO.getBuyNum()+buyNum);
myCart.put(videoId+"", JsonUtil.objectToJson(cartItemVO));
}
return JsonData.buildSuccess();
}
// 抽取我的购物车通用方法
private BoundHashOperations<String,Object,Object> getMyCartOps(){
String key = getCartKey();
return redisTemplate.boundHashOps(key);
}
//获取购物车的key
private String getCartKey(){
//用户的id,一般从拦截器获取,这里为了方便,写死即可,每个用户不一样
int userId = 66;
String cartKey = String.format("video:cart:%s",userId);
return cartKey;
}
}
我们去浏览器输入http://localhost:8080/api/v1/cart/add?videoId=1&buyNum=2,我们可以去redis可视化查看到视频商品id是1的,购买数量是2件,总价格是2000。
我们再次访问http://localhost:8080/api/v1/cart/add?videoId=1&buyNum=2这个网址,视频商品id是1的,购买数量变成4件,总价格变成4000。
下面我们就来实现查看我的购物车接口开发:
/**
* 查看我的购物车
* @return
*/
@RequestMapping("myCart")
public JsonData getMyCart(){
//获取购物车
BoundHashOperations<String, Object, Object> myCart = getMyCartOps();
List<Object> itemList = myCart.values();
List<CartItemVO> cartItemVOList = new ArrayList<>();
for(Object item : itemList){
CartItemVO cartItemVO = JsonUtil.jsonToPojo((String) item,CartItemVO.class);
cartItemVOList.add(cartItemVO);
}
CartVO cartVO = new CartVO();
cartVO.setCartItems(cartItemVOList);
return JsonData.buildSuccess(cartVO);
}
当我们去访问该接口时,就可以查询到我的购物车有什么商品。
最后我们来开发一下清空购物车:
/**
* 清空我的购物车
* @return
*/
@RequestMapping("clear")
public JsonData clear(){
String key = getCartKey();
redisTemplate.delete(key);
return JsonData.buildSuccess();
}
清空购物车之后查看我的购物车就是空的。