Merge remote-tracking branch 'origin/cc_2250513_api' into cc_2250513_api

This commit is contained in:
shuo.wang
2025-05-14 10:03:24 +08:00
8 changed files with 256 additions and 3 deletions

View File

@@ -1,14 +1,17 @@
package com.cool.store.utils; package com.cool.store.utils;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import okhttp3.internal.http.HttpMethod;
import javax.crypto.Mac; import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException; import java.security.*;
import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*; import java.util.*;
/** /**
@@ -18,6 +21,9 @@ import java.util.*;
*/ */
@Slf4j @Slf4j
public class SignatureUtils { public class SignatureUtils {
private static final String privateKey = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCML5dEU4e6BAopiSjxeA2ZNs3VeIXoWe8HpHHGhZP1xE+4vbPv3L4Z7XexeoPMiBcluyGKSRl5jwuvYvcF9A2nGPhxVugYOxpC+TuGVu3TE7a2E/+1iMFfdNCYgqMxfnTe2Svo+X5anc5AcpKar2+AYcdZ4emMQLwrJkEB9endpWPruqC7dCUmAwxByMi525fdipFEDsAIf9ozkBuqwyQa0/RwQ+fyOT87pJ58z7gLXcZxW8YaXNAS5hmdA6/0GqZQQyDTXxZ/nDPpeIIk8PC8VNrgllCxwDtyDMWfUg8+yTlHAjsCX7MGRM3Gd3QcfqrvlIK/Lvn/9qAWcY3KOU1JAgMBAAECggEADwvnokmN6qEW3yTt7FcMrJqFhslgJBONFKapoJuDSVCIFwXji5YHoHxMydOi1F/yDhZzD+bvMU0xfaJzbaPnmD0Vo4KmuD0VwUUDhtSLQ8ncpVfprRgv2zlMT2ZMMlqGnEm2jRPEZGEGO4QgqLI2V1MHZOYa3XcwSzhYp2d38dZvPxWnF9mDt26BbM5Eb3D4oAb+J8rOVsbXEbVTdVTsJmhLOUEK84r2fUCLqHALAOc/za28ToupYD/QRWT6K5nWKDclDWn2tnz3MQJrEYMVvGDVCryqltljd2XB4MXnBZxEYDZyonxxI5M1Avb9mHUyy2wMYSeTUocSnVjgCZPnFQKBgQC34xs/Q0lDTpRdS0a8odzey4jew/nfo8TqzAa2/vomRHkOAoXyqci71tCos5836hxprkhvj1KMauB/rFzCZYfr5a2UMM70p/vuYv5JhTMDStTVx9OPcydZDeK77OY39qPhkW4Tm10BBUodzVV0YhxbCfRmSRSF2FuZCSLdd5bmpQKBgQDDKTUYuA+s/cFt0QsKkRRBm/ONQnETCCXo87NP9RzspcydkULnmuRMLWP4zqHN5TccL0MyI7hyHZ7AwU3qoi4JXFo8k5rRcSdwaBy99B+ZKF159l7chVHE8p4tYSmxwoIOIbpfXA2nfp+zaknt6lb8DSeCw8lewsEZUwmU++bu1QKBgB5M5t16yeS0WxAwajyE828m3KvQYSkvxC/oZKbcxv1tRQFA+y9sOK5zDDjwOCkvg3UKidcQJ8sJhWqkTTifIFGHrB2CGWgJEiNK3pngv/In74A2iC7YC8DJ3/WVaK4KDyEJJLlIXX/gjkDw58bgr8l0j3gXTLFtYFKMRdl2bkAdAoGAHwhNZhnWfEZ5XHHyFIMPOoOGzb5z5nfUN1+Q8rTiHYsbpy5y+gr6JFbPX7/ToL7C7ZtiTvW5HYQyOypBrn6NXXWu75T0+DrK76fOcjIAWEU/3+XaHayhXPTLTCH4FEj+jyqzzPgBP9H1qPzMtqUdocShtLxXJ9dGBzXZ/zOy++UCgYAHgaZC5Jz8+p5Ka7YtPZXpDXt1gUh2bmzIcAOBlizE+ElWZN/skybumos3lP8JOXdB65jEuXI9V0rc6n/KpOLY2Xfy6CX78RpWVXPc30D10DxUVI9hyEU+Xg4t9rwOAVhZaOwjhaEqpdvDrjMmX0bBVwCI2QXt7GnjUPqibNyEbQ==";
/** /**
* 生成 HmacSHA256 签名 * 生成 HmacSHA256 签名
@@ -37,6 +43,47 @@ public class SignatureUtils {
} }
} }
/**
* 正新菜品市场系统 验签
* @param httpMethod
* @param uri
* @param requestBody
* @param timeStamp
* @param random
* @return
*/
public static String sign(String httpMethod, String uri, String requestBody, String timeStamp, String random) throws Exception {
log.info("timeStamp+random:{}",timeStamp + "/" + random);
String plainText = new StringJoiner("&")
.add("CRM")
.add(httpMethod.toUpperCase())
.add(uri)
.add(requestBody)
.add(timeStamp)
.add(random)
.toString();
// 签名算法
Signature signature = Signature.getInstance("SHA256WithRSA");
PrivateKey privateKey = readPrivateKey(SignatureUtils.privateKey);
// 签名
signature.initSign(privateKey);
signature.update(plainText.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(signature.sign());
}
private static PrivateKey readPrivateKey(String key) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] keys = Base64.getDecoder().decode(key);
try {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keys);
return keyFactory.generatePrivate(keySpec);
} catch (InvalidKeySpecException e) {
throw new IllegalStateException("私钥⽆效:", e);
}
}
/** /**
* 生成待签名字符串(参数按字母排序 + appkey + timestamp * 生成待签名字符串(参数按字母排序 + appkey + timestamp
* @param params 请求参数Map需提前过滤空值 * @param params 请求参数Map需提前过滤空值

View File

@@ -0,0 +1,14 @@
package com.cool.store.dto;
import lombok.Data;
/**
* @Author suzhuhong
* @Date 2025/5/13 17:02
* @Version 1.0
*/
@Data
public class FoodTokenDTO {
private String mobile ;
}

