seatonwan9
2025-08-19 84c6e1df4b6bd48ee0517a33778b514008022875
产品订购
7个文件已添加
12个文件已修改
506 ■■■■ 已修改文件
src/main/java/com/webmanage/WebManageApplication.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/webmanage/config/AsyncConfig.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/webmanage/config/AsyncExecutorProperties.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/webmanage/config/CartProperties.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/webmanage/config/RedisConfig.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/webmanage/controller/OrderController.java 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/webmanage/entity/Cart.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/webmanage/mapper/CartMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/webmanage/service/CartPersistenceService.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/webmanage/service/OrderInfoService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/webmanage/service/TokenService.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/webmanage/service/impl/CartPersistenceServiceImpl.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/webmanage/service/impl/CartServiceImpl.java 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/webmanage/service/impl/OrderInfoServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/webmanage/service/impl/ProductPricingServiceImpl.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/webmanage/service/impl/TokenServiceImpl.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/webmanage/vo/CartItemVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application.yml 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/CartMapper.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/webmanage/WebManageApplication.java
@@ -1,6 +1,7 @@
package com.webmanage;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -11,6 +12,7 @@
 * @date 2024-08-07
 */
@SpringBootApplication
@EnableAsync
@MapperScan("com.webmanage.mapper")
public class WebManageApplication {
src/main/java/com/webmanage/config/AsyncConfig.java
New file
@@ -0,0 +1,52 @@
package com.webmanage.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class AsyncConfig implements AsyncConfigurer {
    private static final Logger log = LoggerFactory.getLogger(AsyncConfig.class);
    @Resource
    private AsyncExecutorProperties properties;
    @Override
    @Bean("asyncExecutor")
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(properties.getCorePoolSize());
        executor.setMaxPoolSize(properties.getMaxPoolSize());
        executor.setQueueCapacity(properties.getQueueCapacity());
        executor.setKeepAliveSeconds(properties.getKeepAliveSeconds());
        executor.setThreadNamePrefix(properties.getThreadNamePrefix());
        executor.setWaitForTasksToCompleteOnShutdown(properties.isWaitForTasksToCompleteOnShutdown());
        executor.setAwaitTerminationSeconds(properties.getAwaitTerminationSeconds());
        // 饱和策略:调用方线程执行,避免任务被丢弃
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncUncaughtExceptionHandler() {
            @Override
            public void handleUncaughtException(Throwable ex, Method method, Object... params) {
                log.error("异步任务执行异常, method={}, params={}", method, params, ex);
            }
        };
    }
}
src/main/java/com/webmanage/config/AsyncExecutorProperties.java
New file
@@ -0,0 +1,40 @@
package com.webmanage.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "async.executor")
public class AsyncExecutorProperties {
    /** 核心线程数 */
    private int corePoolSize = 4;
    /** 最大线程数 */
    private int maxPoolSize = 8;
    /** 队列容量 */
    private int queueCapacity = 200;
    /** 线程存活时间(秒) */
    private int keepAliveSeconds = 60;
    /** 线程名前缀 */
    private String threadNamePrefix = "async-exec-";
    /** 关闭时是否等待任务完成 */
    private boolean waitForTasksToCompleteOnShutdown = true;
    /** 关闭时最大等待秒数 */
    private int awaitTerminationSeconds = 30;
    public int getCorePoolSize() { return corePoolSize; }
    public void setCorePoolSize(int corePoolSize) { this.corePoolSize = corePoolSize; }
    public int getMaxPoolSize() { return maxPoolSize; }
    public void setMaxPoolSize(int maxPoolSize) { this.maxPoolSize = maxPoolSize; }
    public int getQueueCapacity() { return queueCapacity; }
    public void setQueueCapacity(int queueCapacity) { this.queueCapacity = queueCapacity; }
    public int getKeepAliveSeconds() { return keepAliveSeconds; }
    public void setKeepAliveSeconds(int keepAliveSeconds) { this.keepAliveSeconds = keepAliveSeconds; }
    public String getThreadNamePrefix() { return threadNamePrefix; }
    public void setThreadNamePrefix(String threadNamePrefix) { this.threadNamePrefix = threadNamePrefix; }
    public boolean isWaitForTasksToCompleteOnShutdown() { return waitForTasksToCompleteOnShutdown; }
    public void setWaitForTasksToCompleteOnShutdown(boolean waitForTasksToCompleteOnShutdown) { this.waitForTasksToCompleteOnShutdown = waitForTasksToCompleteOnShutdown; }
    public int getAwaitTerminationSeconds() { return awaitTerminationSeconds; }
    public void setAwaitTerminationSeconds(int awaitTerminationSeconds) { this.awaitTerminationSeconds = awaitTerminationSeconds; }
}
src/main/java/com/webmanage/config/CartProperties.java
New file
@@ -0,0 +1,31 @@
package com.webmanage.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "cart")
public class CartProperties {
    /** Redis缓存过期天数 */
    private Integer expireDays = 30;
    /** 是否启用数据库持久化 */
    private Boolean enablePersistence = true;
    /** 是否启用一致性检查 */
    private Boolean enableConsistencyCheck = true;
    /** 同步策略:realtime|batch|manual */
    private String syncStrategy = "realtime";
    public Integer getExpireDays() { return expireDays; }
    public void setExpireDays(Integer expireDays) { this.expireDays = expireDays; }
    public Boolean getEnablePersistence() { return enablePersistence; }
    public void setEnablePersistence(Boolean enablePersistence) { this.enablePersistence = enablePersistence; }
    public Boolean getEnableConsistencyCheck() { return enableConsistencyCheck; }
    public void setEnableConsistencyCheck(Boolean enableConsistencyCheck) { this.enableConsistencyCheck = enableConsistencyCheck; }
    public String getSyncStrategy() { return syncStrategy; }
    public void setSyncStrategy(String syncStrategy) { this.syncStrategy = syncStrategy; }
}
src/main/java/com/webmanage/config/RedisConfig.java
@@ -3,7 +3,9 @@
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
@@ -27,6 +29,9 @@
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        objectMapper.registerModule(new JavaTimeModule());
        // 禁用日期时间作为时间戳
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
src/main/java/com/webmanage/controller/OrderController.java
@@ -3,7 +3,9 @@
import com.webmanage.common.Result;
import com.webmanage.dto.CreateOrderDTO;
import com.webmanage.dto.OrderQueryDTO;
import com.webmanage.entity.OrderInfo;
import com.webmanage.service.OrderInfoService;
import com.webmanage.service.TokenService;
import com.webmanage.service.OrderNoService;
import com.webmanage.vo.OrderDetailVO;
import io.swagger.annotations.Api;
@@ -29,6 +31,7 @@
public class OrderController {
    @Resource private OrderInfoService orderInfoService;
    @Resource private OrderNoService orderNoService;
    @Resource private TokenService tokenService;
    @PostMapping("/buyer/page")
    @ApiOperation("分页查询买家订单列表")
@@ -38,17 +41,33 @@
    }
    @PostMapping("/create")
    @ApiOperation("创建订单(包含订单详情)")
    public Result<Object> createOrder(@Valid @RequestBody CreateOrderDTO createOrderDTO) {
    @ApiOperation("创建订单(包含订单详情),需在 Header 携带 Idempotency-Token 防重复提交")
    public Result<OrderInfo> createOrder(@RequestHeader(value = "Idempotency-Token", required = false) String token,
                                         @Valid @RequestBody CreateOrderDTO createOrderDTO) {
        try {
            String orderId = orderInfoService.createOrder(createOrderDTO);
            return Result.success(orderId);
            if (!tokenService.verifyAndConsume(token)) {
                return Result.error("请求无效或重复提交,请刷新页面后重试");
            }
            OrderInfo orderInfo = orderInfoService.createOrder(createOrderDTO);
            return Result.success(orderInfo);
        } catch (Exception e) {
            log.error("创建订单失败", e);
            return Result.error("创建订单失败:" + e.getMessage());
        }
    }
    @GetMapping("/idempotency/token")
    @ApiOperation("获取一次性防重复提交 Token")
    public Result<Object> getIdempotencyToken(@RequestParam(required = false) Long userId) {
        try {
            String token = tokenService.generateToken(userId);
            return Result.success("token生成",token);
        } catch (Exception e) {
            log.error("生成防重复提交 Token 失败", e);
            return Result.error("生成防重复提交 Token 失败:" + e.getMessage());
        }
    }
    @GetMapping("/no/new")
    @ApiOperation("生成唯一订单号")
    public Result<Object> generateOrderNo() {
src/main/java/com/webmanage/entity/Cart.java
@@ -106,7 +106,6 @@
    private LocalDateTime updateTime;
    @ApiModelProperty("逻辑删除:1-已删除,0-未删除")
    @TableLogic
    @TableField("deleted")
    private Integer deleted;
}
src/main/java/com/webmanage/mapper/CartMapper.java
@@ -32,4 +32,6 @@
     * 根据用户ID和单位ID计算购物车总金额
     */
    java.math.BigDecimal sumTotalAmountByUserIdAndUnitId(@Param("userId") Long userId, @Param("unitId") Long unitId);
    Integer deleteByCustomerCondition(@Param("id") Long id);
}
src/main/java/com/webmanage/service/CartPersistenceService.java
New file
@@ -0,0 +1,11 @@
package com.webmanage.service;
import com.webmanage.vo.CartItemVO;
public interface CartPersistenceService {
    void saveOrUpdate(Long userId, Long unitId, CartItemVO item);
    void remove(Long userId, Long unitId, Long pricingId);
    void clear(Long userId, Long unitId);
}
src/main/java/com/webmanage/service/OrderInfoService.java
@@ -35,7 +35,7 @@
    /**
     * 创建订单(包含订单头与明细插入),返回订单编号
     */
    String createOrder(CreateOrderDTO createOrderDTO);
    OrderInfo createOrder(CreateOrderDTO createOrderDTO);
    /**
     * 上传订单附件
src/main/java/com/webmanage/service/TokenService.java
New file
@@ -0,0 +1,22 @@
package com.webmanage.service;
/**
 * 防重复提交 Token 服务
 */
