数币迁移;简易签名认证

This commit is contained in:
DB 2024-01-29 18:29:33 +08:00
parent e7759a5610
commit a9a83afdd6
22 changed files with 573 additions and 177 deletions

View File

@ -1,21 +0,0 @@
package com.cpop.api.ecpp.config;
import lombok.Getter;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* Description 数字人民币API配置类
* @authors DB
* @date 2023-11-24 17:27:07
* @version 1.0.0
*/
@Getter
@Configuration
@EnableConfigurationProperties(EcppProperties.class)
public class EcppApiConfig {
EcppProperties properties;
}

View File

@ -1,14 +0,0 @@
package com.cpop.api.ecpp.core;
/**
* @author DB
* @version 1.0.0
* @since 2023-12-12 11:39
*/
public interface EcppApiConstant {
/**
* 安全会话密钥接口
*/
String CONSULT_SESSION_KEY = "/prepay/security/consultSessionKey";
}

View File

@ -1,10 +0,0 @@
package com.cpop.api.ecpp.core;
/**
* 数币加密数据
* @author DB
* @version 1.0.0
* @since 2023-12-12 9:48
*/
public class EcppVerifyJson {
}

View File

@ -1,98 +0,0 @@
package com.cpop.api.ecpp.handler;
import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSON;
import com.cpop.api.ecpp.config.EcppApiConfig;
import com.cpop.api.ecpp.core.EcppBaseRequest;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
/**
* 数币请求工具类
* @author DB
* @version 1.0.0
* @since 2023-12-12 9:27
*/
@Component
@Slf4j
public class EcppHandler {
@Autowired
private EcppApiConfig ecppApiConfig;
/**
* 发送Post请求
* @author DB
* @since 2023/12/12
* @param url 请求路径
* @param baseRequest Ecpp核心请求
* @return Response
*/
private Response sendPost(String url, EcppBaseRequest baseRequest) throws IOException {
OkHttpClient client = new OkHttpClient().newBuilder().build();
MediaType mediaType = MediaType.Companion.parse("application/json;charset=utf-8");
RequestBody body = RequestBody.Companion.create(JSONObject.toJSONString(baseRequest), mediaType);
Request request = new Request
.Builder()
.url(url)
.post(body)
.addHeader("Content-Type", "application/json")
//请求头校验
.addHeader("ecpp-heade", getEcppHeader(baseRequest))
.build();
return client.newCall(request).execute();
}
/**
* 获取签名
* @author DB
* @since 2023/12/12
* @return String
*/
private <T extends EcppBaseRequest> String getEcppHeader(T request) {
HashMap<String, String> ecppHeader = new HashMap<>(5);
ecppHeader.put("appId", ecppApiConfig.getProperties().getAppId());
ecppHeader.put("signatureIdType", ecppApiConfig.getProperties().getSignatureIdType());
ecppHeader.put("signatureId", ecppApiConfig.getProperties().getSignatureId());
ecppHeader.put("timestamp", String.valueOf(System.currentTimeMillis()));
//TODO: 设置签名
ecppHeader.put("signature", null);
return JSON.toJSONString(ecppHeader);
}
/**
* sha256私钥加密
* @author DB
* @since 2023/12/12
* @param strSrc 加密内容
* @param privateKey 私钥
* @return String
*/
public String genSha256HexSign(String strSrc, String privateKey) {
Sign sign = new Sign(SignAlgorithm.SHA256withRSA, privateKey, null);
return HexUtil.encodeHexStr(sign.sign(strSrc.getBytes(StandardCharsets.UTF_8)));
}
/**
* sha256验证签名
* @param strSrc 加密内容
* @param signature 签名
* @param publicKey 公钥
*/
public Boolean verifySha256HexSign(String strSrc, String signature, String publicKey) {
Sign sign = new Sign(SignAlgorithm.SHA256withRSA, null, publicKey);
return sign.verify(strSrc.getBytes(StandardCharsets.UTF_8), HexUtil.decodeHex(signature));
}
}

View File

