From 7f975aac29b378c9d0ac2d36f6c8e53a04f77779 Mon Sep 17 00:00:00 2001 From: dzq Date: Fri, 9 Jan 2026 08:26:16 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(agent):=20=E6=96=B0=E5=A2=9EAg?= =?UTF-8?q?ent=E5=AE=9E=E4=BD=93=E6=90=9C=E7=B4=A2=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E5=8F=8A=E8=AF=A6=E6=83=85=E6=9F=A5=E8=AF=A2API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增Agent模块,提供统一的实体搜索和详情查询接口: - 根据名称模糊查询店铺、柜机、商品实体列表 - 根据柜体ID获取智能柜详细信息(含格口和商品信息) - 根据店铺ID获取店铺详细信息(含下属柜机列表) - 新增库存异常数据查询SQL脚本用于排查数据问题 Co-authored-by: Claude --- .../api/controller/agent/AgentController.java | 89 +++++++++++++++++++ .../domain/agent/AgentApplicationService.java | 89 +++++++++++++++++++ .../agileboot/domain/agent/dto/IdNameDTO.java | 20 +++++ .../SmartCabinetApplicationService.java | 75 ++++++++++++++++ .../smartCabinet/dto/CabinetDetailDTO.java | 38 ++++++++ .../shop/shop/ShopApplicationService.java | 53 +++++++++++ .../domain/shop/shop/dto/ShopDetailDTO.java | 73 +++++++++++++++ doc/sql/recalc_cell_data.sql | 33 +++++++ 8 files changed, 470 insertions(+) create mode 100644 agileboot-api/src/main/java/com/agileboot/api/controller/agent/AgentController.java create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/agent/AgentApplicationService.java create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/agent/dto/IdNameDTO.java create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/shop/shop/dto/ShopDetailDTO.java create mode 100644 doc/sql/recalc_cell_data.sql diff --git a/agileboot-api/src/main/java/com/agileboot/api/controller/agent/AgentController.java b/agileboot-api/src/main/java/com/agileboot/api/controller/agent/AgentController.java new file mode 100644 index 0000000..b0bdd93 --- /dev/null +++ b/agileboot-api/src/main/java/com/agileboot/api/controller/agent/AgentController.java @@ -0,0 +1,89 @@ +package com.agileboot.api.controller.agent; + +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.domain.agent.AgentApplicationService; +import com.agileboot.domain.cabinet.smartCabinet.SmartCabinetApplicationService; +import com.agileboot.domain.cabinet.smartCabinet.dto.CabinetDetailDTO; +import com.agileboot.domain.agent.dto.IdNameDTO; +import com.agileboot.domain.shop.shop.ShopApplicationService; +import com.agileboot.domain.shop.shop.dto.ShopDetailDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * Agent实体搜索控制器 + * + * @author valarchie + */ +@Slf4j +@RestController +@CrossOrigin(origins = "*", allowedHeaders = "*") +@RequiredArgsConstructor +@RequestMapping("/api/agent") +@Tag(name = "Agent相关API") +public class AgentController { + + private final AgentApplicationService agentApplicationService; + private final SmartCabinetApplicationService smartCabinetApplicationService; + private final ShopApplicationService shopApplicationService; + + /** + * 根据名称模糊查询实体列表 + * 支持查询店铺、柜机、商品三种类型的实体 + * + * @param name 名称关键词,支持模糊匹配 + * @param entityType 实体类型,可选值:shop(店铺)、cabinet(柜机)、goods(商品) + * @return 匹配的实体ID和名称列表 + */ + @Operation(summary = "根据名称模糊查询实体列表") + @GetMapping("/searchEntity") + public ResponseDTO> searchEntity( + @RequestParam String name, + @RequestParam String entityType + ) { + if (!StringUtils.hasText(name)) { + return ResponseDTO.ok(null); + } + + List result = agentApplicationService.searchEntities(name, entityType); + return ResponseDTO.ok(result); + } + + /** + * 根据柜体ID获取智能柜详细信息 + * @param cabinetId 柜体ID + * @return 包含柜体信息、单元格信息和商品信息的详情 + */ + @Operation(summary = "根据柜体ID获取智能柜详细信息") + @GetMapping("/cabinetDetail") + public ResponseDTO getCabinetDetail(@RequestParam Long cabinetId) { + if (cabinetId == null) { + return ResponseDTO.fail(null); + } + + CabinetDetailDTO result = smartCabinetApplicationService.getCabinetDetailById(cabinetId); + return ResponseDTO.ok(result); + } + + /** + * 根据店铺ID获取店铺详细信息,包含下属柜机列表(不包含格口信息) + * @param shopId 店铺ID + * @return 店铺详细信息及柜机列表 + */ + @Operation(summary = "根据店铺ID获取店铺详细信息") + @GetMapping("/shopDetail") + public ResponseDTO getShopDetail(@RequestParam Long shopId) { + if (shopId == null) { + return ResponseDTO.fail(null); + } + + ShopDetailDTO result = shopApplicationService.getShopDetailById(shopId); + return ResponseDTO.ok(result); + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/agent/AgentApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/agent/AgentApplicationService.java new file mode 100644 index 0000000..757159e --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/agent/AgentApplicationService.java @@ -0,0 +1,89 @@ +package com.agileboot.domain.agent; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.agileboot.domain.agent.dto.IdNameDTO; +import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetEntity; +import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetService; +import com.agileboot.domain.shop.goods.db.ShopGoodsEntity; +import com.agileboot.domain.shop.goods.db.ShopGoodsService; +import com.agileboot.domain.shop.shop.db.ShopEntity; +import com.agileboot.domain.shop.shop.db.ShopService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Agent应用服务层 + * + * @author valarchie + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class AgentApplicationService { + + private final ShopService shopService; + private final SmartCabinetService cabinetService; + private final ShopGoodsService goodsService; + + /** + * 根据名称模糊查询实体列表 + * + * @param name 名称关键词,支持模糊匹配 + * @param entityType 实体类型 (shop/cabinet/goods) + * @return 实体ID名称列表,如果没有匹配项返回空列表 + */ + public List searchEntities(String name, String entityType) { + if (name == null || name.trim().isEmpty()) { + return Collections.emptyList(); + } + + String type = entityType.toLowerCase().trim(); + if ("shop".equals(type)) { + return searchShops(name); + } else if ("cabinet".equals(type)) { + return searchCabinets(name); + } else if ("goods".equals(type)) { + return searchGoods(name); + } else { + return Collections.emptyList(); + } + } + + private List searchShops(String name) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.like(ShopEntity::getShopName, name) + .eq(ShopEntity::getDeleted, 0) + .orderByDesc(ShopEntity::getCreateTime); + + return shopService.list(wrapper).stream() + .map(entity -> new IdNameDTO(entity.getShopId(), entity.getShopName())) + .collect(Collectors.toList()); + } + + private List searchCabinets(String name) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.like(SmartCabinetEntity::getCabinetName, name) + .eq(SmartCabinetEntity::getDeleted, 0) + .orderByDesc(SmartCabinetEntity::getCreateTime); + + return cabinetService.list(wrapper).stream() + .map(entity -> new IdNameDTO(entity.getCabinetId(), entity.getCabinetName())) + .collect(Collectors.toList()); + } + + private List searchGoods(String name) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.like(ShopGoodsEntity::getGoodsName, name) + .eq(ShopGoodsEntity::getDeleted, 0) + .orderByDesc(ShopGoodsEntity::getCreateTime); + + return goodsService.list(wrapper).stream() + .map(entity -> new IdNameDTO(entity.getGoodsId(), entity.getGoodsName())) + .collect(Collectors.toList()); + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/agent/dto/IdNameDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/agent/dto/IdNameDTO.java new file mode 100644 index 0000000..f1c9fac --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/agent/dto/IdNameDTO.java @@ -0,0 +1,20 @@ +package com.agileboot.domain.agent.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 通用ID名称DTO + * + * @author valarchie + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class IdNameDTO { + + private Long id; + + private String name; +} 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 d1555e4..9a783d7 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 @@ -448,4 +448,79 @@ public class SmartCabinetApplicationService { .map(cell -> new AvailableStorageCellDTO(cell, cabinetMap.get(cell.getCabinetId()))) .collect(Collectors.toList()); } + + /** + * 根据柜体ID获取智能柜详细信息 + * @param cabinetId 柜体ID + * @return 包含柜体信息、单元格信息和商品信息的单个柜体详情 + */ + public CabinetDetailDTO getCabinetDetailById(Long cabinetId) { + // 1. 查询智能柜 + SmartCabinetEntity cabinet = smartCabinetService.getById(cabinetId); + if (cabinet == null || cabinet.getDeleted()) { + return null; + } + + // 2. 查询该柜体下的所有单元格 + QueryWrapper cabinetCellEntityQueryWrapper = new QueryWrapper<>(); + cabinetCellEntityQueryWrapper.eq("cabinet_id", cabinetId) + .eq("deleted", false); + List cabinetCells = cabinetCellService.list(cabinetCellEntityQueryWrapper); + + // 3. 查询单元格关联的商品信息 + List goodsList = new ArrayList<>(); + List goodsIds = cabinetCells.stream() + .map(CabinetCellEntity::getGoodsId) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + if (!goodsIds.isEmpty()) { + QueryWrapper shopGoodsEntityQueryWrapper = new QueryWrapper<>(); + shopGoodsEntityQueryWrapper.in("goods_id", goodsIds) + .eq("deleted", false); + goodsList = shopGoodsService.list(shopGoodsEntityQueryWrapper); + } + final List finalGoodsList = goodsList; + + // 4. 构建柜体详情DTO + CabinetDetailDTO cabinetDetailDTO = new CabinetDetailDTO(); + cabinetDetailDTO.setCabinetId(cabinet.getCabinetId()); + cabinetDetailDTO.setCabinetName(cabinet.getCabinetName()); + cabinetDetailDTO.setLockControlNo(cabinet.getLockControlNo()); + + // 5. 处理柜体下的所有单元格 + List cellInfoList = cabinetCells.stream() + .map(cell -> { + CabinetDetailDTO.CellInfoDTO cellInfo = new CabinetDetailDTO.CellInfoDTO(); + cellInfo.setCellId(cell.getCellId()); + cellInfo.setCellNo(cell.getCellNo()); + cellInfo.setPinNo(cell.getPinNo()); + cellInfo.setStock(cell.getStock()); + cellInfo.setPassword(cell.getPassword()); + cellInfo.setUsageStatus(cell.getUsageStatus()); + cellInfo.setCellType(cell.getCellType()); + cellInfo.setRemark(cell.getRemark()); + + // 处理单元格关联的商品信息 + if (cell.getGoodsId() != null) { + ShopGoodsEntity goods = finalGoodsList.stream() + .filter(g -> g.getGoodsId().equals(cell.getGoodsId())) + .findFirst() + .orElse(null); + if (goods != null) { + CabinetDetailDTO.ProductInfoDTO product = new CabinetDetailDTO.ProductInfoDTO(); + product.setGoodsId(goods.getGoodsId()); + product.setGoodsName(goods.getGoodsName()); + product.setPrice(goods.getPrice()); + product.setCoverImg(goods.getCoverImg()); + cellInfo.setProduct(product); + } + } + return cellInfo; + }) + .collect(Collectors.toList()); + + cabinetDetailDTO.setCells(cellInfoList); + return cabinetDetailDTO; + } } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/smartCabinet/dto/CabinetDetailDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/smartCabinet/dto/CabinetDetailDTO.java index 98ec290..a065795 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/smartCabinet/dto/CabinetDetailDTO.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/smartCabinet/dto/CabinetDetailDTO.java @@ -1,5 +1,6 @@ package com.agileboot.domain.cabinet.smartCabinet.dto; +import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.math.BigDecimal; @@ -7,32 +8,69 @@ import java.util.List; @Data public class CabinetDetailDTO { + @ApiModelProperty("柜机ID") private Long cabinetId; + + @ApiModelProperty("柜机名称") private String cabinetName; + + @ApiModelProperty("锁控编号") private Integer lockControlNo; + + @ApiModelProperty("格子列表") private List cells; @Data public static class CellInfoDTO { + @ApiModelProperty("格子ID") private Long cellId; + + @ApiModelProperty("格子编号") private Integer cellNo; + + @ApiModelProperty("引脚编号") private Integer pinNo; + + @ApiModelProperty("库存数量") private Integer stock; + + @ApiModelProperty("格子价格") private BigDecimal cellPrice; + + @ApiModelProperty("订单ID") private Long orderId; + + @ApiModelProperty("订单商品ID") private Long orderGoodsId; + + @ApiModelProperty("商品信息") private ProductInfoDTO product; + + @ApiModelProperty("密码") private String password; + + @ApiModelProperty("使用状态(0-空闲 1-占用 2-维护 3-禁用)") private Integer usageStatus; + + @ApiModelProperty("格子类型(0-普通 1-冷藏 2-加热)") private Integer cellType; + + @ApiModelProperty("备注") private String remark; } @Data public static class ProductInfoDTO { + @ApiModelProperty("商品ID") private Long goodsId; + + @ApiModelProperty("商品名称") private String goodsName; + + @ApiModelProperty("商品价格") private BigDecimal price; + + @ApiModelProperty("封面图URL") private String coverImg; } } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/shop/ShopApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/shop/ShopApplicationService.java index e6a2a1b..c52b32d 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/shop/ShopApplicationService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/shop/ShopApplicationService.java @@ -11,11 +11,13 @@ import com.agileboot.domain.shop.shop.command.UpdateShopCommand; import com.agileboot.domain.shop.shop.db.ShopEntity; import com.agileboot.domain.shop.shop.db.ShopService; import com.agileboot.domain.shop.shop.dto.ShopDTO; +import com.agileboot.domain.shop.shop.dto.ShopDetailDTO; import com.agileboot.domain.shop.shop.model.ShopModel; import com.agileboot.domain.shop.shop.model.ShopModelFactory; import com.agileboot.domain.shop.shop.query.SearchShopQuery; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -109,4 +111,55 @@ public class ShopApplicationService { public List getDistinctModeList() { return shopService.getDistinctModeList(); } + + /** + * 根据店铺ID获取店铺详细信息,包含下属柜机列表(不包含格口信息) + * @param shopId 店铺ID + * @return 店铺详细信息及柜机列表 + */ + public ShopDetailDTO getShopDetailById(Long shopId) { + if (shopId == null) { + return null; + } + + ShopEntity shop = shopService.getById(shopId); + if (shop == null || shop.getDeleted()) { + return null; + } + + QueryWrapper cabinetWrapper = new QueryWrapper<>(); + cabinetWrapper.eq("shop_id", shopId) + .eq("deleted", false); + List cabinets = smartCabinetService.list(cabinetWrapper); + + ShopDetailDTO shopDetailDTO = new ShopDetailDTO(); + shopDetailDTO.setShopId(shop.getShopId()); + shopDetailDTO.setShopName(shop.getShopName()); + shopDetailDTO.setCorpid(shop.getCorpid()); + shopDetailDTO.setBelongType(shop.getBelongType()); + shopDetailDTO.setMode(shop.getMode()); + shopDetailDTO.setBalanceEnable(shop.getBalanceEnable()); + shopDetailDTO.setCoverImg(shop.getCoverImg()); + + List cabinetInfoList = new ArrayList<>(); + for (SmartCabinetEntity cabinet : cabinets) { + ShopDetailDTO.CabinetInfoDTO cabinetInfo = new ShopDetailDTO.CabinetInfoDTO(); + cabinetInfo.setCabinetId(cabinet.getCabinetId()); + cabinetInfo.setCabinetName(cabinet.getCabinetName()); + cabinetInfo.setCabinetType(cabinet.getCabinetType()); + cabinetInfo.setMainCabinet(cabinet.getMainCabinet()); + cabinetInfo.setMode(cabinet.getMode()); + cabinetInfo.setBalanceEnable(cabinet.getBalanceEnable()); + cabinetInfo.setBelongType(cabinet.getBelongType()); + cabinetInfo.setMqttServerId(cabinet.getMqttServerId()); + cabinetInfo.setTemplateNo(cabinet.getTemplateNo()); + cabinetInfo.setLockControlNo(cabinet.getLockControlNo()); + cabinetInfo.setLocation(cabinet.getLocation()); + cabinetInfo.setReturnDeadline(cabinet.getReturnDeadline()); + cabinetInfoList.add(cabinetInfo); + } + + shopDetailDTO.setCabinets(cabinetInfoList); + return shopDetailDTO; + } } \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/shop/dto/ShopDetailDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/shop/dto/ShopDetailDTO.java new file mode 100644 index 0000000..a948b70 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/shop/dto/ShopDetailDTO.java @@ -0,0 +1,73 @@ +package com.agileboot.domain.shop.shop.dto; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class ShopDetailDTO { + + @ApiModelProperty("商店ID") + private Long shopId; + + @ApiModelProperty("商店名称") + private String shopName; + + @ApiModelProperty("企业微信id") + private String corpid; + + @ApiModelProperty("归属类型(0-借还柜 1-固资通)") + private Integer belongType; + + @ApiModelProperty("运行模式(0-支付模式 1-审批模式 2-借还模式 3-会员模式 4-耗材模式 5-暂存模式)") + private Integer mode; + + @ApiModelProperty("借呗支付(1-正常使用 0-禁止使用)") + private Integer balanceEnable; + + @ApiModelProperty("封面图URL") + private String coverImg; + + @ApiModelProperty("柜机列表") + private List cabinets; + + @Data + public static class CabinetInfoDTO { + @ApiModelProperty("柜机唯一ID") + private Long cabinetId; + + @ApiModelProperty("柜机名称") + private String cabinetName; + + @ApiModelProperty("柜机类型(0主柜 1副柜)") + private Integer cabinetType; + + @ApiModelProperty("归属主柜ID") + private Long mainCabinet; + + @ApiModelProperty("运行模式(0-支付模式 1-审批模式 2-借还模式 3-会员模式 4-耗材模式 5-暂存模式)") + private Integer mode; + + @ApiModelProperty("借呗支付(1-正常使用 0-禁止使用)") + private Integer balanceEnable; + + @ApiModelProperty("归属类型(0-借还柜 1-固资通)") + private Integer belongType; + + @ApiModelProperty("MQTT服务ID") + private Long mqttServerId; + + @ApiModelProperty("柜机模版编号") + private String templateNo; + + @ApiModelProperty("锁控板序号") + private Integer lockControlNo; + + @ApiModelProperty("柜机位置") + private Integer location; + + @ApiModelProperty("归还期限(天),0表示不限制") + private Integer returnDeadline; + } +} diff --git a/doc/sql/recalc_cell_data.sql b/doc/sql/recalc_cell_data.sql new file mode 100644 index 0000000..bf5834b --- /dev/null +++ b/doc/sql/recalc_cell_data.sql @@ -0,0 +1,33 @@ +-- 此SQL用于查询商品库存与格口库存不一致的数据 +-- 筛选条件:商品库存 < 格口库存(说明格口库存超出商品库存,需要重新计算) +-- 主要用途:发现库存异常数据,定位需要重新计算格口数据的商品 +SELECT + sg.goods_id, + sg.goods_name, + sg.stock AS '商品库存', + COALESCE(SUM(cc.stock), 0) AS '格口库存', + sc.cabinet_name, + GROUP_CONCAT(DISTINCT cc.cell_no ORDER BY cc.cell_no ASC) AS '格口号' +FROM + shop_goods sg +LEFT JOIN + cabinet_cell cc ON sg.goods_id = cc.goods_id + AND cc.deleted = 0 + AND cc.goods_id IS NOT NULL +LEFT JOIN + smart_cabinet sc ON cc.cabinet_id = sc.cabinet_id + AND sc.deleted = 0 +WHERE + sg.deleted = 0 +GROUP BY + sg.goods_id, + sg.goods_name, + sg.stock, + sc.cabinet_id, + sc.cabinet_name +HAVING + sg.stock < COALESCE(SUM(cc.stock), 0) + AND sc.cabinet_name IS NOT NULL +ORDER BY + sg.goods_id, + sc.cabinet_name; \ No newline at end of file