From 84c6e1df4b6bd48ee0517a33778b514008022875 Mon Sep 17 00:00:00 2001
From: seatonwan9
Date: 星期二, 19 八月 2025 18:10:44 +0800
Subject: [PATCH] 产品订购

---
 src/main/java/com/webmanage/service/OrderInfoService.java                |    2 
 src/main/java/com/webmanage/WebManageApplication.java                    |    2 
 src/main/java/com/webmanage/mapper/CartMapper.java                       |    2 
 src/main/java/com/webmanage/service/impl/ProductPricingServiceImpl.java  |   10 
 src/main/resources/mapper/CartMapper.xml                                 |    3 
 src/main/java/com/webmanage/service/impl/CartServiceImpl.java            |  165 ++++++++++--------
 src/main/java/com/webmanage/config/AsyncExecutorProperties.java          |   40 ++++
 src/main/java/com/webmanage/service/TokenService.java                    |   22 ++
 src/main/java/com/webmanage/config/CartProperties.java                   |   31 +++
 src/main/java/com/webmanage/controller/OrderController.java              |   27 ++
 src/main/java/com/webmanage/entity/Cart.java                             |    1 
 src/main/java/com/webmanage/service/impl/TokenServiceImpl.java           |   45 +++++
 src/main/java/com/webmanage/config/RedisConfig.java                      |    5 
 src/main/java/com/webmanage/service/impl/CartPersistenceServiceImpl.java |   64 +++++++
 src/main/java/com/webmanage/service/impl/OrderInfoServiceImpl.java       |    4 
 src/main/java/com/webmanage/config/AsyncConfig.java                      |   52 +++++
 src/main/java/com/webmanage/vo/CartItemVO.java                           |    3 
 src/main/resources/application.yml                                       |   17 +
 src/main/java/com/webmanage/service/CartPersistenceService.java          |   11 +
 19 files changed, 417 insertions(+), 89 deletions(-)

diff --git a/src/main/java/com/webmanage/WebManageApplication.java b/src/main/java/com/webmanage/WebManageApplication.java
index 4af56a8..e54698e 100644
--- a/src/main/java/com/webmanage/WebManageApplication.java
+++ b/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 {
 
diff --git a/src/main/java/com/webmanage/config/AsyncConfig.java b/src/main/java/com/webmanage/config/AsyncConfig.java
new file mode 100644
index 0000000..d712fac
--- /dev/null
+++ b/src/main/java/com/webmanage/config/AsyncConfig.java
@@ -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);
+            }
+        };
+    }
+}
+
+
diff --git a/src/main/java/com/webmanage/config/AsyncExecutorProperties.java b/src/main/java/com/webmanage/config/AsyncExecutorProperties.java
new file mode 100644
index 0000000..4b06d45
--- /dev/null
+++ b/src/main/java/com/webmanage/config/AsyncExecutorProperties.java
@@ -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; }
+}
+
+
diff --git a/src/main/java/com/webmanage/config/CartProperties.java b/src/main/java/com/webmanage/config/CartProperties.java
new file mode 100644
index 0000000..d4fd690
--- /dev/null
+++ b/src/main/java/com/webmanage/config/CartProperties.java
@@ -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;
+    /** 鍚屾绛栫暐锛歳ealtime|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; }
+}
+
+
diff --git a/src/main/java/com/webmanage/config/RedisConfig.java b/src/main/java/com/webmanage/config/RedisConfig.java
index 65383b7..803da7b 100644
--- a/src/main/java/com/webmanage/config/RedisConfig.java
+++ b/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鏉ュ簭鍒楀寲鍜屽弽搴忓垪鍖杛edis鐨刱ey鍊�
diff --git a/src/main/java/com/webmanage/controller/OrderController.java b/src/main/java/com/webmanage/controller/OrderController.java
index 6680838..d2d30e7 100644
--- a/src/main/java/com/webmanage/controller/OrderController.java
+++ b/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() {
diff --git a/src/main/java/com/webmanage/entity/Cart.java b/src/main/java/com/webmanage/entity/Cart.java
index 2504dd7..5b5c741 100644
--- a/src/main/java/com/webmanage/entity/Cart.java
+++ b/src/main/java/com/webmanage/entity/Cart.java
@@ -106,7 +106,6 @@
     private LocalDateTime updateTime;
 
     @ApiModelProperty("閫昏緫鍒犻櫎锛�1-宸插垹闄わ紝0-鏈垹闄�")
-    @TableLogic
     @TableField("deleted")
     private Integer deleted;
 }
diff --git a/src/main/java/com/webmanage/mapper/CartMapper.java b/src/main/java/com/webmanage/mapper/CartMapper.java
index 76abe93..5cc4d07 100644
--- a/src/main/java/com/webmanage/mapper/CartMapper.java
+++ b/src/main/java/com/webmanage/mapper/CartMapper.java
@@ -32,4 +32,6 @@
      * 鏍规嵁鐢ㄦ埛ID鍜屽崟浣岻D璁$畻璐墿杞︽�婚噾棰�
      */
     java.math.BigDecimal sumTotalAmountByUserIdAndUnitId(@Param("userId") Long userId, @Param("unitId") Long unitId);
