拉卡拉 入网电子合同申请 相关业务逻辑,修改加密方法
This commit is contained in:
parent
40beddaaa5
commit
8eb9cf3ce6
@ -41,6 +41,7 @@ public class LklLedgerEc implements Serializable {
|
||||
private String ec_status;
|
||||
private Long ec_apply_id;
|
||||
private String result_url;
|
||||
private String notify_url;
|
||||
private Integer status;
|
||||
private Date created_at;
|
||||
private Date updated_at;
|
||||
|
||||
@ -14,6 +14,9 @@ import org.springframework.stereotype.Component;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 结巴分词工具类
|
||||
*/
|
||||
@Component
|
||||
public class JiebaUtils {
|
||||
|
||||
|
||||
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (c) 2025. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
* Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
|
||||
* Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
|
||||
* Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
|
||||
* Vestibulum commodo. Ut rhoncus gravida arcu.
|
||||
*/
|
||||
|
||||
package com.suisung.mall.common.utils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.*;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
public class RSAUtil {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RSAUtil.class);
|
||||
|
||||
/**
|
||||
* 使用私钥对数据进行 SHA256withRSA 签名
|
||||
*
|
||||
* @param data 待签名的数据
|
||||
* @param privateKey 私钥(Base64编码)
|
||||
* @return 签名结果(Base64编码)
|
||||
*/
|
||||
public static String signSHA256withRSA(String data, String privateKey) {
|
||||
try {
|
||||
// 解码私钥
|
||||
byte[] keyBytes = Base64.getDecoder().decode(privateKey);
|
||||
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
|
||||
|
||||
// 生成私钥对象
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
PrivateKey priKey = keyFactory.generatePrivate(keySpec);
|
||||
|
||||
// 初始化签名
|
||||
Signature signature = Signature.getInstance("SHA256withRSA");
|
||||
signature.initSign(priKey);
|
||||
signature.update(data.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// 生成签名并编码为Base64
|
||||
byte[] signBytes = signature.sign();
|
||||
return Base64.getEncoder().encodeToString(signBytes);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
logger.error("不支持的加密算法: {}", e.getMessage(), e);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
logger.error("私钥格式错误: {}", e.getMessage(), e);
|
||||
} catch (InvalidKeyException e) {
|
||||
logger.error("无效的私钥: {}", e.getMessage(), e);
|
||||
} catch (SignatureException e) {
|
||||
logger.error("签名处理异常: {}", e.getMessage(), e);
|
||||
} catch (Exception e) {
|
||||
logger.error("签名过程发生未知异常: {}", e.getMessage(), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用公钥验证 SHA256withRSA 签名
|
||||
*
|
||||
* @param data 原始数据
|
||||
* @param sign 签名结果(Base64编码)
|
||||
* @param publicKey 公钥(Base64编码)
|
||||
* @return 验证结果
|
||||
*/
|
||||
public static boolean verifySHA256withRSA(String data, String sign, String publicKey) {
|
||||
try {
|
||||
// 解码公钥
|
||||
byte[] keyBytes = Base64.getDecoder().decode(publicKey);
|
||||
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
|
||||
|
||||
// 生成公钥对象
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
PublicKey pubKey = keyFactory.generatePublic(keySpec);
|
||||
|
||||
// 初始化签名验证
|
||||
Signature signature = Signature.getInstance("SHA256withRSA");
|
||||
signature.initVerify(pubKey);
|
||||
signature.update(data.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// 验证签名
|
||||
byte[] signBytes = Base64.getDecoder().decode(sign);
|
||||
return signature.verify(signBytes);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
logger.error("不支持的加密算法: {}", e.getMessage(), e);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
logger.error("公钥格式错误: {}", e.getMessage(), e);
|
||||
} catch (InvalidKeyException e) {
|
||||
logger.error("无效的公钥: {}", e.getMessage(), e);
|
||||
} catch (SignatureException e) {
|
||||
logger.error("签名验证异常: {}", e.getMessage(), e);
|
||||
} catch (Exception e) {
|
||||
logger.error("验证过程发生未知异常: {}", e.getMessage(), e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 RSA 密钥对
|
||||
*
|
||||
* @param keySize 密钥长度,推荐 2048
|
||||
* @return 密钥对
|
||||
*/
|
||||
public static KeyPair generateKeyPair(int keySize) {
|
||||
KeyPairGenerator keyPairGen = null;
|
||||
try {
|
||||
keyPairGen = KeyPairGenerator.getInstance("RSA");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
keyPairGen.initialize(keySize);
|
||||
return keyPairGen.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公钥的 Base64 编码字符串
|
||||
*/
|
||||
public static String getPublicKeyString(KeyPair keyPair) {
|
||||
PublicKey publicKey = keyPair.getPublic();
|
||||
return Base64.getEncoder().encodeToString(publicKey.getEncoded());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取私钥的 Base64 编码字符串
|
||||
*/
|
||||
public static String getPrivateKeyString(KeyPair keyPair) {
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
return Base64.getEncoder().encodeToString(privateKey.getEncoded());
|
||||
}
|
||||
|
||||
// 示例用法
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// 生成密钥对
|
||||
KeyPair keyPair = generateKeyPair(2048);
|
||||
String publicKey = getPublicKeyString(keyPair);
|
||||
String privateKey = getPrivateKeyString(keyPair);
|
||||
|
||||
// 待签名数据
|
||||
String data = "Hello, RSA Signature!";
|
||||
|
||||
// 签名
|
||||
String signature = signSHA256withRSA(data, privateKey);
|
||||
System.out.println("签名结果: " + signature);
|
||||
|
||||
// 验证
|
||||
boolean isValid = verifySHA256withRSA(data, signature, publicKey);
|
||||
System.out.println("验证结果: " + isValid);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -11,7 +11,6 @@ package com.suisung.mall.pay.utils;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.lkl.laop.sdk.Config2;
|
||||
import com.lkl.laop.sdk.LKLSDK;
|
||||
import com.lkl.laop.sdk.auth.PrivateKeySigner;
|
||||
import com.suisung.mall.common.exception.ApiException;
|
||||
import com.suisung.mall.common.utils.I18nUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@ -76,6 +75,7 @@ public class LakalaUtil {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取 body 请求数据
|
||||
*
|
||||
@ -130,7 +130,6 @@ public class LakalaUtil {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 签名验证
|
||||
*
|
||||
@ -155,9 +154,9 @@ public class LakalaUtil {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 签名校验
|
||||
*
|
||||
* @param authorization
|
||||
* @param reqBody
|
||||
* @param lklNotifyCerPath
|
||||
@ -176,11 +175,9 @@ public class LakalaUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
StringBuilder preSignData = new StringBuilder();
|
||||
preSignData.append(timestamp).append("\n")
|
||||
.append(nonceStr).append("\n")
|
||||
.append(reqBody).append("\n");
|
||||
String preSignDataStr = preSignData.toString();
|
||||
String preSignDataStr = timestamp + "\n" +
|
||||
nonceStr + "\n" +
|
||||
reqBody + "\n";
|
||||
log.debug("拉卡拉签名明文内容:{}", preSignDataStr);
|
||||
if (verify(lklNotifyCer, preSignDataStr.getBytes(StandardCharsets.UTF_8), signature)) {
|
||||
log.debug("验签成功");
|
||||
@ -237,4 +234,5 @@ public class LakalaUtil {
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -211,9 +211,14 @@ public class EsignContractServiceImpl extends BaseServiceImpl<EsignContractMappe
|
||||
|
||||
//发起接口请求
|
||||
EsignHttpResponse createByDocTemplate = EsignHttpHelper.doCommHttp(serverUrl, apiAddr, requestType, jsonParams, header, debug);
|
||||
if (createByDocTemplate.getStatus() != 200 || createByDocTemplate.getBody() == null) {
|
||||
log.error("e签宝请求失败,返回状态码:{}", createByDocTemplate.getStatus());
|
||||
return Pair.of(false, "e签宝请求失败,状态码:" + createByDocTemplate.getStatus());
|
||||
log.debug("发起合同签署流程返回消息:{},{}", createByDocTemplate.getStatus(), createByDocTemplate.getBody());
|
||||
if (createByDocTemplate.getStatus() != 200) {
|
||||
if (createByDocTemplate.getBody() != null) {
|
||||
JSONObject resBody = JSONUtil.parseObj(createByDocTemplate.getBody());
|
||||
log.error("e签宝请求失败,返回状态码:{}, {}", createByDocTemplate.getStatus(), resBody.getStr("message"));
|
||||
return Pair.of(false, "e签宝请求失败,{}" + resBody.getStr("message"));
|
||||
}
|
||||
return Pair.of(false, "e签宝请求失败!");
|
||||
}
|
||||
|
||||
JSONObject jsonObject = JSONUtil.parseObj(createByDocTemplate.getBody());
|
||||
|
||||
@ -30,6 +30,12 @@ public class LakalaController extends BaseControllerImpl {
|
||||
@Resource
|
||||
private LakalaApiService lakalaPayService;
|
||||
|
||||
@ApiOperation(value = "测试案例", notes = "测试案例")
|
||||
@RequestMapping(value = "/testcase", method = RequestMethod.POST)
|
||||
public Object testcase(@RequestBody JSONObject paramsJSON) {
|
||||
return lakalaPayService.applyLedgerMerEc(paramsJSON.getStr("mchMobile"));
|
||||
}
|
||||
|
||||
@ApiOperation(value = "本地文件转base64", notes = "本地文件转base64")
|
||||
@RequestMapping(value = "/file2base64", method = RequestMethod.POST)
|
||||
public String file2Base64(@RequestParam("file") MultipartFile file) throws IOException {
|
||||
|
||||
@ -15,6 +15,7 @@ import com.suisung.mall.common.modules.lakala.LklBanks;
|
||||
import com.suisung.mall.common.service.impl.BaseControllerImpl;
|
||||
import com.suisung.mall.shop.lakala.service.LklBanksService;
|
||||
import com.suisung.mall.shop.lakala.service.impl.LklTkServiceImpl;
|
||||
import com.suisung.mall.shop.lakala.utils.LakalaUtil;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.data.util.Pair;
|
||||
@ -42,7 +43,7 @@ public class LklTkController extends BaseControllerImpl {
|
||||
public String decryptLklTkData(@RequestParam(name = "data") String data,
|
||||
@RequestParam(name = "key") String key) {
|
||||
|
||||
return lklTkService.decryptNotifyData(key, data);
|
||||
return LakalaUtil.decryptNotifyData(key, data);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -23,20 +23,16 @@ import com.suisung.mall.common.api.CommonResult;
|
||||
import com.suisung.mall.common.constant.CommonConstant;
|
||||
import com.suisung.mall.common.exception.ApiException;
|
||||
import com.suisung.mall.common.feignService.ShopService;
|
||||
import com.suisung.mall.common.modules.lakala.LklLedgerEc;
|
||||
import com.suisung.mall.common.modules.lakala.LklLedgerMember;
|
||||
import com.suisung.mall.common.modules.lakala.LklLedgerMerReceiverBind;
|
||||
import com.suisung.mall.common.modules.lakala.LklLedgerReceiver;
|
||||
import com.suisung.mall.common.modules.store.ShopMchEntry;
|
||||
import com.suisung.mall.common.modules.store.ShopStoreBase;
|
||||
import com.suisung.mall.common.utils.I18nUtil;
|
||||
import com.suisung.mall.common.utils.StringUtils;
|
||||
import com.suisung.mall.common.utils.UploadUtil;
|
||||
import com.suisung.mall.common.utils.*;
|
||||
import com.suisung.mall.shop.esign.service.EsignContractFillingFileService;
|
||||
import com.suisung.mall.shop.esign.service.EsignContractService;
|
||||
import com.suisung.mall.shop.lakala.service.LakalaApiService;
|
||||
import com.suisung.mall.shop.lakala.service.LklLedgerMemberService;
|
||||
import com.suisung.mall.shop.lakala.service.LklLedgerMerReceiverBindService;
|
||||
import com.suisung.mall.shop.lakala.service.LklLedgerReceiverService;
|
||||
import com.suisung.mall.shop.lakala.service.*;
|
||||
import com.suisung.mall.shop.lakala.utils.LakalaUtil;
|
||||
import com.suisung.mall.shop.store.service.ShopMchEntryService;
|
||||
import com.suisung.mall.shop.store.service.ShopStoreBaseService;
|
||||
@ -45,6 +41,8 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.data.util.Pair;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
@ -53,6 +51,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ -119,6 +118,10 @@ public class LakalaApiServiceImpl implements LakalaApiService {
|
||||
@Resource
|
||||
private ShopStoreBaseService shopStoreBaseService;
|
||||
|
||||
@Lazy
|
||||
@Autowired
|
||||
private LklLedgerEcService lklLedgerEcService;
|
||||
|
||||
/**
|
||||
* 初始化 拉卡拉SDK
|
||||
*
|
||||
@ -493,6 +496,7 @@ public class LakalaApiServiceImpl implements LakalaApiService {
|
||||
reqData.put("acct_type_code", shopMchEntry.getAccount_type());//57 对公、 58 对私
|
||||
reqData.put("acct_no", shopMchEntry.getAccount_number());
|
||||
reqData.put("acct_name", shopMchEntry.getAccount_holder_name());
|
||||
reqData.put("remark", "申请入网电子合同");
|
||||
|
||||
String domain = projectDomain;
|
||||
if (isProd()) {
|
||||
@ -538,16 +542,66 @@ public class LakalaApiServiceImpl implements LakalaApiService {
|
||||
ecParams.put("D11", signDate);
|
||||
ecParams.put("D12", signDate);
|
||||
ecParams.put("E1", "桂平发发网络有限公司");
|
||||
ecParams.put("E2", "平台商户入驻合同协议");
|
||||
ecParams.put("E2", "商户入驻小发同城平台合同协议");
|
||||
ecParams.put("E3", "0");
|
||||
ecParams.put("E4", "70");
|
||||
ecParams.put("E5", "桂平发发网络有限公司");
|
||||
ecParams.put("E7", signDate);
|
||||
ecParams.put("E8", shopMchEntry.getAccount_holder_name());
|
||||
|
||||
reqData.put("ec_content_parameters", ecParams);
|
||||
|
||||
return null;
|
||||
// 注:该字段是字符串,不是json
|
||||
reqData.put("ec_content_parameters", ecParams.toString());
|
||||
|
||||
JSONObject reqBody = new JSONObject();
|
||||
reqBody.put("req_time", DateTimeUtils.formatDateTime(LocalDateTime.now(), "yyyyMMddHHmmss"));
|
||||
reqBody.put("version", "3.0");
|
||||
reqBody.put("req_data", reqData);
|
||||
|
||||
|
||||
String reqUrl = serverUrl + "/sit/api/v3/mms/open_api/ec/apply";
|
||||
if (isProd()) {
|
||||
reqUrl = serverUrl + "/api/v3/mms/open_api/ec/apply";
|
||||
}
|
||||
|
||||
String privateKey = LakalaUtil.getResourceFile(priKeyPath, false, true);
|
||||
String authorization = LakalaUtil.genAuthorization(privateKey, appId, serialNo, reqBody.toString());
|
||||
|
||||
JSONObject header = new JSONObject();
|
||||
header.put("Authorization", authorization);
|
||||
|
||||
ResponseEntity<JSONObject> response = RestTemplateHttpUtil.sendPostBodyBackEntity(reqUrl, header, reqBody, JSONObject.class);
|
||||
if (ObjectUtil.isEmpty(response) || response.getStatusCode() != HttpStatus.OK) {
|
||||
return Pair.of(false, "商家入网申请电子合同:请求失败");
|
||||
}
|
||||
|
||||
JSONObject respBody = response.getBody();
|
||||
if (ObjectUtil.isNotEmpty(respBody) && !lklSuccessCode.equals(respBody.getStr("code"))) {
|
||||
String errMsg = StrUtil.isBlank(respBody.getStr("msg")) ? "返回状态有误" : respBody.getStr("msg");
|
||||
return Pair.of(false, "商家入网申请电子合同失败:" + errMsg);
|
||||
}
|
||||
|
||||
JSONObject respData = respBody.getJSONObject("resp_data");
|
||||
if (respBody.getJSONObject("resp_data") == null) {
|
||||
return Pair.of(false, "商家入网申请电子合同失败:返回数据有误");
|
||||
}
|
||||
|
||||
// 商家入网申请电子合同处理数据
|
||||
// 先写入本地数据库表中
|
||||
LklLedgerEc record = new LklLedgerEc();
|
||||
record.setMch_id(shopMchEntry.getId());
|
||||
record.setMch_mobile(shopMchEntry.getLogin_mobile());
|
||||
record.setReq_params(reqBody.toString());
|
||||
record.setNotify_url(retUrl);
|
||||
record.setEc_apply_id(respData.getLong("ec_apply_id"));
|
||||
record.setResult_url(respData.getStr("result_url"));
|
||||
record.setResp_body(respBody.toString());
|
||||
Boolean success = lklLedgerEcService.saveOrUpdateByMchId(record);
|
||||
if (!success) {
|
||||
return Pair.of(false, "商家入网申请电子合同失败:本地数据保存失败");
|
||||
}
|
||||
|
||||
|
||||
return Pair.of(true, "商家入网申请电子合同成功");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ import com.suisung.mall.shop.lakala.mapper.LklLedgerReceiverMapper;
|
||||
import com.suisung.mall.shop.lakala.service.LakalaApiService;
|
||||
import com.suisung.mall.shop.lakala.service.LklLedgerMemberService;
|
||||
import com.suisung.mall.shop.lakala.service.LklLedgerReceiverService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@ -31,6 +32,7 @@ import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class LklLedgerReceiverServiceImpl extends BaseServiceImpl<LklLedgerReceiverMapper, LklLedgerReceiver> implements LklLedgerReceiverService {
|
||||
|
||||
@ -207,13 +209,16 @@ public class LklLedgerReceiverServiceImpl extends BaseServiceImpl<LklLedgerRecei
|
||||
public Boolean innerApplyLedgerReceiver(String merCupNo, Long platformId) {
|
||||
JSONArray buildApplyLedgerReceiverReqParams = buildApplyLedgerReceiverReqParams(platformId);
|
||||
if (CollectionUtil.isEmpty(buildApplyLedgerReceiverReqParams)) {
|
||||
log.error("没有平台信息或代理商信息");
|
||||
return false;
|
||||
}
|
||||
|
||||
int success = 0;
|
||||
for (JSONObject reqParam : buildApplyLedgerReceiverReqParams.jsonIter()) {
|
||||
log.debug("申请分账接收方参数:{}", reqParam.toString());
|
||||
CommonResult result = lakalaApiService.applyLedgerReceiver(reqParam);
|
||||
if (result == null || result.getCode() != 200) {
|
||||
log.error("申请分账接收方出错:{}", result.getMsg());
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@ -41,18 +41,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.*;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -104,108 +93,6 @@ public class LklTkServiceImpl {
|
||||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
/**
|
||||
* 拉卡拉使用私钥加密数据
|
||||
*/
|
||||
public static String encryptNotifyData(String priKey, String data) {
|
||||
ByteArrayOutputStream out = null;
|
||||
try {
|
||||
logger.debug("拉卡拉tk加密私钥:{}", priKey);
|
||||
logger.debug("拉卡拉tk待加密data数据:{}", data);
|
||||
|
||||
// 解码私钥
|
||||
byte[] keyBytes = Base64.getDecoder().decode(priKey);
|
||||
|
||||
// 生成私钥对象
|
||||
PrivateKey privateKey = KeyFactory.getInstance("RSA")
|
||||
.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
|
||||
|
||||
// 初始化加密器
|
||||
Cipher cipher = Cipher.getInstance("RSA");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
|
||||
|
||||
// 分段加密
|
||||
byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
|
||||
out = new ByteArrayOutputStream();
|
||||
for (int offset = 0; offset < dataBytes.length; offset += 117) {
|
||||
int blockSize = Math.min(117, dataBytes.length - offset);
|
||||
byte[] encryptedBlock = cipher.doFinal(dataBytes, offset, blockSize);
|
||||
out.write(encryptedBlock);
|
||||
}
|
||||
|
||||
// 编码为Base64字符串
|
||||
String encryptDataStr = Base64.getEncoder().encodeToString(out.toByteArray());
|
||||
logger.debug("拉卡拉tk数据加密结果:{}", encryptDataStr);
|
||||
return encryptDataStr;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("私钥加密失败", e);
|
||||
} finally {
|
||||
closeQuietly(out);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全关闭流
|
||||
*/
|
||||
private static void closeQuietly(ByteArrayOutputStream out) {
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (Exception e) {
|
||||
// 忽略异常
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拉卡拉异步通知数据公钥解密
|
||||
*
|
||||
* @param pubKey Base64公钥
|
||||
* @param data Base64数据
|
||||
* @return 解密字符串
|
||||
*/
|
||||
public String decryptNotifyData(String pubKey, String data) {
|
||||
ByteArrayOutputStream out = null;
|
||||
try {
|
||||
logger.debug("拉卡拉tk解密公钥:{}", pubKey);
|
||||
logger.debug("拉卡拉tk待解密data数据:{}", data);
|
||||
Base64.Decoder decoder = Base64.getDecoder();
|
||||
byte[] keyBytes = decoder.decode(pubKey.getBytes(StandardCharsets.UTF_8));
|
||||
byte[] dataBytes = decoder.decode(data.getBytes(StandardCharsets.UTF_8));
|
||||
Cipher cipher = Cipher.getInstance("RSA");
|
||||
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(keyBytes));
|
||||
cipher.init(Cipher.DECRYPT_MODE, publicKey);
|
||||
out = new ByteArrayOutputStream();
|
||||
byte[] cache;
|
||||
for (int i = 0, offset = 0, length = dataBytes.length; length - offset > 0; i++, offset = i * 128) {
|
||||
if (length - offset > 128) {
|
||||
cache = cipher.doFinal(dataBytes, offset, 128);
|
||||
} else {
|
||||
cache = cipher.doFinal(dataBytes, offset, length - offset);
|
||||
}
|
||||
out.write(cache, 0, cache.length);
|
||||
}
|
||||
|
||||
String decodedDataStr = out.toString("UTF-8");
|
||||
logger.debug("拉卡拉tk数据解密结果:{}", decodedDataStr);
|
||||
|
||||
return decodedDataStr;
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("Base64解码失败: {}", e.getMessage(), e);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
logger.error("RSA算法初始化失败: {}", e.getMessage(), e);
|
||||
} catch (InvalidKeySpecException | InvalidKeyException e) {
|
||||
logger.error("私钥格式或类型错误: {}", e.getMessage(), e);
|
||||
} catch (BadPaddingException | IllegalBlockSizeException e) {
|
||||
logger.error("解密数据块大小或填充错误: {}", e.getMessage(), e);
|
||||
} catch (Exception e) {
|
||||
logger.error("解密过程中出现未知异常: {}", e.getMessage(), e);
|
||||
} finally {
|
||||
closeQuietly(out);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String buildLklTkUrl(String urlPath) {
|
||||
return tkServerUrl + urlPath;
|
||||
@ -610,8 +497,8 @@ public class LklTkServiceImpl {
|
||||
}
|
||||
|
||||
// 公钥解密出来的数据
|
||||
String notifyPubKey = LakalaUtil.getResourceFileWithEndSeq(notifyPubKeyPath, false);
|
||||
String data = decryptNotifyData(notifyPubKey, srcData);
|
||||
String notifyPubKey = LakalaUtil.getResourceFile(notifyPubKeyPath, false, false);
|
||||
String data = LakalaUtil.decryptNotifyData(notifyPubKey, srcData);
|
||||
if (StrUtil.isBlank(data)) {
|
||||
return new JSONObject().set("code", "500").set("message", "密文解密出错!");
|
||||
}
|
||||
|
||||
@ -13,23 +13,28 @@ import com.lkl.laop.sdk.Config2;
|
||||
import com.lkl.laop.sdk.LKLSDK;
|
||||
import com.suisung.mall.common.exception.ApiException;
|
||||
import com.suisung.mall.common.utils.I18nUtil;
|
||||
import com.suisung.mall.common.utils.RSAUtil;
|
||||
import com.suisung.mall.common.utils.StringUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.*;
|
||||
import java.security.cert.*;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Slf4j
|
||||
public class LakalaUtil {
|
||||
@ -58,10 +63,17 @@ public class LakalaUtil {
|
||||
* @return
|
||||
*/
|
||||
public static String getResourceFile(String fileName) {
|
||||
return getResourceFileWithEndSeq(fileName, true);
|
||||
return getResourceFile(fileName, true, false);
|
||||
}
|
||||
|
||||
public static String getResourceFileWithEndSeq(String fileName, boolean keepLineBreaks) {
|
||||
/**
|
||||
* 获取配置文件内容
|
||||
*
|
||||
* @param fileName recource 文件夹下的路径,如:palyKey/wx/lakala_public_key.cer
|
||||
* @param keepLineBreaks 保留换行符
|
||||
* @return
|
||||
*/
|
||||
public static String getResourceFile(String fileName, boolean keepLineBreaks, boolean stripPemHeaders) {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
try (InputStream inputStream = new ClassPathResource(fileName).getInputStream();
|
||||
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
|
||||
@ -71,6 +83,9 @@ public class LakalaUtil {
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
stringBuilder.append(line).append(endSeq);
|
||||
}
|
||||
if (stripPemHeaders) {
|
||||
return stripPemHeaders(stringBuilder.toString());
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
} catch (IOException e) {
|
||||
// 记录异常信息
|
||||
@ -79,6 +94,76 @@ public class LakalaUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拉卡拉使用私钥加密数据
|
||||
*/
|
||||
public static String encryptNotifyData(String priKey, String data) {
|
||||
ByteArrayOutputStream out = null;
|
||||
try {
|
||||
log.debug("拉卡拉tk加密私钥:{}", priKey);
|
||||
log.debug("拉卡拉tk待加密data数据:{}", data);
|
||||
|
||||
// 解码私钥
|
||||
byte[] keyBytes = java.util.Base64.getDecoder().decode(priKey);
|
||||
|
||||
// 生成私钥对象
|
||||
PrivateKey privateKey = KeyFactory.getInstance("RSA")
|
||||
.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
|
||||
|
||||
// 初始化加密器
|
||||
Cipher cipher = Cipher.getInstance("RSA");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
|
||||
|
||||
// 分段加密
|
||||
byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
|
||||
out = new ByteArrayOutputStream();
|
||||
for (int offset = 0; offset < dataBytes.length; offset += 117) {
|
||||
int blockSize = Math.min(117, dataBytes.length - offset);
|
||||
byte[] encryptedBlock = cipher.doFinal(dataBytes, offset, blockSize);
|
||||
out.write(encryptedBlock);
|
||||
}
|
||||
|
||||
// 编码为Base64字符串
|
||||
String encryptDataStr = java.util.Base64.getEncoder().encodeToString(out.toByteArray());
|
||||
log.debug("拉卡拉tk数据加密结果:{}", encryptDataStr);
|
||||
return encryptDataStr;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("私钥加密失败", e);
|
||||
} finally {
|
||||
closeQuietly(out);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全关闭流
|
||||
*/
|
||||
private static void closeQuietly(ByteArrayOutputStream out) {
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (Exception e) {
|
||||
// 忽略异常
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String stripPemHeaders(String key) {
|
||||
if (key == null || key.isEmpty()) {
|
||||
return key;
|
||||
}
|
||||
|
||||
// 定义正则表达式:匹配标签行、任意数量连字符和空白字符
|
||||
// 1. 包含BEGIN或END的标签行(无论连字符数量)
|
||||
// 2. 单独的连字符(不在标签行中的)
|
||||
// 3. 空白字符(空格、制表符、换行等)
|
||||
Pattern pattern = Pattern.compile(
|
||||
"-*BEGIN[^-]*-*|-*END[^-]*-*|-|\\s");
|
||||
|
||||
// 执行替换
|
||||
Matcher matcher = pattern.matcher(key);
|
||||
return matcher.replaceAll("");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 body 请求数据
|
||||
*
|
||||
@ -133,7 +218,6 @@ public class LakalaUtil {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 签名验证
|
||||
*
|
||||
@ -158,7 +242,6 @@ public class LakalaUtil {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 签名校验
|
||||
*
|
||||
@ -239,4 +322,85 @@ public class LakalaUtil {
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接拉卡拉header中的Authorization字段
|
||||
* 参考文档:https://o.lakala.com/#/home/document/detail?id=33
|
||||
*
|
||||
* @param privateKey
|
||||
* @param appId
|
||||
* @param serialNo
|
||||
* @param reqBody
|
||||
* @return
|
||||
*/
|
||||
public static String genAuthorization(String privateKey, String appId, String serialNo, String reqBody) {
|
||||
if (StrUtil.isBlank(privateKey) || StrUtil.isBlank(appId) || StrUtil.isBlank(serialNo) || StrUtil.isBlank(reqBody)) {
|
||||
log.error("生产拉卡拉签名时缺少参数");
|
||||
return "";
|
||||
}
|
||||
|
||||
//待签字符串格式:${appid}\n+${serialNo}\n+${timeStamp}\n+${nonceStr}\n+${body}\n
|
||||
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
String nonceStr = StringUtils.genRandomNumber(12);
|
||||
String builder = appId + "\n" +
|
||||
serialNo + "\n" +
|
||||
timestamp + "\n" +
|
||||
nonceStr + "\n" +
|
||||
reqBody + "\n";
|
||||
|
||||
String signature = RSAUtil.signSHA256withRSA(builder, privateKey);
|
||||
|
||||
return String.format("LKLAPI-SHA256withRSA appid=\"%s\",serial_no=\"%s\",timestamp=\"%s\",nonce_str=\"%s\",signature=\"%s\"",
|
||||
appId, serialNo, timestamp, nonceStr, signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拉卡拉异步通知数据公钥解密
|
||||
*
|
||||
* @param pubKey Base64公钥
|
||||
* @param data Base64数据
|
||||
* @return 解密字符串
|
||||
*/
|
||||
public static String decryptNotifyData(String pubKey, String data) {
|
||||
ByteArrayOutputStream out = null;
|
||||
try {
|
||||
log.debug("拉卡拉tk解密公钥:{}", pubKey);
|
||||
log.debug("拉卡拉tk待解密data数据:{}", data);
|
||||
java.util.Base64.Decoder decoder = java.util.Base64.getDecoder();
|
||||
byte[] keyBytes = decoder.decode(pubKey.getBytes(StandardCharsets.UTF_8));
|
||||
byte[] dataBytes = decoder.decode(data.getBytes(StandardCharsets.UTF_8));
|
||||
Cipher cipher = Cipher.getInstance("RSA");
|
||||
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(keyBytes));
|
||||
cipher.init(Cipher.DECRYPT_MODE, publicKey);
|
||||
out = new ByteArrayOutputStream();
|
||||
byte[] cache;
|
||||
for (int i = 0, offset = 0, length = dataBytes.length; length - offset > 0; i++, offset = i * 128) {
|
||||
if (length - offset > 128) {
|
||||
cache = cipher.doFinal(dataBytes, offset, 128);
|
||||
} else {
|
||||
cache = cipher.doFinal(dataBytes, offset, length - offset);
|
||||
}
|
||||
out.write(cache, 0, cache.length);
|
||||
}
|
||||
|
||||
String decodedDataStr = out.toString("UTF-8");
|
||||
log.debug("拉卡拉tk数据解密结果:{}", decodedDataStr);
|
||||
|
||||
return decodedDataStr;
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.error("Base64解码失败: {}", e.getMessage(), e);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
log.error("RSA算法初始化失败: {}", e.getMessage(), e);
|
||||
} catch (InvalidKeySpecException | InvalidKeyException e) {
|
||||
log.error("私钥格式或类型错误: {}", e.getMessage(), e);
|
||||
} catch (BadPaddingException | IllegalBlockSizeException e) {
|
||||
log.error("解密数据块大小或填充错误: {}", e.getMessage(), e);
|
||||
} catch (Exception e) {
|
||||
log.error("解密过程中出现未知异常: {}", e.getMessage(), e);
|
||||
} finally {
|
||||
closeQuietly(out);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,7 +144,7 @@ sf-express:
|
||||
#拉卡拉进件配置
|
||||
lakala:
|
||||
#服务地址
|
||||
server_url: https://test.wsmsd.cn/sit
|
||||
server_url: https://test.wsmsd.cn
|
||||
#应用Id
|
||||
app_id: OP00000003
|
||||
#商户证书序列号
|
||||
|
||||
@ -144,7 +144,7 @@ sf-express:
|
||||
#拉卡拉进件配置
|
||||
lakala:
|
||||
#服务地址
|
||||
server_url: https://test.wsmsd.cn/sit
|
||||
server_url: https://test.wsmsd.cn
|
||||
#应用Id
|
||||
app_id: OP00000003
|
||||
#商户证书序列号
|
||||
@ -164,7 +164,7 @@ lakala:
|
||||
# 拉卡拉拓客进件配置
|
||||
tk:
|
||||
#服务地址
|
||||
server_url: https://test.wsmsd.cn
|
||||
server_url: https://test.wsmsd.cn/sit
|
||||
client_id: lsycs
|
||||
client_secret: XPa1HB5d55Ig0qV8
|
||||
user_no: 29153396
|
||||
|
||||
@ -171,7 +171,7 @@ lakala:
|
||||
# #终端号码,M0780629(B2B收银台) M0780798(专业化扫码)
|
||||
# term_no: M0780798
|
||||
#服务地址
|
||||
server_url: https://test.wsmsd.cn/sit
|
||||
server_url: https://test.wsmsd.cn
|
||||
#应用Id
|
||||
app_id: OP00000003
|
||||
#商户证书序列号
|
||||
|
||||
@ -148,7 +148,7 @@ sf-express:
|
||||
#拉卡拉进件配置
|
||||
lakala:
|
||||
#服务地址
|
||||
server_url: https://test.wsmsd.cn/sit
|
||||
server_url: https://test.wsmsd.cn
|
||||
#应用Id
|
||||
app_id: OP00000003
|
||||
#商户证书序列号
|
||||
|
||||
@ -148,7 +148,7 @@ sf-express:
|
||||
#拉卡拉进件配置
|
||||
lakala:
|
||||
#服务地址
|
||||
server_url: https://test.wsmsd.cn/sit
|
||||
server_url: https://test.wsmsd.cn
|
||||
#应用Id
|
||||
app_id: OP00000003
|
||||
#商户证书序列号
|
||||
|
||||
Loading…
Reference in New Issue
Block a user