@ -15,12 +15,20 @@ import org.springframework.util.DigestUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.crypto.Cipher;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
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;
@ -114,25 +122,20 @@ public class SimpleSignatureCheckAspect {
}
private Key stringToKey(String stringKey) {
byte[] bytes = stringKey.getBytes(StandardCharsets.UTF_8);
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
Key key = null;
ObjectInputStream ois = null;
private PrivateKey stringToKey(String stringKey) {
//Java原生base64解码
byte[] priKey = Base64.getDecoder().decode(stringKey);
//创建PKCS8编码密钥规范
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(priKey);
//返回转换指定算法的KeyFactory对象
KeyFactory keyFactory = null;
try {
ois = new ObjectInputStream(bais);
key = (Key) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
throw new UtilException(e.getMessage());
} finally {
try {
assert ois != null;
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
keyFactory = KeyFactory.getInstance("RSA");
//根据PKCS8编码密钥规范产生私钥对象
return keyFactory.generatePrivate(pkcs8KeySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new RuntimeException(e);
}
return key;
}
}

View File

@ -90,4 +90,18 @@ public class MiniEasyLearnController {
easyLearnOrderService.stopUserSignPlans(bo);
return R.ok();
}
/**
* 发送数币手机验证码
* @author DB
* @since 2024/1/29
* @param phone 手机
* @return R<Void>
*/
@ApiOperation("发送数币手机验证码")
@GetMapping("/sendEcppMsmCode/{phone}")
public R<Void> sendEcppMsmCode(@PathVariable String phone) {
easyLearnOrderService.sendEcppMsmCode(phone);
return R.ok();
}
}

View File

@ -117,4 +117,12 @@ public interface EasyLearnOrderService extends IService<EasyLearnOrder> {
* @param bo 请求参数
*/
void stopUserSignPlans(LearnNowPayLaterStopUserSignPlansBo bo);
/**
* 发送数币手机验证码
* @author DB
* @since 2024/1/29
* @param phone 手机号
*/
void sendEcppMsmCode(String phone);
}

View File

@ -25,6 +25,8 @@ import com.cpop.jambox.framework.constant.JamboxCloudUrl;
import com.cpop.jambox.framework.constant.JamboxRedisConstant;
import com.cpop.jambox.framework.enums.EasyLearnPayPayEnum;
import com.cpop.pay.framewok.config.wxPay.WxPayConfiguration;
import com.cpop.pay.framewok.core.dto.ecpp.EcppSmsCodeDto;
import com.cpop.pay.framewok.handler.ecpp.EcppHandler;
import com.cpop.pay.framewok.handler.wxPay.WxPayHandler;
import com.cpop.pay.framewok.task.WxPayAsyncTask;
import com.cpop.system.business.entity.Store;
@ -53,6 +55,7 @@ import com.mybatisflex.core.row.RowUtil;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -649,7 +652,8 @@ public class EasyLearnOrderServiceImpl extends ServiceImpl<EasyLearnOrderMapper,
url = UNION_PAY_DEV_JAMBOX_NOTIFY_URL;
}
//通知晖哥
HttpUtils.sendOkHttpPost(url, jsonObject.toJSONString());
Response response = HttpUtils.sendOkHttpPost(url, jsonObject.toJSONString());
response.close();
//更新订单
this.updateChain().set(EASY_LEARN_ORDER.OUT_ORDER_NO, notifyResult.getTransactionId())
.set(EASY_LEARN_ORDER.ORDER_STATUS, 1)
@ -985,4 +989,17 @@ public class EasyLearnOrderServiceImpl extends ServiceImpl<EasyLearnOrderMapper,
throw new ServiceException(e.getMessage());
}
}
/**
* 发送数币手机验证码
* @author DB
* @since 2024/1/29
* @param phone 验证码
*/
@Override
public void sendEcppMsmCode(String phone) {
EcppSmsCodeDto dto = new EcppSmsCodeDto();
dto.setSmsTpCd("YGJ_SIGN");
SpringUtils.getBean(EcppHandler.class).sendEcppMsmCode(dto);
}
}

View File

