From d1c6630ef53f1265216822bc8bfdaf8ae55a9d76 Mon Sep 17 00:00:00 2001 From: Jack <46790855@qq.com> Date: Wed, 14 May 2025 00:51:51 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8B=89=E5=8D=A1=E6=8B=89?= =?UTF-8?q?=E7=9A=84=E5=8A=A0=E5=AF=86=E8=A7=A3=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/modules/store/ShopMchEntry.java | 3 + .../suisung/mall/common/utils/JiebaUtils.java | 4 +- .../controller/mobile/LklTkController.java | 9 + .../lakala/service/impl/LklTkServiceImpl.java | 163 +++++++++++++----- .../store/service/ShopMchEntryService.java | 17 +- .../service/impl/ShopMchEntryServiceImpl.java | 27 ++- .../lakala/prod/tk_notify_private_key.txt | 2 +- .../lakala/prod/tk_notify_public_key.txt | 2 +- 8 files changed, 165 insertions(+), 62 deletions(-) diff --git a/mall-common/src/main/java/com/suisung/mall/common/modules/store/ShopMchEntry.java b/mall-common/src/main/java/com/suisung/mall/common/modules/store/ShopMchEntry.java index 44600acc..d792a763 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/modules/store/ShopMchEntry.java +++ b/mall-common/src/main/java/com/suisung/mall/common/modules/store/ShopMchEntry.java @@ -217,6 +217,9 @@ public class ShopMchEntry implements Serializable { @ApiModelProperty(value = "拉卡拉进件请求参数") private String lkl_tk_reg_params; + @ApiModelProperty(value = "异步通知的请求参数(加密过)") + private String lkl_tk_reg_notify_req; + @ApiModelProperty(value = "拉卡拉进件成功返回的JSON数据") private String lkl_tk_reg_resp; diff --git a/mall-common/src/main/java/com/suisung/mall/common/utils/JiebaUtils.java b/mall-common/src/main/java/com/suisung/mall/common/utils/JiebaUtils.java index ba6c7760..531a3ad5 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/utils/JiebaUtils.java +++ b/mall-common/src/main/java/com/suisung/mall/common/utils/JiebaUtils.java @@ -21,8 +21,8 @@ public class JiebaUtils { public static void main(String[] args) { JiebaUtils jiebaUtils = new JiebaUtils(); - String text = "中国工商银行桂平市光明支行"; - List words = jiebaUtils.segmentForSearch(text); + String text = "农行桂平"; + List words = jiebaUtils.segment(text); System.out.println(words); } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/controller/mobile/LklTkController.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/controller/mobile/LklTkController.java index e5a31ae2..a9f5a577 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/controller/mobile/LklTkController.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/controller/mobile/LklTkController.java @@ -37,6 +37,15 @@ public class LklTkController extends BaseControllerImpl { @Resource private LklBanksService lklBanksService; + @ApiOperation(value = "解密拉卡拉进件返回的异步通知", notes = "解密拉卡拉进件返回的异步通知") + @RequestMapping(value = "/decode", method = RequestMethod.POST) + public String decryptLklTkData(@RequestParam(name = "data") String data, + @RequestParam(name = "key") String key) { + + return lklTkService.decryptNotifyData(key, data); + + } + @ApiOperation(value = "搜索国内银行(支行)分页列表", notes = "搜索国内银行(支行)分页列表,数据包含有效的收款结清行号") @RequestMapping(value = "/bank/search", method = RequestMethod.POST) public CommonResult searchLklBanksPageList(@RequestBody JSONObject paramsJSON) { diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklTkServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklTkServiceImpl.java index 4846acd9..10928ba9 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklTkServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklTkServiceImpl.java @@ -41,11 +41,16 @@ 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.security.KeyFactory; -import java.security.PublicKey; +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; @@ -70,9 +75,6 @@ public class LklTkServiceImpl { @Value("${lakala.tk.user_no}") private String userNo; - @Value("${lakala.org_code}") - private String orgCode; - @Value("${lakala.tk.notify_pub_key_path}") private String notifyPubKeyPath; @@ -102,6 +104,63 @@ public class LklTkServiceImpl { @Autowired private RedisService redisService; + /** + * 拉卡拉使用私钥加密数据 + */ + public static String encryptNotifyData(String priKey, String data) { + ByteArrayOutputStream out = null; + try { + // pubKey 去掉 空格、制表符(\t)、换行符(\n)、回车符(\r) + priKey = priKey.replaceAll("\\s+", ""); + + 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) { + // 忽略异常 + } + } + } + /** * 拉卡拉异步通知数据公钥解密 * @@ -110,14 +169,19 @@ public class LklTkServiceImpl { * @return 解密字符串 */ public String decryptNotifyData(String pubKey, String data) { + ByteArrayOutputStream out = null; try { + logger.debug("拉卡拉tk解密公钥:{}", pubKey); + logger.debug("拉卡拉tk待解密data数据:{}", data); + // pubKey 去掉 空格、制表符(\t)、换行符(\n)、回车符(\r) + pubKey = pubKey.replaceAll("\\s+", ""); Base64.Decoder decoder = Base64.getDecoder(); - byte[] keyBytes = decoder.decode(pubKey.getBytes()); - byte[] dataBytes = decoder.decode(data.getBytes()); + 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); - ByteArrayOutputStream out = new ByteArrayOutputStream(); + 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) { @@ -127,13 +191,23 @@ public class LklTkServiceImpl { } out.write(cache, 0, cache.length); } - return out.toString(); + + String decodedDataStr = out.toString("UTF-8"); + logger.debug("拉卡拉tk数据解密结果:{}", decodedDataStr); + + return decodedDataStr; } catch (IllegalArgumentException e) { - logger.error("Base64 解码时出现非法参数异常: ", e.getMessage()); + 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) { - // 捕获其他可能的异常,如 NoSuchAlgorithmException、NoSuchPaddingException、 - // InvalidKeySpecException、InvalidKeyException、BadPaddingException 等 - logger.error("解密过程中出现异常: ", e.getMessage()); + logger.error("解密过程中出现未知异常: {}", e.getMessage(), e); + } finally { + closeQuietly(out); } return null; @@ -400,7 +474,6 @@ public class LklTkServiceImpl { formData.put("settleType", "D1"); //结算类型,D0秒到,D1次日结算 // formData.put("settlementType", "AUTOMATIC"); // 结算方式:MANUAL:手动结算(结算至拉卡拉APP钱包),AUTOMATIC:自动结算到银行卡,REGULAR:定时结算(仅企业商户支持) - // 店铺省市区信息 Map areaCode = getAreaCode(shopMchEntry.getStore_area(), false); if (ObjectUtil.isNotEmpty(areaCode)) { @@ -422,7 +495,7 @@ public class LklTkServiceImpl { } - // TODO 咨询拉卡拉清楚 + // 费率和设备、活动 JSONObject bizContent = new JSONObject(); bizContent.put("activityId", 687); bizContent.put("termNum", "1"); @@ -439,24 +512,26 @@ public class LklTkServiceImpl { // 附件文件相关开始 JSONArray attachments = new JSONArray(); - JSONObject ID_CARD_FRONT = updatePhoto(shopMchEntry.getIndividual_id_images(), "ID_CARD_FRONT", false); - if (ID_CARD_FRONT != null) { - attachments.put(ID_CARD_FRONT); // 身份证正面 - } + if (isQy) { + JSONObject SETTLE_ID_CARD_FRONT = updatePhoto(shopMchEntry.getLegal_person_id_images(), "FR_ID_CARD_FRONT", false); + if (SETTLE_ID_CARD_FRONT != null) { + attachments.put(SETTLE_ID_CARD_FRONT); // 法人身份证正面 + } - JSONObject ID_CARD_BEHIND = updatePhoto(shopMchEntry.getIndividual_id_images2(), "ID_CARD_BEHIND", false); - if (ID_CARD_BEHIND != null) { - attachments.put(ID_CARD_BEHIND); // 身份证国徽面 - } + JSONObject SETTLE_ID_CARD_BEHIND = updatePhoto(shopMchEntry.getLegal_person_id_images2(), "FR_ID_CARD_BEHIND", false); + if (SETTLE_ID_CARD_BEHIND != null) { + attachments.put(SETTLE_ID_CARD_BEHIND); // 法人身份证国徽面 + } + } else { + JSONObject ID_CARD_FRONT = updatePhoto(shopMchEntry.getIndividual_id_images(), "ID_CARD_FRONT", false); + if (ID_CARD_FRONT != null) { + attachments.put(ID_CARD_FRONT); // 身份证正面 + } - JSONObject SETTLE_ID_CARD_FRONT = updatePhoto(shopMchEntry.getLegal_person_id_images(), "FR_ID_CARD_FRONT", false); - if (SETTLE_ID_CARD_FRONT != null) { - attachments.put(SETTLE_ID_CARD_FRONT); // 法人身份证正面 - } - - JSONObject SETTLE_ID_CARD_BEHIND = updatePhoto(shopMchEntry.getLegal_person_id_images2(), "FR_ID_CARD_BEHIND", false); - if (SETTLE_ID_CARD_BEHIND != null) { - attachments.put(SETTLE_ID_CARD_BEHIND); // 法人身份证国徽面 + JSONObject ID_CARD_BEHIND = updatePhoto(shopMchEntry.getIndividual_id_images2(), "ID_CARD_BEHIND", false); + if (ID_CARD_BEHIND != null) { + attachments.put(ID_CARD_BEHIND); // 身份证国徽面 + } } JSONObject BUSINESS_LICENCE = updatePhoto(shopMchEntry.getBiz_license_image(), "BUSINESS_LICENCE", false); @@ -486,18 +561,20 @@ public class LklTkServiceImpl { logger.info("进件请求参数:{}", JSONUtil.toJsonStr(formData)); ResponseEntity response = RestTemplateHttpUtil.sendPostBodyBackEntity(buildLklTkUrl(urlPath), header, formData, JSONObject.class); - if (ObjectUtil.isEmpty(response) || response.getStatusCode() != HttpStatus.OK) { - String errMsg = "进件返回空或请求状态异常"; - if (ObjectUtil.isNotEmpty(response.getBody()) && ObjectUtil.isNotEmpty(response.getBody().getStr("message"))) { - errMsg = response.getBody().getStr("message"); - } + if (ObjectUtil.isEmpty(response)) { + return Pair.of(false, "进件失败:进件无返回值"); + } + + JSONObject respBody = response.getBody(); + if (response.getStatusCode() != HttpStatus.OK && ObjectUtil.isNotEmpty(respBody)) { + String errMsg = respBody.getStr("message") == null ? "返回状态有误" : respBody.getStr("message"); return Pair.of(false, "进件失败:" + errMsg); } // 更改入驻记录的拉卡拉内部商户号和进件请求参数 - String lklMerCupNo = response.getBody().getStr("merchantNo"); //拉卡拉内部商户号 + String lklMerCupNo = respBody.getStr("merchantNo"); //拉卡拉内部商户号 // 表中的内部外部商户号暂时都传同一个内部商户号,以便异步通知更改记录 - Boolean success = shopMchEntryService.updateMerchEntryLklMerCupNo(mchMobile, CommonConstant.Disable2, lklMerCupNo, lklMerCupNo, formData.toString()); + Boolean success = shopMchEntryService.updateMerchEntryLklMerCupNo(mchMobile, CommonConstant.Disable2, lklMerCupNo, lklMerCupNo, formData.toString(), respBody.toString()); if (!success) { return Pair.of(false, "提交进件成功,但更新商户号失败!"); } @@ -518,7 +595,6 @@ public class LklTkServiceImpl { */ public JSONObject registrationMerchantNotify(HttpServletRequest request) { logger.debug("拉卡拉进件异步通知开始"); - JSONObject respData = new JSONObject(); // 解密请求参数 String requestBody = LakalaUtil.getBody(request); @@ -539,13 +615,13 @@ public class LklTkServiceImpl { // 公钥解密出来的数据 String notifyPubKey = LakalaUtil.getResourceFile(notifyPubKeyPath); - logger.debug("解密公钥:{}", notifyPubKey); String data = decryptNotifyData(notifyPubKey, srcData); - logger.debug("拉卡拉进件异步通知返回 data 数据:{}, 解密后的 data 数据:{}", srcData, data); if (StrUtil.isBlank(data)) { - return new JSONObject().set("code", "500").set("message", "无法解密出 data 参数"); + return new JSONObject().set("code", "500").set("message", "密文解密出错!"); } + logger.debug("拉卡拉进件异步通知data解密成功,开始处理逻辑"); + // 逻辑处理 JSONObject dataJSON = JSONUtil.parseObj(data); if (dataJSON.isEmpty() || StrUtil.isBlank(dataJSON.getStr("externalCustomerNo"))) { @@ -557,7 +633,8 @@ public class LklTkServiceImpl { String merInnerNo = dataJSON.getStr("customerNo"); //拉卡拉内部商户号 ShopMchEntry shopMchEntry = shopMchEntryService.getShopMerchEntryByMerCupNo(merCupNo); if (ObjectUtil.isEmpty(shopMchEntry)) { - return new JSONObject().set("code", "500").set("message", "商户入驻信息不存在"); + logger.error("拉卡拉进件异步通知:返回的外部商户号:{} 入驻信息不存在!", merCupNo); + return new JSONObject().set("code", "500").set("message", "外部商户号:" + merCupNo + " 入驻信息不存在"); } Boolean success = shopMchEntryService.updateMerchEntryLklAuditStatusByLklMerCupNo(merInnerNo, merCupNo, CommonConstant.Enable, data); diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/store/service/ShopMchEntryService.java b/mall-shop/src/main/java/com/suisung/mall/shop/store/service/ShopMchEntryService.java index 4cfcc46a..b672f8eb 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/store/service/ShopMchEntryService.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/store/service/ShopMchEntryService.java @@ -139,22 +139,23 @@ public interface ShopMchEntryService { /** * 更新商家入驻申请的拉卡拉商户号 * - * @param loginMobile - * @param lklAuditStatus + * @param loginMobile 商家注册的手机号 + * @param lklAuditStatus 拉卡拉审核状态 * @param lklMerCupNo 拉卡拉银联商户号 * @param lklMerInnerNo 拉卡拉内部商户号 - * @param lklTkRegParams + * @param lklTkRegParams 进件请求参数 + * @param lklTkRegResp 进件返回的数据 * @return */ - Boolean updateMerchEntryLklMerCupNo(String loginMobile, Integer lklAuditStatus, String lklMerCupNo, String lklMerInnerNo, String lklTkRegParams); + Boolean updateMerchEntryLklMerCupNo(String loginMobile, Integer lklAuditStatus, String lklMerCupNo, String lklMerInnerNo, String lklTkRegParams, String lklTkRegResp); /** * 更新商家入驻申请的拉卡拉审核状态和响应数据 * - * @param lklMerCupNo - * @param lklInnerMerNo - * @param lklAuditStatus - * @param lklTkRegResp + * @param lklMerCupNo 拉卡拉银联商户号 + * @param lklInnerMerNo 拉卡拉内部商户号 + * @param lklAuditStatus 拉卡拉审核状态 + * @param lklTkRegResp 进件返回的数据 * @return */ Boolean updateMerchEntryLklAuditStatusByLklMerCupNo(String lklMerCupNo, String lklInnerMerNo, Integer lklAuditStatus, String lklTkRegResp); diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/store/service/impl/ShopMchEntryServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/store/service/impl/ShopMchEntryServiceImpl.java index 9e13cc78..10d484ba 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/store/service/impl/ShopMchEntryServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/store/service/impl/ShopMchEntryServiceImpl.java @@ -411,6 +411,10 @@ public class ShopMchEntryServiceImpl extends BaseServiceImpl queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("mer_cup_no", merCupNo).eq("status", CommonConstant.Enable).orderByAsc("id"); + queryWrapper.eq("lkl_mer_cup_no", merCupNo).eq("status", CommonConstant.Enable).orderByAsc("id"); List recordList = list(queryWrapper); if (CollectionUtil.isEmpty(recordList)) { return null; @@ -732,7 +736,7 @@ public class ShopMchEntryServiceImpl extends BaseServiceImpl