Merge branch 'refs/heads/master' into cc_2021104_twelve_points

# Conflicts:
#	coolstore-partner-common/src/main/java/com/cool/store/enums/ErrorCodeEnum.java
#	coolstore-partner-common/src/main/java/com/cool/store/enums/JoinModeEnum.java
This commit is contained in:
wangff
2025-12-02 19:34:14 +08:00
193 changed files with 10187 additions and 167 deletions

View File

@@ -235,4 +235,5 @@ public class CommonConstants {
public static final Integer INDEX_ZERO = 0;
public static final Integer INDEX_ONE = 1;
public static final Integer INDEX_TWO = 2;
}

View File

@@ -289,4 +289,19 @@ public class RedisConstant {
public static final String HUO_MA_STORE_ID = "huo_ma_store_id";
public static final String HUO_MA_TOKEN= "huo_ma_token:{0}";
/**
* 钱包开通失败/打标失败原因 wallet_open_fail:storeId:1/2 1平安/2网商
*/
public static final String WALLET_OPEN_FAIL = "wallet_open_fail:{0}:{1}";
/**
* 打标接口创建网商账户失败标识 wallet_online_bank_tag_fail:storeId
*/
public static final String WALLET_ONLINE_BANK_TAG_FAIL = "wallet_online_bank_tag_fail:{0}";
/**
* 网商账户是否已激活
*/
public static final String WALLET_ONLINE_BANK_ACTIVATED = "wallet_online_bank_activated:{0}";
}

View File

@@ -0,0 +1,37 @@
package com.cool.store.enums.Decoration;
/**
* @Author suzhuhong
* @Date 2025/11/3 9:40
* @Version 1.0
*/
public enum DecorationDescStatus {
TO_BE_ASSIGNED(0, "待分配"),
ASSIGNED(1, "已分配"),
;
private Integer code;
private String descStatus;
DecorationDescStatus(Integer code, String descStatus) {
this.code = code;
this.descStatus = descStatus;
}
public Integer getCode() {
return code;
}
public String getDescStatus() {
return descStatus;
}
}

View File

@@ -0,0 +1,32 @@
package com.cool.store.enums.Decoration;
/**
* @Author suzhuhong
* @Date 2025/10/30 14:35
* @Version 1.0
*/
public enum DecorationUseSystemEnum {
CRM(1,"CRM"),
HQT(2,"红圈通");
private Integer code;
private String userSystemName;
DecorationUseSystemEnum(Integer code, String userSystemName) {
this.code = code;
this.userSystemName = userSystemName;
}
public Integer getCode() {
return code;
}
public String getUserSystemName() {
return userSystemName;
}
}

View File

