# 分布式电商系统 **Repository Path**: bjj_666/distributed-e-commerce-system ## Basic Information - **Project Name**: 分布式电商系统 - **Description**: 一个高性能的分布式电商系统,支持高并发交易、弹性扩展及微服务架构,适用于大规模在线零售业务。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-04-14 - **Last Updated**: 2025-04-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## **注意:** ### (现在不使用)使用双键实现redis中数据自动存储到mysql中 在测试购物车中数据时需开启reids键空间通知(需在reids服务开启后输入命令) 1.查看当前设置 redis-cli config get notify-keyspace-events 2.修改reids键事件为Ex redis-cli config set notify-keyspace-events Ex **carServiceImpl 使用键过期监听器实现redis中数据持久化到mysql中** ```java package com.by.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.by.bean.Car; import com.by.mapper.CarMapper; import com.by.service.CarService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.RedisConnectionFailureException; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.listener.PatternTopic; import org.springframework.data.redis.listener.RedisMessageListenerContainer; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.PostConstruct; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * date 2025/4/11 11:36 * author bjj * decoration * version 1.0 * since jdk11 */ @Service @Slf4j public class CarServiceImpl implements CarService { @Autowired CarMapper carMapper; @Autowired RedisTemplate redisTemplate; //redis监听容器 @Autowired RedisMessageListenerContainer container; private static final String CAR_PREFIX = "car:user:"; private static final String CART_EXPIRE_PREFIX = "car:expire:user:"; private static final long CAR_EXPIRE_MIN = 60; private static final String CAR_CHANNEL_PATTERN = "__keyevent@*__:expired"; private static final String PRODUCT_NAME = "name:"; private static final String PRODUCT_PRICE = "price:"; private static final String PRODUCT_PIC_URL = "picUrl:"; //添加Redis过期事件监听配置 @PostConstruct public void initRedisListener() { //添加键过期监听器 container.addMessageListener((message, pattern) -> { log.info("收到Redis事件:频道[{}] 消息[{}]", new String(Objects.requireNonNull(pattern)), new String(message.getBody())); // 添加事件日志 try { //解析过期的键 String expiredKey = new String(message.getBody()); if (expiredKey.startsWith(CART_EXPIRE_PREFIX)) { //提取用户ID(格式示例:car:user:123) String userIdStr = expiredKey.split(":")[3]; Integer userId = Integer.parseInt(userIdStr); //同步购物车到mysql //异步执行防止阻塞 CompletableFuture.runAsync(() -> { try { syncCarToMysql(userId); } catch (Exception e) { log.error("同步失败", e); } }); } } catch (Exception e) { log.error("监听器异常",e); } }, new PatternTopic(CAR_CHANNEL_PATTERN));//监听所有数据库的键过期事件 } /** * 添加到购物车 * * @param userId 用户id * @param car 购物车信息 * @return */ @Override public boolean addToCar(Integer userId, Car car) { String key = CAR_PREFIX + userId; String expireKey = CART_EXPIRE_PREFIX + userId; // 检查商品是否已存在购物车 Boolean hasKey = redisTemplate.opsForHash().hasKey(key, car.getProductId().toString()); try { if (Boolean.TRUE.equals(hasKey)) { // 商品已存在,增加数量 Integer exist = (Integer) redisTemplate.opsForHash().get(key, car.getProductId().toString()); redisTemplate.opsForHash().put(key, car.getProductId().toString(), exist+ car.getQuantity()); } else { // 商品不存在,添加新商品 redisTemplate.opsForHash().put(key, car.getProductId().toString(), car.getQuantity()); //存储商品信息 String productKey = "product:" + car.getProductId(); //名称 redisTemplate.opsForHash().put(productKey, PRODUCT_NAME, car.getProductName()); //价格 redisTemplate.opsForHash().put(productKey, PRODUCT_PRICE, car.getProductPrice()); //图片 redisTemplate.opsForHash().put(productKey, PRODUCT_PIC_URL, car.getPicUrl()); } // 设置过期时间 redisTemplate.expire(key, CAR_EXPIRE_MIN, TimeUnit.MINUTES); // 设置辅助键过期时间,提前触发同步 redisTemplate.opsForValue().set(expireKey, "1", CAR_EXPIRE_MIN - 1, TimeUnit.MINUTES); log.debug("设置购物车 {} 过期时间:{} 分钟", key, CAR_EXPIRE_MIN); // 确认过期设置生效 return true; } catch (RedisConnectionFailureException e) { log.error("redis连接失败", e); return false; } } /** * 查询购物车列表 * * @param userId 用户id * @return */ @Override public List getCarList(Integer userId) { String key = CAR_PREFIX + userId; //查询购物车列表 Map map = redisTemplate.opsForHash().entries(key); //若为空,则返回null if(map.isEmpty()){ return null; }else { //否则,遍历商品信息 List list = new ArrayList(); map.forEach((k,v)->{ Map tmp = new HashMap(); tmp.put("productId",k); tmp.put("quantity",v); tmp.put("userId",userId); String productKey = "product:" + k; tmp.put("productName",redisTemplate.opsForHash().get(productKey,PRODUCT_NAME)); tmp.put("productPrice",redisTemplate.opsForHash().get(productKey,PRODUCT_PRICE)); tmp.put("picUrl",redisTemplate.opsForHash().get(productKey,PRODUCT_PIC_URL)); list.add(tmp); }); return list; } } /** * 更新购物车中商品数量 * * @param userId 用户id * @param productId 商品id * @param quantity 购买数量 * @return */ @Override public boolean updateQuantity(Integer userId, Integer productId, Integer quantity) { String key = CAR_PREFIX + userId; Integer existNum = (Integer) redisTemplate.opsForHash().get(key, productId.toString()); //购物车不为空则进行更新 if (existNum != null) { redisTemplate.opsForHash().put(key, productId.toString(), quantity); return true; } //更新的购物车为空则返回false return false; } /** * 删除购物车中的商品 * * @param userId 用户id * @param productId 商品id * @return */ @Override public boolean deleteProduct(Integer userId, Integer productId) { String key = CAR_PREFIX + userId; Long rs = redisTemplate.opsForHash().delete(key, productId.toString()); //从redis中获取购物车列表 List carList = redisTemplate.opsForHash().values(key); //如果redis中没有数据,则将mysql中的数据也删除 if (carList.isEmpty()) { QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("product_id", productId); carMapper.delete(wrapper); return true; } return rs > 0; } /** * 清空购物车 * * @param userId 用户id * @return */ @Override public boolean clearCar(Integer userId) { String key = CAR_PREFIX + userId; boolean isDelete = Boolean.TRUE.equals(redisTemplate.delete(key)); if(isDelete){ QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("user_id",userId); carMapper.delete(wrapper); return true; } return false; } /** * 同步购物车到mysql * * @param userId 用户id */ @Override @Transactional public void syncCarToMysql(Integer userId) { String key = CAR_PREFIX + userId; // Map entries = redisTemplate.opsForHash().entries(key); List values = redisTemplate.opsForHash().values(key); List carItems = values.stream() .map(obj -> (Car) obj) .collect(Collectors.toList()); //添加调试日志 log.info("开始同步用户 {} 的购物车数据,共 {} 件商品", userId, carItems.size()); // 先删除数据库中该用户的购物车数据 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("user_id", userId); carMapper.delete(queryWrapper); // 将Redis中的数据保存到数据库 if (!carItems.isEmpty()) { for (Car item : carItems) { item.setUserId(userId); item.setId(0); carMapper.insert(item); } } } /** * 从mysql中查询购物车列表 * * @param userId */ private void syncCarFromMysql(Integer userId) { QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("user_id", userId); List carList = carMapper.selectList(wrapper); String key = CAR_PREFIX + userId; //将购物车列表存入redis if (!carList.isEmpty()) { for (Car car : carList) { redisTemplate.opsForHash().put(key, car.getProductId().toString(), car); } } } } ```