From a9a83afdd69d74f8907a772fe30cac6d8968ad32 Mon Sep 17 00:00:00 2001 From: DB <2502523450@qq.com> Date: Mon, 29 Jan 2024 18:29:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B0=E5=B8=81=E8=BF=81=E7=A7=BB;=E7=AE=80?= =?UTF-8?q?=E6=98=93=E7=AD=BE=E5=90=8D=E8=AE=A4=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cpop/api/ecpp/config/EcppApiConfig.java | 21 --- .../cpop/api/ecpp/core/EcppApiConstant.java | 14 -- .../cpop/api/ecpp/core/EcppVerifyJson.java | 10 - .../cpop/api/ecpp/handler/EcppHandler.java | 98 ---------- .../aspect/SimpleSignatureCheckAspect.java | 37 ++-- .../mini/MiniEasyLearnController.java | 14 ++ .../service/EasyLearnOrderService.java | 8 + .../impl/EasyLearnOrderServiceImpl.java | 19 +- .../src/main/resources/application-dev.yml | 2 +- .../com/cpop/oam/web/CpopDataSyncTests.java | 21 ++- .../CallBackCloudCallbackController.java | 2 +- .../pay/framewok}/anno/EcppUnSignField.java | 2 +- .../config/ecpp/EcppConfiguration.java | 176 ++++++++++++++++++ .../framewok/config/ecpp}/EcppProperties.java | 3 +- .../core/constant/EcppApiConstant.java | 19 ++ .../framewok/core/dto/ecpp/EcppBaseDto.java | 128 +++++++++++++ .../core/dto/ecpp/EcppSmsCodeDto.java | 31 +++ .../framewok/core/dto/ecpp/EcppVerifyDto.java | 23 +++ .../core/entity}/EcppBaseRequest.java | 3 +- .../core/entity}/EcppBaseResponse.java | 2 +- .../framewok/handler/ecpp/EcppHandler.java | 110 +++++++++++ .../src/main/resources/application-pay.yml | 7 +- 22 files changed, 573 insertions(+), 177 deletions(-) delete mode 100644 Cpop-Api/src/main/java/com/cpop/api/ecpp/config/EcppApiConfig.java delete mode 100644 Cpop-Api/src/main/java/com/cpop/api/ecpp/core/EcppApiConstant.java delete mode 100644 Cpop-Api/src/main/java/com/cpop/api/ecpp/core/EcppVerifyJson.java delete mode 100644 Cpop-Api/src/main/java/com/cpop/api/ecpp/handler/EcppHandler.java rename {Cpop-Api/src/main/java/com/cpop/api/ecpp => Cpop-Pay/src/main/java/com/cpop/pay/framewok}/anno/EcppUnSignField.java (89%) create mode 100644 Cpop-Pay/src/main/java/com/cpop/pay/framewok/config/ecpp/EcppConfiguration.java rename {Cpop-Api/src/main/java/com/cpop/api/ecpp/config => Cpop-Pay/src/main/java/com/cpop/pay/framewok/config/ecpp}/EcppProperties.java (91%) create mode 100644 Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/constant/EcppApiConstant.java create mode 100644 Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/dto/ecpp/EcppBaseDto.java create mode 100644 Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/dto/ecpp/EcppSmsCodeDto.java create mode 100644 Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/dto/ecpp/EcppVerifyDto.java rename {Cpop-Api/src/main/java/com/cpop/api/ecpp/core => Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/entity}/EcppBaseRequest.java (89%) rename {Cpop-Api/src/main/java/com/cpop/api/ecpp/core => Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/entity}/EcppBaseResponse.java (93%) create mode 100644 Cpop-Pay/src/main/java/com/cpop/pay/framewok/handler/ecpp/EcppHandler.java diff --git a/Cpop-Api/src/main/java/com/cpop/api/ecpp/config/EcppApiConfig.java b/Cpop-Api/src/main/java/com/cpop/api/ecpp/config/EcppApiConfig.java deleted file mode 100644 index 9ac03ce..0000000 --- a/Cpop-Api/src/main/java/com/cpop/api/ecpp/config/EcppApiConfig.java +++ /dev/null @@ -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; - - -} diff --git a/Cpop-Api/src/main/java/com/cpop/api/ecpp/core/EcppApiConstant.java b/Cpop-Api/src/main/java/com/cpop/api/ecpp/core/EcppApiConstant.java deleted file mode 100644 index eb6548e..0000000 --- a/Cpop-Api/src/main/java/com/cpop/api/ecpp/core/EcppApiConstant.java +++ /dev/null @@ -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"; -} diff --git a/Cpop-Api/src/main/java/com/cpop/api/ecpp/core/EcppVerifyJson.java b/Cpop-Api/src/main/java/com/cpop/api/ecpp/core/EcppVerifyJson.java deleted file mode 100644 index d9270e4..0000000 --- a/Cpop-Api/src/main/java/com/cpop/api/ecpp/core/EcppVerifyJson.java +++ /dev/null @@ -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 { -} diff --git a/Cpop-Api/src/main/java/com/cpop/api/ecpp/handler/EcppHandler.java b/Cpop-Api/src/main/java/com/cpop/api/ecpp/handler/EcppHandler.java deleted file mode 100644 index ba37dfa..0000000 --- a/Cpop-Api/src/main/java/com/cpop/api/ecpp/handler/EcppHandler.java +++ /dev/null @@ -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 String getEcppHeader(T request) { - HashMap 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)); - } -} diff --git a/Cpop-Core/src/main/java/com/cpop/core/aspect/SimpleSignatureCheckAspect.java b/Cpop-Core/src/main/java/com/cpop/core/aspect/SimpleSignatureCheckAspect.java index 18ee66b..09a1107 100644 --- a/Cpop-Core/src/main/java/com/cpop/core/aspect/SimpleSignatureCheckAspect.java +++ b/Cpop-Core/src/main/java/com/cpop/core/aspect/SimpleSignatureCheckAspect.java @@ -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; } } diff --git a/Cpop-Jambox/src/main/java/com/cpop/jambox/business/controller/mini/MiniEasyLearnController.java b/Cpop-Jambox/src/main/java/com/cpop/jambox/business/controller/mini/MiniEasyLearnController.java index 232b224..5784f45 100644 --- a/Cpop-Jambox/src/main/java/com/cpop/jambox/business/controller/mini/MiniEasyLearnController.java +++ b/Cpop-Jambox/src/main/java/com/cpop/jambox/business/controller/mini/MiniEasyLearnController.java @@ -90,4 +90,18 @@ public class MiniEasyLearnController { easyLearnOrderService.stopUserSignPlans(bo); return R.ok(); } + + /** + * 发送数币手机验证码 + * @author DB + * @since 2024/1/29 + * @param phone 手机 + * @return R + */ + @ApiOperation("发送数币手机验证码") + @GetMapping("/sendEcppMsmCode/{phone}") + public R sendEcppMsmCode(@PathVariable String phone) { + easyLearnOrderService.sendEcppMsmCode(phone); + return R.ok(); + } } diff --git a/Cpop-Jambox/src/main/java/com/cpop/jambox/business/service/EasyLearnOrderService.java b/Cpop-Jambox/src/main/java/com/cpop/jambox/business/service/EasyLearnOrderService.java index 16f206c..e1bc19e 100644 --- a/Cpop-Jambox/src/main/java/com/cpop/jambox/business/service/EasyLearnOrderService.java +++ b/Cpop-Jambox/src/main/java/com/cpop/jambox/business/service/EasyLearnOrderService.java @@ -117,4 +117,12 @@ public interface EasyLearnOrderService extends IService { * @param bo 请求参数 */ void stopUserSignPlans(LearnNowPayLaterStopUserSignPlansBo bo); + + /** + * 发送数币手机验证码 + * @author DB + * @since 2024/1/29 + * @param phone 手机号 + */ + void sendEcppMsmCode(String phone); } diff --git a/Cpop-Jambox/src/main/java/com/cpop/jambox/business/service/impl/EasyLearnOrderServiceImpl.java b/Cpop-Jambox/src/main/java/com/cpop/jambox/business/service/impl/EasyLearnOrderServiceImpl.java index cb16d4c..e9411f1 100644 --- a/Cpop-Jambox/src/main/java/com/cpop/jambox/business/service/impl/EasyLearnOrderServiceImpl.java +++ b/Cpop-Jambox/src/main/java/com/cpop/jambox/business/service/impl/EasyLearnOrderServiceImpl.java @@ -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 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 phoneToId = DbChain.table(STAFF.getTableName()) + //无品牌校区 + + + //过滤出已签约的数据 + List waitSignStore = rowList.stream().filter(item -> StringUtils.equals(item.getString("signStatus"), "待签约")).collect(Collectors.toList()); + + System.out.println(JSONArray.toJSONString(rowList)); + /*Map 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 collect = storeList.stream().map(Store::getId).collect(Collectors.toList()); storeService.removeByIds(collect); } + } diff --git a/Cpop-Oam/src/main/java/com/cpop/oam/business/controller/callback/CallBackCloudCallbackController.java b/Cpop-Oam/src/main/java/com/cpop/oam/business/controller/callback/CallBackCloudCallbackController.java index 2930802..abd7a6f 100644 --- a/Cpop-Oam/src/main/java/com/cpop/oam/business/controller/callback/CallBackCloudCallbackController.java +++ b/Cpop-Oam/src/main/java/com/cpop/oam/business/controller/callback/CallBackCloudCallbackController.java @@ -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(); } + } diff --git a/Cpop-Api/src/main/java/com/cpop/api/ecpp/anno/EcppUnSignField.java b/Cpop-Pay/src/main/java/com/cpop/pay/framewok/anno/EcppUnSignField.java similarity index 89% rename from Cpop-Api/src/main/java/com/cpop/api/ecpp/anno/EcppUnSignField.java rename to Cpop-Pay/src/main/java/com/cpop/pay/framewok/anno/EcppUnSignField.java index eb5239e..89dd18a 100644 --- a/Cpop-Api/src/main/java/com/cpop/api/ecpp/anno/EcppUnSignField.java +++ b/Cpop-Pay/src/main/java/com/cpop/pay/framewok/anno/EcppUnSignField.java @@ -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; diff --git a/Cpop-Pay/src/main/java/com/cpop/pay/framewok/config/ecpp/EcppConfiguration.java b/Cpop-Pay/src/main/java/com/cpop/pay/framewok/config/ecpp/EcppConfiguration.java new file mode 100644 index 0000000..c7ca96d --- /dev/null +++ b/Cpop-Pay/src/main/java/com/cpop/pay/framewok/config/ecpp/EcppConfiguration.java @@ -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 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 parseEcppResponse(Response response, Class 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 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 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); + } +} diff --git a/Cpop-Api/src/main/java/com/cpop/api/ecpp/config/EcppProperties.java b/Cpop-Pay/src/main/java/com/cpop/pay/framewok/config/ecpp/EcppProperties.java similarity index 91% rename from Cpop-Api/src/main/java/com/cpop/api/ecpp/config/EcppProperties.java rename to Cpop-Pay/src/main/java/com/cpop/pay/framewok/config/ecpp/EcppProperties.java index dc5d6e5..5e0b701 100644 --- a/Cpop-Api/src/main/java/com/cpop/api/ecpp/config/EcppProperties.java +++ b/Cpop-Pay/src/main/java/com/cpop/pay/framewok/config/ecpp/EcppProperties.java @@ -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; /** diff --git a/Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/constant/EcppApiConstant.java b/Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/constant/EcppApiConstant.java new file mode 100644 index 0000000..ae28f93 --- /dev/null +++ b/Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/constant/EcppApiConstant.java @@ -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"; +} diff --git a/Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/dto/ecpp/EcppBaseDto.java b/Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/dto/ecpp/EcppBaseDto.java new file mode 100644 index 0000000..c0ea5b5 --- /dev/null +++ b/Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/dto/ecpp/EcppBaseDto.java @@ -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 EcppBaseDto(T data) { + setContent(data); + } + + /** + * 交易类型 + */ + private String jSessionType; + + /** + * 交易id + */ + private String jSessionId; + + /** + * + */ + private String type = "1"; + + /** + * 内容 + */ + private String content; + + private 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 String getUnSignStr(T data) { + List 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); + } +} diff --git a/Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/dto/ecpp/EcppSmsCodeDto.java b/Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/dto/ecpp/EcppSmsCodeDto.java new file mode 100644 index 0000000..1840ec0 --- /dev/null +++ b/Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/dto/ecpp/EcppSmsCodeDto.java @@ -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; +} diff --git a/Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/dto/ecpp/EcppVerifyDto.java b/Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/dto/ecpp/EcppVerifyDto.java new file mode 100644 index 0000000..2501615 --- /dev/null +++ b/Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/dto/ecpp/EcppVerifyDto.java @@ -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; +} diff --git a/Cpop-Api/src/main/java/com/cpop/api/ecpp/core/EcppBaseRequest.java b/Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/entity/EcppBaseRequest.java similarity index 89% rename from Cpop-Api/src/main/java/com/cpop/api/ecpp/core/EcppBaseRequest.java rename to Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/entity/EcppBaseRequest.java index a3d9233..80e8dc7 100644 --- a/Cpop-Api/src/main/java/com/cpop/api/ecpp/core/EcppBaseRequest.java +++ b/Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/entity/EcppBaseRequest.java @@ -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; /** diff --git a/Cpop-Api/src/main/java/com/cpop/api/ecpp/core/EcppBaseResponse.java b/Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/entity/EcppBaseResponse.java similarity index 93% rename from Cpop-Api/src/main/java/com/cpop/api/ecpp/core/EcppBaseResponse.java rename to Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/entity/EcppBaseResponse.java index 509c6bf..8ecd756 100644 --- a/Cpop-Api/src/main/java/com/cpop/api/ecpp/core/EcppBaseResponse.java +++ b/Cpop-Pay/src/main/java/com/cpop/pay/framewok/core/entity/EcppBaseResponse.java @@ -1,4 +1,4 @@ -package com.cpop.api.ecpp.core; +package com.cpop.pay.framewok.core.entity; import lombok.Data; diff --git a/Cpop-Pay/src/main/java/com/cpop/pay/framewok/handler/ecpp/EcppHandler.java b/Cpop-Pay/src/main/java/com/cpop/pay/framewok/handler/ecpp/EcppHandler.java new file mode 100644 index 0000000..a2f166c --- /dev/null +++ b/Cpop-Pay/src/main/java/com/cpop/pay/framewok/handler/ecpp/EcppHandler.java @@ -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 ""; + } + } +} diff --git a/Cpop-Pay/src/main/resources/application-pay.yml b/Cpop-Pay/src/main/resources/application-pay.yml index 8332b6f..5bd819e 100644 --- a/Cpop-Pay/src/main/resources/application-pay.yml +++ b/Cpop-Pay/src/main/resources/application-pay.yml @@ -17,4 +17,9 @@ wx: service-app-id: wx1eb0e5fb7dac3c05 logging: level: - com.github.binarywang.wxpay: debug \ No newline at end of file + 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 \ No newline at end of file