@@ -300,6 +300,7 @@ public enum ErrorCodeEnum {
PRODUCTS_DISCARDED(1511034,"产品已报销,无法操作",null),
PRODUCTS_SALES_COMPLETED(1511034,"含有销售完成的产品,无法批量报销",null),
STORE_IS_EXIST(1511035,"该门店已存在",null),
FEE_NOT_CONSISTENT(1511036,"合同金额与缴费账单金额不一致,请确定!",null),
MESSAGE_TEMPLATE_NOT_SUPPORT_EDIT(1610001,"当前消息已发布,不支持编辑!",null),
@@ -316,7 +317,13 @@ public enum ErrorCodeEnum {
NOT_FLAGSHIP_STORE_NOT_EXIST(1610011,"当前阶段加盟类型不能变更!",null),
JOIN_MODE_NOT_ALLOW_OPERATE(1610012,"加盟部人员只能新建加盟店或联营店,请确认!",null),
STORE_NOT_FIND(1610013,"门店不存在",null),
SHOP_NAME_INVALID(1610014, "门店名称包含禁止关键词",null),
//装修
TEAM_USED(1612001,"该装修团队有门店使用,无法删除,请确认!",null),
WALLET_OPEN_ACCOUNT_FAIL(1620001,"钱包开通失败",null),
WALLET_WITH_DRAWER_FAIL(1620002,"提现失败",null),
WALLET_API_ERROR(1620003,"{0}",null),
/**
* 181 十二分制
*/
@@ -334,6 +341,9 @@ public enum ErrorCodeEnum {
TP_PENALTY_APPLY_NO_NEED_PAY(1810011, "该处罚单无需缴费", null),
;
CURRENT_BRAND_SORT_NUMBER_EXIST(16100007,"当前品牌已存在该排序数字!",null),
CONTRACT_CONFIG_NOT_EXIST(16100008,"合同配置不存在!",null);
;
protected static final Map<Integer, ErrorCodeEnum> map = Arrays.stream(values()).collect(

View File

@@ -14,20 +14,27 @@ import java.util.stream.Collectors;
* @注释:
*/
public enum FranchiseBrandEnum {
ZXJP(1,"正新鸡排","option486"),
ZXSMZ(2,"正新三明治","option622"),
ZJS(3,"正烧记","option488"),
DGMX(4,"大鼓米线","option624"),
CXM(5,"串小妹","option626"),
MZG(6,"茂掌柜","option490");
ZXJP(1,"正新鸡排","option486","上海正新食品集团有限公司","海南正新多品牌管理有限公司"),
ZXSMZ(2,"正新三明治","option622","上海小熊与树食品科技有限公司","上海小熊与树食品科技有限公司"),
ZJS(3,"正烧记","option488","上海正烧信息科技有限公司","上海正烧信息科技有限公司"),
DGMX(4,"大鼓米线","option624","",""),
CXM(5,"串小妹","option626","",""),
MZG(6,"茂掌柜","option490","","");
private int code;
private String desc;
//红圈通code
/**法大大合同编号*/
private String hqtCode;
FranchiseBrandEnum(int code, String desc,String hqtCode) {
/**品牌方*/
private String brandOwner;
/**收款方*/
private String payeeName;
FranchiseBrandEnum(int code, String desc,String hqtCode,String brandOwner,String payeeName) {
this.code = code;
this.desc = desc;
this.hqtCode = hqtCode;
this.brandOwner = brandOwner;
this.payeeName = payeeName;
}
public int getCode() {
return code;
@@ -40,6 +47,14 @@ public enum FranchiseBrandEnum {
return hqtCode;
}
public String getBrandOwner() {
return brandOwner;
}
public String getPayeeName() {
return payeeName;
}
public static String getDescByCode(String code) {
if (StringUtils.isBlank(code)){
return null;
@@ -52,6 +67,20 @@ public enum FranchiseBrandEnum {
}
return null;
}
public static FranchiseBrandEnum getEnumByCode(String code) {
if (StringUtils.isBlank(code)){
return null;
}
int i = Integer.parseInt(code);
for (FranchiseBrandEnum e : FranchiseBrandEnum.values()) {
if (i == e.getCode()) {
return e;
}
}
return null;
}
public static String getDescByCode(Integer code) {
if (code==null){
return null;

View File

@@ -45,6 +45,15 @@ public enum JoinModeEnum {
return code == FRANCHISE_DEPARTMENT.code || code == AFFILIATES.code;
}
public static JoinModeEnum getModelByCode(Integer code) {
for (JoinModeEnum e : JoinModeEnum.values()) {
if (e.getCode() == code) {
return e;
}
}
return null;
}
public static String getDescByCode(Integer code) {
for (JoinModeEnum e : JoinModeEnum.values()) {
if (e.getCode() == code) {

View File

@@ -10,7 +10,8 @@ public enum ResponseCodeEnum {
/**
* 成功返回
*/
SUCCESS(200000, "SUCCESS");
SUCCESS(200000, "SUCCESS"),
SUCCESS_WALLET(200, "SUCCESS");
/**
* 返回码

View File

@@ -33,7 +33,8 @@ public enum RocketMqGroupEnum {
FEI_SHU_EVENT_LISTENER("fei_shu_event_listener", new ArrayList<>(Arrays.asList(RocketMqTagEnum.USER_EVENT, RocketMqTagEnum.AUTH_SCOPE_CHANGE, RocketMqTagEnum.DEPT_EVENT))),
STORE_USER_UPDATE("store_user_update", new ArrayList<>(Arrays.asList(RocketMqTagEnum.STORE_USER_UPDATE)))
STORE_USER_UPDATE("store_user_update", new ArrayList<>(Arrays.asList(RocketMqTagEnum.STORE_USER_UPDATE))),
SHOP_DECORATION_ASSIGN("shop_decoration_assign", new ArrayList<>(Arrays.asList(RocketMqTagEnum.DELAY_SHOP_DECORATION_ASSIGN)))
;

View File

@@ -18,7 +18,8 @@ public enum RocketMqTagEnum {
ZXJP_CREATE_STORE("zxjp_create_store", "正新鸡排招商创建门店"),
PARTNER_LICENSE_SYNC_QUEUE("partner_license_sync_queue", "招商证照信息同步"),
BUSINESS_SYNC("business_sync", "工商食安信息同步"),
STORE_USER_UPDATE("store_user_update", "门店信息人员变更同步菜品");
STORE_USER_UPDATE("store_user_update", "门店信息人员变更同步菜品"),
DELAY_SHOP_DECORATION_ASSIGN("shop_decoration_assign","门店装修分配");
;

View File

@@ -7,11 +7,11 @@ package com.cool.store.enums;
*/
public enum SMSMsgEnum {
PAY_FRANCHISE_FEES("缴纳加盟费/保证金", "", "SMS_474655067"),
SIGN_CONTRACT("合同签署", "", "SMS_474450102"),
DESIGN_STAGE("设计阶段", "", "SMS_474490087"),
CONSTRUCTION_STAGE("施工阶段", "", "SMS_474525082"),
PLATFORM_BUILD_STORE("平台建店", "", "SMS_474645064"),
PAY_FRANCHISE_FEES("缴纳加盟费/保证金", "", "SMS_498895215"),
SIGN_CONTRACT("合同签署", "", "SMS_498840165"),
DESIGN_STAGE("设计阶段", "", "SMS_498750214"),
CONSTRUCTION_STAGE("施工阶段", "", "SMS_498870170"),
PLATFORM_BUILD_STORE("平台建店", "", "SMS_498730163"),
;
private String title;

View File

@@ -18,6 +18,7 @@ public enum ShopSubStageEnum {
SHOP_STAGE_3(ShopStageEnum.SHOP_STAGE_2, 30, "营业执照办理", 23),
SHOP_STAGE_4(ShopStageEnum.SHOP_STAGE_2, 40, "食安许可证", 55),
SHOP_STAGE_5(ShopStageEnum.SHOP_STAGE_2, 50, "员工招聘", 23),
SHOP_STAGE_6(ShopStageEnum.SHOP_STAGE_2, 260, "开通门店平安钱包", 1),
SHOP_STAGE_7(ShopStageEnum.SHOP_STAGE_2, 70, "缴纳加盟费/保证金", 1),
SHOP_STAGE_8(ShopStageEnum.SHOP_STAGE_2, 80, "加盟合同签约", 4),
SHOP_STAGE_8_5(ShopStageEnum.SHOP_STAGE_2, 85, "发票回传", 5),
@@ -110,6 +111,8 @@ public enum ShopSubStageEnum {
return ShopSubStageStatusEnum.SHOP_SUB_STAGE_STATUS_10;
case SHOP_STAGE_5:
return ShopSubStageStatusEnum.SHOP_SUB_STAGE_STATUS_50;
// case SHOP_STAGE_6:
// return ShopSubStageStatusEnum.SHOP_SUB_STAGE_STATUS_60;
case SHOP_STAGE_7:
return ShopSubStageStatusEnum.SHOP_SUB_STAGE_STATUS_70;
case SHOP_STAGE_2:

View File

@@ -36,6 +36,10 @@ public enum ShopSubStageStatusEnum {
SHOP_SUB_STAGE_STATUS_50(ShopSubStageEnum.SHOP_STAGE_5, 500, "登记中", Boolean.FALSE),
SHOP_SUB_STAGE_STATUS_51(ShopSubStageEnum.SHOP_STAGE_5, 510, "已完成", Boolean.TRUE),
//开通门店平安钱包
SHOP_SUB_STAGE_STATUS_60(ShopSubStageEnum.SHOP_STAGE_6, 2600, "未开通", Boolean.FALSE),
SHOP_SUB_STAGE_STATUS_61(ShopSubStageEnum.SHOP_STAGE_6, 2610, "已完成", Boolean.TRUE),
//缴纳加盟费/保证金
SHOP_SUB_STAGE_STATUS_70(ShopSubStageEnum.SHOP_STAGE_7, 700, "待内勤发布账单", Boolean.FALSE),
SHOP_SUB_STAGE_STATUS_71(ShopSubStageEnum.SHOP_STAGE_7, 710, "待加盟商缴费", Boolean.FALSE),

View File

@@ -0,0 +1,26 @@
package com.cool.store.enums.wallet;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* <p>
* 银行卡业务类型 枚举类
* </p>
*
* @author wangff
* @since 2025/11/14
*/
@Getter
@AllArgsConstructor
public enum BankAccountTypeEnum {
PUBLIC(1, "对公"),
PRIVATE(2, "对私"),
;
private final Integer type;
private final String desc;
}

View File

@@ -0,0 +1,28 @@
package com.cool.store.enums.wallet;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* <p>
* 银行开户类型 枚举类
* </p>
*
* @author wangff
* @since 2025/11/14
*/
@Getter
@AllArgsConstructor
public enum BankBusinessTypeEnum {
ENTERPRISE(1, "企业"),
INDIVIDUAL(2, "个体工商户"),
PERSONAL(3, "个人(小微商户)"),
;
private final Integer type;
private final String desc;
}

View File

@@ -0,0 +1,27 @@
package com.cool.store.enums.wallet;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* <p>
* 平安钱包账户开通状态
* </p>
*
* @author wangff
* @since 2025/11/14
*/
@Getter
@AllArgsConstructor
public enum PingAnAccountStatusEnum {
UNCOMMITTED(1, "待提交"),
UNAUTHORIZED(2, "待鉴权"),
AUTHENTICATING(3, "鉴权中"),
OPEN(4, "开通"),
;
private final Integer status;
private final String desc;
}

View File

@@ -0,0 +1,47 @@
package com.cool.store.enums.wallet;
import com.cool.store.enums.JoinModeEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* <p>
* 营帐通门店模式
* </p>
*
* @author wangff
* @since 2025/11/19
*/
@Getter
@AllArgsConstructor
public enum YztStoreModel {
// 1.社会加盟 2.强管 3.强加盟 (门店模式)
SOCIAL_JOIN(1, "社会加盟", Arrays.asList(JoinModeEnum.FRANCHISE_DEPARTMENT, JoinModeEnum.FRANCHISE_COMPANIES)),
STRONG_MANAGEMENT(2, "强管", Collections.singletonList(JoinModeEnum.FLAGSHIP_STORE)),
STRONG_JOIN(3, "强加盟", Collections.singletonList(JoinModeEnum.AFFILIATES)),
;
private final Integer model;
private final String desc;
/**
* 对应crm加盟模式枚举类
*/
private final List<JoinModeEnum> joinModelList;
public static Integer getYztStoreModel(Integer joinModel) {
JoinModeEnum joinModelEnum = JoinModeEnum.getModelByCode(joinModel);
for (YztStoreModel e : YztStoreModel.values()) {
if (e.joinModelList.contains(joinModelEnum)) {
return e.model;
}
}
return null;
}
}

View File

@@ -0,0 +1,25 @@
package com.cool.store.enums.wechat;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* <p>
* 钱包类型
* </p>
*
* @author wangff
* @since 2025/11/20
*/
@Getter
@AllArgsConstructor
public enum WalletTypeEnum {
PING_AN(1, "平安银行"),
ONLINE_BANK(2, "网商银行"),
;
private final Integer type;
private final String desc;
}

View File

@@ -0,0 +1,82 @@
package com.cool.store.utils;
/**
* @Author suzhuhong
* @Date 2025/11/13 12:15
* @Version 1.0
*/
public class KeyFormatUtil {
/**
* 将单行密钥转换为标准PEM多行格式
*/
public static String convertToPEMFormat(String singleLineKey, String keyType) {
if (singleLineKey == null || singleLineKey.trim().isEmpty()) {
throw new IllegalArgumentException("密钥不能为空");
}
// 清理密钥,移除可能存在的头尾标记和空白
String cleanedKey = cleanKey(singleLineKey);
// 定义头尾标记
String header = getKeyHeader(keyType);
String footer = getKeyFooter(keyType);
// 构建多行格式
StringBuilder pemKey = new StringBuilder();
pemKey.append(header).append("\n");
// 每64个字符换行
for (int i = 0; i < cleanedKey.length(); i += 64) {
int end = Math.min(i + 64, cleanedKey.length());
pemKey.append(cleanedKey.substring(i, end)).append("\n");
}
pemKey.append(footer);
return pemKey.toString();
}
/**
* 清理密钥字符串
*/
public static String cleanKey(String key) {
return key.replaceAll("-----BEGIN.*-----", "")
.replaceAll("-----END.*-----", "")
.replaceAll("\\s", "") // 移除所有空白字符
.trim();
}
private static String getKeyHeader(String keyType) {
switch (keyType.toUpperCase()) {
case "PRIVATE":
case "PRIVATE_KEY":
return "-----BEGIN PRIVATE KEY-----";
case "PUBLIC":
case "PUBLIC_KEY":
return "-----BEGIN PUBLIC KEY-----";
case "RSA_PRIVATE":
return "-----BEGIN RSA PRIVATE KEY-----";
case "RSA_PUBLIC":
return "-----BEGIN RSA PUBLIC KEY-----";
default:
throw new IllegalArgumentException("不支持的密钥类型: " + keyType);
}
}
private static String getKeyFooter(String keyType) {
switch (keyType.toUpperCase()) {
case "PRIVATE":
case "PRIVATE_KEY":
return "-----END PRIVATE KEY-----";
case "PUBLIC":
case "PUBLIC_KEY":
return "-----END PUBLIC KEY-----";
case "RSA_PRIVATE":
return "-----END RSA PRIVATE KEY-----";
case "RSA_PUBLIC":
return "-----END RSA PUBLIC KEY-----";
default:
throw new IllegalArgumentException("不支持的密钥类型: " + keyType);
}
}
}

View File

@@ -5,9 +5,10 @@ import lombok.extern.slf4j.Slf4j;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
import java.util.stream.Collectors;
@@ -110,4 +111,5 @@ public class OpenSignatureUtil {
}
return result.toString();
}
}

View File

@@ -0,0 +1,348 @@
package com.cool.store.utils;
import java.net.URLEncoder;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
import sun.security.util.DerInputStream;
import sun.security.util.DerValue;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.*;
import java.util.*;
/**
* RSA签名工具类
* @Author suzhuhong
* @Date 2025/11/12 22:57
* @Version 1.0
*/
@Slf4j
public class RsaSignUtil {
/**
* 加签方法
*/
public static String generateSign(Map<String, Object> params, String privateKeyPEM) {
// 移除可能存在的sign字段
params.remove("sign");
String afterSort = objectToSortedString(params);
log.info("排序后的参数:{}", afterSort);
String formattedPrivateKey = KeyFormatUtil.convertToPEMFormat(privateKeyPEM, "RSA_PRIVATE");
return rsaPrivateKeySign(afterSort, privateKeyPEM, "SHA256withRSA");
}
/**
* 验签方法
*/
public static boolean verifySign(Map<String, Object> params, String publicKeyPEM) {
String sign = String.valueOf(params.get("sign"));
params.remove("sign");
String afterSort = objectToSortedString(params);
log.info("排序后的参数:{}", afterSort);
return rsaPublicKeyVerify(afterSort, sign, publicKeyPEM, "SHA256withRSA");
}
/**
* 查询签名验证 - 主入口方法
*/
public static boolean querySignCrm(String publicKey, Map<String, Object> params) {
return verifySign(params, publicKey);
}
/**
* 将对象转换为排序后的字符串
*/
public static String objectToSortedString(Map<String, Object> obj) {
return convertToSortedString(obj, "");
}
/**
* 递归将参数转换为排序字符串
*/
private static String convertToSortedString(Map<String, Object> params, String prefix) {
// 按键排序
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
List<String> parts = new ArrayList<>();
for (String key : keys) {
Object value = params.get(key);
if (value == null || "".equals(value) ||
(value instanceof Number && ((Number) value).doubleValue() == 0)) {
continue;
}
String currentKey = key;
if (!prefix.isEmpty()) {
currentKey = prefix + "[" + key + "]";
}
if (value instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> nestedMap = (Map<String, Object>) value;
String nestedString = convertToSortedString(nestedMap, currentKey);
if (!nestedString.isEmpty()) {
parts.add(nestedString);
}
} else if (value instanceof List) {
List<?> list = (List<?>) value;
List<String> elemStrs = new ArrayList<>();
for (Object element : list) {
String elementStr;
if (element instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> elementMap = (Map<String, Object>) element;
String inner = convertToSortedString(elementMap, "");
elementStr = "{" + inner + "}";
} else if (element instanceof Double) {
elementStr = String.format("%.0f", (Double) element);
} else if (element instanceof Float) {
elementStr = String.format("%.0f", (Float) element);
} else {
elementStr = String.valueOf(element);
}
elemStrs.add(elementStr);
}
// 对元素字符串排序
Collections.sort(elemStrs);
// URL编码并添加到parts
for (String es : elemStrs) {
String encodedValue = urlEncode(es);
parts.add(currentKey + "[]=" + encodedValue);
}
} else if (value instanceof Double) {
String strValue = String.format("%.0f", (Double) value);
parts.add(currentKey + "=" + urlEncode(strValue));
} else if (value instanceof Float) {
String strValue = String.format("%.0f", (Float) value);
parts.add(currentKey + "=" + urlEncode(strValue));
} else {
String strValue = String.valueOf(value);
parts.add(currentKey + "=" + urlEncode(strValue));
}
}
return String.join("&", parts);
}
/**
* URL编码替换+为%20
*/
private static String urlEncode(String value) {
try {
return URLEncoder.encode(value, StandardCharsets.UTF_8.name())
.replace("+", "%20");
} catch (Exception e) {
return value;
}
}
/**
* RSA私钥加签
*/
public static String rsaPrivateKeySign(String text, String privateKeyPEM, String algorithm) {
try {
// 1. 解析私钥
log.debug("Received private key PEM: {}", privateKeyPEM);
PrivateKey privateKey = parsePrivateKey(privateKeyPEM);
if (privateKey == null) {
throw new RuntimeException("解析私钥失败");
}
// 2. 创建签名实例
Signature signature = Signature.getInstance(algorithm);
signature.initSign(privateKey);
signature.update(text.getBytes(StandardCharsets.UTF_8));
// 3. 生成签名并Base64编码
byte[] digitalSignature = signature.sign();
return Base64.getEncoder().encodeToString(digitalSignature);
} catch (Exception e) {
throw new RuntimeException("加签失败: " + e.getMessage(), e);
}
}
/**
* RSA公钥验签
*/
public static boolean rsaPublicKeyVerify(String text, String signBase64,
String publicKeyPEM, String algorithm) {
try {
// 1. 解码Base64签名
byte[] signature = Base64.getDecoder().decode(signBase64);
// 2. 解析公钥
PublicKey publicKey = parsePublicKey(publicKeyPEM);
if (publicKey == null) {
return false;
}
// 3. 验证签名
Signature sig = Signature.getInstance(algorithm);
sig.initVerify(publicKey);
sig.update(text.getBytes(StandardCharsets.UTF_8));
return sig.verify(signature);
} catch (Exception e) {
return false;
}
}
/**
* 解析PEM格式私钥兼容PKCS#1和PKCS#8格式
*/
private static PrivateKey parsePrivateKey(String privateKeyPEM) {
try {
String pemContent = privateKeyPEM
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("-----BEGIN RSA PRIVATE KEY-----", "")
.replace("-----END RSA PRIVATE KEY-----", "")
.replaceAll("\\s", "");
byte[] keyBytes = Base64.getDecoder().decode(pemContent);
// 尝试PKCS#8格式
try {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
} catch (Exception e) {
// 使用Bouncy Castle处理PKCS#1
RSAPrivateKey rsaPrivKey = RSAPrivateKey.getInstance(keyBytes);
return KeyFactory.getInstance("RSA").generatePrivate(
new RSAPrivateKeySpec(rsaPrivKey.getModulus(), rsaPrivKey.getPrivateExponent()));
}
} catch (Exception e) {
throw new RuntimeException("解析私钥失败: " + e.getMessage(), e);
}
}
/**
* 解析PKCS#1格式私钥
*/
private static PrivateKey parsePKCS1PrivateKey(byte[] keyBytes) {
try {
int length = keyBytes.length;
byte[] pkcs8Header = new byte[] {
0x30, (byte) 0x82,
(byte) ((length + 26) >> 8), (byte) (length + 26), // 总长度
0x02, 0x01, 0x00,
0x30, 0x0D, 0x06, 0x09, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00,
0x04, (byte) 0x82,
(byte) (length >> 8), (byte) length // BIT STRING 长度
};
byte[] pkcs8Key = new byte[pkcs8Header.length + keyBytes.length];
System.arraycopy(pkcs8Header, 0, pkcs8Key, 0, pkcs8Header.length);
System.arraycopy(keyBytes, 0, pkcs8Key, pkcs8Header.length, keyBytes.length);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Key);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
} catch (Exception e) {
throw new RuntimeException("解析PKCS#1私钥失败: " + e.getMessage(), e);
}
}
/**
* 解析PEM格式公钥兼容PKCS#1和PKCS#8格式
*/
private static PublicKey parsePublicKey(String publicKeyPEM) {
try {
// 移除PEM头尾标记和空白字符
String pemContent = publicKeyPEM
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replace("-----BEGIN RSA PUBLIC KEY-----", "")
.replace("-----END RSA PUBLIC KEY-----", "")
.replaceAll("\\s", "");
byte[] keyBytes = Base64.getDecoder().decode(pemContent);
// 先尝试PKCS#8格式
try {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(keySpec);
} catch (Exception e) {
// 如果PKCS#8失败尝试PKCS#1格式
return parsePKCS1PublicKey(keyBytes);
}
} catch (Exception e) {
return null;
}
}
/**
* 解析PKCS#1格式公钥
*/
private static PublicKey parsePKCS1PublicKey(byte[] keyBytes) {
try {
// PKCS#1 RSA公钥转换为PKCS#8格式
byte[] pkcs8Prefix = new byte[] {
0x30, (byte) 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, (byte) 0x86,
0x48, (byte) 0x86, (byte) 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03,
(byte) 0x82, 0x01, 0x0f, 0x00
};
byte[] pkcs8Key = new byte[pkcs8Prefix.length + keyBytes.length];
System.arraycopy(pkcs8Prefix, 0, pkcs8Key, 0, pkcs8Prefix.length);
System.arraycopy(keyBytes, 0, pkcs8Key, pkcs8Prefix.length, keyBytes.length);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(pkcs8Key);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(keySpec);
} catch (Exception e) {
return null;
}
}
/**
* 针对 /zxjp/open/v1/wallet/** 接口的专用验签方法
* @param params 包含签名参数的Map
* @param privateKeyPEM 用于生成签名的私钥
* @return 验签是否通过
*/
public static boolean verifyWalletSign(Map<String, Object> params, String privateKeyPEM) {
try {
// 1. 获取请求中的签名
String requestSign = String.valueOf(params.get("sign"));
if (requestSign == null || requestSign.isEmpty()) {
log.warn("请求中缺少签名参数");
return false;
}
// 2. 移除签名参数,生成待签名字符串
Map<String, Object> paramsToSign = new HashMap<>(params);
paramsToSign.remove("sign");
// 3. 使用相同的私钥生成签名
String generatedSign = generateSign(paramsToSign, privateKeyPEM);
// 4. 对比签名
boolean isVerified = Objects.equals(requestSign, generatedSign);
if (!isVerified) {
log.warn("签名验证失败,请求签名: {}, 生成签名: {}", requestSign, generatedSign);
}
return isVerified;
} catch (Exception e) {
log.error("验签过程中发生异常", e);
return false;
}
}
}