@ -4,7 +4,7 @@ cpop:
profile: E:/Cpop/uploadPath
jwt:
#白名单
whiteList: /websocket/*,/login,/doc.html,/webjars/**,/favicon.ico,/v2/api-docs/**,/swagger-resources,/sysCommon/miniSyncBrandAndStore,/easyLearn/callback/*/*,/easyLearn/*,/mini/cardTemplate/*,/website/**,/backstage/wxCp/*,/callback/wxCp/*/registerCode,/callback/easyLearn/**
whiteList: /websocket/*,/login,/doc.html,/webjars/**,/favicon.ico,/v2/api-docs/**,/swagger-resources,/sysCommon/miniSyncBrandAndStore,/easyLearn/callback/*/*,/easyLearn/*,/mini/cardTemplate/*,/website/**,/backstage/wxCp/*,/callback/wxCp/*/registerCode,/callback/easyLearn/**,/cloudCallback/*
gateway:
rsa-keypair:
# 公钥文件

View File

@ -218,17 +218,21 @@ public class CpopDataSyncTests {
List<Row> rowList;
try {
DataSourceKey.use("jambox");
rowList = DbChain.table("t_signContract_mechanism")
.select("tsm.store_id as storeCloudId", "tsm.creation_time as createTime", "tsm.last_modification_date as updateTime","os.phone")
.from("t_signContract_mechanism").as("tsm")
.leftJoin("t_mechanism_info").as("tmi").on("tmi.store_id = tsm.store_id")
.leftJoin("OAM_staff").as("os").on("os.staff_id = tmi.clue_id")
.where("tsm.deleted = 1")
rowList = DbChain.table("OAM_data_sign")
.select("sign_status","store_id","brand_id","sign_status","sign_time","end_time","sign_name","cre_time")
.from("OAM_data_sign")
.list();
} finally {
DataSourceKey.clear();
}
Map<String, String> phoneToId = DbChain.table(STAFF.getTableName())
//无品牌校区
//过滤出已签约的数据
List<Row> waitSignStore = rowList.stream().filter(item -> StringUtils.equals(item.getString("signStatus"), "待签约")).collect(Collectors.toList());
System.out.println(JSONArray.toJSONString(rowList));
/*Map<String, String> phoneToId = DbChain.table(STAFF.getTableName())
.select(STAFF.ID)
.select(SYS_USER.PHONE_NUMBER.as("phone"))
.from(STAFF)
@ -253,7 +257,7 @@ public class CpopDataSyncTests {
.set(STORE_SIGN.SIGN_STAFF_ID,storeSign.getSignStaffId())
.where(STORE_SIGN.STORE_ID.eq(storeSign.getStoreId()))
.update();
});
});*/
}
@Test
@ -306,4 +310,5 @@ public class CpopDataSyncTests {
List<String> collect = storeList.stream().map(Store::getId).collect(Collectors.toList());
storeService.removeByIds(collect);
}
}

View File

@ -5,7 +5,6 @@ import com.cpop.core.base.R;
import com.cpop.oam.business.bo.CloudUnionCallbackBo;
import com.cpop.oam.framework.enums.CloudCallbackEnums;
import com.cpop.oam.framework.strategy.cloud.CloudCallbackStrategy;
import com.cpop.system.business.vo.SysFileVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
@ -40,4 +39,5 @@ public class CallBackCloudCallbackController {
cloudCallbackStrategy.callback(callback);
return R.ok();
}
}

View File

@ -1,4 +1,4 @@
package com.cpop.api.ecpp.anno;
package com.cpop.pay.framewok.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;

View File