+
+    Integer deleteByCustomerCondition(@Param("id") Long id);
 }
diff --git a/src/main/java/com/webmanage/service/CartPersistenceService.java b/src/main/java/com/webmanage/service/CartPersistenceService.java
new file mode 100644
index 0000000..bd7cb3d
--- /dev/null
+++ b/src/main/java/com/webmanage/service/CartPersistenceService.java
@@ -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);
+}
+
+
diff --git a/src/main/java/com/webmanage/service/OrderInfoService.java b/src/main/java/com/webmanage/service/OrderInfoService.java
index 8e85fc7..d50282a 100644
--- a/src/main/java/com/webmanage/service/OrderInfoService.java
+++ b/src/main/java/com/webmanage/service/OrderInfoService.java
@@ -35,7 +35,7 @@
     /**
      * 鍒涘缓璁㈠崟锛堝寘鍚鍗曞ご涓庢槑缁嗘彃鍏ワ級锛岃繑鍥炶鍗曠紪鍙�
      */
-    String createOrder(CreateOrderDTO createOrderDTO);
+    OrderInfo createOrder(CreateOrderDTO createOrderDTO);
 
     /**
      * 涓婁紶璁㈠崟闄勪欢
diff --git a/src/main/java/com/webmanage/service/TokenService.java b/src/main/java/com/webmanage/service/TokenService.java
new file mode 100644
index 0000000..3312ff8
--- /dev/null
+++ b/src/main/java/com/webmanage/service/TokenService.java
@@ -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);
+}
+
+
diff --git a/src/main/java/com/webmanage/service/impl/CartPersistenceServiceImpl.java b/src/main/java/com/webmanage/service/impl/CartPersistenceServiceImpl.java
new file mode 100644
index 0000000..cdbe2c2
--- /dev/null
+++ b/src/main/java/com/webmanage/service/impl/CartPersistenceServiceImpl.java
@@ -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) {}
+    }
+}
+
+
diff --git a/src/main/java/com/webmanage/service/impl/CartServiceImpl.java b/src/main/java/com/webmanage/service/impl/CartServiceImpl.java
index 304a9fe..7072069 100644
--- a/src/main/java/com/webmanage/service/impl/CartServiceImpl.java
+++ b/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("鍟嗗搧瀹氫环涓嶅瓨鍦�");
             }
-            
+
             // 鏋勫缓璐墿杞ey
             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())));
                 }
             }
-            
+
             // 淇濆瓨鍒癛edis
-            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);
-            
+
             // 浠嶳edis涓垹闄ゅ晢鍝侀」
             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());
-            
+
             // 鏇存柊鍒癛edis
-            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());
-                
+
                 // 淇濆瓨鍒癛edis
                 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("鎴愬姛浠庢暟鎹簱鍔犺浇鐢ㄦ埛{}鐨勮喘鐗╄溅鏁版嵁鍒癛edis", 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("浠嶳edis鑾峰彇璐墿杞﹀晢鍝佸垪琛ㄥけ璐�", 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);
diff --git a/src/main/java/com/webmanage/service/impl/OrderInfoServiceImpl.java b/src/main/java/com/webmanage/service/impl/OrderInfoServiceImpl.java
index 92a9b54..c981a09 100644
--- a/src/main/java/com/webmanage/service/impl/OrderInfoServiceImpl.java
+++ b/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
diff --git a/src/main/java/com/webmanage/service/impl/ProductPricingServiceImpl.java b/src/main/java/com/webmanage/service/impl/ProductPricingServiceImpl.java
index 25076bf..0d59412 100644
--- a/src/main/java/com/webmanage/service/impl/ProductPricingServiceImpl.java
+++ b/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) {
diff --git a/src/main/java/com/webmanage/service/impl/TokenServiceImpl.java b/src/main/java/com/webmanage/service/impl/TokenServiceImpl.java
new file mode 100644
index 0000000..964e8b4
--- /dev/null
+++ b/src/main/java/com/webmanage/service/impl/TokenServiceImpl.java
@@ -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;
+    }
+}
+
+
diff --git a/src/main/java/com/webmanage/vo/CartItemVO.java b/src/main/java/com/webmanage/vo/CartItemVO.java
index b41078d..ab0275a 100644
--- a/src/main/java/com/webmanage/vo/CartItemVO.java
+++ b/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;
 }
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 3e402c4..52bb679 100644
--- a/src/main/resources/application.yml
+++ b/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
   # 鍚屾绛栫暐锛歳ealtime锛堝疄鏃跺悓姝ワ級銆乥atch锛堟壒閲忓悓姝ワ級銆乵anual锛堟墜鍔ㄥ悓姝ワ級
   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
diff --git a/src/main/resources/mapper/CartMapper.xml b/src/main/resources/mapper/CartMapper.xml
index 086a599..3d56c30 100644
--- a/src/main/resources/mapper/CartMapper.xml
+++ b/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>

--
Gitblit v1.8.0