View File

@@ -0,0 +1,22 @@
package com.cool.store.service;
import com.cool.store.dto.FoodTokenDTO;
import com.cool.store.dto.GetAccessTokenDTO;
/**
* 正新菜品市场api对接-埃林哲-对接人徐哲
* @Author suzhuhong
* @Date 2025/5/13 15:20
* @Version 1.0
*/
public interface ThirdFoodService {
/**
* 获取正新菜品市场token
* @param dto
* @return
*/
String getFoodToken(FoodTokenDTO dto);
}

View File

@@ -0,0 +1,148 @@
package com.cool.store.service.impl;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSONObject;
import com.cool.store.dto.FoodTokenDTO;
import com.cool.store.dto.GetAccessTokenDTO;
import com.cool.store.enums.ErrorCodeEnum;
import com.cool.store.exception.ServiceException;
import com.cool.store.response.oppty.OpportunityApiResponse;
import com.cool.store.service.ThirdFoodService;
import com.cool.store.utils.SignatureUtils;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.net.URI;
import java.util.Map;
/**
* @Author suzhuhong
* @Date 2025/5/13 15:21
* @Version 1.0
*/
@Service
@Slf4j
public class ThirdFoodServiceImpl implements ThirdFoodService {
@Value("${zx.food.url}")
private String apiUrl;
@Resource
OkHttpClient okHttpClient;
@Resource
ObjectMapper objectMapper;
@Override
public String getFoodToken(FoodTokenDTO dto) {
// 1. 发送POST请求
String url = apiUrl + "/interface/v1/user/getToken";
return executeApiCall(url, dto, String.class);
}
private <T> T executeApiCall(String url, Object requestBody, Class<T> responseType) {
// 1. 打印请求前日志
//logRequest(url, requestBody);
try {
Request request = buildRequest(requestBody, url);
try (Response response = okHttpClient.newCall(request).execute()) {
// 2. 获取原始响应内容
String responseBody = response.body().string();
// 3. 打印响应日志
logResponse(url, response.code(), responseBody);
if (!response.isSuccessful()) {
throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR,
"HTTP请求失败状态码: " + response.code());
}
// 4. 解析响应
JavaType javaType = objectMapper.getTypeFactory()
.constructParametricType(OpportunityApiResponse.class, responseType);
OpportunityApiResponse<T> apiResponse = objectMapper.readValue(responseBody, javaType);
if (apiResponse.getCode() != 200) {
throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR, apiResponse.getMsg());
}
return apiResponse.getData();
}
} catch (ServiceException e) {
throw e;
} catch (Exception e) {
log.error("API调用异常 - URL: {}, 错误: {}", url, e.getMessage(), e);
throw new ServiceException(ErrorCodeEnum.THIRD_API_ERROR, "接口调用异常: " + e.getMessage());
}
}
private Request buildRequest(Object requestBody, String url) {
String timestamp = String.valueOf(System.currentTimeMillis());
String random = RandomUtil.randomString(20);
String signString = null;
try {
signString = SignatureUtils.sign("POST","/v1/user/getToken", objectMapper.writeValueAsString(requestBody),timestamp,random);
} catch (Exception e) {
throw new ServiceException(ErrorCodeEnum.THIRD_API_SIGN_ERROR,"加密失败");
}
log.info("签名生成 - 签名结果: {}", signString);
RequestBody body = RequestBody.create(okhttp3.MediaType.parse("application/json"),
JSONObject.toJSONString(requestBody)
);
return new Request.Builder()
.url(url)
.post(body)
.addHeader("X-ZhengXin-Sign", signString)
.addHeader("X-ZhengXin-SignTime", timestamp)
.addHeader("X-ZhengXin-SignRandom", random)
.addHeader("X-ZhengXin-appId", "CRM")
.build();
}
private void logResponse(String url, int statusCode, String responseBody) {
if (log.isInfoEnabled()) {
try {
// 尝试美化JSON输出
Object json = objectMapper.readValue(responseBody, Object.class);
String prettyResponse = objectMapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(json);
log.info("\n======= 响应开始 =======\n" +
"API地址: {}\n" +
"HTTP状态码: {}\n" +
"响应内容: {}\n" +
"======= 响应结束 =======",
url,
statusCode,
prettyResponse);
} catch (Exception e) {
// 非JSON响应或解析失败时直接输出原始内容
log.info("\n======= 响应开始 =======\n" +
"API地址: {}\n" +
"HTTP状态码: {}\n" +
"原始响应: {}\n" +
"======= 响应结束 =======",
url,
statusCode,
responseBody);
}
}
}
}

