From cf07d1dd524cb4e75b35900506457e898a40be24 Mon Sep 17 00:00:00 2001 From: dzq Date: Thu, 3 Apr 2025 11:32:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=99=BA=E8=83=BD?= =?UTF-8?q?=E6=9F=9C=E8=AF=A6=E6=83=85=E6=8E=A5=E5=8F=A3=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=8D=95=E5=85=83=E6=A0=BC=E7=AE=A1=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增智能柜详情接口,返回柜体、单元格及商品信息 - 优化单元格管理逻辑,提取清除商品ID和释放单元格的方法 - 移除未使用的登录控制器代码 --- .../api/controller/CabinetCellController.java | 45 ++++++++ .../api/controller/PaymentController.java | 104 ++++++++++++++++++ .../controller/common/LoginController.java | 32 ------ .../cell/CabinetCellApplicationService.java | 4 +- .../cabinet/cell/db/CabinetCellMapper.java | 7 ++ .../cabinet/cell/db/CabinetCellService.java | 3 + .../cell/db/CabinetCellServiceImpl.java | 7 ++ .../SmartCabinetApplicationService.java | 64 ++++++++++- .../db/SmartCabinetServiceImpl.java | 1 + .../smartCabinet/dto/CabinetDetailDTO.java | 30 +++++ 10 files changed, 261 insertions(+), 36 deletions(-) create mode 100644 agileboot-api/src/main/java/com/agileboot/api/controller/CabinetCellController.java create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/cabinet/smartCabinet/dto/CabinetDetailDTO.java 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 new file mode 100644 index 0000000..764d6c4 --- /dev/null +++ b/agileboot-api/src/main/java/com/agileboot/api/controller/CabinetCellController.java @@ -0,0 +1,45 @@ +package com.agileboot.api.controller; + + +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.domain.cabinet.smartCabinet.SmartCabinetApplicationService; +import com.agileboot.domain.cabinet.smartCabinet.dto.CabinetDetailDTO; +import com.agileboot.domain.mqtt.MqttService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Slf4j +@RestController +@CrossOrigin(origins = "*", allowedHeaders = "*") +@RequiredArgsConstructor +@RequestMapping("/api/cabinet") +public class CabinetCellController { + private final SmartCabinetApplicationService smartCabinetApplicationService; + private final MqttService mqttService; + + @GetMapping("/detail") + public ResponseDTO> getCabinetDetail() { + return ResponseDTO.ok(smartCabinetApplicationService.getCabinetDetail()); + } + + @PostMapping("/openCabinet/{lockControlNo}/{pinNo}") + public ResponseDTO openCabinet(@PathVariable Integer lockControlNo, @PathVariable Integer pinNo) { + // 发送指令 + String mqttDate = "8A"; + mqttDate += String.format("%02X", lockControlNo); + mqttDate += String.format("%02X", pinNo); + mqttDate += "11"; + try { + mqttService.publish(mqttDate); + } catch (Exception e) { + log.error("mqtt publish error", e); + throw new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "发送指令失败"); + } + return ResponseDTO.ok(); + } +} diff --git a/agileboot-api/src/main/java/com/agileboot/api/controller/PaymentController.java b/agileboot-api/src/main/java/com/agileboot/api/controller/PaymentController.java index 52dc759..fad09fe 100644 --- a/agileboot-api/src/main/java/com/agileboot/api/controller/PaymentController.java +++ b/agileboot-api/src/main/java/com/agileboot/api/controller/PaymentController.java @@ -10,19 +10,27 @@ import com.agileboot.common.exception.ApiException; import com.agileboot.common.exception.error.ErrorCode; import com.agileboot.common.exception.error.ErrorCode.Client; import com.agileboot.common.utils.OpenSignUtil; +import com.agileboot.domain.common.dto.QyLoginDTO; import com.agileboot.domain.qywx.accessToken.AccessTokenApplicationService; import com.agileboot.domain.qywx.accessToken.db.QyAccessTokenEntity; import com.agileboot.domain.qywx.api.QywxApiUtil; +import com.agileboot.domain.qywx.api.response.OpenidResponse; import com.agileboot.domain.qywx.api.response.UserIdResponse; import com.agileboot.domain.qywx.authCorpInfo.AuthCorpInfoApplicationService; import com.agileboot.domain.qywx.authCorpInfo.db.QyAuthCorpInfoEntity; import com.agileboot.domain.qywx.user.QyUserApplicationService; import com.agileboot.domain.qywx.user.db.QyUserEntity; +import com.agileboot.domain.qywx.userQySys.SysUserQyUserApplicationService; import com.agileboot.domain.shop.order.OrderApplicationService; import com.agileboot.domain.shop.payment.dto.PaymentCallbackRequest; import java.nio.charset.StandardCharsets; import java.util.*; import javax.servlet.http.HttpServletRequest; + +import com.agileboot.domain.system.menu.MenuApplicationService; +import com.agileboot.domain.system.menu.dto.RouterDTO; +import com.agileboot.domain.system.user.db.SysUserEntity; +import com.agileboot.infrastructure.user.web.SystemLoginUser; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -38,6 +46,10 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; +/** + * 支付相关接口控制器 + *

处理微信支付回调、用户身份认证、余额查询等支付相关业务

+ */ @Slf4j @RestController @CrossOrigin(origins = "*", allowedHeaders = "*") @@ -48,8 +60,20 @@ public class PaymentController { private final AccessTokenApplicationService accessTokenApplicationService; private final QyUserApplicationService qyUserApplicationService; private final AuthCorpInfoApplicationService authCorpInfoApplicationService; + private final SysUserQyUserApplicationService sysUserQyUserApplicationService; + private final MenuApplicationService menuApplicationService; // 新增回调接口 + /** + * 支付结果回调接口 + * @param request HTTP请求对象 + * @param requestBody 回调请求体(URL编码格式) + * @return 处理结果:"success"表示成功,"fail"表示失败 + * @apiNote 该接口需要处理以下流程: + * 1. 参数解析和签名验证 + * 2. 支付状态判断(成功/失败) + * 3. 支付成功后的业务处理(需保证幂等性) + */ @PostMapping("/callback") public String paymentCallback(HttpServletRequest request, @RequestBody String requestBody) { log.info("支付回调requestBody:{}", requestBody); @@ -86,6 +110,12 @@ public class PaymentController { } } + /** + * 获取微信用户OpenID + * @param code 微信授权码 + * @return 包含openid的响应结果 + * @throws ApiException 当code无效或微信接口调用失败时抛出 + */ @GetMapping("/getOpenId") public ResponseDTO getOpenId(String code) { try { @@ -109,6 +139,74 @@ public class PaymentController { } } + /** + * 企业微信用户登录 + * @param corpid 企业ID + * @param code 企业微信授权码 + * @param state 状态参数(保留字段) + * @return 包含用户身份信息的响应结果 + * @apiNote 返回数据结构包含: + * - userid: 企业微信用户ID + * - openid: 关联的微信openid + * - isCabinetAdmin: 是否是柜机管理员(0否1是) + */ + @GetMapping("/login/qy") + public ResponseDTO qyLogin(String corpid, String code, String state) { + try { + QyAccessTokenEntity qyAccessToken = accessTokenApplicationService.getByAppid("QWTONG_YS_WXSHOP", corpid); + // 通过企业微信code获取用户ID + String userid = QywxApiUtil.getQyUserid(qyAccessToken.getAccessToken(), code); + // 根据企业微信用户ID查询系统用户名 + SysUserEntity sysUserEntity = sysUserQyUserApplicationService.getSysUserByQyUserid(userid); + + SystemLoginUser loginUser = new SystemLoginUser(); + loginUser.setAdmin(false); + loginUser.setUserId(sysUserEntity.getUserId()); + List routerTree = menuApplicationService.getRouterTree(loginUser); + log.info("getRouterTree,userid: {}, routerTree: {}", userid, JSONUtil.toJsonStr(routerTree)); + + int isCabinetAdmin = 0; + if (routerTree != null && !routerTree.isEmpty()) { + isCabinetAdmin = isCabinetAdmin(routerTree) + ? 1 : 0; + } + + OpenidResponse openidResponse = QywxApiUtil.convertToOpenid(qyAccessToken.getAccessToken(), userid); + + return ResponseDTO.ok(new QyLoginDTO(userid, openidResponse.getOpenid(), isCabinetAdmin)); + } catch (RestClientException e) { + log.error("qyLogin失败", e); + return ResponseDTO.fail(new ApiException(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, "微信服务调用失败")); + } + } + + private static boolean isCabinetAdmin(List routerTree) { + if (routerTree == null || routerTree.isEmpty()) { + return false; + } + boolean isAdmin = routerTree.stream().anyMatch(router -> router.getName().equals("CabinetCell")); + if (isAdmin) { + return true; + } + + for (RouterDTO router : routerTree) { + isAdmin = isCabinetAdmin(router.getChildren()); + if (isAdmin) { + return true; + } + } + return false; + } + + /** + * 通过openid查询用户余额 + * @param openid 微信openid + * @return 包含最高余额的用户信息 + * @apiNote 实现逻辑: + * 1. 查询所有关联企业微信应用 + * 2. 通过openid获取所有关联用户 + * 3. 返回余额最大的用户信息 + */ @GetMapping("/getBalance") public ResponseDTO getBalance(@RequestParam String openid) { String appid = "QWTONG_YS_WXSHOP"; @@ -142,6 +240,12 @@ public class PaymentController { return ResponseDTO.ok(response); } + /** + * 通过企业微信用户ID直接查询余额 + * @param corpid 企业ID + * @param userid 企业微信用户ID + * @return 包含用户余额的响应结果 + */ @GetMapping("/getBalanceByQyUserid") public ResponseDTO getBalanceByQyUserid(@RequestParam String corpid, @RequestParam String userid) { QyUserEntity qyUser = qyUserApplicationService.getUserByUserId(userid, corpid); diff --git a/agileboot-api/src/main/java/com/agileboot/api/controller/common/LoginController.java b/agileboot-api/src/main/java/com/agileboot/api/controller/common/LoginController.java index 506c63b..d8fae8a 100644 --- a/agileboot-api/src/main/java/com/agileboot/api/controller/common/LoginController.java +++ b/agileboot-api/src/main/java/com/agileboot/api/controller/common/LoginController.java @@ -40,9 +40,6 @@ import org.springframework.web.client.RestClientException; public class LoginController extends BaseController { private final JwtTokenService jwtTokenService; - private final AccessTokenApplicationService accessTokenApplicationService; - private final SysUserQyUserApplicationService sysUserQyUserApplicationService; - private final MenuApplicationService menuApplicationService; /** * 访问首页,提示语 @@ -53,34 +50,5 @@ public class LoginController extends BaseController { return ResponseDTO.ok(token); } - @GetMapping("/login/qy") - public ResponseDTO qyLogin(String corpid, String code, String state) { - try { - QyAccessTokenEntity qyAccessToken = accessTokenApplicationService.getByAppid("QWTONG_YS_WXSHOP", corpid); - // 通过企业微信code获取用户ID - String userid = QywxApiUtil.getQyUserid(qyAccessToken.getAccessToken(), code); - // 根据企业微信用户ID查询系统用户名 - SysUserEntity sysUserEntity = sysUserQyUserApplicationService.getSysUserByQyUserid(userid); - - SystemLoginUser loginUser = new SystemLoginUser(); - loginUser.setAdmin(false); - loginUser.setUserId(sysUserEntity.getUserId()); - List routerTree = menuApplicationService.getRouterTree(loginUser); - log.info("getRouterTree,userid: {}, routerTree: {}", userid, JSONUtil.toJsonStr(routerTree)); - - int isCabinetAdmin = 0; - if (routerTree != null && !routerTree.isEmpty()) { - isCabinetAdmin = routerTree.stream().anyMatch(router -> router.getName().equals("CabinetCell")) - ? 1 : 0; - } - - OpenidResponse openidResponse = QywxApiUtil.convertToOpenid(qyAccessToken.getAccessToken(), userid); - - return ResponseDTO.ok(new QyLoginDTO(userid, openidResponse.getOpenid(), isCabinetAdmin)); - } catch (RestClientException e) { - log.error("qyLogin失败", e); - return ResponseDTO.fail(new ApiException(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, "微信服务调用失败")); - } - } } 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 089b9b7..db9d633 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 @@ -59,9 +59,7 @@ public class CabinetCellApplicationService { public void configureGoodsCells(Long cellId, Long goodsId) { List cells = cabinetCellService.selectByGoodsId(goodsId); for (CabinetCellEntity cell : cells) { - cell.setGoodsId(null); - cell.setUsageStatus(1); - cell.updateById(); + cabinetCellService.clearGoodsIdAndFreeCell(cell.getCellId()); } CabinetCellModel model = cabinetCellModelFactory.loadById(cellId); 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 6bfe9b3..123610b 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 @@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import java.util.List; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; /** *

@@ -44,4 +45,10 @@ public interface CabinetCellMapper extends BaseMapper { "WHERE goods_id = #{goodsId} " + "ORDER BY cell_id DESC") List selectByGoodsId(@Param("goodsId")Long goodsId); + + @Update("UPDATE cabinet_cell " + + "SET goods_id = NULL, usage_status = 1 " + + "WHERE cell_id = #{cellId}") + void clearGoodsIdAndFreeCell(@Param("cellId") Long cellId); + } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellService.java b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellService.java index a78d211..037eec3 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellService.java @@ -1,6 +1,7 @@ package com.agileboot.domain.cabinet.cell.db; import com.agileboot.common.core.page.AbstractPageQuery; +import com.agileboot.domain.cabinet.smartCabinet.dto.CabinetDetailDTO; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import java.util.List; @@ -20,4 +21,6 @@ public interface CabinetCellService extends IService { List selectAll(); List selectByGoodsId(Long goodsId); + + void clearGoodsIdAndFreeCell(Long cellId); } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellServiceImpl.java b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellServiceImpl.java index a7611ba..6da9fb8 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellServiceImpl.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellServiceImpl.java @@ -1,6 +1,7 @@ package com.agileboot.domain.cabinet.cell.db; import com.agileboot.common.core.page.AbstractPageQuery; +import com.agileboot.domain.cabinet.smartCabinet.dto.CabinetDetailDTO; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -26,6 +27,7 @@ public class CabinetCellServiceImpl extends ServiceImpl selectAll() { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(CabinetCellEntity::getDeleted, false); return this.list(wrapper); } @@ -33,4 +35,9 @@ public class CabinetCellServiceImpl extends ServiceImpl selectByGoodsId(Long goodsId) { return baseMapper.selectByGoodsId(goodsId); } + + @Override + public void clearGoodsIdAndFreeCell(Long cellId) { + baseMapper.clearGoodsIdAndFreeCell(cellId); + } } 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 65f322e..69532d3 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 @@ -1,6 +1,9 @@ package com.agileboot.domain.cabinet.smartCabinet; import com.agileboot.common.core.page.PageDTO; +import com.agileboot.domain.cabinet.cell.db.CabinetCellEntity; +import com.agileboot.domain.cabinet.cell.db.CabinetCellService; +import com.agileboot.domain.cabinet.smartCabinet.dto.CabinetDetailDTO; import com.agileboot.domain.common.command.BulkOperationCommand; import com.agileboot.domain.cabinet.smartCabinet.command.AddSmartCabinetCommand; import com.agileboot.domain.cabinet.smartCabinet.command.UpdateSmartCabinetCommand; @@ -10,7 +13,11 @@ import com.agileboot.domain.cabinet.smartCabinet.dto.SmartCabinetDTO; import com.agileboot.domain.cabinet.smartCabinet.model.SmartCabinetModel; import com.agileboot.domain.cabinet.smartCabinet.model.SmartCabinetModelFactory; import com.agileboot.domain.cabinet.smartCabinet.query.SearchSmartCabinetQuery; +import com.agileboot.domain.shop.goods.db.ShopGoodsEntity; +import com.agileboot.domain.shop.goods.db.ShopGoodsService; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; + +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -21,6 +28,8 @@ import org.springframework.stereotype.Service; public class SmartCabinetApplicationService { private final SmartCabinetService smartCabinetService; private final SmartCabinetModelFactory smartCabinetModelFactory; + private final CabinetCellService cabinetCellService; + private final ShopGoodsService shopGoodsService; public PageDTO getSmartCabinetList(SearchSmartCabinetQuery query) { Page page = smartCabinetService.getCabinetList(query); @@ -52,4 +61,57 @@ public class SmartCabinetApplicationService { public List selectAll() { return smartCabinetService.selectAll(); } -} \ No newline at end of file + + /** + * 获取所有智能柜的详细信息 + * @return 包含柜体信息、单元格信息和商品信息的列表 + */ + public List getCabinetDetail() { + // 获取所有智能柜、单元格和商品的基础数据 + List smartCabinets = smartCabinetService.selectAll(); + List cabinetCells = cabinetCellService.selectAll(); + List goodsList = shopGoodsService.selectAll(); + + List result = new ArrayList<>(); + // 遍历每个智能柜构建详细信息 + for (SmartCabinetEntity cabinet : smartCabinets) { + CabinetDetailDTO cabinetDetailDTO = new CabinetDetailDTO(); + // 设置柜体基础信息 + cabinetDetailDTO.setCabinetId(cabinet.getCabinetId()); + cabinetDetailDTO.setCabinetName(cabinet.getCabinetName()); + cabinetDetailDTO.setLockControlNo(cabinet.getLockControlNo()); + + // 处理柜体下的所有单元格 + List cellInfoList = cabinetCells.stream() + .filter(cell -> cell.getCabinetId().equals(cabinet.getCabinetId())) + .map(cell -> { + CabinetDetailDTO.CellInfoDTO cellInfo = new CabinetDetailDTO.CellInfoDTO(); + // 设置单元格基础信息 + cellInfo.setCellId(cell.getCellId()); + cellInfo.setPinNo(cell.getPinNo()); + + // 处理单元格关联的商品信息 + if (cell.getGoodsId() != null) { + ShopGoodsEntity goods = goodsList.stream() + .filter(g -> g.getGoodsId().equals(cell.getGoodsId())) + .findFirst().orElse(null); + if (goods != null) { + CabinetDetailDTO.ProductInfoDTO product = new CabinetDetailDTO.ProductInfoDTO(); + // 转换商品信息到DTO + 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); + result.add(cabinetDetailDTO); + } + return result; + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/smartCabinet/db/SmartCabinetServiceImpl.java b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/smartCabinet/db/SmartCabinetServiceImpl.java index f156e71..16a3ac7 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/smartCabinet/db/SmartCabinetServiceImpl.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/smartCabinet/db/SmartCabinetServiceImpl.java @@ -26,6 +26,7 @@ public class SmartCabinetServiceImpl extends ServiceImpl selectAll() { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SmartCabinetEntity::getDeleted, false); return this.list(wrapper); } 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 new file mode 100644 index 0000000..fedecdc --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/smartCabinet/dto/CabinetDetailDTO.java @@ -0,0 +1,30 @@ +package com.agileboot.domain.cabinet.smartCabinet.dto; + +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +@Data +public class CabinetDetailDTO { + private Long cabinetId; + private String cabinetName; + private Integer lockControlNo; + private List cells; + + @Data + public static class CellInfoDTO { + private Long cellId; + private Integer pinNo; + private ProductInfoDTO product; + } + + @Data + public static class ProductInfoDTO { + private Long goodsId; + private String goodsName; + private BigDecimal price; + private String coverImg; + } +} +