数币迁移;简易签名认证
This commit is contained in:
parent
e7759a5610
commit
a9a83afdd6
@ -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;
|
||||
|
||||
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
@ -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 {
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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:
|
||||
# 公钥文件
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
/**
|
||||
@ -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";
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
|
||||
/**
|
||||
@ -1,4 +1,4 @@
|
||||
package com.cpop.api.ecpp.core;
|
||||
package com.cpop.pay.framewok.core.entity;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@ -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 "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user