feat(格口管理): 新增可用暂存格口查询功能并优化密码生成逻辑
新增AvailableStorageCellDTO用于返回可用暂存格口信息 添加查询指定门店下可用暂存格口的接口 优化格口密码生成逻辑确保在门店内唯一 添加重复请求过滤防止并发问题
This commit is contained in:
parent
1ab3d7ea13
commit
fa3637ffb5
|
|
@ -50,4 +50,5 @@ nbdist/
|
|||
|
||||
/.svn
|
||||
|
||||
.trae
|
||||
.trae
|
||||
/.claude
|
||||
|
|
|
|||
|
|
@ -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<List<AvailableStorageCellDTO>> getAvailableStorageCells(@RequestParam Long shopId) {
|
||||
try {
|
||||
if (shopId == null) {
|
||||
throw new ApiException(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, "shopId不能为空");
|
||||
}
|
||||
|
||||
List<AvailableStorageCellDTO> 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<CabinetCellDTO> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<CabinetCellWithOrderCountDTO> getCabinetCellList(SearchCabinetCellWithOrdersQuery<CabinetCellEntity> query) {
|
||||
Page<CabinetCellWithOrderCountDTO> 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();
|
||||
|
|
|
|||
|
|
@ -119,4 +119,10 @@ public interface CabinetCellMapper extends BaseMapper<CabinetCellEntity> {
|
|||
"INNER JOIN ab98_user au ON so.name = au.name AND so.mobile = au.tel AND au.deleted = 0 " +
|
||||
"${ew.customSqlSegment}")
|
||||
List<CabinetCellEntity> 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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字段
|
||||
}
|
||||
|
|
@ -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<AvailableStorageCellDTO> getAvailableStorageCells(Long shopId) {
|
||||
// 参数校验
|
||||
if (shopId == null) {
|
||||
throw new IllegalArgumentException("shopId不能为空");
|
||||
}
|
||||
|
||||
// 1. 查询shopId下的所有智能柜
|
||||
QueryWrapper<SmartCabinetEntity> smartCabinetWrapper = new QueryWrapper<>();
|
||||
smartCabinetWrapper.eq("shop_id", shopId)
|
||||
.eq("deleted", false);
|
||||
List<SmartCabinetEntity> smartCabinets = smartCabinetService.list(smartCabinetWrapper);
|
||||
|
||||
if (smartCabinets.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// 创建柜子ID到柜子实体的映射
|
||||
Map<Long, SmartCabinetEntity> cabinetMap = smartCabinets.stream()
|
||||
.collect(Collectors.toMap(SmartCabinetEntity::getCabinetId, Function.identity()));
|
||||
|
||||
// 2. 提取所有cabinet_id
|
||||
List<Long> cabinetIds = new ArrayList<>(cabinetMap.keySet());
|
||||
|
||||
// 3. 查询满足条件的格口
|
||||
QueryWrapper<CabinetCellEntity> 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<CabinetCellEntity> availableCells = cabinetCellService.list(cellWrapper);
|
||||
|
||||
// 4. 转换为DTO列表
|
||||
return availableCells.stream()
|
||||
.map(cell -> new AvailableStorageCellDTO(cell, cabinetMap.get(cell.getCabinetId())))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue