feat(asset): 添加外部商品推送功能及相关字段
- 在shop_goods表中新增belong_type和external_goods_id字段 - 实现AssetApiController接收外部商品推送接口 - 添加AssetApplicationService处理商品创建/更新逻辑 - 扩展ShopGoodsDTO、SearchShopGoodsQuery等支持新字段 - 新增PostAssetGoodsCommand和PostAssetGoodsBody数据传输对象 - 添加单元测试验证接口功能
This commit is contained in:
parent
1b057f6615
commit
3b80c87f52
|
@ -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<Void> pushExternalGoods(@RequestBody String body) throws UnsupportedEncodingException {
|
||||||
|
CommonRequest<PostAssetGoodsCommand> 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String, String> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -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<PostAssetGoodsCommand> goodsList;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -40,6 +40,10 @@ public class ShopGoodsEntity extends BaseEntity<ShopGoodsEntity> {
|
||||||
@TableField("category_id")
|
@TableField("category_id")
|
||||||
private Long categoryId;
|
private Long categoryId;
|
||||||
|
|
||||||
|
@ApiModelProperty("外部归属类型的商品ID")
|
||||||
|
@TableField("external_goods_id")
|
||||||
|
private Long externalGoodsId;
|
||||||
|
|
||||||
@ApiModelProperty("销售价格")
|
@ApiModelProperty("销售价格")
|
||||||
@TableField("price")
|
@TableField("price")
|
||||||
private BigDecimal price;
|
private BigDecimal price;
|
||||||
|
@ -72,6 +76,10 @@ public class ShopGoodsEntity extends BaseEntity<ShopGoodsEntity> {
|
||||||
@TableField("usage_instruction")
|
@TableField("usage_instruction")
|
||||||
private String usageInstruction;
|
private String usageInstruction;
|
||||||
|
|
||||||
|
@ApiModelProperty("归属类型(0-借还柜 1-固资通)")
|
||||||
|
@TableField("belong_type")
|
||||||
|
private Integer belongType;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Serializable pkVal() {
|
public Serializable pkVal() {
|
||||||
return this.goodsId;
|
return this.goodsId;
|
||||||
|
|
|
@ -71,7 +71,7 @@ public interface ShopGoodsMapper extends BaseMapper<ShopGoodsEntity> {
|
||||||
"FROM shop_goods g " +
|
"FROM shop_goods g " +
|
||||||
"LEFT JOIN cabinet_cell cc ON g.goods_id = cc.goods_id " +
|
"LEFT JOIN cabinet_cell cc ON g.goods_id = cc.goods_id " +
|
||||||
"LEFT JOIN smart_cabinet sc ON cc.cabinet_id = sc.cabinet_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<SearchGoodsWithCabinetDO> getGoodsWithCabinetList();
|
List<SearchGoodsWithCabinetDO> getGoodsWithCabinetList();
|
||||||
|
|
||||||
@Select("SELECT g.goods_id, g.goods_name, g.category_id, g.price, " +
|
@Select("SELECT g.goods_id, g.goods_name, g.category_id, g.price, " +
|
||||||
|
@ -81,7 +81,7 @@ public interface ShopGoodsMapper extends BaseMapper<ShopGoodsEntity> {
|
||||||
"FROM shop_goods g " +
|
"FROM shop_goods g " +
|
||||||
"LEFT JOIN cabinet_cell cc ON g.goods_id = cc.goods_id " +
|
"LEFT JOIN cabinet_cell cc ON g.goods_id = cc.goods_id " +
|
||||||
"LEFT JOIN smart_cabinet sc ON cc.cabinet_id = sc.cabinet_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<SearchGoodsWithCabinetDO> getGoodsWithCabinetListByShopId(@Param("shopId")Long shopId);
|
List<SearchGoodsWithCabinetDO> getGoodsWithCabinetListByShopId(@Param("shopId")Long shopId);
|
||||||
|
|
||||||
@Select("SELECT g.goods_id, g.goods_name, g.category_id, g.price, " +
|
@Select("SELECT g.goods_id, g.goods_name, g.category_id, g.price, " +
|
||||||
|
|
|
@ -27,4 +27,6 @@ public interface ShopGoodsService extends IService<ShopGoodsEntity> {
|
||||||
Long countAllRecord();
|
Long countAllRecord();
|
||||||
|
|
||||||
BigDecimal calculateTotalAmount();
|
BigDecimal calculateTotalAmount();
|
||||||
|
|
||||||
|
ShopGoodsEntity getGoodsByExternalGoodsId(Long externalGoodsId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,4 +57,13 @@ public class ShopGoodsServiceImpl extends ServiceImpl<ShopGoodsMapper, ShopGoods
|
||||||
public BigDecimal calculateTotalAmount() {
|
public BigDecimal calculateTotalAmount() {
|
||||||
return baseMapper.calculateTotalAmount();
|
return baseMapper.calculateTotalAmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ShopGoodsEntity getGoodsByExternalGoodsId(Long externalGoodsId) {
|
||||||
|
QueryWrapper<ShopGoodsEntity> wrapper = new QueryWrapper<>();
|
||||||
|
wrapper.eq("external_goods_id", externalGoodsId)
|
||||||
|
.eq("deleted", 0)
|
||||||
|
.last("LIMIT 1");
|
||||||
|
return baseMapper.selectOne(wrapper);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,9 +48,15 @@ public class ShopGoodsDTO {
|
||||||
@ExcelColumn(name = "分类ID")
|
@ExcelColumn(name = "分类ID")
|
||||||
private Long categoryId;
|
private Long categoryId;
|
||||||
|
|
||||||
|
@ExcelColumn(name = "外部商品ID")
|
||||||
|
private Long externalGoodsId;
|
||||||
|
|
||||||
@ExcelColumn(name = "分类名称")
|
@ExcelColumn(name = "分类名称")
|
||||||
private String categoryName;
|
private String categoryName;
|
||||||
|
|
||||||
|
@ExcelColumn(name = "归属类型(0-借还柜 1-固资通)")
|
||||||
|
private Long belongType;
|
||||||
|
|
||||||
@ExcelColumn(name = "销售价格")
|
@ExcelColumn(name = "销售价格")
|
||||||
private BigDecimal price;
|
private BigDecimal price;
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,12 @@ public class SearchShopGoodsQuery<T> extends AbstractPageQuery<T> {
|
||||||
|
|
||||||
protected String goodsName;
|
protected String goodsName;
|
||||||
protected Long categoryId;
|
protected Long categoryId;
|
||||||
|
protected Long externalGoodsId;
|
||||||
protected Integer status;
|
protected Integer status;
|
||||||
protected Integer autoApproval;
|
protected Integer autoApproval;
|
||||||
protected BigDecimal minPrice;
|
protected BigDecimal minPrice;
|
||||||
protected BigDecimal maxPrice;
|
protected BigDecimal maxPrice;
|
||||||
|
protected Integer belongType;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public QueryWrapper<T> addQueryCondition() {
|
public QueryWrapper<T> addQueryCondition() {
|
||||||
|
@ -29,6 +31,8 @@ public class SearchShopGoodsQuery<T> extends AbstractPageQuery<T> {
|
||||||
.eq(autoApproval != null, "g.auto_approval", autoApproval)
|
.eq(autoApproval != null, "g.auto_approval", autoApproval)
|
||||||
.ge(minPrice != null, "g.price", minPrice)
|
.ge(minPrice != null, "g.price", minPrice)
|
||||||
.le(maxPrice != null, "g.price", maxPrice)
|
.le(maxPrice != null, "g.price", maxPrice)
|
||||||
|
.eq(belongType != null, "g.belong_type", belongType)
|
||||||
|
.eq("g.external_goods_id", externalGoodsId)
|
||||||
.eq("g.deleted", 0)
|
.eq("g.deleted", 0)
|
||||||
.groupBy("g.goods_id");
|
.groupBy("g.goods_id");
|
||||||
|
|
||||||
|
|
|
@ -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`;
|
Loading…
Reference in New Issue