From fa3637ffb5f499893515656ea3a4224f423581f9 Mon Sep 17 00:00:00 2001 From: dzq Date: Fri, 19 Dec 2025 09:08:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=A0=BC=E5=8F=A3=E7=AE=A1=E7=90=86):=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8F=AF=E7=94=A8=E6=9A=82=E5=AD=98=E6=A0=BC?= =?UTF-8?q?=E5=8F=A3=E6=9F=A5=E8=AF=A2=E5=8A=9F=E8=83=BD=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=AF=86=E7=A0=81=E7=94=9F=E6=88=90=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增AvailableStorageCellDTO用于返回可用暂存格口信息 添加查询指定门店下可用暂存格口的接口 优化格口密码生成逻辑确保在门店内唯一 添加重复请求过滤防止并发问题 --- .gitignore | 3 +- .../api/controller/CabinetCellController.java | 43 +++++++++- .../cell/CabinetCellApplicationService.java | 34 +++++--- .../cabinet/cell/db/CabinetCellMapper.java | 6 ++ .../cell/dto/AvailableStorageCellDTO.java | 83 +++++++++++++++++++ .../SmartCabinetApplicationService.java | 51 ++++++++++++ 6 files changed, 206 insertions(+), 14 deletions(-) create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/dto/AvailableStorageCellDTO.java diff --git a/.gitignore b/.gitignore index 6f9b1ab..02b878d 100644 --- a/.gitignore +++ b/.gitignore @@ -50,4 +50,5 @@ nbdist/ /.svn -.trae \ No newline at end of file +.trae +/.claude diff --git a/agileboot-api/src/main/java/com/agileboot/api/controller/CabinetCellController.java b/agileboot-api/src/main/java/com/agileboot/api/controller/CabinetCellController.java index a484650..be1cdee 100644 --- a/agileboot-api/src/main/java/com/agileboot/api/controller/CabinetCellController.java +++ b/agileboot-api/src/main/java/com/agileboot/api/controller/CabinetCellController.java @@ -10,6 +10,7 @@ import com.agileboot.domain.cabinet.cell.CabinetCellApplicationService; import com.agileboot.domain.cabinet.cell.command.OpenCellByPasswordCommand; import com.agileboot.domain.cabinet.cell.command.StoreItemToCellCommand; import com.agileboot.domain.cabinet.cell.dto.CabinetCellDTO; +import com.agileboot.domain.cabinet.cell.dto.AvailableStorageCellDTO; import com.agileboot.domain.cabinet.cell.db.CabinetCellEntity; import com.agileboot.domain.cabinet.cell.model.CabinetCellModel; import com.agileboot.domain.cabinet.cell.model.CabinetCellModelFactory; @@ -32,6 +33,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.web.bind.annotation.*; +import com.agileboot.domain.common.cache.CaffeineCacheService; import java.util.Date; import java.util.List; @@ -50,6 +52,7 @@ public class CabinetCellController { private final SmartCabinetModelFactory smartCabinetModelFactory; private final CabinetMainboardModelFactory cabinetMainboardModelFactory; private final CabinetCellApplicationService cabinetCellApplicationService; + private final CaffeineCacheService caffeineCacheService; /** * 管理员获取机柜格口列表 @@ -71,6 +74,26 @@ public class CabinetCellController { return ResponseDTO.ok(smartCabinetApplicationService.getRentingCabinetDetail(shopId)); } + @Operation(summary = "获取可使用的暂存柜格口列表", + description = "查询条件:is_rented=0(未租用),usage_status=1(空闲),available_status=1(正常)") + @GetMapping("/availableStorageCells") + public ResponseDTO> getAvailableStorageCells(@RequestParam Long shopId) { + try { + if (shopId == null) { + throw new ApiException(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, "shopId不能为空"); + } + + List result = smartCabinetApplicationService.getAvailableStorageCells(shopId); + return ResponseDTO.ok(result); + } catch (IllegalArgumentException e) { + log.error("获取可用暂存格口列表参数错误", e); + throw new ApiException(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, e.getMessage()); + } catch (Exception e) { + log.error("获取可用暂存格口列表失败", e); + throw new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "获取可用暂存格口列表失败"); + } + } + /** * 用户获取自己租用的机柜格口列表 * @param corpid @@ -184,7 +207,23 @@ public class CabinetCellController { @Operation(summary = "存入物品分配格口") @PostMapping("/storeItem") public ResponseDTO storeItem(@RequestBody StoreItemToCellCommand command) { - CabinetCellDTO result = cabinetCellApplicationService.storeItemToCell(command); - return ResponseDTO.ok(result); + // 构造缓存键 + String cacheKey = "storeItem:" + command.getShopId() + ":" + command.getCellType(); + + // 重复请求过滤 + Boolean cached = caffeineCacheService.apiCache.get(cacheKey); + if (cached != null && cached) { + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "重复请求,请稍后重试")); + } + // 放入缓存,标记请求已处理 + caffeineCacheService.apiCache.put(cacheKey, Boolean.TRUE); + + try { + CabinetCellDTO result = cabinetCellApplicationService.storeItemToCell(command); + return ResponseDTO.ok(result); + } catch (Exception e) { + log.error("storeItem error: ", e); + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, e)); + } } } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/CabinetCellApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/CabinetCellApplicationService.java index f6052ee..fabca1b 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/CabinetCellApplicationService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/CabinetCellApplicationService.java @@ -12,6 +12,7 @@ import com.agileboot.domain.cabinet.cell.command.UpdateCabinetCellCommand; import com.agileboot.domain.cabinet.cell.command.StoreItemToCellCommand; import com.agileboot.domain.cabinet.cell.db.CabinetCellEntity; import com.agileboot.domain.cabinet.cell.db.CabinetCellService; +import com.agileboot.domain.cabinet.cell.db.CabinetCellMapper; import com.agileboot.domain.cabinet.cell.dto.CabinetCellDTO; import com.agileboot.domain.cabinet.cell.model.CabinetCellModel; import com.agileboot.domain.cabinet.cell.model.CabinetCellModelFactory; @@ -47,6 +48,7 @@ public class CabinetCellApplicationService { private final SmartCabinetModelFactory smartCabinetModelFactory; private final SmartCabinetService smartCabinetService; private final CabinetMainboardModelFactory cabinetMainboardModelFactory; + private final CabinetCellMapper cabinetCellMapper; public PageDTO getCabinetCellList(SearchCabinetCellWithOrdersQuery query) { Page page = cabinetCellService.getCellListWithOrders(query); @@ -140,7 +142,7 @@ public class CabinetCellApplicationService { public void openCellByPassword(OpenCellByPasswordCommand command) { // 根据密码查找格口 - CabinetCellEntity cell; + CabinetCellEntity cell = null; if (command.getShopId() != null) { // 在指定店铺中查找匹配密码的格口 // 先获取该店铺下的柜子ID列表 @@ -161,12 +163,6 @@ public class CabinetCellApplicationService { .eq(CabinetCellEntity::getPassword, command.getPassword()) .eq(CabinetCellEntity::getDeleted, false) .one(); - } else { - // 全局查找匹配密码的格口 - cell = cabinetCellService.lambdaQuery() - .eq(CabinetCellEntity::getPassword, command.getPassword()) - .eq(CabinetCellEntity::getDeleted, false) - .one(); } if (cell == null) { @@ -225,17 +221,33 @@ public class CabinetCellApplicationService { .eq(CabinetCellEntity::getUsageStatus, 1) // 空闲 .eq(CabinetCellEntity::getAvailableStatus, 1) // 正常 .eq(CabinetCellEntity::getDeleted, false) + .last("limit 1") .one(); if (cell == null) { throw new RuntimeException("找不到符合条件的可用格口"); } - // 生成4位随机数字密码 - String password = generateFourDigitPassword(); - - // 加载格口模型并更新密码和状态 + // 加载格口模型 CabinetCellModel cellModel = cabinetCellModelFactory.loadById(cell.getCellId()); + + // 生成4位随机数字密码,并确保在shopId下不重复,最多尝试20次 + String password = null; + int maxAttempts = 20; + for (int i = 0; i < maxAttempts; i++) { + String candidate = generateFourDigitPassword(); + int count = cabinetCellMapper.countByPasswordAndShopId(candidate, command.getShopId()); + if (count == 0) { + password = candidate; + break; + } + } + + if (password == null) { + throw new RuntimeException("无法生成唯一密码,请稍后重试"); + } + + // 更新密码和状态 cellModel.setPassword(password); cellModel.setUsageStatus(2); // 设置为已占用 cellModel.updateById(); diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellMapper.java b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellMapper.java index f275a9b..8a081b4 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellMapper.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellMapper.java @@ -119,4 +119,10 @@ public interface CabinetCellMapper extends BaseMapper { "INNER JOIN ab98_user au ON so.name = au.name AND so.mobile = au.tel AND au.deleted = 0 " + "${ew.customSqlSegment}") List selectRentedCellsByAb98UserIdAndCorpid(@Param(Constants.WRAPPER) Wrapper queryWrapper); + + @Select("SELECT COUNT(1) FROM cabinet_cell cc " + + "INNER JOIN smart_cabinet sc ON cc.cabinet_id = sc.cabinet_id " + + "WHERE cc.password = #{password} AND sc.shop_id = #{shopId} " + + "AND cc.deleted = 0 AND sc.deleted = 0") + int countByPasswordAndShopId(@Param("password") String password, @Param("shopId") Long shopId); } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/dto/AvailableStorageCellDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/dto/AvailableStorageCellDTO.java new file mode 100644 index 0000000..1b29092 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/dto/AvailableStorageCellDTO.java @@ -0,0 +1,83 @@ +package com.agileboot.domain.cabinet.cell.dto; + +import cn.hutool.core.bean.BeanUtil; +import com.agileboot.common.annotation.ExcelColumn; +import com.agileboot.common.annotation.ExcelSheet; +import com.agileboot.domain.cabinet.cell.db.CabinetCellEntity; +import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetEntity; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 可用暂存格口DTO + * 用于返回可使用的暂存柜格口列表 + */ +@ExcelSheet(name = "可用暂存格口列表") +@Data +public class AvailableStorageCellDTO { + + public AvailableStorageCellDTO(CabinetCellEntity entity, SmartCabinetEntity cabinet) { + if (entity != null) { + BeanUtil.copyProperties(entity, this); + if (cabinet != null) { + this.cabinetName = cabinet.getCabinetName(); + } + // 判断password是否为空 + this.hasPassword = entity.getPassword() != null && !entity.getPassword().trim().isEmpty(); + } + } + + @ExcelColumn(name = "格口ID") + private Long cellId; + + @ExcelColumn(name = "关联柜机ID") + private Long cabinetId; + + @ExcelColumn(name = "柜子名称") + private String cabinetName; + + @ExcelColumn(name = "关联主板ID") + private Long mainboardId; + + @ExcelColumn(name = "格口号") + private Integer cellNo; + + @ExcelColumn(name = "针脚序号") + private Integer pinNo; + + @ExcelColumn(name = "库存数量") + private Integer stock; + + @ExcelColumn(name = "格口租用价格") + private BigDecimal cellPrice; + + @ExcelColumn(name = "是否已租用") + private Integer isRented; + + @ExcelColumn(name = "格口类型") + private Integer cellType; + + @ExcelColumn(name = "使用状态") + private Integer usageStatus; + + @ExcelColumn(name = "可用状态") + private Integer availableStatus; + + @ExcelColumn(name = "是否有密码") + private Boolean hasPassword; + + @ExcelColumn(name = "关联商品ID") + private Long goodsId; + + @ExcelColumn(name = "商品名称") + private String goodsName; + + @ExcelColumn(name = "商品价格") + private BigDecimal price; + + @ExcelColumn(name = "封面图URL") + private String coverImg; + + // 注意:不包含password字段 +} \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/smartCabinet/SmartCabinetApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/smartCabinet/SmartCabinetApplicationService.java index ba8b687..0137f56 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/smartCabinet/SmartCabinetApplicationService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/smartCabinet/SmartCabinetApplicationService.java @@ -21,6 +21,7 @@ import com.agileboot.domain.cabinet.smartCabinet.command.UpdateSmartCabinetComma import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetEntity; import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetService; import com.agileboot.domain.cabinet.smartCabinet.dto.SmartCabinetDTO; +import com.agileboot.domain.cabinet.cell.dto.AvailableStorageCellDTO; import com.agileboot.domain.cabinet.smartCabinet.model.SmartCabinetModel; import com.agileboot.domain.cabinet.smartCabinet.model.SmartCabinetModelFactory; import com.agileboot.domain.cabinet.smartCabinet.query.SearchSmartCabinetQuery; @@ -33,8 +34,11 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.function.Function; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -388,4 +392,51 @@ public class SmartCabinetApplicationService { } return result; } + + /** + * 获取可用暂存格口列表 + * 查询条件:is_rented=0(未租用),usage_status=1(空闲),available_status=1(正常) + * + * @param shopId 门店ID + * @return 可使用的暂存格口列表(平铺结构,不按柜子分组) + */ + public List getAvailableStorageCells(Long shopId) { + // 参数校验 + if (shopId == null) { + throw new IllegalArgumentException("shopId不能为空"); + } + + // 1. 查询shopId下的所有智能柜 + QueryWrapper smartCabinetWrapper = new QueryWrapper<>(); + smartCabinetWrapper.eq("shop_id", shopId) + .eq("deleted", false); + List smartCabinets = smartCabinetService.list(smartCabinetWrapper); + + if (smartCabinets.isEmpty()) { + return new ArrayList<>(); + } + + // 创建柜子ID到柜子实体的映射 + Map cabinetMap = smartCabinets.stream() + .collect(Collectors.toMap(SmartCabinetEntity::getCabinetId, Function.identity())); + + // 2. 提取所有cabinet_id + List cabinetIds = new ArrayList<>(cabinetMap.keySet()); + + // 3. 查询满足条件的格口 + QueryWrapper cellWrapper = new QueryWrapper<>(); + cellWrapper.in("cabinet_id", cabinetIds) + .eq("is_rented", 0) + .eq("usage_status", 1) + .eq("available_status", 1) + .eq("deleted", false) + .orderByAsc("cell_id"); + + List availableCells = cabinetCellService.list(cellWrapper); + + // 4. 转换为DTO列表 + return availableCells.stream() + .map(cell -> new AvailableStorageCellDTO(cell, cabinetMap.get(cell.getCabinetId()))) + .collect(Collectors.toList()); + } }