View File

@@ -1,6 +1,7 @@
package com.cool.store.controller.webb; package com.cool.store.controller.webb;
import com.cool.store.dao.*; import com.cool.store.dao.*;
import com.cool.store.dto.FoodTokenDTO;
import com.cool.store.dto.GetAccessTokenDTO; import com.cool.store.dto.GetAccessTokenDTO;
import com.cool.store.dto.ModifyPasswordDTO; import com.cool.store.dto.ModifyPasswordDTO;
import com.cool.store.entity.*; import com.cool.store.entity.*;
@@ -295,6 +296,12 @@ public class PCTestController {
public ResponseResult<List<String>> getAuthRegionIdAndSubRegionIdByUserId(@RequestParam(value = "userId", required = true) String userId) { public ResponseResult<List<String>> getAuthRegionIdAndSubRegionIdByUserId(@RequestParam(value = "userId", required = true) String userId) {
return ResponseResult.success(userAuthMappingService.getAuthRegionIdAndSubRegionIdByUserId(userId)); return ResponseResult.success(userAuthMappingService.getAuthRegionIdAndSubRegionIdByUserId(userId));
} }
@Resource
ThirdFoodService thirdFoodService;
@ApiOperation("获取菜品市场token")
@PostMapping("/getFoodToken")
public ResponseResult<String> getFoodToken(@RequestBody @Validated FoodTokenDTO dto) {
return ResponseResult.success(thirdFoodService.getFoodToken(dto));
}
} }