@ -0,0 +1,176 @@
package com.cpop.pay.framewok.config.ecpp;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.cpop.pay.framewok.core.dto.ecpp.EcppBaseDto;
import com.cpop.pay.framewok.core.dto.ecpp.EcppVerifyDto;
import com.google.common.base.Joiner;
import lombok.Getter;
import lombok.Setter;
import okhttp3.*;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.crypto.Cipher;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* Description 数字人民币API配置类
* @authors DB
* @date 2023-11-24 17:27:07
* @version 1.0.0
*/
@Getter
@Configuration
@EnableConfigurationProperties(EcppProperties.class)
public class EcppConfiguration {
/**
* 数币参数
*/
private EcppProperties ecppProperties;
@Setter
private volatile String jSessionId;
@Setter
private volatile String sessionKey;
@Setter
private volatile String sessionIv;
/**
* 发送数币请求
* @author DB
* @since 2024/1/29
* @param url 路径
* @param dto 请求数据
* @return Response
*/
public <T extends EcppVerifyDto> Response sendEcppPost(String url, T dto) throws IOException {
OkHttpClient client = new OkHttpClient().newBuilder().build();
MediaType mediaType = MediaType.Companion.parse("application/json;charset=utf-8");
EcppBaseDto ecppBaseDto = new EcppBaseDto(dto);
RequestBody body = RequestBody.Companion.create(JSONObject.toJSONString(ecppBaseDto), mediaType);
String timestamp = String.valueOf(System.currentTimeMillis());
Request request = new Request
.Builder()
.url(url)
.post(body)
.addHeader("Content-Type", "application/json")
.addHeader("VERIFY",getVerify(timestamp))
.addHeader("ecpp-header",getEcppHeader(timestamp))
.build();
return client.newCall(request).execute();
}
public <V extends EcppVerifyDto> V parseEcppResponse(Response response, Class<V> clazz) throws IOException {
//读取响应体
ResponseBody body = response.body();
JSONObject result = JSONObject.parseObject(body.string());
if (result == null) {
throw new IOException("Http响应为空");
}
return JSON.parseObject(result.getString("data"), clazz);
}
/**
* 获取认证
* @author DB
* @since 2024/1/29
* @param timestamp 时间戳
* @return String
*/
private String getVerify(String timestamp) {
List<String> args = new ArrayList<>();
args.add(ecppProperties.getAppType());
args.add(ecppProperties.getAppId());
args.add(sha256withRsaSign(timestamp));
args.add(timestamp);
return Joiner.on(':').join(args);
}
/**
* 获取请求头信息
* @param timestamp 时间戳
*/
private String getEcppHeader(String timestamp) {
HashMap<String, String> ecppHeader = new HashMap<>(5);
ecppHeader.put("appId", ecppProperties.getAppId());
ecppHeader.put("signatureIdType", ecppProperties.getAppType());
ecppHeader.put("signatureId", "100");
ecppHeader.put("timestamp", timestamp);
ecppHeader.put("signature", sha256withRsaSign(timestamp));
return JSON.toJSONString(ecppHeader);
}
private String sha256withRsaSign(String dataSting) {
Sign sign = new Sign(SignAlgorithm.SHA256withRSA, ecppProperties.getPrivateKey(), null);
return Base64.encode(sign.sign(dataSting.getBytes(StandardCharsets.UTF_8)));
}
public String genSha256HexSign(String privateKey, String strSrc) {
Sign sign = new Sign(SignAlgorithm.SHA256withRSA, privateKey, null);
return HexUtil.encodeHexStr(sign.sign(strSrc.getBytes(StandardCharsets.UTF_8)));
}
public final long EXPIRE_TIME = 3300000;
/**
* 超时时间
*/
@Setter
private volatile long nextExpireTime = 0;
public Boolean checkKeyTime() {
return this.nextExpireTime > System.currentTimeMillis();
}
/**
* 公钥加密
*
* @param publicKeyText 公钥
* @param text 内容
*/
public String encrypt(String publicKeyText, String text) throws Exception {
X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decode(publicKeyText));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] result = cipher.doFinal(text.getBytes());
return Base64.encode(result);
}
/**
* 私钥解密
* @author DB
* @since 2024/1/29
* @param privateKeyText 私钥内容
* @param text 内容
* @return String
*/
public String decrypt(String privateKeyText, String text) throws Exception{
PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decode(privateKeyText));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] result = cipher.doFinal(Base64.decode(text));
return new String(result);
}
}

View File

@ -1,5 +1,6 @@
package com.cpop.api.ecpp.config;
package com.cpop.pay.framewok.config.ecpp;
import lombok.Data;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**

View File

@ -0,0 +1,19 @@
package com.cpop.pay.framewok.core.constant;
/**
* @author DB
* @version 1.0.0
* @since 2024-01-29 16:02
*/
public interface EcppApiConstant {
/**
* 安全密钥
*/
String SECURITY_KEY = "/prepay/security/consultSessionKey";
/**
* 发送短信
*/
String SEND_MSG = "/prepay/sendSmsCode";
}

View File