public interface TokenService {
    /**
     * 生成一次性防重复提交 Token(默认有效期短时间)
     * @param userId 可选的用户ID,仅用于追踪
     * @return token 字符串
     */
    String generateToken(Long userId);
    /**
     * 校验并消费 Token(一次性)。成功返回 true,失败/不存在/过期返回 false。
     * @param token header 中传递的 token
     * @return 校验并删除成功返回 true,否则 false
     */
    boolean verifyAndConsume(String token);
}
src/main/java/com/webmanage/service/impl/CartPersistenceServiceImpl.java
New file
@@ -0,0 +1,64 @@
package com.webmanage.service.impl;
import com.webmanage.entity.Cart;
import com.webmanage.mapper.CartMapper;
import com.webmanage.service.CartPersistenceService;
import com.webmanage.vo.CartItemVO;
import org.springframework.beans.BeanUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDateTime;
@Service
public class CartPersistenceServiceImpl implements CartPersistenceService {
    @Resource
    private CartMapper cartMapper;
    @Override
    @Async("asyncExecutor")
    public void saveOrUpdate(Long userId, Long unitId, CartItemVO item) {
        try {
            Cart cart = new Cart();
            BeanUtils.copyProperties(item, cart);
            cart.setUserId(userId);
            cart.setUnitId(unitId);
            cart.setUpdateTime(LocalDateTime.now());
            Cart existing = cartMapper.selectByUserIdUnitIdAndPricingId(userId, unitId, item.getPricingId());
            if (existing != null) {
                cart.setId(existing.getId());
                cartMapper.updateById(cart);
            } else {
                cart.setAddTime(LocalDateTime.now());
                cartMapper.insert(cart);
            }
        } catch (Exception ignored) {}
    }
    @Override
    @Async("asyncExecutor")
    public void remove(Long userId, Long unitId, Long pricingId) {
        try {
            Cart existing = cartMapper.selectByUserIdUnitIdAndPricingId(userId, unitId, pricingId);
            if (existing != null) {
                cartMapper.deleteById(existing.getId());
            }
        } catch (Exception ignored) {}
    }
    @Override
    @Async("asyncExecutor")
    public void clear(Long userId, Long unitId) {
        try {
            java.util.List<Cart> cartItems = cartMapper.selectByUserIdAndUnitId(userId, unitId);
            for (Cart item : cartItems) {
                cartMapper.deleteById(item.getId());
            }
        } catch (Exception ignored) {}
    }
}
src/main/java/com/webmanage/service/impl/CartServiceImpl.java
@@ -7,11 +7,14 @@
import com.webmanage.mapper.CartMapper;
import com.webmanage.mapper.ProductPricingMapper;
import com.webmanage.service.CartService;
import com.webmanage.service.CartPersistenceService;
import com.webmanage.vo.CartItemVO;
import com.webmanage.vo.CartVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import com.webmanage.config.CartProperties;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.format.datetime.DateFormatter;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
@@ -39,11 +42,17 @@
    @Resource
    private ProductPricingMapper productPricingMapper;
    
    @Resource
    private CartPersistenceService cartPersistenceService;
    @Resource
    private CartProperties cartProperties;
    // Redis key前缀
    private static final String CART_KEY_PREFIX = "cart:";
    private static final String CART_ITEM_KEY_PREFIX = "cart_item:";
    private static final int CART_EXPIRE_DAYS = 30; // 购物车过期时间30天
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean addToCart(Long userId, Long unitId, CartItemDTO cartItemDTO) {
@@ -53,11 +62,11 @@
            if (pricing == null) {
                throw new BusinessException("商品定价不存在");
            }
            // 构建购物车key
            String cartKey = buildCartKey(userId, unitId);
            String cartItemKey = buildCartItemKey(userId, unitId, cartItemDTO.getPricingId());
            // 检查商品是否已在购物车中
            CartItemVO existingItem = (CartItemVO) redisTemplate.opsForValue().get(cartItemKey);
            if (existingItem != null) {
@@ -76,19 +85,21 @@
                    existingItem.setTotalPrice(existingItem.getUnitPrice().multiply(BigDecimal.valueOf(existingItem.getQuantity())));
                }
            }
            // 保存到Redis
            redisTemplate.opsForValue().set(cartItemKey, existingItem, CART_EXPIRE_DAYS, TimeUnit.DAYS);
            redisTemplate.opsForValue().set(cartItemKey, existingItem, (cartProperties.getExpireDays() != null ? cartProperties.getExpireDays() : CART_EXPIRE_DAYS), TimeUnit.DAYS);
            // 更新购物车商品列表
            updateCartItemList(userId, unitId, cartItemDTO.getPricingId(), true);
            // 设置购物车过期时间
            redisTemplate.expire(cartKey, CART_EXPIRE_DAYS, TimeUnit.DAYS);
            // 同步到数据库
            syncCartItemToDatabase(userId, unitId, existingItem);
            redisTemplate.expire(cartKey, (cartProperties.getExpireDays() != null ? cartProperties.getExpireDays() : CART_EXPIRE_DAYS), TimeUnit.DAYS);
            // 异步持久化到数据库(根据配置)
            if (Boolean.TRUE.equals(cartProperties.getEnablePersistence()) && "realtime".equalsIgnoreCase(cartProperties.getSyncStrategy())) {
                cartPersistenceService.saveOrUpdate(userId, unitId, existingItem);
            }
            log.info("用户{}成功添加商品{}到购物车", userId, cartItemDTO.getProductName());
            return true;
        } catch (Exception e) {
@@ -96,22 +107,24 @@
            throw new BusinessException("添加商品到购物车失败:" + e.getMessage());
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeFromCart(Long userId, Long unitId, Long pricingId) {
        try {
            String cartItemKey = buildCartItemKey(userId, unitId, pricingId);
            // 从Redis中删除商品项
            Boolean removed = redisTemplate.delete(cartItemKey);
            if (Boolean.TRUE.equals(removed)) {
                // 更新购物车商品列表
                updateCartItemList(userId, unitId, pricingId, false);
                // 从数据库中删除
                removeCartItemFromDatabase(userId, unitId, pricingId);
                // 异步从数据库中删除(根据配置)
                if (Boolean.TRUE.equals(cartProperties.getEnablePersistence()) && "realtime".equalsIgnoreCase(cartProperties.getSyncStrategy())) {
                    cartPersistenceService.remove(userId, unitId, pricingId);
                }
                log.info("用户{}成功从购物车移除商品{}", userId, pricingId);
                return true;
            }
@@ -121,7 +134,7 @@
            throw new BusinessException("从购物车移除商品失败:" + e.getMessage());
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateCartItemQuantity(Long userId, Long unitId, Long pricingId, Integer quantity) {
@@ -129,23 +142,25 @@
            if (quantity <= 0) {
                return removeFromCart(userId, unitId, pricingId);
            }
            String cartItemKey = buildCartItemKey(userId, unitId, pricingId);
            CartItemVO cartItem = (CartItemVO) redisTemplate.opsForValue().get(cartItemKey);
            if (cartItem == null) {
                throw new BusinessException("购物车商品不存在");
            }
            cartItem.setQuantity(quantity);
            cartItem.setTotalPrice(cartItem.getUnitPrice().multiply(BigDecimal.valueOf(quantity)));
            cartItem.setUpdateTime(LocalDateTime.now());
            // 更新到Redis
            redisTemplate.opsForValue().set(cartItemKey, cartItem, CART_EXPIRE_DAYS, TimeUnit.DAYS);
            // 同步到数据库
            syncCartItemToDatabase(userId, unitId, cartItem);
            redisTemplate.opsForValue().set(cartItemKey, cartItem, (cartProperties.getExpireDays() != null ? cartProperties.getExpireDays() : CART_EXPIRE_DAYS), TimeUnit.DAYS);
            // 异步持久化到数据库(根据配置)
            if (Boolean.TRUE.equals(cartProperties.getEnablePersistence()) && "realtime".equalsIgnoreCase(cartProperties.getSyncStrategy())) {
                cartPersistenceService.saveOrUpdate(userId, unitId, cartItem);
            }
            log.info("用户{}成功更新购物车商品{}数量为{}", userId, pricingId, quantity);
            return true;
        } catch (Exception e) {
@@ -153,26 +168,28 @@
            throw new BusinessException("更新购物车商品数量失败:" + e.getMessage());
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean clearCart(Long userId, Long unitId) {
        try {
            String cartKey = buildCartKey(userId, unitId);
            List<Long> pricingIds = getCartItemPricingIds(userId, unitId);
            // 删除所有商品项
            for (Long pricingId : pricingIds) {
                String cartItemKey = buildCartItemKey(userId, unitId, pricingId);
                redisTemplate.delete(cartItemKey);
            }
            // 删除购物车列表
            redisTemplate.delete(cartKey);
            // 清空数据库中的购物车数据
            clearCartFromDatabase(userId, unitId);
            // 异步清空数据库中的购物车数据(根据配置)
            if (Boolean.TRUE.equals(cartProperties.getEnablePersistence()) && "realtime".equalsIgnoreCase(cartProperties.getSyncStrategy())) {
                cartPersistenceService.clear(userId, unitId);
            }
            log.info("用户{}成功清空购物车", userId);
            return true;
        } catch (Exception e) {
@@ -180,34 +197,34 @@
            throw new BusinessException("清空购物车失败:" + e.getMessage());
        }
    }
    @Override
    public CartVO getCart(Long userId, Long unitId) {
        try {
            CartVO cartVO = new CartVO();
            cartVO.setUserId(userId);
            cartVO.setUnitId(unitId);
            List<CartItemVO> items = getCartItems(userId, unitId);
            cartVO.setItems(items);
            // 计算总数量和总金额
            int totalQuantity = items.stream().mapToInt(item -> item.getQuantity()).sum();
            BigDecimal totalAmount = items.stream()
                    .map(item -> item.getTotalPrice())
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            cartVO.setTotalQuantity(totalQuantity);
            cartVO.setTotalAmount(totalAmount);
            cartVO.setLastUpdateTime(LocalDateTime.now());
            return cartVO;
        } catch (Exception e) {
            log.error("获取购物车信息失败", e);
            throw new BusinessException("获取购物车信息失败:" + e.getMessage());
        }
    }
    @Override
    public List<CartItemVO> getCartItems(Long userId, Long unitId) {
        try {
@@ -216,7 +233,7 @@
            if (items != null && !items.isEmpty()) {
                return items;
            }
            // Redis中没有数据,从数据库加载
            log.info("Redis中无购物车数据,从数据库加载用户{}的购物车", userId);
            return loadCartFromDatabase(userId, unitId) ? getCartItemsFromRedis(userId, unitId) : new ArrayList<>();
@@ -226,13 +243,13 @@
            return getCartItemsFromDatabase(userId, unitId);
        }
    }
    @Override
    public boolean checkCartItemStock(Long userId, Long unitId, Long pricingId) {
        // TODO: 实现库存检查逻辑
        return true;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean batchRemoveFromCart(Long userId, Long unitId, List<Long> pricingIds) {
@@ -240,18 +257,18 @@
            if (CollectionUtils.isEmpty(pricingIds)) {
                return true;
            }
            for (Long pricingId : pricingIds) {
                removeFromCart(userId, unitId, pricingId);
            }
            return true;
        } catch (Exception e) {
            log.error("批量删除购物车商品失败", e);
            throw new BusinessException("批量删除购物车商品失败:" + e.getMessage());
        }
    }
    @Override
    public Integer getCartItemCount(Long userId, Long unitId) {
        try {
@@ -261,7 +278,7 @@
            if (pricingIds != null) {
                return pricingIds.size();
            }
            // 从数据库获取
            return cartMapper.countByUserIdAndUnitId(userId, unitId);
        } catch (Exception e) {
@@ -269,7 +286,7 @@
            return 0;
        }
    }
    @Override
    public boolean loadCartFromDatabase(Long userId, Long unitId) {
        try {
@@ -277,22 +294,22 @@
            if (CollectionUtils.isEmpty(cartItems)) {
                return false;
            }
            String cartKey = buildCartKey(userId, unitId);
            List<Long> pricingIds = new ArrayList<>();
            for (Cart cartItem : cartItems) {
                CartItemVO itemVO = convertCartToCartItemVO(cartItem);
                String cartItemKey = buildCartItemKey(userId, unitId, cartItem.getPricingId());
                // 保存到Redis
                redisTemplate.opsForValue().set(cartItemKey, itemVO, CART_EXPIRE_DAYS, TimeUnit.DAYS);
                pricingIds.add(cartItem.getPricingId());
            }
            // 保存购物车列表到Redis
            redisTemplate.opsForValue().set(cartKey, pricingIds, CART_EXPIRE_DAYS, TimeUnit.DAYS);
            log.info("成功从数据库加载用户{}的购物车数据到Redis", userId);
            return true;
        } catch (Exception e) {
@@ -300,7 +317,7 @@
            return false;
        }
    }
    @Override
    public boolean syncCartToDatabase(Long userId, Long unitId) {
        try {
@@ -308,15 +325,15 @@
            if (CollectionUtils.isEmpty(redisItems)) {
                return true;
            }
            // 清空数据库中的购物车数据
            clearCartFromDatabase(userId, unitId);
            // 同步Redis数据到数据库
            for (CartItemVO item : redisItems) {
                syncCartItemToDatabase(userId, unitId, item);
            }
            log.info("成功同步Redis购物车数据到数据库,用户{}", userId);
            return true;
        } catch (Exception e) {
@@ -324,18 +341,18 @@
            return false;
        }
    }
    @Override
    public boolean checkCartConsistency(Long userId, Long unitId) {
        try {
            List<CartItemVO> redisItems = getCartItemsFromRedis(userId, unitId);
            List<Cart> dbItems = cartMapper.selectByUserIdAndUnitId(userId, unitId);
            if (redisItems.size() != dbItems.size()) {
                log.warn("购物车数据不一致:Redis数量{},数据库数量{}", redisItems.size(), dbItems.size());
                return false;
            }
            // 检查每个商品项是否一致
            for (CartItemVO redisItem : redisItems) {
                boolean found = false;
@@ -351,56 +368,56 @@
                    return false;
                }
            }
            return true;
        } catch (Exception e) {
            log.error("检查购物车数据一致性失败", e);
            return false;
        }
    }
    // ==================== 私有方法 ====================
    private String buildCartKey(Long userId, Long unitId) {
        return CART_KEY_PREFIX + userId + ":" + unitId;
    }
    private String buildCartItemKey(Long userId, Long unitId, Long pricingId) {
        return CART_ITEM_KEY_PREFIX + userId + ":" + unitId + ":" + pricingId;
    }
    private void updateCartItemList(Long userId, Long unitId, Long pricingId, boolean add) {
        String cartKey = buildCartKey(userId, unitId);
        List<Long> pricingIds = (List<Long>) redisTemplate.opsForValue().get(cartKey);
        if (pricingIds == null) {
            pricingIds = new ArrayList<>();
        }
        if (add && !pricingIds.contains(pricingId)) {
            pricingIds.add(pricingId);
        } else if (!add) {
            pricingIds.remove(pricingId);
        }
        redisTemplate.opsForValue().set(cartKey, pricingIds, CART_EXPIRE_DAYS, TimeUnit.DAYS);
    }
    private List<Long> getCartItemPricingIds(Long userId, Long unitId) {
        String cartKey = buildCartKey(userId, unitId);
        List<Long> pricingIds = (List<Long>) redisTemplate.opsForValue().get(cartKey);
        return pricingIds != null ? pricingIds : new ArrayList<>();
    }
    private List<CartItemVO> getCartItemsFromRedis(Long userId, Long unitId) {
        try {
            String cartKey = buildCartKey(userId, unitId);
            List<Long> pricingIds = (List<Long>) redisTemplate.opsForValue().get(cartKey);
            if (CollectionUtils.isEmpty(pricingIds)) {
                return new ArrayList<>();
            }
            List<CartItemVO> items = new ArrayList<>();
            for (Long pricingId : pricingIds) {
                String cartItemKey = buildCartItemKey(userId, unitId, pricingId);
@@ -409,7 +426,7 @@
                    items.add(item);
                }
            }
            return items;
        } catch (Exception e) {
            log.error("从Redis获取购物车商品列表失败", e);
@@ -467,7 +484,7 @@
        try {
            Cart existingCart = cartMapper.selectByUserIdUnitIdAndPricingId(userId, unitId, pricingId);
            if (existingCart != null) {
                cartMapper.deleteById(existingCart.getId());
                cartMapper.deleteByCustomerCondition(existingCart.getId());
            }
        } catch (Exception e) {
            log.error("从数据库删除购物车商品失败", e);
src/main/java/com/webmanage/service/impl/OrderInfoServiceImpl.java
@@ -190,7 +190,7 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String createOrder(CreateOrderDTO createOrderDTO) {
    public OrderInfo createOrder(CreateOrderDTO createOrderDTO) {
        if (createOrderDTO == null || CollectionUtils.isEmpty(createOrderDTO.getItems())) {
            throw new BusinessException("订单信息不完整");
        }
@@ -258,7 +258,7 @@
            orderDetailMapper.insert(detail);
        }
        return orderId;
        return orderInfo;
    }
    @Override
src/main/java/com/webmanage/service/impl/ProductPricingServiceImpl.java
@@ -48,10 +48,16 @@
            if (!StringUtils.hasText(productPricing.getPriceType())) {
                throw new BusinessException("价格设置不能为空");
            }
            if (productPricing.getPointsPrice() == null || productPricing.getPointsPrice().doubleValue() < 0) {
            if (productPricing.getPriceType().indexOf(PriceTypeEnum.POINTS.getName()) > -1
                    && productPricing.getPointsPrice() == null ||
                    productPricing.getPriceType().indexOf(PriceTypeEnum.POINTS.getName()) > -1
                            &&productPricing.getPointsPrice().doubleValue() < 0) {
                throw new BusinessException("积分价格值不能为空且不能为负数");
            }
            if (productPricing.getCurrencyPrice()== null || productPricing.getCurrencyPrice().doubleValue() < 0) {
            if (productPricing.getPriceType().indexOf(PriceTypeEnum.CURRENCY.getName()) > -1
                    && productPricing.getCurrencyPrice()== null ||
                    productPricing.getPriceType().indexOf(PriceTypeEnum.CURRENCY.getName()) > -1
                            && productPricing.getCurrencyPrice().doubleValue() < 0){
                throw new BusinessException("货币价格值不能为空且不能为负数");
            }
            if (productPricing.getProductId() == null) {
src/main/java/com/webmanage/service/impl/TokenServiceImpl.java
New file
@@ -0,0 +1,45 @@
package com.webmanage.service.impl;
import com.webmanage.service.TokenService;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Service
public class TokenServiceImpl implements TokenService {
    private static final String IDEMPOTENCY_TOKEN_PREFIX = "idempotency:token:";
    private static final long DEFAULT_EXPIRE_SECONDS = 60 * 5; // 5分钟
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Override
    public String generateToken(Long userId) {
        String token = UUID.randomUUID().toString().replace("-", "");
        String key = IDEMPOTENCY_TOKEN_PREFIX + token;
        // 值不重要,设置一个标记即可
        redisTemplate.opsForValue().set(key, userId == null ? 0L : userId, DEFAULT_EXPIRE_SECONDS, TimeUnit.SECONDS);
        return token;
    }
    @Override
    public boolean verifyAndConsume(String token) {
        if (token == null || token.isEmpty()) {
            return false;
        }
        String key = IDEMPOTENCY_TOKEN_PREFIX + token;
        Boolean existed = redisTemplate.hasKey(key);
        if (Boolean.TRUE.equals(existed)) {
            // 消费后删除,确保一次性
            redisTemplate.delete(key);
            return true;
        }
        return false;
    }
}
src/main/java/com/webmanage/vo/CartItemVO.java
@@ -1,5 +1,6 @@
package com.webmanage.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -66,8 +67,10 @@
    private String remarks;
    @ApiModelProperty("添加时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime addTime;
    @ApiModelProperty("最后更新时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
}
src/main/resources/application.yml
@@ -7,11 +7,12 @@
  application:
    name: web-manage-back
  profiles:
    active: test
    active: dev
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    serialization:
      write-dates-as-timestamps: false
  #mcv配置
  mvc:
    pathmatch:
@@ -29,9 +30,6 @@
      # 主键类型
      id-type: auto
      # 逻辑删除配置
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0
  mapper-locations: classpath*:/mapper/**/*.xml
# 日志配置
@@ -63,3 +61,12 @@
  enable-consistency-check: true
  # 同步策略:realtime(实时同步)、batch(批量同步)、manual(手动同步)
  sync-strategy: realtime
  async:
    executor:
      core-pool-size: 4
      max-pool-size: 16
      queue-capacity: 500
      keep-alive-seconds: 60
      thread-name-prefix: async-cart-
      wait-for-tasks-to-complete-on-shutdown: true
      await-termination-seconds: 30
src/main/resources/mapper/CartMapper.xml
@@ -69,5 +69,8 @@
        AND user_id = #{userId} 
        AND unit_id = #{unitId}
    </select>
    <delete id="deleteByCustomerCondition">
        delete from cart where id = #{id}
    </delete>
    
</mapper>