feat(格口管理): 新增可用暂存格口查询功能并优化密码生成逻辑
新增AvailableStorageCellDTO用于返回可用暂存格口信息 添加查询指定门店下可用暂存格口的接口 优化格口密码生成逻辑确保在门店内唯一 添加重复请求过滤防止并发问题
This commit is contained in:
parent
1ab3d7ea13
commit
fa3637ffb5
|
|
@ -51,3 +51,4 @@ nbdist/
|
||||||
/.svn
|
/.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.OpenCellByPasswordCommand;
|
||||||
import com.agileboot.domain.cabinet.cell.command.StoreItemToCellCommand;
|
import com.agileboot.domain.cabinet.cell.command.StoreItemToCellCommand;
|
||||||
import com.agileboot.domain.cabinet.cell.dto.CabinetCellDTO;
|
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.db.CabinetCellEntity;
|
||||||
import com.agileboot.domain.cabinet.cell.model.CabinetCellModel;
|
import com.agileboot.domain.cabinet.cell.model.CabinetCellModel;
|
||||||
import com.agileboot.domain.cabinet.cell.model.CabinetCellModelFactory;
|
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.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import com.agileboot.domain.common.cache.CaffeineCacheService;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -50,6 +52,7 @@ public class CabinetCellController {
|
||||||
private final SmartCabinetModelFactory smartCabinetModelFactory;
|
private final SmartCabinetModelFactory smartCabinetModelFactory;
|
||||||
private final CabinetMainboardModelFactory cabinetMainboardModelFactory;
|
private final CabinetMainboardModelFactory cabinetMainboardModelFactory;
|
||||||
private final CabinetCellApplicationService cabinetCellApplicationService;
|
private final CabinetCellApplicationService cabinetCellApplicationService;
|
||||||
|
private final CaffeineCacheService caffeineCacheService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 管理员获取机柜格口列表
|
* 管理员获取机柜格口列表
|
||||||
|
|
@ -71,6 +74,26 @@ public class CabinetCellController {
|
||||||
return ResponseDTO.ok(smartCabinetApplicationService.getRentingCabinetDetail(shopId));
|
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
|
* @param corpid
|
||||||
|
|
@ -184,7 +207,23 @@ public class CabinetCellController {
|
||||||
@Operation(summary = "存入物品分配格口")
|
@Operation(summary = "存入物品分配格口")
|
||||||
@PostMapping("/storeItem")
|
@PostMapping("/storeItem")
|
||||||
public ResponseDTO<CabinetCellDTO> storeItem(@RequestBody StoreItemToCellCommand command) {
|
public ResponseDTO<CabinetCellDTO> storeItem(@RequestBody StoreItemToCellCommand command) {
|
||||||
|
// 构造缓存键
|
||||||
|
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);
|
CabinetCellDTO result = cabinetCellApplicationService.storeItemToCell(command);
|
||||||
return ResponseDTO.ok(result);
|
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.command.StoreItemToCellCommand;
|
||||||
import com.agileboot.domain.cabinet.cell.db.CabinetCellEntity;
|
import com.agileboot.domain.cabinet.cell.db.CabinetCellEntity;
|
||||||
import com.agileboot.domain.cabinet.cell.db.CabinetCellService;
|
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.dto.CabinetCellDTO;
|
||||||
import com.agileboot.domain.cabinet.cell.model.CabinetCellModel;
|
import com.agileboot.domain.cabinet.cell.model.CabinetCellModel;
|
||||||
import com.agileboot.domain.cabinet.cell.model.CabinetCellModelFactory;
|
import com.agileboot.domain.cabinet.cell.model.CabinetCellModelFactory;
|
||||||
|
|
@ -47,6 +48,7 @@ public class CabinetCellApplicationService {
|
||||||
private final SmartCabinetModelFactory smartCabinetModelFactory;
|
private final SmartCabinetModelFactory smartCabinetModelFactory;
|
||||||
private final SmartCabinetService smartCabinetService;
|
private final SmartCabinetService smartCabinetService;
|
||||||
private final CabinetMainboardModelFactory cabinetMainboardModelFactory;
|
private final CabinetMainboardModelFactory cabinetMainboardModelFactory;
|
||||||
|
private final CabinetCellMapper cabinetCellMapper;
|
||||||
|
|
||||||
public PageDTO<CabinetCellWithOrderCountDTO> getCabinetCellList(SearchCabinetCellWithOrdersQuery<CabinetCellEntity> query) {
|
public PageDTO<CabinetCellWithOrderCountDTO> getCabinetCellList(SearchCabinetCellWithOrdersQuery<CabinetCellEntity> query) {
|
||||||
Page<CabinetCellWithOrderCountDTO> page = cabinetCellService.getCellListWithOrders(query);
|
Page<CabinetCellWithOrderCountDTO> page = cabinetCellService.getCellListWithOrders(query);
|
||||||
|
|
@ -140,7 +142,7 @@ public class CabinetCellApplicationService {
|
||||||
|
|
||||||
public void openCellByPassword(OpenCellByPasswordCommand command) {
|
public void openCellByPassword(OpenCellByPasswordCommand command) {
|
||||||
// 根据密码查找格口
|
// 根据密码查找格口
|
||||||
CabinetCellEntity cell;
|
CabinetCellEntity cell = null;
|
||||||
if (command.getShopId() != null) {
|
if (command.getShopId() != null) {
|
||||||
// 在指定店铺中查找匹配密码的格口
|
// 在指定店铺中查找匹配密码的格口
|
||||||
// 先获取该店铺下的柜子ID列表
|
// 先获取该店铺下的柜子ID列表
|
||||||
|
|
@ -161,12 +163,6 @@ public class CabinetCellApplicationService {
|
||||||
.eq(CabinetCellEntity::getPassword, command.getPassword())
|
.eq(CabinetCellEntity::getPassword, command.getPassword())
|
||||||
.eq(CabinetCellEntity::getDeleted, false)
|
.eq(CabinetCellEntity::getDeleted, false)
|
||||||
.one();
|
.one();
|
||||||
} else {
|
|
||||||
// 全局查找匹配密码的格口
|
|
||||||
cell = cabinetCellService.lambdaQuery()
|
|
||||||
.eq(CabinetCellEntity::getPassword, command.getPassword())
|
|
||||||
.eq(CabinetCellEntity::getDeleted, false)
|
|
||||||
.one();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cell == null) {
|
if (cell == null) {
|
||||||
|
|
@ -225,17 +221,33 @@ public class CabinetCellApplicationService {
|
||||||
.eq(CabinetCellEntity::getUsageStatus, 1) // 空闲
|
.eq(CabinetCellEntity::getUsageStatus, 1) // 空闲
|
||||||
.eq(CabinetCellEntity::getAvailableStatus, 1) // 正常
|
.eq(CabinetCellEntity::getAvailableStatus, 1) // 正常
|
||||||
.eq(CabinetCellEntity::getDeleted, false)
|
.eq(CabinetCellEntity::getDeleted, false)
|
||||||
|
.last("limit 1")
|
||||||
.one();
|
.one();
|
||||||
|
|
||||||
if (cell == null) {
|
if (cell == null) {
|
||||||
throw new RuntimeException("找不到符合条件的可用格口");
|
throw new RuntimeException("找不到符合条件的可用格口");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成4位随机数字密码
|
// 加载格口模型
|
||||||
String password = generateFourDigitPassword();
|
|
||||||
|
|
||||||
// 加载格口模型并更新密码和状态
|
|
||||||
CabinetCellModel cellModel = cabinetCellModelFactory.loadById(cell.getCellId());
|
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.setPassword(password);
|
||||||
cellModel.setUsageStatus(2); // 设置为已占用
|
cellModel.setUsageStatus(2); // 设置为已占用
|
||||||
cellModel.updateById();
|
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 " +
|
"INNER JOIN ab98_user au ON so.name = au.name AND so.mobile = au.tel AND au.deleted = 0 " +
|
||||||
"${ew.customSqlSegment}")
|
"${ew.customSqlSegment}")
|
||||||
List<CabinetCellEntity> selectRentedCellsByAb98UserIdAndCorpid(@Param(Constants.WRAPPER) Wrapper<?> queryWrapper);
|
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.SmartCabinetEntity;
|
||||||
import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetService;
|
import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetService;
|
||||||
import com.agileboot.domain.cabinet.smartCabinet.dto.SmartCabinetDTO;
|
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.SmartCabinetModel;
|
||||||
import com.agileboot.domain.cabinet.smartCabinet.model.SmartCabinetModelFactory;
|
import com.agileboot.domain.cabinet.smartCabinet.model.SmartCabinetModelFactory;
|
||||||
import com.agileboot.domain.cabinet.smartCabinet.query.SearchSmartCabinetQuery;
|
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.math.BigDecimal;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
@ -388,4 +392,51 @@ public class SmartCabinetApplicationService {
|
||||||
}
|
}
|
||||||
return result;
|
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