@ -0,0 +1,128 @@
package com.cpop.pay.framewok.core.dto.ecpp;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.DESede;
import com.cpop.core.base.exception.ServiceException;
import com.cpop.core.utils.SpringUtils;
import com.cpop.pay.framewok.anno.EcppUnSignField;
import com.cpop.pay.framewok.config.ecpp.EcppConfiguration;
import com.cpop.pay.framewok.config.ecpp.EcppProperties;
import com.cpop.pay.framewok.handler.ecpp.EcppHandler;
import com.google.common.base.Joiner;
import com.google.gson.Gson;
import jodd.util.StringUtil;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author DB
* @version 1.0.0
* @since 2024-01-29 15:42
*/
@Data
@Slf4j
@NoArgsConstructor
public class EcppBaseDto {
{
jSessionType = "00";
}
public <T extends EcppVerifyDto > EcppBaseDto(T data) {
setContent(data);
}
/**
* 交易类型
*/
private String jSessionType;
/**
* 交易id
*/
private String jSessionId;
/**
*
*/
private String type = "1";
/**
* 内容
*/
private String content;
private <T extends EcppVerifyDto> void setContent(T data) {
String unSignStr = getUnSignStr(data);
EcppConfiguration ecppConfiguration = SpringUtils.getBean(EcppConfiguration.class);
EcppProperties ecppProperties = ecppConfiguration.getEcppProperties();
if (StringUtil.isNotEmpty(unSignStr)) {
data.setSignature(ecppConfiguration.genSha256HexSign(ecppProperties.getPrivateKey(), unSignStr));
}
try {
//检查是否超时
if(!ecppConfiguration.checkKeyTime()) {
SpringUtils.getBean(EcppHandler.class).getSecurityKey();
}
String encryptContent = encrypt(new Gson().toJson(data), ecppConfiguration.getSessionKey(), ecppConfiguration.getSessionIv());
this.jSessionId = ecppConfiguration.getJSessionId();
this.content = encryptContent;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private String encrypt(String content, String key, String iv) throws Exception {
if (content == null) {
return "";
}
if (ObjectUtil.isEmpty(key)) {
throw new ServiceException("网络异常");
}
DESede deSede = new DESede(Mode.CBC, Padding.PKCS5Padding, key.getBytes(), StrUtil.sub(iv, 0, 8).getBytes());
return deSede.encryptBase64(content);
}
private <T> String getUnSignStr(T data) {
List<String> templist = new ArrayList<>();
Class<?> clazz = data.getClass();
for (Field field : clazz.getDeclaredFields()) {
try {
if(field.getAnnotation(EcppUnSignField.class) != null) {
continue;
}
String methodName = "get" + firstCharUppercase(field.getName());
Method getMethod = clazz.getDeclaredMethod(methodName);
Object prop = getMethod.invoke(data);
if (prop == null) {
templist.add(field.getName() + "=");
continue;
}
if (prop instanceof Collection) {
for (Object _prop : (Collection) prop) {
templist.add(getUnSignStr(_prop));
}
continue;
}
templist.add(field.getName() + "=" + prop);
} catch (Exception ignored) {
}
}
return Joiner.on('&').join(templist);
}
private String firstCharUppercase(String propName) {
char[] chars = propName.toCharArray();
chars[0] -= 32;
return String.valueOf(chars);
}
}

View File

@ -0,0 +1,31 @@
package com.cpop.pay.framewok.core.dto.ecpp;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author DB
* @version 1.0.0
* @since 2024-01-29 16:54
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class EcppSmsCodeDto extends EcppVerifyDto {
/**
* 渠道编号 01建行
*/
private String channelCode = "01";
/**
* 手机号码
*/
@JSONField(name = "mobileNumber")
private String phone;
/**
* 短信类型代码 YGJ_SIGN签约 YGJ_WRITE_OFF核销
*/
private String smsTpCd;
}

View File

@ -0,0 +1,23 @@
package com.cpop.pay.framewok.core.dto.ecpp;
import com.cpop.pay.framewok.anno.EcppUnSignField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author DB
* @version 1.0.0
* @since 2024-01-29 15:28
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EcppVerifyDto {
/**
* 签名
*/
@EcppUnSignField
private String signature;
}

View File

@ -1,6 +1,5 @@
package com.cpop.api.ecpp.core;
package com.cpop.pay.framewok.core.entity;
import com.google.gson.Gson;
import lombok.Data;
/**

View File

@ -1,4 +1,4 @@
package com.cpop.api.ecpp.core;
package com.cpop.pay.framewok.core.entity;
import lombok.Data;

View File

@ -0,0 +1,110 @@
package com.cpop.pay.framewok.handler.ecpp;
import com.alibaba.fastjson.JSONObject;
import com.cpop.common.utils.StringUtils;
import com.cpop.core.base.exception.ServiceException;
import com.cpop.core.base.exception.UtilException;
import com.cpop.core.utils.SpringUtils;
import com.cpop.core.utils.uuid.IdUtils;
import com.cpop.pay.framewok.config.ecpp.EcppConfiguration;
import com.cpop.pay.framewok.core.constant.EcppApiConstant;
import com.cpop.pay.framewok.core.dto.ecpp.EcppBaseDto;
import com.cpop.pay.framewok.core.dto.ecpp.EcppSmsCodeDto;
import com.cpop.pay.framewok.core.dto.ecpp.EcppVerifyDto;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* @author DB
* @version 1.0.0
* @since 2024-01-29 16:00
*/
@Component
@Slf4j
public class EcppHandler {
/**
* 获取安全密钥
* @author DB
* @since 2024/1/29
*/
public void getSecurityKey() {
EcppConfiguration ecppConfiguration = SpringUtils.getBean(EcppConfiguration.class);
if (ecppConfiguration.checkKeyTime()) {
return;
}
log.info("======================= 获取速达会话密钥 ========================");
Response response = null;
String s1 = IdUtils.fastSimpleUUID();
try {
EcppVerifyDto ecppVerifyDto = new EcppVerifyDto(ecppConfiguration.encrypt(ecppConfiguration.getEcppProperties().getPublicKey(), s1));
response = ecppConfiguration.sendEcppPost(ecppConfiguration.getEcppProperties().getBaseUrl() + EcppApiConstant.SECURITY_KEY, ecppVerifyDto);
JSONObject result = JSONObject.parseObject(response.body().string());
String code = result.getString("code");
if (StringUtils.equals(code, "402.2")) {
log.error("获取速达会话密钥header出错" + result);
getSecurityKey();
return;
}
if (StringUtils.equals(code, "200") && result.getJSONObject("data") != null) {
String resolve = ecppConfiguration.decrypt(ecppConfiguration.getEcppProperties().getPrivateKey(), result.getJSONObject("data").getString("content"));
String[] argv = resolve.split(":");
String s2 = argv[0];
ecppConfiguration.setSessionIv(argv[1]);
ecppConfiguration.setJSessionId(argv[2]);
ecppConfiguration.setSessionKey(hexXor(s1, s2));
log.info("速达会话密钥:{} , 速达密钥iv向量{}速达JSessionId: {}", ecppConfiguration.getSessionKey(), ecppConfiguration.getSessionIv(), ecppConfiguration.getJSessionId());
log.info("======================= 获取速达会话密钥成功 ========================");
ecppConfiguration.setNextExpireTime(System.currentTimeMillis() + ecppConfiguration.getEXPIRE_TIME());
}
} catch (Exception e) {
throw new UtilException(e);
} finally {
assert response != null;
response.close();
}
}
/**
* 获取短信验证码
* @author DB
* @since 2024/1/29
* @param dto 请求
*/
public void sendEcppMsmCode(EcppSmsCodeDto dto) {
EcppConfiguration ecppConfiguration = SpringUtils.getBean(EcppConfiguration.class);
Response response = null;
try {
response = ecppConfiguration.sendEcppPost(ecppConfiguration.getEcppProperties().getBaseUrl() + EcppApiConstant.SEND_MSG, dto);
} catch (IOException e) {
throw new ServiceException(e.getMessage());
} finally {
assert response != null;
response.close();
}
}
private String hexXor(String str1, String str2) {
try {
String[] arr1 = str1.split("");
String[] arr2 = str2.split("");
int length = Math.max(str1.length(), str2.length());
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
Integer a = arr1.length > i ? Integer.parseInt(arr1[i], 16) : 0;
Integer b = arr2.length > i ? Integer.parseInt(arr2[i], 16) : 0;
sb.append(Long.toHexString(a ^ b).toUpperCase(Locale.ROOT));
}
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
}

View File

@ -17,4 +17,9 @@ wx:
service-app-id: wx1eb0e5fb7dac3c05
logging:
level:
com.github.binarywang.wxpay: debug
com.github.binarywang.wxpay: debug
ecpp:
pay:
app-id: 1000
app-type: 1
public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA32A6vFBgXZgVgzaboSHjMMfJxrxXITDkdgzMR/WpTdxJvqZyyZtPOtdL4wg6St4lki4W/SMH6zTWJX07HYeWl8KiSOAXr2kcqHWPhO+rmtq8rMaR831f9ot7GK/6hkKUFRQZ8Uev8pGnOO4w5n+AaLOkohd+K0WXoaNokD+vxvZO7+yTYY/WAT30ItaMY0Ld5o3rRRo63lHuNlBjQ6mP8U6pPXek0J+oONraxJwZPsCEGTw3tfsXi0uMViRRXfp33H2xBZ11wrRrY/l5Pp8YPpJCxrCyzGcwlNqsT4VuEW4NglMyyU0g0J9mR+eIZS2kmuhpwjshyGC2Soc7DYdNUwIDAQAB