View File

@@ -1,5 +1,6 @@
package com.cool.store.controller.webc; package com.cool.store.controller.webc;
import com.cool.store.dto.FoodTokenDTO;
import com.cool.store.dto.GetAccessTokenDTO; import com.cool.store.dto.GetAccessTokenDTO;
import com.cool.store.dto.ModifyPasswordDTO; import com.cool.store.dto.ModifyPasswordDTO;
import com.cool.store.dto.ShopAccount.ShopAccountDTO; import com.cool.store.dto.ShopAccount.ShopAccountDTO;
@@ -7,6 +8,7 @@ import com.cool.store.request.GetPasswordDTO;
import com.cool.store.response.ResponseResult; import com.cool.store.response.ResponseResult;
import com.cool.store.service.PushService; import com.cool.store.service.PushService;
import com.cool.store.service.ShopAccountService; import com.cool.store.service.ShopAccountService;
import com.cool.store.service.ThirdFoodService;
import com.cool.store.service.ThirdXinGuanJiaService; import com.cool.store.service.ThirdXinGuanJiaService;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
@@ -36,6 +38,9 @@ public class MiniShopAccountController {
@Resource @Resource
ThirdXinGuanJiaService thirdXinGuanJiaService; ThirdXinGuanJiaService thirdXinGuanJiaService;
@Resource
ThirdFoodService thirdFoodService;
@ApiOperation("根据门店shopId查询平台账号") @ApiOperation("根据门店shopId查询平台账号")
@GetMapping("/getShopAccountByShopId") @GetMapping("/getShopAccountByShopId")
@@ -68,6 +73,12 @@ public class MiniShopAccountController {
return ResponseResult.success(pushService.getXzgToken(dto)); return ResponseResult.success(pushService.getXzgToken(dto));
} }
@ApiOperation("获取菜品市场token")
@PostMapping("/getFoodToken")
public ResponseResult<String> getFoodToken(@RequestBody @Validated FoodTokenDTO dto) {
return ResponseResult.success(thirdFoodService.getFoodToken(dto));
}
@ApiOperation("获取加密子串") @ApiOperation("获取加密子串")
@PostMapping("/getEncryptedSubstring") @PostMapping("/getEncryptedSubstring")
public ResponseResult<String> getEncryptedSubstring(@RequestBody GetPasswordDTO dto) { public ResponseResult<String> getEncryptedSubstring(@RequestBody GetPasswordDTO dto) {

View File

@@ -118,6 +118,8 @@ zx.big.data.appSecret=35b8b9a400b4430fa022190be0913cd6
xzg.api.auth.url=http://webapi.zhengxinfood.com xzg.api.auth.url=http://webapi.zhengxinfood.com
zx.food.url=https://datacenter.zhengxinshipin.com
cool.api.appKey=k8J7fG2qR5tY9vX3 cool.api.appKey=k8J7fG2qR5tY9vX3
cool.api.secret=wP4sN6dL8zK2xM9c cool.api.secret=wP4sN6dL8zK2xM9c

View File

@@ -121,6 +121,8 @@ yls.api.auth.secret=3b56198f096d4009072c927c96fbc8b6
#新掌柜账号 #新掌柜账号
xzg.api.auth.url=http://webapi.zhengxinfood.com xzg.api.auth.url=http://webapi.zhengxinfood.com
zx.food.url=https://datacenter.zhengxinfood.com
cool.api.appKey=k8J7fG2qR5tY9vX3 cool.api.appKey=k8J7fG2qR5tY9vX3
cool.api.secret=wP4sN6dL8zK2xM9c cool.api.secret=wP4sN6dL8zK2xM9c