Browse Source

添加清空购物车 管理员重置餐桌 购物车锁定功能

lutong 2 months ago
parent
commit
d77a16e870

+ 15 - 10
alien-dining/src/main/java/shop/alien/dining/controller/DiningController.java

@@ -34,8 +34,9 @@ public class DiningController {
             @ApiParam(value = "桌号ID", required = true) @RequestParam Integer tableId,
             @ApiParam(value = "就餐人数", required = true) @RequestParam Integer dinerCount) {
         try {
-            // 验证 token
-            if (!TokenUtil.hasValidToken()) {
+            // 从 token 获取用户信息
+            Integer userId = TokenUtil.getCurrentUserId();
+            if (userId == null) {
                 return R.fail("用户未登录");
             }
             DiningPageInfoVO vo = diningService.getDiningPageInfo(tableId, dinerCount);
@@ -53,8 +54,9 @@ public class DiningController {
             @ApiParam(value = "搜索关键词", required = false) @RequestParam(required = false) String keyword,
             @ApiParam(value = "桌号ID", required = true) @RequestParam Integer tableId) {
         try {
-            // 验证 token
-            if (!TokenUtil.hasValidToken()) {
+            // 从 token 获取用户信息
+            Integer userId = TokenUtil.getCurrentUserId();
+            if (userId == null) {
                 return R.fail("用户未登录");
             }
             // 限制关键词长度
@@ -78,8 +80,9 @@ public class DiningController {
             @ApiParam(value = "页码", required = false) @RequestParam(defaultValue = "1") Integer page,
             @ApiParam(value = "每页数量", required = false) @RequestParam(defaultValue = "12") Integer size) {
         try {
-            // 验证 token
-            if (!TokenUtil.hasValidToken()) {
+            // 从 token 获取用户信息
+            Integer userId = TokenUtil.getCurrentUserId();
+            if (userId == null) {
                 return R.fail("用户未登录");
             }
             List<CuisineListVO> list = diningService.getCuisinesByCategory(storeId, categoryId, tableId, page, size);
@@ -96,8 +99,9 @@ public class DiningController {
             @ApiParam(value = "菜品ID", required = true) @PathVariable Integer cuisineId,
             @ApiParam(value = "桌号ID", required = true) @RequestParam Integer tableId) {
         try {
-            // 验证 token
-            if (!TokenUtil.hasValidToken()) {
+            // 从 token 获取用户信息
+            Integer userId = TokenUtil.getCurrentUserId();
+            if (userId == null) {
                 return R.fail("用户未登录");
             }
             CuisineDetailVO vo = diningService.getCuisineDetail(cuisineId, tableId);
@@ -207,8 +211,9 @@ public class DiningController {
     public R<Integer> checkOrderLock(
             @ApiParam(value = "桌号ID", required = true) @RequestParam Integer tableId) {
         try {
-            // 验证 token
-            if (!TokenUtil.hasValidToken()) {
+            // 从 token 获取用户信息
+            Integer userId = TokenUtil.getCurrentUserId();
+            if (userId == null) {
                 return R.fail("用户未登录");
             }
             Integer lockUserId = diningService.checkOrderLock(tableId);

+ 85 - 0
alien-dining/src/main/java/shop/alien/dining/controller/StoreOrderController.java

@@ -131,6 +131,55 @@ public class StoreOrderController {
         }
     }
 
+    @ApiOperation(value = "清空购物车", notes = "清空购物车中所有商品,并推送SSE和WebSocket消息")
+    @DeleteMapping("/cart/clear")
+    public R<CartDTO> clearCart(
+            @ApiParam(value = "桌号ID", required = true) @RequestParam Integer tableId) {
+        try {
+            // 验证 token
+            if (!TokenUtil.hasValidToken()) {
+                return R.fail("用户未登录");
+            }
+            
+            // 检查购物车中是否有锁定数量的商品(已下单的商品)
+            CartDTO cart = cartService.getCart(tableId);
+            if (cart.getItems() != null && !cart.getItems().isEmpty()) {
+                boolean hasLockedItems = cart.getItems().stream()
+                        .anyMatch(item -> item.getLockedQuantity() != null && item.getLockedQuantity() > 0);
+                if (hasLockedItems) {
+                    return R.fail("购物车中有已下单的商品,无法清空。请先取消订单或等待订单完成后再清空。");
+                }
+            }
+            
+            // 清空购物车
+            cartService.clearCart(tableId);
+            
+            // 创建空的购物车对象用于返回和推送
+            CartDTO emptyCart = new CartDTO();
+            emptyCart.setTableId(tableId);
+            emptyCart.setItems(java.util.Collections.emptyList());
+            emptyCart.setTotalAmount(java.math.BigDecimal.ZERO);
+            emptyCart.setTotalQuantity(0);
+            
+            // 查询桌号信息
+            StoreTable table = storeTableMapper.selectById(tableId);
+            if (table != null) {
+                emptyCart.setTableNumber(table.getTableNumber());
+                emptyCart.setStoreId(table.getStoreId());
+            }
+            
+            // 推送购物车更新消息(SSE)
+            sseService.pushCartUpdate(tableId, emptyCart);
+            // 推送购物车更新消息(WebSocket)
+//            CartWebSocketProcess.pushCartUpdate(tableId, emptyCart);
+            
+            return R.data(emptyCart);
+        } catch (Exception e) {
+            log.error("清空购物车失败: {}", e.getMessage(), e);
+            return R.fail("清空购物车失败: " + e.getMessage());
+        }
+    }
+
     @ApiOperation(value = "设置用餐人数", notes = "设置用餐人数,自动添加或更新餐具到购物车")
     @PostMapping("/cart/set-diner-count")
     public R<CartDTO> setDinerCount(
@@ -443,4 +492,40 @@ public class StoreOrderController {
             return R.fail("更新订单优惠券失败: " + e.getMessage());
         }
     }
+
+    @ApiOperation(value = "管理员重置餐桌", notes = "管理员重置餐桌:删除购物车数据、订单数据,并重置餐桌表初始化")
+    @PostMapping("/admin/reset-table/{tableId}")
+    public R<Boolean> resetTable(@ApiParam(value = "餐桌ID", required = true) @PathVariable Integer tableId) {
+        try {
+            // TODO: 这里可以添加管理员权限验证
+            // if (!isAdmin(userId)) {
+            //     return R.fail("无权限执行此操作");
+            // }
+            
+            boolean result = orderService.resetTable(tableId);
+            if (result) {
+                // 推送购物车更新消息(清空购物车)
+                CartDTO emptyCart = new CartDTO();
+                emptyCart.setTableId(tableId);
+                emptyCart.setItems(java.util.Collections.emptyList());
+                emptyCart.setTotalAmount(java.math.BigDecimal.ZERO);
+                emptyCart.setTotalQuantity(0);
+                
+                // 查询桌号信息
+                StoreTable table = storeTableMapper.selectById(tableId);
+                if (table != null) {
+                    emptyCart.setTableNumber(table.getTableNumber());
+                    emptyCart.setStoreId(table.getStoreId());
+                }
+                
+                sseService.pushCartUpdate(tableId, emptyCart);
+                return R.data(true);
+            } else {
+                return R.fail("重置餐桌失败");
+            }
+        } catch (Exception e) {
+            log.error("重置餐桌失败: {}", e.getMessage(), e);
+            return R.fail("重置餐桌失败: " + e.getMessage());
+        }
+    }
 }

+ 8 - 0
alien-dining/src/main/java/shop/alien/dining/service/CartService.java

@@ -102,4 +102,12 @@ public interface CartService {
      * @return 购物车信息
      */
     CartDTO updateTablewareQuantity(Integer tableId, Integer quantity);
+
+    /**
+     * 锁定购物车商品数量(下单时调用,将当前数量锁定,不允许减少或删除)
+     *
+     * @param tableId 桌号ID
+     * @return 购物车信息
+     */
+    CartDTO lockCartItems(Integer tableId);
 }

+ 8 - 0
alien-dining/src/main/java/shop/alien/dining/service/StoreOrderService.java

@@ -93,4 +93,12 @@ public interface StoreOrderService extends IService<StoreOrder> {
      * @return 订单信息
      */
     StoreOrder updateOrderCoupon(Integer orderId, Integer couponId);
+
+    /**
+     * 管理员重置餐桌(删除购物车数据、订单数据,并重置餐桌表)
+     *
+     * @param tableId 餐桌ID
+     * @return 是否成功
+     */
+    boolean resetTable(Integer tableId);
 }

+ 101 - 19
alien-dining/src/main/java/shop/alien/dining/service/impl/CartServiceImpl.java

@@ -134,6 +134,7 @@ public class CartServiceImpl implements CartService {
                 item.setCuisineImage(cartItem.getCuisineImage());
                 item.setUnitPrice(cartItem.getUnitPrice());
                 item.setQuantity(cartItem.getQuantity());
+                item.setLockedQuantity(cartItem.getLockedQuantity());
                 item.setSubtotalAmount(cartItem.getSubtotalAmount());
                 item.setAddUserId(cartItem.getAddUserId());
                 item.setAddUserPhone(cartItem.getAddUserPhone());
@@ -191,24 +192,38 @@ public class CartServiceImpl implements CartService {
                 .orElse(null);
 
         if (existingItem != null) {
-            // 商品已存在,不允许重复添加
-            throw new RuntimeException("已添加过本商品");
-        }
-
-        // 添加新商品
-        CartItemDTO newItem = new CartItemDTO();
-        newItem.setCuisineId(cuisine.getId());
-        newItem.setCuisineName(cuisine.getName());
-        newItem.setCuisineType(cuisine.getCuisineType());
-        newItem.setCuisineImage(cuisine.getImages());
-        newItem.setUnitPrice(cuisine.getTotalPrice());
-        newItem.setQuantity(dto.getQuantity());
-        newItem.setSubtotalAmount(cuisine.getTotalPrice()
-                .multiply(BigDecimal.valueOf(dto.getQuantity())));
-        newItem.setAddUserId(userId);
-        newItem.setAddUserPhone(userPhone);
-        newItem.setRemark(dto.getRemark());
-        items.add(newItem);
+            // 商品已存在
+            Integer lockedQuantity = existingItem.getLockedQuantity();
+            if (lockedQuantity != null && lockedQuantity > 0) {
+                // 如果商品有锁定数量(已下单),将新数量叠加到当前数量和锁定数量上
+                Integer newQuantity = existingItem.getQuantity() + dto.getQuantity();
+                Integer newLockedQuantity = lockedQuantity + dto.getQuantity();
+                existingItem.setQuantity(newQuantity);
+                existingItem.setLockedQuantity(newLockedQuantity);
+                existingItem.setSubtotalAmount(existingItem.getUnitPrice()
+                        .multiply(BigDecimal.valueOf(newQuantity)));
+                log.info("商品已存在且有锁定数量,叠加数量, cuisineId={}, oldQuantity={}, newQuantity={}, oldLockedQuantity={}, newLockedQuantity={}", 
+                        dto.getCuisineId(), existingItem.getQuantity() - dto.getQuantity(), newQuantity, lockedQuantity, newLockedQuantity);
+            } else {
+                // 商品已存在但没有锁定数量,不允许重复添加
+                throw new RuntimeException("已添加过本商品");
+            }
+        } else {
+            // 添加新商品
+            CartItemDTO newItem = new CartItemDTO();
+            newItem.setCuisineId(cuisine.getId());
+            newItem.setCuisineName(cuisine.getName());
+            newItem.setCuisineType(cuisine.getCuisineType());
+            newItem.setCuisineImage(cuisine.getImages());
+            newItem.setUnitPrice(cuisine.getTotalPrice());
+            newItem.setQuantity(dto.getQuantity());
+            newItem.setSubtotalAmount(cuisine.getTotalPrice()
+                    .multiply(BigDecimal.valueOf(dto.getQuantity())));
+            newItem.setAddUserId(userId);
+            newItem.setAddUserPhone(userPhone);
+            newItem.setRemark(dto.getRemark());
+            items.add(newItem);
+        }
 
         // 重新计算总金额和总数量
         BigDecimal totalAmount = items.stream()
@@ -244,6 +259,14 @@ public class CartServiceImpl implements CartService {
                 .orElse(null);
 
         if (item != null) {
+            // 检查锁定数量:不允许将数量减少到小于锁定数量
+            Integer lockedQuantity = item.getLockedQuantity();
+            if (lockedQuantity != null && lockedQuantity > 0) {
+                if (quantity < lockedQuantity) {
+                    throw new RuntimeException("商品数量不能少于锁定数量(" + lockedQuantity + "),该数量已下单");
+                }
+            }
+            
             item.setQuantity(quantity);
             item.setSubtotalAmount(item.getUnitPrice()
                     .multiply(BigDecimal.valueOf(quantity)));
@@ -269,7 +292,21 @@ public class CartServiceImpl implements CartService {
         log.info("删除购物车商品, tableId={}, cuisineId={}", tableId, cuisineId);
         CartDTO cart = getCart(tableId);
         List<CartItemDTO> items = cart.getItems();
-        items.removeIf(item -> item.getCuisineId().equals(cuisineId));
+        
+        // 检查是否有锁定数量,如果有则不允许删除
+        CartItemDTO item = items.stream()
+                .filter(i -> i.getCuisineId().equals(cuisineId))
+                .findFirst()
+                .orElse(null);
+        
+        if (item != null) {
+            Integer lockedQuantity = item.getLockedQuantity();
+            if (lockedQuantity != null && lockedQuantity > 0) {
+                throw new RuntimeException("商品已下单,锁定数量为 " + lockedQuantity + ",不允许删除");
+            }
+        }
+        
+        items.removeIf(i -> i.getCuisineId().equals(cuisineId));
 
         // 重新计算总金额和总数量
         BigDecimal totalAmount = items.stream()
@@ -640,6 +677,50 @@ public class CartServiceImpl implements CartService {
         return cart;
     }
 
+    @Override
+    public CartDTO lockCartItems(Integer tableId) {
+        log.info("锁定购物车商品数量, tableId={}", tableId);
+        
+        // 获取购物车
+        CartDTO cart = getCart(tableId);
+        List<CartItemDTO> items = cart.getItems();
+        
+        if (items == null || items.isEmpty()) {
+            log.warn("购物车为空,无需锁定, tableId={}", tableId);
+            return cart;
+        }
+        
+        // 遍历所有商品,将当前数量设置为锁定数量
+        boolean hasChanges = false;
+        for (CartItemDTO item : items) {
+            Integer currentQuantity = item.getQuantity();
+            Integer lockedQuantity = item.getLockedQuantity();
+            
+            if (currentQuantity != null && currentQuantity > 0) {
+                if (lockedQuantity == null || lockedQuantity == 0) {
+                    // 如果还没有锁定数量,将当前数量设置为锁定数量
+                    item.setLockedQuantity(currentQuantity);
+                    hasChanges = true;
+                    log.info("锁定商品数量, cuisineId={}, quantity={}", item.getCuisineId(), currentQuantity);
+                } else if (currentQuantity > lockedQuantity) {
+                    // 如果已有锁定数量,且当前数量大于锁定数量(再次下单的情况),将新增数量累加到锁定数量
+                    Integer newLockedQuantity = lockedQuantity + (currentQuantity - lockedQuantity);
+                    item.setLockedQuantity(newLockedQuantity);
+                    hasChanges = true;
+                    log.info("更新锁定商品数量, cuisineId={}, oldLockedQuantity={}, newLockedQuantity={}", 
+                            item.getCuisineId(), lockedQuantity, newLockedQuantity);
+                }
+            }
+        }
+        
+        // 如果有变化,保存购物车
+        if (hasChanges) {
+            saveCart(cart);
+        }
+        
+        return cart;
+    }
+
     /**
      * 保存购物车到Redis和数据库(双写策略)
      */
@@ -693,6 +774,7 @@ public class CartServiceImpl implements CartService {
                     storeCart.setCuisineImage(item.getCuisineImage());
                     storeCart.setUnitPrice(item.getUnitPrice());
                     storeCart.setQuantity(item.getQuantity());
+                    storeCart.setLockedQuantity(item.getLockedQuantity());
                     storeCart.setSubtotalAmount(item.getSubtotalAmount());
                     storeCart.setAddUserId(item.getAddUserId());
                     storeCart.setAddUserPhone(item.getAddUserPhone());

+ 87 - 0
alien-dining/src/main/java/shop/alien/dining/service/impl/StoreOrderServiceImpl.java

@@ -9,6 +9,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.StringUtils;
+import shop.alien.dining.config.BaseRedisService;
 import shop.alien.dining.service.CartService;
 import shop.alien.dining.service.StoreOrderService;
 import shop.alien.dining.util.TokenUtil;
@@ -43,6 +44,8 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
     private final CartService cartService;
     private final StoreOrderLockMapper storeOrderLockMapper;
     private final StoreCouponUsageMapper storeCouponUsageMapper;
+    private final StoreCartMapper storeCartMapper;
+    private final BaseRedisService baseRedisService;
 
     @Override
     public StoreOrder createOrder(CreateOrderDTO dto) {
@@ -203,6 +206,9 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
         table.setStatus(1); // 就餐中
         storeTableMapper.updateById(table);
 
+        // 锁定购物车商品数量(禁止减少或删除已下单的商品)
+        cartService.lockCartItems(dto.getTableId());
+
         // 下单后不清空购物车,允许加餐(加餐时添加到同一订单)
         // 只有在支付完成后才清空购物车
 
@@ -578,6 +584,87 @@ public class StoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper, StoreOr
         return order;
     }
 
+    @Override
+    public boolean resetTable(Integer tableId) {
+        log.info("管理员重置餐桌, tableId={}", tableId);
+        
+        // 验证餐桌是否存在
+        StoreTable table = storeTableMapper.selectById(tableId);
+        if (table == null) {
+            throw new RuntimeException("餐桌不存在");
+        }
+        
+        // 1. 删除购物车数据(逻辑删除)
+        LambdaQueryWrapper<StoreCart> cartWrapper = new LambdaQueryWrapper<>();
+        cartWrapper.eq(StoreCart::getTableId, tableId);
+        cartWrapper.eq(StoreCart::getDeleteFlag, 0);
+        List<StoreCart> cartList = storeCartMapper.selectList(cartWrapper);
+        if (cartList != null && !cartList.isEmpty()) {
+            Date now = new Date();
+            for (StoreCart cart : cartList) {
+                cart.setDeleteFlag(1);
+                cart.setUpdatedTime(now);
+                storeCartMapper.updateById(cart);
+            }
+            log.info("删除购物车数据, tableId={}, count={}", tableId, cartList.size());
+        }
+        
+        // 2. 删除订单数据(逻辑删除,包括订单明细)
+        LambdaQueryWrapper<StoreOrder> orderWrapper = new LambdaQueryWrapper<>();
+        orderWrapper.eq(StoreOrder::getTableId, tableId);
+        orderWrapper.eq(StoreOrder::getDeleteFlag, 0);
+        List<StoreOrder> orderList = this.list(orderWrapper);
+        if (orderList != null && !orderList.isEmpty()) {
+            Date now = new Date();
+            for (StoreOrder order : orderList) {
+                // 删除订单明细(逻辑删除)
+                LambdaQueryWrapper<StoreOrderDetail> detailWrapper = new LambdaQueryWrapper<>();
+                detailWrapper.eq(StoreOrderDetail::getOrderId, order.getId());
+                detailWrapper.eq(StoreOrderDetail::getDeleteFlag, 0);
+                List<StoreOrderDetail> detailList = orderDetailMapper.selectList(detailWrapper);
+                if (detailList != null && !detailList.isEmpty()) {
+                    for (StoreOrderDetail detail : detailList) {
+                        detail.setDeleteFlag(1);
+                        detail.setUpdatedTime(now);
+                        orderDetailMapper.updateById(detail);
+                    }
+                    log.info("删除订单明细, orderId={}, count={}", order.getId(), detailList.size());
+                }
+                
+                // 删除订单(逻辑删除)
+                order.setDeleteFlag(1);
+                order.setUpdatedTime(now);
+                this.updateById(order);
+            }
+            log.info("删除订单数据, tableId={}, count={}", tableId, orderList.size());
+        }
+        
+        // 3. 清空Redis中的购物车缓存
+        String cartKey = "cart:table:" + tableId;
+        baseRedisService.delete(cartKey);
+        log.info("清空Redis购物车缓存, tableId={}", tableId);
+        
+        // 4. 清除优惠券使用标记
+        cartService.clearCouponUsed(tableId);
+        log.info("清除优惠券使用标记, tableId={}", tableId);
+        
+        // 5. 重置餐桌表
+        table.setCurrentOrderId(null);
+        table.setCurrentCouponId(null);
+        table.setCartItemCount(0);
+        table.setCartTotalAmount(BigDecimal.ZERO);
+        table.setStatus(0); // 空闲
+        table.setUpdatedTime(new Date());
+        Integer userId = TokenUtil.getCurrentUserId();
+        if (userId != null) {
+            table.setUpdatedUserId(userId);
+        }
+        storeTableMapper.updateById(table);
+        log.info("重置餐桌表完成, tableId={}", tableId);
+        
+        return true;
+    }
+
     /**
      * 生成订单号
      */

+ 4 - 0
alien-entity/src/main/java/shop/alien/entity/store/StoreCart.java

@@ -54,6 +54,10 @@ public class StoreCart {
     @TableField("quantity")
     private Integer quantity;
 
+    @ApiModelProperty(value = "锁定数量(下单时锁定的数量,不允许减少或删除)")
+    @TableField("locked_quantity")
+    private Integer lockedQuantity;
+
     @ApiModelProperty(value = "小计金额")
     @TableField("subtotal_amount")
     private BigDecimal subtotalAmount;

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/store/dto/CartItemDTO.java

@@ -34,6 +34,9 @@ public class CartItemDTO {
     @ApiModelProperty(value = "数量")
     private Integer quantity;
 
+    @ApiModelProperty(value = "锁定数量(下单时锁定的数量,不允许减少或删除)")
+    private Integer lockedQuantity;
+
     @ApiModelProperty(value = "小计金额")
     private BigDecimal subtotalAmount;