|
@@ -1,36 +1,40 @@
|
|
|
package org.dromara.external.controller.zhongche;
|
|
package org.dromara.external.controller.zhongche;
|
|
|
|
|
|
|
|
|
|
+import cn.dev33.satoken.stp.StpUtil;
|
|
|
import cn.hutool.core.codec.Base64;
|
|
import cn.hutool.core.codec.Base64;
|
|
|
-import cn.hutool.core.collection.CollUtil;
|
|
|
|
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
|
|
+import cn.hutool.core.util.ReUtil;
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
import cn.hutool.json.JSONUtil;
|
|
import cn.hutool.json.JSONUtil;
|
|
|
|
|
+import com.fasterxml.jackson.core.JsonProcessingException;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.RequiredArgsConstructor;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
import org.apache.dubbo.config.annotation.DubboReference;
|
|
import org.apache.dubbo.config.annotation.DubboReference;
|
|
|
import org.dromara.common.core.domain.zhongche.domain.Goods;
|
|
import org.dromara.common.core.domain.zhongche.domain.Goods;
|
|
|
import org.dromara.common.core.domain.zhongche.domain.Prices;
|
|
import org.dromara.common.core.domain.zhongche.domain.Prices;
|
|
|
import org.dromara.common.core.domain.zhongche.vo.PricesVo;
|
|
import org.dromara.common.core.domain.zhongche.vo.PricesVo;
|
|
|
-import org.dromara.common.json.utils.JsonUtils;
|
|
|
|
|
import org.dromara.external.api.zhongche.domain.*;
|
|
import org.dromara.external.api.zhongche.domain.*;
|
|
|
import org.dromara.external.api.zhongche.domain.bo.AreaStockBo;
|
|
import org.dromara.external.api.zhongche.domain.bo.AreaStockBo;
|
|
|
import org.dromara.external.api.zhongche.domain.bo.*;
|
|
import org.dromara.external.api.zhongche.domain.bo.*;
|
|
|
import org.dromara.external.api.zhongche.domain.vo.*;
|
|
import org.dromara.external.api.zhongche.domain.vo.*;
|
|
|
import org.dromara.external.api.zhongche.domain.Catalog;
|
|
import org.dromara.external.api.zhongche.domain.Catalog;
|
|
|
-import org.dromara.external.service.IExternalProductCategoryService;
|
|
|
|
|
-import org.dromara.external.util.SM2SignUtil;
|
|
|
|
|
|
|
+import org.dromara.external.util.SM2SignatureUtils;
|
|
|
|
|
+import org.dromara.external.util.SignParamUtils;
|
|
|
import org.dromara.product.api.RemoteExternalOrderService;
|
|
import org.dromara.product.api.RemoteExternalOrderService;
|
|
|
import org.dromara.product.api.RemoteProductService;
|
|
import org.dromara.product.api.RemoteProductService;
|
|
|
import org.dromara.product.api.domain.ProductCategoryRemoteVo;
|
|
import org.dromara.product.api.domain.ProductCategoryRemoteVo;
|
|
|
|
|
+import org.dromara.product.api.domain.zhongche.dto.StocksResult;
|
|
|
import org.dromara.product.api.domain.zhongche.dto.StocksResultDto;
|
|
import org.dromara.product.api.domain.zhongche.dto.StocksResultDto;
|
|
|
|
|
+import org.springframework.beans.BeanUtils;
|
|
|
import org.springframework.validation.annotation.Validated;
|
|
import org.springframework.validation.annotation.Validated;
|
|
|
import org.springframework.web.bind.annotation.PostMapping;
|
|
import org.springframework.web.bind.annotation.PostMapping;
|
|
|
import org.springframework.web.bind.annotation.RequestBody;
|
|
import org.springframework.web.bind.annotation.RequestBody;
|
|
|
import org.springframework.web.bind.annotation.RequestMapping;
|
|
import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
import org.springframework.web.bind.annotation.RestController;
|
|
import org.springframework.web.bind.annotation.RestController;
|
|
|
|
|
|
|
|
|
|
+import java.math.BigDecimal;
|
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.charset.StandardCharsets;
|
|
|
-import java.util.HashMap;
|
|
|
|
|
-import java.util.List;
|
|
|
|
|
-import java.util.Map;
|
|
|
|
|
|
|
+import java.util.*;
|
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
import static org.dromara.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
|
|
import static org.dromara.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
|
|
@@ -39,14 +43,15 @@ import static org.dromara.common.core.constant.GlobalConstants.GLOBAL_REDIS_KEY;
|
|
|
* author
|
|
* author
|
|
|
* 时间:2026/1/5,19:03
|
|
* 时间:2026/1/5,19:03
|
|
|
*/
|
|
*/
|
|
|
|
|
+@Slf4j
|
|
|
@Validated
|
|
@Validated
|
|
|
@RequiredArgsConstructor
|
|
@RequiredArgsConstructor
|
|
|
@RestController
|
|
@RestController
|
|
|
@RequestMapping("/api/mall/auth")
|
|
@RequestMapping("/api/mall/auth")
|
|
|
public class ZhongChePushController {
|
|
public class ZhongChePushController {
|
|
|
- private final String url = "";
|
|
|
|
|
private final String key = GLOBAL_REDIS_KEY+"external:zhongche:token:";
|
|
private final String key = GLOBAL_REDIS_KEY+"external:zhongche:token:";
|
|
|
- private final String CLIENT_ID = "ZC_CLIENT_ID";
|
|
|
|
|
|
|
+ private final String CLIENT_ID = "KFZAVuIyC56";
|
|
|
|
|
+ private final String VERSION = "1.0.0";
|
|
|
|
|
|
|
|
@DubboReference
|
|
@DubboReference
|
|
|
private final RemoteProductService remoteProductService;
|
|
private final RemoteProductService remoteProductService;
|
|
@@ -54,9 +59,9 @@ public class ZhongChePushController {
|
|
|
@DubboReference
|
|
@DubboReference
|
|
|
private final RemoteExternalOrderService remoteOrderService;
|
|
private final RemoteExternalOrderService remoteOrderService;
|
|
|
|
|
|
|
|
- private final IExternalProductCategoryService externalProductCategoryService;
|
|
|
|
|
|
|
+ private final String DEVELOPER_PRIVATE_KEY = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE1YybOl0QDE2e9humlm4AgI3wJ1tI+UfVRZx8kk4hfPtZjorHN8Tjq/cP07t4Yscy+R9oFci8xw0VpBbcnlaq1w=="; // 电商提供的私钥
|
|
|
|
|
|
|
|
- private final SM2SignUtil sm2SignUtil;
|
|
|
|
|
|
|
+ private final String DEVELOPER_PUBLIC_KEY = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgpQdXwMi21Mg1FhWad2AQLOwfNiDHgwhootau0YerQbagCgYIKoEcz1UBgi2hRANCAATVjJs6XRAMTZ72G6aWbgCAjfAnW0j5R9VFnHySTiF8+1mOisc3xOOr9w/Tu3hixzL5H2gVyLzHDRWkFtyeVqrX"; // 电商提供的公钥
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -71,43 +76,52 @@ public class ZhongChePushController {
|
|
|
|
|
|
|
|
// 获取品目列表
|
|
// 获取品目列表
|
|
|
@PostMapping("/catalog/query")
|
|
@PostMapping("/catalog/query")
|
|
|
- public ZCR<String> catelogQuery(@RequestBody ZCTokenBo zcTokenBo) {
|
|
|
|
|
- //TODO 1. 公共请求参数校验
|
|
|
|
|
- ZCR<Void> checkResult = checkPublicParams(zcTokenBo);
|
|
|
|
|
- if (!"0".equals(checkResult.getRespCode())) {
|
|
|
|
|
- // 校验失败,直接返回错误响应
|
|
|
|
|
- return ZCR.fail(checkResult.getRespCode(), checkResult.getRespMsg());
|
|
|
|
|
- }
|
|
|
|
|
- // 2.业务参数校验
|
|
|
|
|
- String bizJson = Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
|
|
|
|
|
- if (!"{}".equals(bizJson.trim())){
|
|
|
|
|
- return ZCR.fail("5007", "业务参数data格式错误,需为空JSON的Base64编码");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // TODO 3. AccessToken有效性校验
|
|
|
|
|
- if (!checkAccessTokenValid(zcTokenBo.getAccessToken())) {
|
|
|
|
|
- return ZCR.fail("5007", "accessToken无效或已过期");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 4. 核心业务:查询品目列表并转换为文档要求的结构
|
|
|
|
|
- List<Catalog> catalogVoList = remoteProductService.queryList()
|
|
|
|
|
- .stream()
|
|
|
|
|
- .map(this::convertToCatalogVo)
|
|
|
|
|
- .collect(Collectors.toList());
|
|
|
|
|
-
|
|
|
|
|
- // 5. 构造响应业务参数并Base64编码(放入ZCR的data字段)
|
|
|
|
|
- CatalogsVo catalogVo = new CatalogsVo();
|
|
|
|
|
- catalogVo.setCatalogs(catalogVoList);
|
|
|
|
|
-
|
|
|
|
|
- // 业务参数→JSON→Base64编码
|
|
|
|
|
- String respBizJson = JSONUtil.toJsonStr(catalogVo);
|
|
|
|
|
- String respDataBase64 = Base64.encode(respBizJson, StandardCharsets.UTF_8);
|
|
|
|
|
-
|
|
|
|
|
- //TODO 6. 生成响应签名(复用你的ZCR的sign字段)
|
|
|
|
|
- String respSign = "";
|
|
|
|
|
-
|
|
|
|
|
- // 7. 封装最终响应(严格复用你的ZCR.resetR)
|
|
|
|
|
- return ZCR.ok(respDataBase64,respSign);
|
|
|
|
|
|
|
+ public ZCR catelogQuery(@RequestBody ZCTokenBo zcTokenBo) {
|
|
|
|
|
+ // 1. 公共请求参数校验
|
|
|
|
|
+ ZCR checkResult = checkPublicParams(zcTokenBo);
|
|
|
|
|
+ if (!"0".equals(checkResult.getRespCode())) {
|
|
|
|
|
+ // 校验失败,直接返回错误响应
|
|
|
|
|
+ return ZCR.fail(checkResult.getRespCode(), checkResult.getRespMsg());
|
|
|
|
|
+ }
|
|
|
|
|
+ // 2.业务参数校验
|
|
|
|
|
+ String bizJson = Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
|
|
|
|
|
+ if (!"{}".equals(bizJson.trim())){
|
|
|
|
|
+ return ZCR.fail("5007", "业务参数data格式错误,需为空JSON的Base64编码");
|
|
|
|
|
+ }
|
|
|
|
|
+ //3. AccessToken有效性校验
|
|
|
|
|
+ ZCR zcr = checkAccessTokenValid(zcTokenBo.getAccessToken());
|
|
|
|
|
+ if (!zcr.getRespCode().equals("0")){
|
|
|
|
|
+ return zcr;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 4. 核心业务:查询品目列表并转换为文档要求的结构 TODO这里标准品目没有做
|
|
|
|
|
+ List<Catalog> catalogVoList = remoteProductService.queryList()
|
|
|
|
|
+ .stream()
|
|
|
|
|
+ .map(this::convertToCatalogVo)
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+ // 5. 构造响应业务参数并Base64编码(放入ZCR的data字段)
|
|
|
|
|
+ CatalogsVo catalogVo = new CatalogsVo();
|
|
|
|
|
+ catalogVo.setCatalogs(catalogVoList);
|
|
|
|
|
+ // 业务参数→JSON→Base64编码
|
|
|
|
|
+ String respBizJson = JSONUtil.toJsonStr(catalogVo);
|
|
|
|
|
+ String respDataBase64 = Base64.encode(respBizJson, StandardCharsets.UTF_8);
|
|
|
|
|
+ // 6. 生成响应签名(核心:复用工具类,完成SM2withSM3签名,补全TODO)
|
|
|
|
|
+ String respSign = "";
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 步骤6.1:封装空签名的成功响应实体(用于生成待签名字符串)
|
|
|
|
|
+ ZCR successZcr = ZCR.ok(respDataBase64, "");
|
|
|
|
|
+ // 步骤6.2:生成待签名字符串(筛选、排序、拼接JSON,严格贴合文档)
|
|
|
|
|
+ String respSignContent = SignParamUtils.getSignContent(successZcr);
|
|
|
|
|
+ // 步骤6.3:用电商私钥生成SM2签名(Base64编码,直接赋值给sign)
|
|
|
|
|
+ respSign = SM2SignatureUtils.sign(respSignContent, DEVELOPER_PRIVATE_KEY);
|
|
|
|
|
+ } catch (JsonProcessingException e) {
|
|
|
|
|
+ log.error("中车电商品目查询接口 - 待签名字符串生成失败,业务JSON:{}", respBizJson, e);
|
|
|
|
|
+ return ZCR.fail("5009", "接口响应签名生成异常(JSON转换失败)");
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("中车电商品目查询接口 - 响应签名生成失败,业务JSON:{}", respBizJson, e);
|
|
|
|
|
+ return ZCR.fail("5010", "接口响应签名生成失败,请稍后重试");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 7. 封装最终响应(严格复用你的ZCR.resetR)
|
|
|
|
|
+ return ZCR.ok(respDataBase64,respSign);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -116,7 +130,7 @@ public class ZhongChePushController {
|
|
|
private Catalog convertToCatalogVo(ProductCategoryRemoteVo category) {
|
|
private Catalog convertToCatalogVo(ProductCategoryRemoteVo category) {
|
|
|
Catalog catalog = new Catalog();
|
|
Catalog catalog = new Catalog();
|
|
|
// 以下字段映射需根据你的实体实际字段调整
|
|
// 以下字段映射需根据你的实体实际字段调整
|
|
|
- catalog.setId(category.getCategoryNo()); // 品目id
|
|
|
|
|
|
|
+ catalog.setId(category.getId().toString()); // 品目id
|
|
|
catalog.setPid(category.getParentId().toString()); // 父id
|
|
catalog.setPid(category.getParentId().toString()); // 父id
|
|
|
catalog.setName(category.getCategoryName()); // 品目名称
|
|
catalog.setName(category.getCategoryName()); // 品目名称
|
|
|
catalog.setLevel(category.getClassLevel().toString()); // 品目级次
|
|
catalog.setLevel(category.getClassLevel().toString()); // 品目级次
|
|
@@ -150,77 +164,100 @@ public class ZhongChePushController {
|
|
|
* @return
|
|
* @return
|
|
|
*/
|
|
*/
|
|
|
@PostMapping("/egoods/stock/query")
|
|
@PostMapping("/egoods/stock/query")
|
|
|
- public ZCR<String> egoodsStockQuery(@RequestBody ZCTokenBo zcTokenBo) {
|
|
|
|
|
- //TODO 1. 公共请求参数校验
|
|
|
|
|
- ZCR<Void> checkResult = checkPublicParams(zcTokenBo);
|
|
|
|
|
- if (!"0".equals(checkResult.getRespCode())) {
|
|
|
|
|
- return ZCR.fail(checkResult.getRespCode(), checkResult.getRespMsg());
|
|
|
|
|
|
|
+ public ZCR egoodsStockQuery(@RequestBody ZCTokenBo zcTokenBo) {
|
|
|
|
|
+
|
|
|
|
|
+ //1. 公共请求参数校验
|
|
|
|
|
+ ZCR checkResult = checkPublicParams(zcTokenBo);
|
|
|
|
|
+ if (!"0".equals(checkResult.getRespCode())) {
|
|
|
|
|
+ return checkResult;
|
|
|
|
|
+ }
|
|
|
|
|
+ //2. AccessToken有效性校验
|
|
|
|
|
+ ZCR zcr = checkAccessTokenValid(zcTokenBo.getAccessToken());
|
|
|
|
|
+ if (!zcr.getRespCode().equals("0")){
|
|
|
|
|
+ return zcr;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 3. 业务参数解析
|
|
|
|
|
+ AreaStockBo areaStockBo;
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 解码data字段(Base64→JSON→业务BO)
|
|
|
|
|
+ String decodedData = Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
|
|
|
|
|
+ if (StrUtil.isBlank(decodedData)) {
|
|
|
|
|
+ return ZCR.fail("5011", "业务参数data不能为空");
|
|
|
}
|
|
}
|
|
|
- // TODO 2. AccessToken有效性校验
|
|
|
|
|
- if (!checkAccessTokenValid(zcTokenBo.getAccessToken())) {
|
|
|
|
|
- return ZCR.fail("5007", "accessToken无效或已过期");
|
|
|
|
|
|
|
+ areaStockBo = JSONUtil.toBean(decodedData, AreaStockBo.class);
|
|
|
|
|
+ if (areaStockBo == null) {
|
|
|
|
|
+ return ZCR.fail("5012", "业务参数data格式错误,无法解析");
|
|
|
}
|
|
}
|
|
|
- // 3. 业务参数解析
|
|
|
|
|
- AreaStockBo areaStockBo;
|
|
|
|
|
- try {
|
|
|
|
|
- // 解码data字段(Base64→JSON→业务BO)
|
|
|
|
|
- String decodedData = Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
|
|
|
|
|
- areaStockBo = JSONUtil.toBean(decodedData, AreaStockBo.class);
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- return ZCR.fail("5007", "业务参数data解析失败:" + e.getMessage());
|
|
|
|
|
|
|
+ // 3.3 业务参数必填项校验
|
|
|
|
|
+ if (areaStockBo.getGoods() == null || areaStockBo.getGoods().isEmpty()) {
|
|
|
|
|
+ return ZCR.fail("5013", "商品信息列表goods不能为空");
|
|
|
}
|
|
}
|
|
|
- // 4. 业务参数校验
|
|
|
|
|
- String validateMsg = validateBizParams(areaStockBo);
|
|
|
|
|
- if (StrUtil.isNotBlank(validateMsg)) {
|
|
|
|
|
- return ZCR.fail("5007", validateMsg);
|
|
|
|
|
|
|
+ if (areaStockBo.getGoods().size() > 50) {
|
|
|
|
|
+ return ZCR.fail("5014", "商品信息列表goods最多支持50条");
|
|
|
}
|
|
}
|
|
|
- // 5.核心业务:库存查询
|
|
|
|
|
- String areaId = areaStockBo.getAreaId();
|
|
|
|
|
- Map<String, Integer> collect = areaStockBo.getGoods().stream()
|
|
|
|
|
- .collect(Collectors.toMap(Goods::getGoodsId,
|
|
|
|
|
- Goods::getGoodsNum));
|
|
|
|
|
- StocksResultDto stocksResultDto = remoteProductService.queryProductStock(collect,areaId);
|
|
|
|
|
-
|
|
|
|
|
- // 6. 构造响应业务参数
|
|
|
|
|
- // JSON→Base64编码(响应data字段值)
|
|
|
|
|
- String respBizJson = JSONUtil.toJsonStr(stocksResultDto);
|
|
|
|
|
- String respDataBase64 = Base64.encode(respBizJson, StandardCharsets.UTF_8);
|
|
|
|
|
- // 6. 生成响应签名
|
|
|
|
|
- String respSign = "";
|
|
|
|
|
-
|
|
|
|
|
- // 7. 封装最终响应
|
|
|
|
|
- return ZCR.ok(respDataBase64, respSign);
|
|
|
|
|
- }
|
|
|
|
|
- private String validateBizParams(AreaStockBo areaStockBo) {
|
|
|
|
|
- // 1. goods列表校验
|
|
|
|
|
- if (CollUtil.isEmpty(areaStockBo.getGoods())) {
|
|
|
|
|
- return "商品信息goods列表不能为空";
|
|
|
|
|
|
|
+ // 3.4 单个商品信息校验(避免无效数据)
|
|
|
|
|
+ for (Goods goods : areaStockBo.getGoods()) {
|
|
|
|
|
+ if (StrUtil.isBlank(goods.getGoodsId()) || goods.getGoodsNum() == null || goods.getGoodsNum() <= 0) {
|
|
|
|
|
+ return ZCR.fail("5015", "商品sku(goodsId)不能为空,所需库存(goodsNum)必须为正整数");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ return ZCR.fail("5007", "业务参数data解析失败:" + e.getMessage());
|
|
|
}
|
|
}
|
|
|
- if (areaStockBo.getGoods().size() > 50) {
|
|
|
|
|
- return "商品信息goods列表最多支持50个商品";
|
|
|
|
|
|
|
+
|
|
|
|
|
+ GoodsStockVo goodsStockVo = new GoodsStockVo();
|
|
|
|
|
+ // 4. 核心业务:查询商品库存(封装业务逻辑,映射文档返回格式)
|
|
|
|
|
+ List<Stocks> stockRespList = queryGoodsStock(areaStockBo);
|
|
|
|
|
+ goodsStockVo.setStocks(stockRespList);
|
|
|
|
|
+ // 7. 业务参数→JSON→Base64编码(响应data字段要求)
|
|
|
|
|
+ String respBizJson = JSONUtil.toJsonStr(goodsStockVo);
|
|
|
|
|
+ String respDataBase64 = Base64.encode(respBizJson, StandardCharsets.UTF_8);
|
|
|
|
|
+
|
|
|
|
|
+ // 8. 生成响应签名(贴合签名机制,复用工具类)
|
|
|
|
|
+ String respSign = "";
|
|
|
|
|
+ try {
|
|
|
|
|
+ ZCR successZcr = ZCR.ok(respDataBase64, "");
|
|
|
|
|
+ String respSignContent = SignParamUtils.getSignContent(successZcr);
|
|
|
|
|
+ respSign = SM2SignatureUtils.sign(respSignContent, DEVELOPER_PRIVATE_KEY);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("响应签名生成失败,业务JSON:{}", respBizJson, e);
|
|
|
|
|
+ return ZCR.fail("5017", "接口响应签名生成失败");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 2. 遍历校验每个商品
|
|
|
|
|
- for (int i = 0; i < areaStockBo.getGoods().size(); i++) {
|
|
|
|
|
- Goods goods = areaStockBo.getGoods().get(i);
|
|
|
|
|
- if (StrUtil.isBlank(goods.getGoodsId())) {
|
|
|
|
|
- return "第" + (i + 1) + "个商品的goodsId(商品sku)不能为空";
|
|
|
|
|
- }
|
|
|
|
|
- if (goods.getGoodsNum() == null || goods.getGoodsNum() <= 0) {
|
|
|
|
|
- return "第" + (i + 1) + "个商品的goodsNum(所需库存)必须大于0";
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 9. 封装最终响应(符合文档要求)
|
|
|
|
|
+ return ZCR.ok(respDataBase64, respSign);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 核心业务:查询商品库存,映射文档返回格式(包含库存状态、剩余数量等规则)
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<Stocks> queryGoodsStock(AreaStockBo bo) {
|
|
|
|
|
+ String areaId = bo.getAreaId() == null ? "" : bo.getAreaId();
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Integer> goodsMap = new HashMap<>();
|
|
|
|
|
+ for (Goods goodsReq : bo.getGoods()) {
|
|
|
|
|
+ String goodsId = goodsReq.getGoodsId();
|
|
|
|
|
+ Integer goodsNum = goodsReq.getGoodsNum();
|
|
|
|
|
+ goodsMap.put(goodsId, goodsNum); // 对应Dubbo服务要求:key=商品ID,value=所需库存
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- //TODO 3. areaId校验(虚拟库存可不填,有地区库存则必填)
|
|
|
|
|
- //
|
|
|
|
|
- /*boolean isVirtualStock = false; // 假设从配置获取:是否虚拟库存
|
|
|
|
|
- if (!isVirtualStock && StrUtil.isBlank(areaStockBo.getAreaId())) {
|
|
|
|
|
- return "非虚拟库存模式下,地区id areaId不能为空";
|
|
|
|
|
- }*/
|
|
|
|
|
|
|
+ // 3. 调用Dubbo服务,获取库存结果(你的remoteProductService)
|
|
|
|
|
+ StocksResultDto stocksResultDto = remoteProductService.queryProductStock(goodsMap, areaId);
|
|
|
|
|
+
|
|
|
|
|
+ List<Stocks> goodsStockRespList = new ArrayList<>();
|
|
|
|
|
+ if (stocksResultDto == null || stocksResultDto.getStocks() == null || stocksResultDto.getStocks().isEmpty()) {
|
|
|
|
|
+ // 无库存数据返回,遍历入参商品,按「未查询到」处理
|
|
|
|
|
+ for (StocksResult stocksResult : stocksResultDto.getStocks()) {
|
|
|
|
|
+ Stocks stocks =new Stocks();
|
|
|
|
|
+ BeanUtils.copyProperties(stocksResult, stocks);
|
|
|
|
|
+ goodsStockRespList.add(stocks);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- return null; // 校验通过
|
|
|
|
|
|
|
+ return goodsStockRespList;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+
|
|
|
//TODO 查询商品价格
|
|
//TODO 查询商品价格
|
|
|
/*
|
|
/*
|
|
|
//请求业务参数
|
|
//请求业务参数
|
|
@@ -230,91 +267,174 @@ public class ZhongChePushController {
|
|
|
PricesVo pricesVo = new PricesVo();
|
|
PricesVo pricesVo = new PricesVo();
|
|
|
*/
|
|
*/
|
|
|
@PostMapping("/egoods/price/query")
|
|
@PostMapping("/egoods/price/query")
|
|
|
- public ZCR<String> egoodsPriceQuery(@RequestBody ZCTokenBo zcTokenBo) {
|
|
|
|
|
- //TODO 1. 公共请求参数校验
|
|
|
|
|
- ZCR<Void> checkResult = checkPublicParams(zcTokenBo);
|
|
|
|
|
- if (!"0".equals(checkResult.getRespCode())) {
|
|
|
|
|
- return ZCR.fail(checkResult.getRespCode(), checkResult.getRespMsg());
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ public ZCR egoodsPriceQuery(@RequestBody ZCTokenBo zcTokenBo) {
|
|
|
|
|
+ //1. 公共请求参数校验
|
|
|
|
|
+ ZCR checkResult = checkPublicParams(zcTokenBo);
|
|
|
|
|
+ if (!"0".equals(checkResult.getRespCode())) {
|
|
|
|
|
+ return ZCR.fail(checkResult.getRespCode(), checkResult.getRespMsg());
|
|
|
|
|
+ }
|
|
|
|
|
+ //2. AccessToken有效性校验
|
|
|
|
|
+ ZCR zcr = checkAccessTokenValid(zcTokenBo.getAccessToken());
|
|
|
|
|
+ if (!zcr.getRespCode().equals("0")){
|
|
|
|
|
+ return zcr;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 3. 业务参数解析
|
|
|
|
|
+ GoodsPrieceBo goods;
|
|
|
|
|
+ List<String> goodsIdList;
|
|
|
|
|
+ // 解码data字段(Base64→JSON→业务BO)
|
|
|
|
|
+ String decodedData = Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
|
|
|
|
|
+ if (StrUtil.isBlank(decodedData)) {
|
|
|
|
|
+ return ZCR.fail("5021", "业务参数data不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ goods = JSONUtil.toBean(decodedData, GoodsPrieceBo.class);
|
|
|
|
|
+ if (goods == null || StrUtil.isBlank(goods.getGoodsIds())) {
|
|
|
|
|
+ return ZCR.fail("5022", "商品sku(goodsIds)不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 3.3 分割商品sku,转换为列表(多个用,分隔)
|
|
|
|
|
+ goodsIdList = Arrays.stream(goods.getGoodsIds().split(","))
|
|
|
|
|
+ .map(String::trim)
|
|
|
|
|
+ .filter(StrUtil::isNotBlank)
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+ if (goodsIdList.isEmpty()) {
|
|
|
|
|
+ return ZCR.fail("5023", "商品sku(goodsIds)格式错误,有效sku不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 4. 核心业务:调用Dubbo服务查询商品价格(适配你的业务逻辑)
|
|
|
|
|
+ PricesVo pricesVo = queryGoodsPrice(goodsIdList);
|
|
|
|
|
|
|
|
- // TODO 2. AccessToken有效性校验
|
|
|
|
|
- if (!checkAccessTokenValid(zcTokenBo.getAccessToken())) {
|
|
|
|
|
- return ZCR.fail("5007", "accessToken无效或已过期");
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 5. 构造响应业务参数并Base64编码(符合文档data字段要求)
|
|
|
|
|
+ String respBizJson = JSONUtil.toJsonStr(pricesVo);
|
|
|
|
|
+ String respDataBase64 = Base64.encode(respBizJson, StandardCharsets.UTF_8);
|
|
|
|
|
|
|
|
- // 3. 业务参数解析
|
|
|
|
|
- GoodsPrieceBo goods;
|
|
|
|
|
- // 解码data字段(Base64→JSON→业务BO)
|
|
|
|
|
- String decodedData = Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
|
|
|
|
|
- goods = JSONUtil.toBean(decodedData, GoodsPrieceBo.class);
|
|
|
|
|
|
|
+ // 6. 生成响应签名(复用工具类,贴合签名机制)
|
|
|
|
|
+ String respSign = "";
|
|
|
|
|
+ try {
|
|
|
|
|
+ ZCR successZcr = ZCR.ok(respDataBase64, "");
|
|
|
|
|
+ String respSignContent = SignParamUtils.getSignContent(successZcr);
|
|
|
|
|
+ respSign = SM2SignatureUtils.sign(respSignContent, DEVELOPER_PRIVATE_KEY);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("商品价格查询 - 响应签名生成失败,业务JSON:{}", respBizJson, e);
|
|
|
|
|
+ return ZCR.fail("5025", "接口响应签名生成失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 7. 封装最终响应(符合文档要求)
|
|
|
|
|
+ return ZCR.ok(respDataBase64, respSign);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 4. 业务参数校验
|
|
|
|
|
- String validateMsg = validateGoodsPriceParams(goods);
|
|
|
|
|
- if (StrUtil.isNotBlank(validateMsg)) {
|
|
|
|
|
- return ZCR.fail("5007", validateMsg);
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 核心业务:查询商品价格,映射文档返回格式
|
|
|
|
|
+ */
|
|
|
|
|
+ private PricesVo queryGoodsPrice(List<String> goodsIdList) {
|
|
|
|
|
+ // 1. 初始化响应结果
|
|
|
|
|
+ PricesVo pricesVo = new PricesVo();
|
|
|
|
|
+ pricesVo.setPrices(new ArrayList<>());
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 调用Dubbo服务查询商品价格(替换为你的真实Dubbo服务方法)
|
|
|
|
|
+ // 示例:假设你的Dubbo服务提供了查询商品价格的方法
|
|
|
|
|
+ Map<String, Prices> priceMap = remoteProductService.queryProductPrice(goodsIdList);
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 遍历商品sku,封装价格信息(贴合文档规则)
|
|
|
|
|
+ for (String goodsId : goodsIdList) {
|
|
|
|
|
+ Prices priceResp = new Prices();
|
|
|
|
|
+ priceResp.setGoodsId(goodsId);
|
|
|
|
|
+
|
|
|
|
|
+ // 3.1 获取Dubbo查询结果,判断是否查询到
|
|
|
|
|
+ Prices productPrice = priceMap.get(goodsId);
|
|
|
|
|
+ if (productPrice == null) {
|
|
|
|
|
+ // 未查询到,按文档规则赋值-1(BigDecimal类型)
|
|
|
|
|
+ priceResp.setDsPrice(new BigDecimal(-1));
|
|
|
|
|
+ priceResp.setPrice(new BigDecimal(-1));
|
|
|
|
|
+ priceResp.setTaxFreePrice(null);
|
|
|
|
|
+ priceResp.setTax(null);
|
|
|
|
|
+ priceResp.setTaxCode("");
|
|
|
|
|
+ pricesVo.getPrices().add(priceResp);
|
|
|
|
|
+ continue;
|
|
|
}
|
|
}
|
|
|
- // 5. 查询商品价格
|
|
|
|
|
- List<String> goodsIds = List.of(goods.toString().split(","));
|
|
|
|
|
- List<Prices> prices = remoteProductService.queryProductPrice(goodsIds);
|
|
|
|
|
|
|
|
|
|
|
|
+ // 3.2 填充查询到的价格信息(保证精度,最多精确到分)
|
|
|
|
|
+ // 电商价格(dsPrice)
|
|
|
|
|
+ BigDecimal dsPrice = productPrice.getDsPrice() == null ? new BigDecimal(-1) : productPrice.getDsPrice();
|
|
|
|
|
+ priceResp.setDsPrice(dsPrice); // 保留2位小数,四舍五入
|
|
|
|
|
+
|
|
|
|
|
+ // 协议价格(price)
|
|
|
|
|
+ BigDecimal protocolPrice = productPrice.getPrice() == null ? new BigDecimal(-1) : productPrice.getPrice();
|
|
|
|
|
+ priceResp.setPrice(protocolPrice);
|
|
|
|
|
+ priceResp.setTaxFreePrice(null);
|
|
|
|
|
+ priceResp.setTax(productPrice.getTax());
|
|
|
|
|
+ // 税收编码(非必填)
|
|
|
|
|
+ priceResp.setTaxCode(null);
|
|
|
|
|
+
|
|
|
|
|
+ // 3.3 添加到响应列表
|
|
|
|
|
+ pricesVo.getPrices().add(priceResp);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 6. 构造响应业务参数
|
|
|
|
|
- // 封装响应根对象(包含prices列表)
|
|
|
|
|
- PricesVo pricesVo = new PricesVo();
|
|
|
|
|
- pricesVo.setPrices(prices);
|
|
|
|
|
- System.out.println(pricesVo);
|
|
|
|
|
|
|
+ return pricesVo;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // JSON→Base64编码(响应data字段值)
|
|
|
|
|
- String respBizJson = JSONUtil.toJsonStr(pricesVo);
|
|
|
|
|
- String respDataBase64 = Base64.encode(respBizJson, StandardCharsets.UTF_8);
|
|
|
|
|
|
|
+ // 4.5查询物流信息(物流信息尚未实现)
|
|
|
|
|
+ @PostMapping("/get/track")
|
|
|
|
|
+ public ZCR getTrack(@RequestBody ZCTokenBo zcTokenBo) {
|
|
|
|
|
+ // 1. 公共请求参数校验(含签名、版本、clientId等,复用已有逻辑)
|
|
|
|
|
+ ZCR checkResult = checkPublicParams(zcTokenBo);
|
|
|
|
|
+ if (!"0".equals(checkResult.getRespCode())) {
|
|
|
|
|
+ return checkResult;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 7. 生成响应签名
|
|
|
|
|
- Map<String, String> map = new HashMap<>();
|
|
|
|
|
- map.put("respCode", "0");
|
|
|
|
|
- map.put("data", respBizJson);
|
|
|
|
|
- String respSign = JsonUtils.toJsonString(map);
|
|
|
|
|
|
|
+ //2. AccessToken有效性校验
|
|
|
|
|
+ ZCR zcr = checkAccessTokenValid(zcTokenBo.getAccessToken());
|
|
|
|
|
+ if (!zcr.getRespCode().equals("0")){
|
|
|
|
|
+ return zcr;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- //8. 封装最终响应
|
|
|
|
|
- return ZCR.ok(respDataBase64, respSign);
|
|
|
|
|
- }
|
|
|
|
|
- /**
|
|
|
|
|
- * 业务参数校验(匹配4.4.4.1文档规则)
|
|
|
|
|
- */
|
|
|
|
|
- private String validateGoodsPriceParams(GoodsPrieceBo goods) {
|
|
|
|
|
- // 1. goodsIds非空校验
|
|
|
|
|
- if (StrUtil.isBlank(goods.getGoodsIds())) {
|
|
|
|
|
- return "商品sku(goodsIds)不能为空";
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 2. 拆分goodsIds并校验格式/数量
|
|
|
|
|
- String[] goodsIdArray = goods.getGoodsIds().split(",");
|
|
|
|
|
- if (goodsIdArray.length == 0) {
|
|
|
|
|
- return "商品sku(goodsIds)格式错误,多个sku需用英文逗号分隔";
|
|
|
|
|
- }
|
|
|
|
|
- // 3. 校验每个sku非空
|
|
|
|
|
- for (int i = 0; i < goodsIdArray.length; i++) {
|
|
|
|
|
- String goodsId = goodsIdArray[i].trim();
|
|
|
|
|
- if (StrUtil.isBlank(goodsId)) {
|
|
|
|
|
- return "第" + (i + 1) + "个商品sku为空,请检查goodsId格式";
|
|
|
|
|
|
|
+ // 3. 业务参数解析与校验(贴合文档要求)
|
|
|
|
|
+ DeliveryTrackBo bizReq;
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 3.1 Base64解码data字段
|
|
|
|
|
+ String decodedData = Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
|
|
|
|
|
+ if (StrUtil.isBlank(decodedData)) {
|
|
|
|
|
+ return ZCR.fail("5031", "业务参数data不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3.2 JSON转业务请求实体
|
|
|
|
|
+ bizReq = JSONUtil.toBean(decodedData, DeliveryTrackBo.class);
|
|
|
|
|
+ if (bizReq == null) {
|
|
|
|
|
+ return ZCR.fail("5032", "业务参数data格式错误,无法解析");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 3.3 业务参数必填项校验
|
|
|
|
|
+ if (StrUtil.isBlank(bizReq.getOutgoingCode()) || bizReq.getOutgoingCode().length() > 20) {
|
|
|
|
|
+ return ZCR.fail("5033", "发货单编号(outgoingCode)不能为空,且长度不超过20");
|
|
|
}
|
|
}
|
|
|
|
|
+ if (StrUtil.isBlank(bizReq.getWaybillType()) || !List.of("0", "1").contains(bizReq.getWaybillType())) {
|
|
|
|
|
+ return ZCR.fail("5034", "运单类型(waybillType)必填,且仅支持0(订单)、1(换新单)");
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("物流查询 - 业务参数解析/校验失败,data:{}", zcTokenBo.getData(), e);
|
|
|
|
|
+ return ZCR.fail("5035", "业务参数data解析失败:" + e.getMessage());
|
|
|
}
|
|
}
|
|
|
- return null; // 校验通过
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- //TODO 4.5查询物流信息(物流信息尚未实现)
|
|
|
|
|
- @PostMapping("/get/track")
|
|
|
|
|
- public ZCR<TrackVo> getTrack(@RequestBody ZCTokenBo zcTokenBo) {
|
|
|
|
|
- //请求业务参数
|
|
|
|
|
- DeliveryTrack deliveryTrack = new DeliveryTrack();
|
|
|
|
|
|
|
+ //******4. 核心业务:调用Dubbo服务查询物流信息 我们没有换新单 所以第二个参数先抛弃掉
|
|
|
|
|
+ TrackVo trackVo = remoteOrderService.queryLogisticsTrack(bizReq.getOutgoingCode(), bizReq.getWaybillType());
|
|
|
|
|
+ if (trackVo == null){
|
|
|
|
|
+ return ZCR.fail("5035", "物流信息为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 6. 业务参数→JSON→Base64编码(符合文档data字段要求)
|
|
|
|
|
+ String respBizJson = JSONUtil.toJsonStr(trackVo);
|
|
|
|
|
+ String respDataBase64 = Base64.encode(respBizJson, StandardCharsets.UTF_8);
|
|
|
|
|
|
|
|
- //响应业务参数
|
|
|
|
|
- TrackVo trackVo = new TrackVo();
|
|
|
|
|
- DeliveryTrack deliveryTrack1 = new DeliveryTrack();
|
|
|
|
|
|
|
+ // 7. 生成响应签名(复用工具类,贴合签名机制)
|
|
|
|
|
+ String respSign = "";
|
|
|
|
|
+ try {
|
|
|
|
|
+ ZCR successZcr = ZCR.ok(respDataBase64, "");
|
|
|
|
|
+ String respSignContent = SignParamUtils.getSignContent(successZcr);
|
|
|
|
|
+ respSign = SM2SignatureUtils.sign(respSignContent, DEVELOPER_PRIVATE_KEY);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("物流查询 - 响应签名生成失败,业务JSON:{}", respBizJson, e);
|
|
|
|
|
+ return ZCR.fail("5036", "接口响应签名生成失败");
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- return null;
|
|
|
|
|
|
|
+ // 8. 封装最终响应(符合文档要求)
|
|
|
|
|
+ return ZCR.ok(respDataBase64, respSign);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- //TODO 4.6 查询电商平台订单号
|
|
|
|
|
|
|
+ //4.6 查询电商平台订单号
|
|
|
/*
|
|
/*
|
|
|
//请求业务参数
|
|
//请求业务参数
|
|
|
MallOrderNoQueryBo mallOrderNoQueryBo = new MallOrderNoQueryBo();
|
|
MallOrderNoQueryBo mallOrderNoQueryBo = new MallOrderNoQueryBo();
|
|
@@ -323,87 +443,215 @@ public class ZhongChePushController {
|
|
|
MallOrderNoVo mallOrderNoVo = new MallOrderNoVo();
|
|
MallOrderNoVo mallOrderNoVo = new MallOrderNoVo();
|
|
|
*/
|
|
*/
|
|
|
@PostMapping("/get/mallOrderNo")
|
|
@PostMapping("/get/mallOrderNo")
|
|
|
- public ZCR<String> getMallOrderNo(@RequestBody ZCTokenBo zcTokenBo) {
|
|
|
|
|
|
|
+ public ZCR getMallOrderNo(@RequestBody ZCTokenBo zcTokenBo) {
|
|
|
//1. 公共请求参数校验
|
|
//1. 公共请求参数校验
|
|
|
- ZCR<Void> checkResult = checkPublicParams(zcTokenBo);
|
|
|
|
|
|
|
+ ZCR checkResult = checkPublicParams(zcTokenBo);
|
|
|
if (!"0".equals(checkResult.getRespCode())) {
|
|
if (!"0".equals(checkResult.getRespCode())) {
|
|
|
return ZCR.fail(checkResult.getRespCode(), checkResult.getRespMsg());
|
|
return ZCR.fail(checkResult.getRespCode(), checkResult.getRespMsg());
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- // TODO 2. AccessToken有效性校验
|
|
|
|
|
- if (!checkAccessTokenValid(zcTokenBo.getAccessToken())) {
|
|
|
|
|
- return ZCR.fail("5007", "accessToken无效或已过期");
|
|
|
|
|
|
|
+ // 2. 请求签名校验(必须)
|
|
|
|
|
+ boolean verifyResult;
|
|
|
|
|
+ try {
|
|
|
|
|
+ verifyResult = SignParamUtils.verifyRequestSign(
|
|
|
|
|
+ zcTokenBo,
|
|
|
|
|
+ DEVELOPER_PUBLIC_KEY // 中车给你的公钥
|
|
|
|
|
+ );
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("查询电商订单号 - 请求验签异常", e);
|
|
|
|
|
+ return ZCR.fail("5005", "请求签名校验异常");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- //2. 业务参数解析(Base64→JSON→BO)
|
|
|
|
|
|
|
+ if (!verifyResult) {
|
|
|
|
|
+ return ZCR.fail("5006", "请求签名校验失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ //3. AccessToken 有效性校验
|
|
|
|
|
+ ZCR zcr = checkAccessTokenValid(zcTokenBo.getAccessToken());
|
|
|
|
|
+ if (!zcr.getRespCode().equals("0")){
|
|
|
|
|
+ return zcr;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 4. 业务参数解析(Base64 → JSON → BO)
|
|
|
MallOrderNoQueryBo orderNoQueryBo;
|
|
MallOrderNoQueryBo orderNoQueryBo;
|
|
|
-
|
|
|
|
|
- // 解码data字段(核心:Base64解码为JSON字符串)
|
|
|
|
|
- String decodedData = Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
|
|
|
|
|
- // JSON转业务BO(你已创建的MallOrderNoQueryBo)
|
|
|
|
|
- orderNoQueryBo = JSONUtil.toBean(decodedData, MallOrderNoQueryBo.class);
|
|
|
|
|
-
|
|
|
|
|
- //3. 业务参数校验
|
|
|
|
|
- String validateMsg = validateOrderParams(orderNoQueryBo);
|
|
|
|
|
- if (StrUtil.isNotBlank(validateMsg)) {
|
|
|
|
|
- return ZCR.fail("5007", validateMsg);
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ String decodedData = Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
|
|
|
|
|
+ orderNoQueryBo = JSONUtil.toBean(decodedData, MallOrderNoQueryBo.class);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("查询电商订单号 - 业务参数解析失败,data={}", zcTokenBo.getData(), e);
|
|
|
|
|
+ return ZCR.fail("5008", "业务参数解析失败");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- //5.查询电商订单号
|
|
|
|
|
|
|
+ // 5. 查询电商平台订单号(你的内部逻辑)
|
|
|
String orderNo = orderNoQueryBo.getOrderNo();
|
|
String orderNo = orderNoQueryBo.getOrderNo();
|
|
|
- String mallOrderNo = remoteOrderService.getOrderNo(orderNo);
|
|
|
|
|
- // 6. 构造响应业务参数
|
|
|
|
|
|
|
+ String mallOrderNo;
|
|
|
|
|
+ try {
|
|
|
|
|
+ mallOrderNo = remoteOrderService.getOrderNo(orderNo);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("查询电商订单号 - 查询失败,orderNo={}", orderNo, e);
|
|
|
|
|
+ return ZCR.fail("5010", "查询电商订单号失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 6. 构造业务响应 data(VO → JSON → Base64)
|
|
|
MallOrderNoVo orderNoVo = new MallOrderNoVo();
|
|
MallOrderNoVo orderNoVo = new MallOrderNoVo();
|
|
|
orderNoVo.setMallOrderNo(mallOrderNo);
|
|
orderNoVo.setMallOrderNo(mallOrderNo);
|
|
|
- // VO→JSON→Base64编码(响应的data字段值)
|
|
|
|
|
|
|
+
|
|
|
String respBizJson = JSONUtil.toJsonStr(orderNoVo);
|
|
String respBizJson = JSONUtil.toJsonStr(orderNoVo);
|
|
|
String respDataBase64 = Base64.encode(respBizJson, StandardCharsets.UTF_8);
|
|
String respDataBase64 = Base64.encode(respBizJson, StandardCharsets.UTF_8);
|
|
|
- //7. 生成响应签名
|
|
|
|
|
- String respSign = "";
|
|
|
|
|
- //8. 封装最终响应
|
|
|
|
|
- return ZCR.ok(respDataBase64, respSign);
|
|
|
|
|
- }
|
|
|
|
|
- private String validateOrderParams(MallOrderNoQueryBo orderNoQueryBo) {
|
|
|
|
|
- // 1. orderNo非空校验
|
|
|
|
|
- if (StrUtil.isBlank(orderNoQueryBo.getOrderNo())) {
|
|
|
|
|
- return "中车电子商城订单编号(orderNo)不能为空";
|
|
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- // 2. orderNo长度校验(≤20)
|
|
|
|
|
- if (orderNoQueryBo.getOrderNo().length() > 20) {
|
|
|
|
|
- return "中车电子商城订单编号(orderNo)长度不能超过20位";
|
|
|
|
|
|
|
+ // 7. 生成响应签名
|
|
|
|
|
+ ZCR resp = ZCR.ok(respDataBase64);
|
|
|
|
|
+ String respSign;
|
|
|
|
|
+ try {
|
|
|
|
|
+ String respSignContent = SignParamUtils.getSignContent(resp);
|
|
|
|
|
+ respSign = SM2SignatureUtils.sign(respSignContent, DEVELOPER_PRIVATE_KEY);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("查询电商订单号 - 响应签名生成失败,业务JSON={}", respBizJson, e);
|
|
|
|
|
+ return ZCR.fail("5036", "接口响应签名生成失败");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return null; // 校验通过
|
|
|
|
|
|
|
+ // 9. 回填 sign 并返回
|
|
|
|
|
+ resp.setSign(respSign);
|
|
|
|
|
+ return resp;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- //TODO 4.7 查询电商平台售后单号
|
|
|
|
|
|
|
+ //4.7 查询电商平台售后单号
|
|
|
@PostMapping("/get/mallAfterSaleNo")
|
|
@PostMapping("/get/mallAfterSaleNo")
|
|
|
- public ZCR<MallAfterSaleNoVo> getMallAfterSaleNo(@RequestBody ZCTokenBo zcTokenBo) {
|
|
|
|
|
- //请求业务参数
|
|
|
|
|
- MallAfterSaleNoQueryBo mallAfterSaleNoQueryBo = new MallAfterSaleNoQueryBo();
|
|
|
|
|
|
|
+ public ZCR getMallAfterSaleNo(@RequestBody ZCTokenBo zcTokenBo) {
|
|
|
|
|
+ // 1. 公共请求参数校验
|
|
|
|
|
+ ZCR checkResult = checkPublicParams(zcTokenBo);
|
|
|
|
|
+ if (!"0".equals(checkResult.getRespCode())) {
|
|
|
|
|
+ return ZCR.fail(checkResult.getRespCode(), checkResult.getRespMsg());
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- //响应业务参数
|
|
|
|
|
- MallAfterSaleNoVo mallAfterSaleNoVo = new MallAfterSaleNoVo();
|
|
|
|
|
|
|
+ //2. AccessToken 有效性校验
|
|
|
|
|
+ ZCR zcr = checkAccessTokenValid(zcTokenBo.getAccessToken());
|
|
|
|
|
+ if (!zcr.getRespCode().equals("0")){
|
|
|
|
|
+ return zcr;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- return null;
|
|
|
|
|
|
|
+ // 3. 业务参数解析(Base64 → JSON → BO)
|
|
|
|
|
+ MallAfterSaleNoQueryBo queryBo;
|
|
|
|
|
+ try {
|
|
|
|
|
+ String decodedData = Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
|
|
|
|
|
+ queryBo = JSONUtil.toBean(decodedData, MallAfterSaleNoQueryBo.class);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("售后单号查询 - 业务参数解析失败", e);
|
|
|
|
|
+ return ZCR.fail("5007", "业务参数解析失败");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 业务参数校验
|
|
|
|
|
+ if (queryBo == null || StrUtil.isBlank(queryBo.getAfterSaleNo())) {
|
|
|
|
|
+ return ZCR.fail("5007", "afterSaleNo不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 查询电商平台售后单号(内部逻辑你自己实现)
|
|
|
|
|
+ String afterSaleNo = queryBo.getAfterSaleNo();
|
|
|
|
|
+ String mallAfterSaleNo = remoteOrderService.getReturnOrderNo(afterSaleNo);
|
|
|
|
|
+
|
|
|
|
|
+ if (StrUtil.isBlank(mallAfterSaleNo)) {
|
|
|
|
|
+ return ZCR.fail("5004", "未查询到对应的电商平台售后单号");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 6. 构造响应业务参数
|
|
|
|
|
+ MallAfterSaleNoVo vo = new MallAfterSaleNoVo();
|
|
|
|
|
+ vo.setMallAfterSaleNo(mallAfterSaleNo);
|
|
|
|
|
+
|
|
|
|
|
+ String respBizJson = JSONUtil.toJsonStr(vo);
|
|
|
|
|
+ String respDataBase64 = Base64.encode(respBizJson, StandardCharsets.UTF_8);
|
|
|
|
|
+
|
|
|
|
|
+ // 7. 生成响应签名(关键补全点)
|
|
|
|
|
+ String respSign;
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 先构造不带 sign 的响应对象
|
|
|
|
|
+ ZCR successZcr = ZCR.ok(respDataBase64);
|
|
|
|
|
+
|
|
|
|
|
+ // 按平台规则生成待签名字符串
|
|
|
|
|
+ String respSignContent = SignParamUtils.getSignContent(successZcr);
|
|
|
|
|
+
|
|
|
|
|
+ // 使用【电商私钥】进行 SM2 签名
|
|
|
|
|
+ respSign = SM2SignatureUtils.sign(respSignContent, DEVELOPER_PRIVATE_KEY);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("售后单号查询 - 响应签名生成失败,业务JSON:{}", respBizJson, e);
|
|
|
|
|
+ return ZCR.fail("5036", "接口响应签名生成失败");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 8. 返回最终响应
|
|
|
|
|
+ return ZCR.ok(respDataBase64, respSign);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//TODO 4.8 消息监听 消息类型枚举没做
|
|
//TODO 4.8 消息监听 消息类型枚举没做
|
|
|
@PostMapping("/message/listening")
|
|
@PostMapping("/message/listening")
|
|
|
- public ZCR<MessageVo> listener(@RequestBody ZCTokenBo zcTokenBo) {
|
|
|
|
|
- //请求业务参数
|
|
|
|
|
- MessageBo messageBo = new MessageBo();
|
|
|
|
|
|
|
+ public ZCR listener(@RequestBody ZCTokenBo zcTokenBo) {
|
|
|
|
|
+ // 1. 公共请求参数校验
|
|
|
|
|
+ ZCR checkResult = checkPublicParams(zcTokenBo);
|
|
|
|
|
+ if (!"0".equals(checkResult.getRespCode())) {
|
|
|
|
|
+ return ZCR.fail(checkResult.getRespCode(), checkResult.getRespMsg());
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- //响应业务参数
|
|
|
|
|
|
|
+ // 2. accessToken 校验
|
|
|
|
|
+ ZCR tokenCheck = checkAccessTokenValid(zcTokenBo.getAccessToken());
|
|
|
|
|
+ if (!"0".equals(tokenCheck.getRespCode())) {
|
|
|
|
|
+ return tokenCheck;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 3. 业务参数解析
|
|
|
|
|
+ MessageBo messageBo;
|
|
|
|
|
+ try {
|
|
|
|
|
+ String decodedData = Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
|
|
|
|
|
+ messageBo = JSONUtil.toBean(decodedData, MessageBo.class);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("消息监听 - 业务参数解析失败", e);
|
|
|
|
|
+ return ZCR.fail("5007", "业务参数解析失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 4. 业务参数必填校验
|
|
|
|
|
+ if (messageBo == null
|
|
|
|
|
+ || StrUtil.isBlank(messageBo.getId())
|
|
|
|
|
+ || StrUtil.isBlank(messageBo.getType())
|
|
|
|
|
+ || messageBo.getContent() == null
|
|
|
|
|
+ || StrUtil.isBlank(messageBo.getTime())) {
|
|
|
|
|
+ return ZCR.fail("5007", "消息业务参数不完整");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 按消息类型处理业务
|
|
|
|
|
+ try {
|
|
|
|
|
+ MessageTypeEnum typeEnum = MessageTypeEnum.of(messageBo.getType());
|
|
|
|
|
+ switch (typeEnum) {
|
|
|
|
|
+ case NEW_ORDER:
|
|
|
|
|
+ handleNewOrder(messageBo);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case AFTER_SALE_APPLY:
|
|
|
|
|
+ handleAfterSale(messageBo);
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ log.warn("未处理的消息类型:{}", messageBo.getType());
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("消息监听 - 业务处理失败,msgId={}", messageBo.getId(), e);
|
|
|
|
|
+ return ZCR.fail("5006", "消息处理失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 6. 构造响应业务参数
|
|
|
MessageVo messageVo = new MessageVo();
|
|
MessageVo messageVo = new MessageVo();
|
|
|
|
|
+ messageVo.setResult("1");
|
|
|
|
|
+ messageVo.setMessage("success");
|
|
|
|
|
+
|
|
|
|
|
+ String respBizJson = JSONUtil.toJsonStr(messageVo);
|
|
|
|
|
+ String respDataBase64 = Base64.encode(respBizJson, StandardCharsets.UTF_8);
|
|
|
|
|
+
|
|
|
|
|
+ // 7. 生成响应签名(和 4.7 一模一样)
|
|
|
|
|
+ String respSign;
|
|
|
|
|
+ try {
|
|
|
|
|
+ ZCR successZcr = ZCR.ok(respDataBase64);
|
|
|
|
|
+ String respSignContent = SignParamUtils.getSignContent(successZcr);
|
|
|
|
|
+ respSign = SM2SignatureUtils.sign(respSignContent, DEVELOPER_PRIVATE_KEY);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("消息监听 - 响应签名生成失败,业务JSON:{}", respBizJson, e);
|
|
|
|
|
+ return ZCR.fail("5036", "接口响应签名生成失败");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 8. 返回响应
|
|
|
|
|
+ return ZCR.ok(respDataBase64, respSign);
|
|
|
|
|
|
|
|
- return null;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//TODO 4.9 非必接模块接入校验
|
|
//TODO 4.9 非必接模块接入校验
|
|
|
@PostMapping("/notRequireApi/check")
|
|
@PostMapping("/notRequireApi/check")
|
|
|
- public ZCR<ModuleCheckVo> notRequireApiCheck(@RequestBody ZCTokenBo zcTokenBo) {
|
|
|
|
|
|
|
+ public ZCR notRequireApiCheck(@RequestBody ZCTokenBo zcTokenBo) {
|
|
|
//请求业务参数
|
|
//请求业务参数
|
|
|
ModuleCheckBo moduleCheckBo = new ModuleCheckBo();
|
|
ModuleCheckBo moduleCheckBo = new ModuleCheckBo();
|
|
|
|
|
|
|
@@ -415,7 +663,7 @@ public class ZhongChePushController {
|
|
|
|
|
|
|
|
//TODO 4.10 查询运费
|
|
//TODO 4.10 查询运费
|
|
|
@PostMapping("egoods/getfreight")
|
|
@PostMapping("egoods/getfreight")
|
|
|
- public ZCR<FreightVo> getFreight(@RequestBody ZCTokenBo zcTokenBo) {
|
|
|
|
|
|
|
+ public ZCR getFreight(@RequestBody ZCTokenBo zcTokenBo) {
|
|
|
//请求业务参数
|
|
//请求业务参数
|
|
|
AreaStockBo areaStock = new AreaStockBo();
|
|
AreaStockBo areaStock = new AreaStockBo();
|
|
|
|
|
|
|
@@ -429,7 +677,7 @@ public class ZhongChePushController {
|
|
|
/**
|
|
/**
|
|
|
* 校验公共请求参数(基于你的ZCTokenBo)
|
|
* 校验公共请求参数(基于你的ZCTokenBo)
|
|
|
*/
|
|
*/
|
|
|
- private ZCR<Void> checkPublicParams(ZCTokenBo zcTokenBo) {
|
|
|
|
|
|
|
+ private ZCR checkPublicParams(ZCTokenBo zcTokenBo) {
|
|
|
// 1. 必填项非空校验(严格按文档4.2.4)
|
|
// 1. 必填项非空校验(严格按文档4.2.4)
|
|
|
if (StrUtil.isBlank(zcTokenBo.getVersion())) {
|
|
if (StrUtil.isBlank(zcTokenBo.getVersion())) {
|
|
|
return ZCR.fail("5007", "接口版本version不能为空");
|
|
return ZCR.fail("5007", "接口版本version不能为空");
|
|
@@ -447,32 +695,36 @@ public class ZhongChePushController {
|
|
|
return ZCR.fail("5007", "签名sign不能为空");
|
|
return ZCR.fail("5007", "签名sign不能为空");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 2. TODO 版本校验(固定1.0.0)
|
|
|
|
|
- /*if (!"1.0.0".equals(zcTokenBo.getVersion())) {
|
|
|
|
|
|
|
+ // 2.版本校验(固定1.0.0)
|
|
|
|
|
+ if (!VERSION.equals(zcTokenBo.getVersion())) {
|
|
|
return ZCR.fail("1008", "仅支持接口版本1.0.0");
|
|
return ZCR.fail("1008", "仅支持接口版本1.0.0");
|
|
|
- }*/
|
|
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// 3. 时间戳格式校验(14位)
|
|
// 3. 时间戳格式校验(14位)
|
|
|
- if (zcTokenBo.getTimestamp().length() != 14) {
|
|
|
|
|
- return ZCR.fail("5007", "时间戳格式错误,需为14位YYYYMMDDHHMMSS");
|
|
|
|
|
|
|
+ if (!ReUtil.isMatch("\\d{14}", zcTokenBo.getTimestamp())) {
|
|
|
|
|
+ return ZCR.fail("5007", "时间戳格式错误,需为14位数字YYYYMMDDHHMMSS");
|
|
|
}
|
|
}
|
|
|
- if (zcTokenBo.getClientId() !=CLIENT_ID){
|
|
|
|
|
|
|
+ // 4. clientId 校验
|
|
|
|
|
+ if (!CLIENT_ID.equals(zcTokenBo.getClientId())){
|
|
|
return ZCR.fail("5007", "clientId错误");
|
|
return ZCR.fail("5007", "clientId错误");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 4.TODO 签名校验(验证中车请求的签名)
|
|
|
|
|
- /*try {
|
|
|
|
|
- boolean signValid = sm2SignUtil.sm2Verify(
|
|
|
|
|
- JSONUtil.toJsonStr(SM2SignUtil.beanToFilteredMap(zcTokenBo)).getBytes(),
|
|
|
|
|
- zcTokenBo.getSign(),
|
|
|
|
|
- "中车提供的公钥(16进制)" // 替换为真实公钥
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ //
|
|
|
|
|
+ // 5. 签名校验
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 防御性 Base64 校验
|
|
|
|
|
+ Base64.decodeStr(zcTokenBo.getData(), StandardCharsets.UTF_8);
|
|
|
|
|
+ // 调用 SignParamUtils 验证请求签名,直接复用之前的逻辑
|
|
|
|
|
+ boolean signValid = SignParamUtils.verifyRequestSign(zcTokenBo, DEVELOPER_PUBLIC_KEY);
|
|
|
if (!signValid) {
|
|
if (!signValid) {
|
|
|
- return ZCR.fail("1010", "签名验证失败");
|
|
|
|
|
|
|
+ // 验签失败,返回错误响应
|
|
|
|
|
+ return ZCR.fail("5007", "接口签名错误(验签失败)");
|
|
|
}
|
|
}
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
- return ZCR.fail("1011", "签名校验异常:" + e.getMessage());
|
|
|
|
|
- }*/
|
|
|
|
|
|
|
+ // 捕获验签过程中的异常(如JSON转换、Base64解码、SM2算法异常等)
|
|
|
|
|
+ log.error("中车电商接口签名校验异常,请求参数:{}", JSONUtil.toJsonStr(zcTokenBo), e);
|
|
|
|
|
+ return ZCR.fail("5007", "接口签名校验异常,请稍后重试");
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// 校验通过
|
|
// 校验通过
|
|
|
return ZCR.ok(null);
|
|
return ZCR.ok(null);
|
|
@@ -481,11 +733,16 @@ public class ZhongChePushController {
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 校验AccessToken有效性(替换为你的真实逻辑)
|
|
|
|
|
|
|
+ * 校验AccessToken有效性
|
|
|
*/
|
|
*/
|
|
|
- private boolean checkAccessTokenValid(String accessToken) {
|
|
|
|
|
- // 临时占位,需替换
|
|
|
|
|
- return true;
|
|
|
|
|
|
|
+ private ZCR checkAccessTokenValid(String accessToken) {
|
|
|
|
|
+ if(ObjectUtil.isEmpty(accessToken)){
|
|
|
|
|
+ return ZCR.fail("5006","token不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ if(StpUtil.getTokenTimeout(accessToken) == -2l){
|
|
|
|
|
+ return ZCR.fail("5005","token_expired");
|
|
|
|
|
+ }
|
|
|
|
|
+ return ZCR.ok( null);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|