feat: 添加智能柜详情接口并优化单元格管理逻辑

- 新增智能柜详情接口,返回柜体、单元格及商品信息
- 优化单元格管理逻辑,提取清除商品ID和释放单元格的方法
- 移除未使用的登录控制器代码
This commit is contained in:
dzq 2025-04-03 11:32:01 +08:00
parent 05f72a9a44
commit cf07d1dd52
10 changed files with 261 additions and 36 deletions

View File

@ -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<List<CabinetDetailDTO>> 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();
}
}

View File

@ -10,19 +10,27 @@ import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode; import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.common.exception.error.ErrorCode.Client; import com.agileboot.common.exception.error.ErrorCode.Client;
import com.agileboot.common.utils.OpenSignUtil; 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.AccessTokenApplicationService;
import com.agileboot.domain.qywx.accessToken.db.QyAccessTokenEntity; import com.agileboot.domain.qywx.accessToken.db.QyAccessTokenEntity;
import com.agileboot.domain.qywx.api.QywxApiUtil; 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.api.response.UserIdResponse;
import com.agileboot.domain.qywx.authCorpInfo.AuthCorpInfoApplicationService; import com.agileboot.domain.qywx.authCorpInfo.AuthCorpInfoApplicationService;
import com.agileboot.domain.qywx.authCorpInfo.db.QyAuthCorpInfoEntity; import com.agileboot.domain.qywx.authCorpInfo.db.QyAuthCorpInfoEntity;
import com.agileboot.domain.qywx.user.QyUserApplicationService; import com.agileboot.domain.qywx.user.QyUserApplicationService;
import com.agileboot.domain.qywx.user.db.QyUserEntity; 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.order.OrderApplicationService;
import com.agileboot.domain.shop.payment.dto.PaymentCallbackRequest; import com.agileboot.domain.shop.payment.dto.PaymentCallbackRequest;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import javax.servlet.http.HttpServletRequest; 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.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; 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.RestClientException;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
/**
* 支付相关接口控制器
* <p>处理微信支付回调用户身份认证余额查询等支付相关业务</p>
*/
@Slf4j @Slf4j
@RestController @RestController
@CrossOrigin(origins = "*", allowedHeaders = "*") @CrossOrigin(origins = "*", allowedHeaders = "*")
@ -48,8 +60,20 @@ public class PaymentController {
private final AccessTokenApplicationService accessTokenApplicationService; private final AccessTokenApplicationService accessTokenApplicationService;
private final QyUserApplicationService qyUserApplicationService; private final QyUserApplicationService qyUserApplicationService;
private final AuthCorpInfoApplicationService authCorpInfoApplicationService; 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") @PostMapping("/callback")
public String paymentCallback(HttpServletRequest request, @RequestBody String requestBody) { public String paymentCallback(HttpServletRequest request, @RequestBody String requestBody) {
log.info("支付回调requestBody{}", requestBody); log.info("支付回调requestBody{}", requestBody);
@ -86,6 +110,12 @@ public class PaymentController {
} }
} }
/**
* 获取微信用户OpenID
* @param code 微信授权码
* @return 包含openid的响应结果
* @throws ApiException 当code无效或微信接口调用失败时抛出
*/
@GetMapping("/getOpenId") @GetMapping("/getOpenId")
public ResponseDTO<String> getOpenId(String code) { public ResponseDTO<String> getOpenId(String code) {
try { 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<QyLoginDTO> 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<RouterDTO> routerTree = menuApplicationService.getRouterTree(loginUser);
log.info("getRouterTreeuserid: {}, 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<RouterDTO> 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") @GetMapping("/getBalance")
public ResponseDTO<GetBalanceResponse> getBalance(@RequestParam String openid) { public ResponseDTO<GetBalanceResponse> getBalance(@RequestParam String openid) {
String appid = "QWTONG_YS_WXSHOP"; String appid = "QWTONG_YS_WXSHOP";
@ -142,6 +240,12 @@ public class PaymentController {
return ResponseDTO.ok(response); return ResponseDTO.ok(response);
} }
/**
* 通过企业微信用户ID直接查询余额
* @param corpid 企业ID
* @param userid 企业微信用户ID
* @return 包含用户余额的响应结果
*/
@GetMapping("/getBalanceByQyUserid") @GetMapping("/getBalanceByQyUserid")
public ResponseDTO<GetBalanceResponse> getBalanceByQyUserid(@RequestParam String corpid, @RequestParam String userid) { public ResponseDTO<GetBalanceResponse> getBalanceByQyUserid(@RequestParam String corpid, @RequestParam String userid) {
QyUserEntity qyUser = qyUserApplicationService.getUserByUserId(userid, corpid); QyUserEntity qyUser = qyUserApplicationService.getUserByUserId(userid, corpid);

View File

@ -40,9 +40,6 @@ import org.springframework.web.client.RestClientException;
public class LoginController extends BaseController { public class LoginController extends BaseController {
private final JwtTokenService jwtTokenService; 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); return ResponseDTO.ok(token);
} }
@GetMapping("/login/qy")
public ResponseDTO<QyLoginDTO> 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<RouterDTO> routerTree = menuApplicationService.getRouterTree(loginUser);
log.info("getRouterTreeuserid: {}, 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, "微信服务调用失败"));
}
}
} }

View File

@ -59,9 +59,7 @@ public class CabinetCellApplicationService {
public void configureGoodsCells(Long cellId, Long goodsId) { public void configureGoodsCells(Long cellId, Long goodsId) {
List<CabinetCellEntity> cells = cabinetCellService.selectByGoodsId(goodsId); List<CabinetCellEntity> cells = cabinetCellService.selectByGoodsId(goodsId);
for (CabinetCellEntity cell : cells) { for (CabinetCellEntity cell : cells) {
cell.setGoodsId(null); cabinetCellService.clearGoodsIdAndFreeCell(cell.getCellId());
cell.setUsageStatus(1);
cell.updateById();
} }
CabinetCellModel model = cabinetCellModelFactory.loadById(cellId); CabinetCellModel model = cabinetCellModelFactory.loadById(cellId);

View File

@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List; import java.util.List;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
/** /**
* <p> * <p>
@ -44,4 +45,10 @@ public interface CabinetCellMapper extends BaseMapper<CabinetCellEntity> {
"WHERE goods_id = #{goodsId} " + "WHERE goods_id = #{goodsId} " +
"ORDER BY cell_id DESC") "ORDER BY cell_id DESC")
List<CabinetCellEntity> selectByGoodsId(@Param("goodsId")Long goodsId); List<CabinetCellEntity> 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);
} }

View File

@ -1,6 +1,7 @@
package com.agileboot.domain.cabinet.cell.db; package com.agileboot.domain.cabinet.cell.db;
import com.agileboot.common.core.page.AbstractPageQuery; 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.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List; import java.util.List;
@ -20,4 +21,6 @@ public interface CabinetCellService extends IService<CabinetCellEntity> {
List<CabinetCellEntity> selectAll(); List<CabinetCellEntity> selectAll();
List<CabinetCellEntity> selectByGoodsId(Long goodsId); List<CabinetCellEntity> selectByGoodsId(Long goodsId);
void clearGoodsIdAndFreeCell(Long cellId);
} }

View File

@ -1,6 +1,7 @@
package com.agileboot.domain.cabinet.cell.db; package com.agileboot.domain.cabinet.cell.db;
import com.agileboot.common.core.page.AbstractPageQuery; 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.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@ -26,6 +27,7 @@ public class CabinetCellServiceImpl extends ServiceImpl<CabinetCellMapper, Cabin
@Override @Override
public List<CabinetCellEntity> selectAll() { public List<CabinetCellEntity> selectAll() {
LambdaQueryWrapper<CabinetCellEntity> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<CabinetCellEntity> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CabinetCellEntity::getDeleted, false);
return this.list(wrapper); return this.list(wrapper);
} }
@ -33,4 +35,9 @@ public class CabinetCellServiceImpl extends ServiceImpl<CabinetCellMapper, Cabin
public List<CabinetCellEntity> selectByGoodsId(Long goodsId) { public List<CabinetCellEntity> selectByGoodsId(Long goodsId) {
return baseMapper.selectByGoodsId(goodsId); return baseMapper.selectByGoodsId(goodsId);
} }
@Override
public void clearGoodsIdAndFreeCell(Long cellId) {
baseMapper.clearGoodsIdAndFreeCell(cellId);
}
} }

View File

@ -1,6 +1,9 @@
package com.agileboot.domain.cabinet.smartCabinet; package com.agileboot.domain.cabinet.smartCabinet;
import com.agileboot.common.core.page.PageDTO; 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.common.command.BulkOperationCommand;
import com.agileboot.domain.cabinet.smartCabinet.command.AddSmartCabinetCommand; import com.agileboot.domain.cabinet.smartCabinet.command.AddSmartCabinetCommand;
import com.agileboot.domain.cabinet.smartCabinet.command.UpdateSmartCabinetCommand; 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.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;
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 com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -21,6 +28,8 @@ import org.springframework.stereotype.Service;
public class SmartCabinetApplicationService { public class SmartCabinetApplicationService {
private final SmartCabinetService smartCabinetService; private final SmartCabinetService smartCabinetService;
private final SmartCabinetModelFactory smartCabinetModelFactory; private final SmartCabinetModelFactory smartCabinetModelFactory;
private final CabinetCellService cabinetCellService;
private final ShopGoodsService shopGoodsService;
public PageDTO<SmartCabinetDTO> getSmartCabinetList(SearchSmartCabinetQuery<SmartCabinetEntity> query) { public PageDTO<SmartCabinetDTO> getSmartCabinetList(SearchSmartCabinetQuery<SmartCabinetEntity> query) {
Page<SmartCabinetEntity> page = smartCabinetService.getCabinetList(query); Page<SmartCabinetEntity> page = smartCabinetService.getCabinetList(query);
@ -52,4 +61,57 @@ public class SmartCabinetApplicationService {
public List<SmartCabinetEntity> selectAll() { public List<SmartCabinetEntity> selectAll() {
return smartCabinetService.selectAll(); return smartCabinetService.selectAll();
} }
/**
* 获取所有智能柜的详细信息
* @return 包含柜体信息单元格信息和商品信息的列表
*/
public List<CabinetDetailDTO> getCabinetDetail() {
// 获取所有智能柜单元格和商品的基础数据
List<SmartCabinetEntity> smartCabinets = smartCabinetService.selectAll();
List<CabinetCellEntity> cabinetCells = cabinetCellService.selectAll();
List<ShopGoodsEntity> goodsList = shopGoodsService.selectAll();
List<CabinetDetailDTO> result = new ArrayList<>();
// 遍历每个智能柜构建详细信息
for (SmartCabinetEntity cabinet : smartCabinets) {
CabinetDetailDTO cabinetDetailDTO = new CabinetDetailDTO();
// 设置柜体基础信息
cabinetDetailDTO.setCabinetId(cabinet.getCabinetId());
cabinetDetailDTO.setCabinetName(cabinet.getCabinetName());
cabinetDetailDTO.setLockControlNo(cabinet.getLockControlNo());
// 处理柜体下的所有单元格
List<CabinetDetailDTO.CellInfoDTO> 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;
}
} }

View File

@ -26,6 +26,7 @@ public class SmartCabinetServiceImpl extends ServiceImpl<SmartCabinetMapper, Sma
@Override @Override
public List<SmartCabinetEntity> selectAll() { public List<SmartCabinetEntity> selectAll() {
LambdaQueryWrapper<SmartCabinetEntity> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<SmartCabinetEntity> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SmartCabinetEntity::getDeleted, false);
return this.list(wrapper); return this.list(wrapper);
} }

View File

@ -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<CellInfoDTO> 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;
}
}