From 3b80c87f52d96ba69429acc526472ebf6f573c9f Mon Sep 17 00:00:00 2001 From: dzq Date: Thu, 5 Jun 2025 16:24:41 +0800 Subject: [PATCH] =?UTF-8?q?feat(asset):=20=E6=B7=BB=E5=8A=A0=E5=A4=96?= =?UTF-8?q?=E9=83=A8=E5=95=86=E5=93=81=E6=8E=A8=E9=80=81=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=8F=8A=E7=9B=B8=E5=85=B3=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在shop_goods表中新增belong_type和external_goods_id字段 - 实现AssetApiController接收外部商品推送接口 - 添加AssetApplicationService处理商品创建/更新逻辑 - 扩展ShopGoodsDTO、SearchShopGoodsQuery等支持新字段 - 新增PostAssetGoodsCommand和PostAssetGoodsBody数据传输对象 - 添加单元测试验证接口功能 --- .../controller/asset/AssetApiController.java | 45 ++++++++++++ .../controller/AssetApiControllerTest.java | 61 ++++++++++++++++ .../domain/asset/AssetApplicationService.java | 73 +++++++++++++++++++ .../domain/asset/api/PostAssetGoodsBody.java | 14 ++++ .../asset/command/PostAssetGoodsCommand.java | 41 +++++++++++ .../domain/shop/goods/db/ShopGoodsEntity.java | 8 ++ .../domain/shop/goods/db/ShopGoodsMapper.java | 4 +- .../shop/goods/db/ShopGoodsService.java | 2 + .../shop/goods/db/ShopGoodsServiceImpl.java | 9 +++ .../domain/shop/goods/dto/ShopGoodsDTO.java | 6 ++ .../goods/query/SearchShopGoodsQuery.java | 4 + sql/20250605.sql | 7 ++ 12 files changed, 272 insertions(+), 2 deletions(-) create mode 100644 agileboot-api/src/main/java/com/agileboot/api/controller/asset/AssetApiController.java create mode 100644 agileboot-api/src/test/java/com/agileboot/api/controller/AssetApiControllerTest.java create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/asset/AssetApplicationService.java create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/asset/api/PostAssetGoodsBody.java create mode 100644 agileboot-domain/src/main/java/com/agileboot/domain/asset/command/PostAssetGoodsCommand.java create mode 100644 sql/20250605.sql diff --git a/agileboot-api/src/main/java/com/agileboot/api/controller/asset/AssetApiController.java b/agileboot-api/src/main/java/com/agileboot/api/controller/asset/AssetApiController.java new file mode 100644 index 0000000..c18b2dc --- /dev/null +++ b/agileboot-api/src/main/java/com/agileboot/api/controller/asset/AssetApiController.java @@ -0,0 +1,45 @@ +package com.agileboot.api.controller.asset; + + +import cn.hutool.json.JSONUtil; +import com.agileboot.common.constant.OpenApiConstants; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.common.exception.ApiException; +import com.agileboot.common.exception.error.ErrorCode; +import com.agileboot.domain.asset.AssetApplicationService; +import com.agileboot.domain.asset.api.PostAssetGoodsBody; +import com.agileboot.domain.asset.command.PostAssetGoodsCommand; +import com.agileboot.domain.shop.payment.SignUtils; +import com.agileboot.domain.shop.payment.dto.CommonRequest; +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.List; + +@Slf4j +@RestController +@CrossOrigin(origins = "*", allowedHeaders = "*") +@RequiredArgsConstructor +@RequestMapping("/api/asset") +public class AssetApiController { + private final AssetApplicationService assetApplicationService; + + @PostMapping("/pushExternalGoods") + public ResponseDTO pushExternalGoods(@RequestBody String body) throws UnsupportedEncodingException { + CommonRequest notifyRequest = CommonRequest.build(body, PostAssetGoodsCommand.class); + if (notifyRequest == null) { + return ResponseDTO.fail(new ApiException(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, "请求参数无效")); + } + log.info("pushExternalGoods sign:{}, body:{}", notifyRequest.getSign(), body); + if (!SignUtils.checkOpenSign(OpenApiConstants.appKey, notifyRequest.getSign(), body)) { + return ResponseDTO.fail(new ApiException(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, "sign校验失败")); + } + assetApplicationService.pushExternalGoods(notifyRequest.getBizContent()); + return ResponseDTO.ok(); + } +} diff --git a/agileboot-api/src/test/java/com/agileboot/api/controller/AssetApiControllerTest.java b/agileboot-api/src/test/java/com/agileboot/api/controller/AssetApiControllerTest.java new file mode 100644 index 0000000..391cfed --- /dev/null +++ b/agileboot-api/src/test/java/com/agileboot/api/controller/AssetApiControllerTest.java @@ -0,0 +1,61 @@ +package com.agileboot.api.controller; + +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONUtil; +import com.agileboot.common.constant.OpenApiConstants; +import com.agileboot.common.core.dto.ResponseDTO; +import com.agileboot.domain.asset.api.PostAssetGoodsBody; +import com.agileboot.domain.asset.command.PostAssetGoodsCommand; +import com.agileboot.domain.shop.payment.SignUtils; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; + +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.*; + +@Slf4j +public class AssetApiControllerTest { + + @Test + public void testPushExternalGoods() throws UnsupportedEncodingException { + // 1.构建测试商品数据 + PostAssetGoodsCommand goods = new PostAssetGoodsCommand(); + goods.setExternalGoodsId(1002L); + goods.setGoodsName("测试笔记本电脑"); + goods.setPrice(new BigDecimal("5999.00")); + goods.setStock(10); + goods.setAutoApproval(1); + goods.setCoverImg("https://example.com/laptop.jpg"); + goods.setGoodsDetail("商品详情内容,支持2000汉字和10张图片链接"); + goods.setUsageInstruction("1. 使用前请充电\n2. 避免液体接触\n3. 定期清理灰尘"); + goods.setBelongType(1); + + // 2.构造请求体 + Map params = new HashMap<>(); + params.put("timestamp", String.valueOf(System.currentTimeMillis())); + params.put("biz_content", URLEncoder.encode(JSONUtil.toJsonStr(goods), StandardCharsets.UTF_8.toString())); + + // 3.生成签名参数 + + String appKey = OpenApiConstants.appKey; + String sign = SignUtils.openSign(appKey, params); + params.put("sign", sign); + + // 4.构造完整请求JSON + StringBuilder sb = new StringBuilder(); + for (String key : params.keySet()) { + if (sb.length() > 0) { + sb.append("&"); + } + sb.append(key).append("=").append(params.get(key)); + } + + // 5.发送POST请求 + String res = HttpUtil.post("http://localhost:8090/api/asset/pushExternalGoods", sb.toString()); + + log.info("接口响应: {}", res); + } +} \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/asset/AssetApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/asset/AssetApplicationService.java new file mode 100644 index 0000000..121d309 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/asset/AssetApplicationService.java @@ -0,0 +1,73 @@ +package com.agileboot.domain.asset; + +import cn.hutool.json.JSONUtil; +import com.agileboot.domain.asset.api.PostAssetGoodsBody; +import com.agileboot.domain.asset.command.PostAssetGoodsCommand; +import com.agileboot.domain.shop.goods.db.ShopGoodsEntity; +import com.agileboot.domain.shop.goods.db.ShopGoodsService; +import com.agileboot.domain.shop.goods.model.GoodsModel; +import com.agileboot.domain.shop.goods.model.GoodsModelFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AssetApplicationService { + private final ShopGoodsService shopGoodsService; + private final GoodsModelFactory goodsModelFactory; + + public void pushExternalGoods(PostAssetGoodsCommand command) { + if (command == null || command.getExternalGoodsId() == null) { + throw new IllegalArgumentException("ExternalGoodsId cannot be null"); + } + ShopGoodsEntity shopGoodsEntity = shopGoodsService.getGoodsByExternalGoodsId(command.getExternalGoodsId()); + if (shopGoodsEntity != null) { + if (command.getGoodsName() != null) { + shopGoodsEntity.setGoodsName(command.getGoodsName()); + } + if (command.getPrice() != null) { + shopGoodsEntity.setPrice(command.getPrice()); + } + if (command.getStock() != null) { + shopGoodsEntity.setStock(command.getStock()); + } + if (command.getAutoApproval() != null) { + shopGoodsEntity.setAutoApproval(command.getAutoApproval()); + } + if (command.getCoverImg() != null) { + shopGoodsEntity.setCoverImg(command.getCoverImg()); + } + if (command.getGoodsDetail() != null) { + shopGoodsEntity.setGoodsDetail(command.getGoodsDetail()); + } + if (command.getUsageInstruction() != null) { + shopGoodsEntity.setUsageInstruction(command.getUsageInstruction()); + } + if (command.getBelongType() != null) { + shopGoodsEntity.setBelongType(command.getBelongType()); + } + shopGoodsEntity.updateById(); + } else { + GoodsModel goodsModel = goodsModelFactory.create(); + goodsModel.setExternalGoodsId(command.getExternalGoodsId()); + goodsModel.setGoodsName(command.getGoodsName()); + goodsModel.setPrice(command.getPrice()); + goodsModel.setStock(command.getStock()); + goodsModel.setAutoApproval(command.getAutoApproval()); + goodsModel.setCoverImg(command.getCoverImg()); + goodsModel.setGoodsDetail(command.getGoodsDetail()); + goodsModel.setUsageInstruction(command.getUsageInstruction()); + goodsModel.setBelongType(command.getBelongType()); + goodsModel.initBaseEntity(); + goodsModel.setCategoryId(0L); + goodsModel.setStatus(1); + goodsModel.insert(); + } + } + + +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/asset/api/PostAssetGoodsBody.java b/agileboot-domain/src/main/java/com/agileboot/domain/asset/api/PostAssetGoodsBody.java new file mode 100644 index 0000000..10f6bd0 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/asset/api/PostAssetGoodsBody.java @@ -0,0 +1,14 @@ +package com.agileboot.domain.asset.api; + +import com.agileboot.domain.asset.command.PostAssetGoodsCommand; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@EqualsAndHashCode +@Data +public class PostAssetGoodsBody { + + List goodsList; +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/asset/command/PostAssetGoodsCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/asset/command/PostAssetGoodsCommand.java new file mode 100644 index 0000000..66a4076 --- /dev/null +++ b/agileboot-domain/src/main/java/com/agileboot/domain/asset/command/PostAssetGoodsCommand.java @@ -0,0 +1,41 @@ +package com.agileboot.domain.asset.command; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; + +@EqualsAndHashCode +@Data +public class PostAssetGoodsCommand { + @ApiModelProperty("外部归属类型的商品ID") + private Long externalGoodsId; + + @ApiModelProperty("商品名称") + private String goodsName; + + @ApiModelProperty("销售价格") + private BigDecimal price; + + @ApiModelProperty("库存数量") + private Integer stock; + + @ApiModelProperty("免审批(0否 1是)") + private Integer autoApproval; + + @ApiModelProperty("封面图URL") + private String coverImg; + + @ApiModelProperty("商品详情(支持2000汉字+10个图片链接)") + private String goodsDetail; + + @ApiModelProperty("商品使用说明") + private String usageInstruction; + + @ApiModelProperty("归属类型(0-借还柜 1-固资通)") + private Integer belongType; +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/ShopGoodsEntity.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/ShopGoodsEntity.java index 15ce07d..7d94ec5 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/ShopGoodsEntity.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/ShopGoodsEntity.java @@ -39,6 +39,10 @@ public class ShopGoodsEntity extends BaseEntity { @ApiModelProperty("商品分类ID") @TableField("category_id") private Long categoryId; + + @ApiModelProperty("外部归属类型的商品ID") + @TableField("external_goods_id") + private Long externalGoodsId; @ApiModelProperty("销售价格") @TableField("price") @@ -72,6 +76,10 @@ public class ShopGoodsEntity extends BaseEntity { @TableField("usage_instruction") private String usageInstruction; + @ApiModelProperty("归属类型(0-借还柜 1-固资通)") + @TableField("belong_type") + private Integer belongType; + @Override public Serializable pkVal() { return this.goodsId; diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/ShopGoodsMapper.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/ShopGoodsMapper.java index d225a56..0007619 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/ShopGoodsMapper.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/ShopGoodsMapper.java @@ -71,7 +71,7 @@ public interface ShopGoodsMapper extends BaseMapper { "FROM shop_goods g " + "LEFT JOIN cabinet_cell cc ON g.goods_id = cc.goods_id " + "LEFT JOIN smart_cabinet sc ON cc.cabinet_id = sc.cabinet_id " + - "WHERE g.deleted = 0 AND sc.deleted = 0 AND cc.deleted = 0 AND cc.goods_id IS NOT NULL ") + "WHERE g.deleted = 0 AND g.belong_type = 0 AND sc.deleted = 0 AND cc.deleted = 0 AND cc.goods_id IS NOT NULL ") List getGoodsWithCabinetList(); @Select("SELECT g.goods_id, g.goods_name, g.category_id, g.price, " + @@ -81,7 +81,7 @@ public interface ShopGoodsMapper extends BaseMapper { "FROM shop_goods g " + "LEFT JOIN cabinet_cell cc ON g.goods_id = cc.goods_id " + "LEFT JOIN smart_cabinet sc ON cc.cabinet_id = sc.cabinet_id " + - "WHERE g.deleted = 0 AND sc.deleted = 0 AND sc.shop_id = #{shopId} AND cc.deleted = 0 AND cc.goods_id IS NOT NULL ") + "WHERE g.deleted = 0 AND g.belong_type = 0 AND sc.deleted = 0 AND sc.shop_id = #{shopId} AND cc.deleted = 0 AND cc.goods_id IS NOT NULL ") List getGoodsWithCabinetListByShopId(@Param("shopId")Long shopId); @Select("SELECT g.goods_id, g.goods_name, g.category_id, g.price, " + diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/ShopGoodsService.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/ShopGoodsService.java index 2c8dad3..403f5ca 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/ShopGoodsService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/ShopGoodsService.java @@ -27,4 +27,6 @@ public interface ShopGoodsService extends IService { Long countAllRecord(); BigDecimal calculateTotalAmount(); + + ShopGoodsEntity getGoodsByExternalGoodsId(Long externalGoodsId); } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/ShopGoodsServiceImpl.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/ShopGoodsServiceImpl.java index 37f03e7..eac1450 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/ShopGoodsServiceImpl.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/db/ShopGoodsServiceImpl.java @@ -57,4 +57,13 @@ public class ShopGoodsServiceImpl extends ServiceImpl wrapper = new QueryWrapper<>(); + wrapper.eq("external_goods_id", externalGoodsId) + .eq("deleted", 0) + .last("LIMIT 1"); + return baseMapper.selectOne(wrapper); + } } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/dto/ShopGoodsDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/dto/ShopGoodsDTO.java index 0b5780a..fa3763c 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/dto/ShopGoodsDTO.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/dto/ShopGoodsDTO.java @@ -47,10 +47,16 @@ public class ShopGoodsDTO { @ExcelColumn(name = "分类ID") private Long categoryId; + + @ExcelColumn(name = "外部商品ID") + private Long externalGoodsId; @ExcelColumn(name = "分类名称") private String categoryName; + @ExcelColumn(name = "归属类型(0-借还柜 1-固资通)") + private Long belongType; + @ExcelColumn(name = "销售价格") private BigDecimal price; diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/query/SearchShopGoodsQuery.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/query/SearchShopGoodsQuery.java index 51f5a15..a877d43 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/query/SearchShopGoodsQuery.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/query/SearchShopGoodsQuery.java @@ -13,10 +13,12 @@ public class SearchShopGoodsQuery extends AbstractPageQuery { protected String goodsName; protected Long categoryId; + protected Long externalGoodsId; protected Integer status; protected Integer autoApproval; protected BigDecimal minPrice; protected BigDecimal maxPrice; + protected Integer belongType; @Override public QueryWrapper addQueryCondition() { @@ -29,6 +31,8 @@ public class SearchShopGoodsQuery extends AbstractPageQuery { .eq(autoApproval != null, "g.auto_approval", autoApproval) .ge(minPrice != null, "g.price", minPrice) .le(maxPrice != null, "g.price", maxPrice) + .eq(belongType != null, "g.belong_type", belongType) + .eq("g.external_goods_id", externalGoodsId) .eq("g.deleted", 0) .groupBy("g.goods_id"); diff --git a/sql/20250605.sql b/sql/20250605.sql new file mode 100644 index 0000000..9f44dac --- /dev/null +++ b/sql/20250605.sql @@ -0,0 +1,7 @@ +ALTER TABLE `shop_goods` +ADD COLUMN `belong_type` TINYINT NOT NULL DEFAULT 0 COMMENT '归属类型(0-借还柜 1-固资通)' +AFTER `category_id`; + +ALTER TABLE `shop_goods` +ADD COLUMN `external_goods_id` BIGINT NULL COMMENT '外部归属类型的商品ID' +AFTER `category_id`; \ No newline at end of file