Ver Fonte

Merge branch 'test-cyr' of sxzgd/java-insagent-pc into test

caiyaru há 2 anos atrás
pai
commit
47a77dd4e6

+ 1 - 1
pom.xml

@@ -587,7 +587,7 @@
         <dependency>
             <groupId>org.bouncycastle</groupId>
             <artifactId>bcprov-jdk15on</artifactId>
-            <version>1.56</version>
+            <version>1.68</version>
         </dependency>
         <!--读取pdf-->
         <dependency>

+ 244 - 3
src/main/java/com/ydtech/modules/admin/components/UnionPayRequestApiComponent.java

@@ -1,12 +1,253 @@
 package com.ydtech.modules.admin.components;
 
-
+import com.alibaba.fastjson.JSONObject;
+import com.ydtech.exception.SystemException;
+import com.ydtech.modules.admin.components.request.UnionPayRequest;
+import com.ydtech.modules.admin.components.response.UnionPayResponse;
+import com.ydtech.utils.HMAC256Uitil;
+import com.ydtech.utils.SM2Utils;
+import com.ydtech.utils.StringUtils;
+import lombok.RequiredArgsConstructor;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.Environment;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Component;
+import org.springframework.util.ObjectUtils;
+import org.springframework.web.client.RestTemplate;
 
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.security.InvalidParameterException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
 
 /**
- * 银联API接口
- */
+ * @description: 银联Api
+ **/
 @Component
+@RequiredArgsConstructor
 public class UnionPayRequestApiComponent {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(UnionPayRequestApiComponent.class);
+    @Autowired
+    Environment environment;
+
+    @Autowired
+    private  RedissonClient redissonClient;
+    @Autowired
+    private  StringRedisTemplate redisTemplate;
+
+    private final RestTemplate restTemplate;
+
+    private  final String UNION_PAY_MAP = "unionPay";
+    private  final String UNION_PAY_ACCESS_TOKEN_KEY = "unionPayAccessToken";
+    private  final String UNION_PAY_ACCESS_TOKEN_LOCK = "unionPayAccessTokenLock";
+    private  final int UNION_PAY_LOCK_TIME = 10000;
+    private  final int UNION_PAY_SLEEP_TIME = 5000;
+    private  final int RANDOM_MAX = 999999999;
+    private  final int RANDOM_MIN = 100000000;
+
+
+    /**
+     * 运营商二要素验证接口
+     * @param request
+     */
+    public UnionPayResponse twoFactorVerifyRequest(UnionPayRequest request) {
+        String url = environment.getProperty("unionPay.2FactorVerifyUrl");
+        JSONObject params=new JSONObject();
+        if (StringUtils.isBlank(request.getPhoneNo())) {
+            throw new InvalidParameterException("手机号不允许为空!");
+        }
+        params.put("phoneNo", request.getPhoneNo());
+
+        //01:验证姓名+手机号,此时用户姓名必输 02:身份证+手机号,此时证件类型、证件
+        if (StringUtils.isNotBlank(request.getCertNo())) {
+            params.put("verifyType", "02");
+            params.put("certType", "01");//证件类型 01:身份证
+            params.put("certNo", request.getCertNo());
+        } else if (StringUtils.isNotBlank(request.getName())) {
+            params.put("verifyType", "01");
+            params.put("name", request.getName());
+        } else {
+            throw new InvalidParameterException("身份证、用户姓名至少传值一个!");
+        }
+        return commonRequest(url, params);
+    }
+
+    /**
+     * 银行卡验证接口
+     * @param request
+     */
+    public UnionPayResponse bankCardVerifyRequest(UnionPayRequest request)  {
+        String url = environment.getProperty("unionPay.2FactorVerifyUrl");
+        JSONObject params=new JSONObject();
+        if (StringUtils.isBlank(request.getCardNo())) {
+            throw new InvalidParameterException("卡号不允许为空!");
+        }
+        if (StringUtils.isBlank(request.getCertNo()) && StringUtils.isBlank(request.getName()) &&
+                StringUtils.isBlank(request.getPhoneNo())) {
+            throw new InvalidParameterException("证件号、用户姓名、手机号至少传值一个!");
+        }
+        params.put("cardNo", request.getCardNo());
+        params.put("personalMandate", "1");//是否取得个人授权,字段取值0或1。1:是 0:否
+        params.put("sceneId", "13"); //TODO 业务场景 13:车险
+        if (StringUtils.isNotBlank(request.getCertNo())) {
+            params.put("certType", "01");//证件类型 01:身份证
+            params.put("certNo", request.getCertNo());
+        }
+        if (StringUtils.isNotBlank(request.getName())) {
+            params.put("name", request.getName());
+        }
+        if (StringUtils.isNotBlank(request.getPhoneNo())) {
+            params.put("phoneNo", request.getPhoneNo());
+        }
+        params.put("appName", "01晋掌柜");//TODO 交易发起应用名称
+        params.put("ipType", "04");//IP版本号 04:IPV4 06:IPV6
+        try {
+            params.put("sourceIp", InetAddress.getLocalHost().getHostAddress()); // ip
+        } catch (UnknownHostException e) {
+            LOGGER.error("获取ipv4地址异常",e);
+            throw new RuntimeException("获取ipv4地址异常:",e);
+        }
+        params.put("protocolVersion", ""); // 用户授权协议版本号 //TODO
+        params.put("protocolNo", ""); // 用户授权协议流水号
+        return commonRequest(url, params);
+    }
+
+
+    /**
+     * 调用银联接口
+     * @param url 地址
+     * @param params 入参
+     * @return 返参
+     */
+    private UnionPayResponse commonRequest(String url,JSONObject params)  {
+        try {
+            String publicKey = environment.getProperty("unionPay.publicKey");
+            JSONObject data=new JSONObject();
+            String dataValue = Base64.getEncoder().encodeToString(SM2Utils.encrypt(params.toString(),
+                    publicKey).getBytes());
+            data.put("data", dataValue);
+
+            HttpHeaders httpHeaders = new HttpHeaders();
+            httpHeaders.set("Content-Type","application/json;charset=UTF-8");
+            httpHeaders.set("Accept","application/json");
+            httpHeaders.set("Authorization",getAccessToken());
+            // 请求
+            HttpEntity<String> httpEntity = new HttpEntity<>(params.toString(), httpHeaders);
+            ResponseEntity<String> response = restTemplate.postForEntity(url, httpEntity, String.class);
+            JSONObject resp = JSONObject.parseObject(response.getBody());
+
+            if ("20000000".equals(resp.getString("errCode"))) {
+                String privateKey = environment.getProperty("unionPay.privateKey");
+                String respData = SM2Utils.decrypt(new String(Base64.getDecoder().decode(resp.getString("data"))),
+                        privateKey);
+                return JSONObject.parseObject(respData, UnionPayResponse.class);
+            } else {
+                throw new SystemException("银联信息验证异常["+resp.getString("errCode")+":"+
+                        resp.getString("errInfo")+"]");
+            }
+        } catch (Exception e) {
+            LOGGER.error("银联接口调用异常",e);
+            throw new RuntimeException("银联接口调用异常:",e);
+        }
+    }
+
+    /**
+     * 获取银联token
+     * @return
+     */
+    private String getAccessToken() {
+
+        Object token = redisTemplate.opsForHash().get(UNION_PAY_MAP,UNION_PAY_ACCESS_TOKEN_KEY);
+        // 缓存中不存在就去请求token
+        if (!ObjectUtils.isEmpty(token)) {
+            return (String)token;
+        }
+
+        // 使用分布式锁 只有一个人可以访问登录
+        RLock lock = redissonClient.getLock(UNION_PAY_ACCESS_TOKEN_LOCK);
+        try {
+            boolean isLock = lock.tryLock(1, UNION_PAY_LOCK_TIME, TimeUnit.MILLISECONDS);
+            if (isLock) {
+                try {
+                    return this.invokeToken();
+                } finally {
+                    lock.unlock();
+                }
+            }
+
+        } catch (InterruptedException e) {
+            LOGGER.error("getAccessToken InterruptedException",e);
+            lock.unlock();
+            Thread.currentThread().interrupt();
+            throw new RuntimeException("获取银联Token异常,请重试!");
+        }
+
+        // 睡眠一段时间 自调用 重新获取缓存中的 header
+        try {
+            Thread.sleep(UNION_PAY_SLEEP_TIME);
+        } catch (InterruptedException e) {
+            LOGGER.error("getAccessToken InterruptedException",e);
+            Thread.currentThread().interrupt();
+            throw new RuntimeException("获取银联Token异常,请重试!");
+        }
+        return getAccessToken();
+    }
+
+    /**
+     * 调用银联接口获取token
+     * @return token
+     */
+    private String invokeToken() {
+        String appId = environment.getProperty("unionPay.appId");
+        String appKey = environment.getProperty("unionPay.appKey");
+        String accessTokenUrl = environment.getProperty("unionPay.accessTokenUrl");
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
+        String timestamp = sdf.format(new Date());
+        Random rand = new Random();
+        String randNumber = String.valueOf(rand.nextInt(RANDOM_MAX - RANDOM_MIN + 1) + RANDOM_MIN);
+
+        String signature = HMAC256Uitil.hmac256(appId+timestamp+randNumber,appKey);//TODO
+
+        JSONObject params=new JSONObject();
+        params.put("appId", appId);
+        params.put("timestamp", timestamp);
+        params.put("nonce", randNumber);
+        params.put("signMethod", "SHA256");
+        params.put("signature", signature);
+        try{
+            HttpHeaders httpHeaders = new HttpHeaders();
+            httpHeaders.set("Content-Type","application/json;charset=UTF-8");
+            httpHeaders.set("Accept","application/json");
+
+            // 请求
+            HttpEntity<String> httpEntity = new HttpEntity<>(params.toString(), httpHeaders);
+            ResponseEntity<String> response = restTemplate.postForEntity(accessTokenUrl, httpEntity, String.class);
+            JSONObject resp = JSONObject.parseObject(response.getBody());
+            if ("20000000".equals(resp.getString("errCode"))) {
+                String token = resp.getString("accessToken");
+                int expiresIn = resp.getIntValue("expiresIn"); //失效时间
+                Map<String, String> map = new HashMap<>();
+                map.put(UNION_PAY_MAP, "OPEN-ACCESS-TOKEN AccessToken=\""+token+"\", appId=\""+appId+"\"");
+                redisTemplate.opsForHash().putAll(UNION_PAY_ACCESS_TOKEN_KEY, map);
+                redisTemplate.expire(UNION_PAY_ACCESS_TOKEN_KEY, expiresIn-30, TimeUnit.SECONDS);
+                return token;
+            } else {
+                throw new SystemException("获取银联Token异常["+resp.getString("errCode")+":"+
+                        resp.getString("errInfo")+"]");
+            }
+        }catch(Exception e){
+            LOGGER.error("获取银联Token异常",e);
+            throw new RuntimeException("获取银联Token异常,请重试!",e);
+        }
+    }
 }

+ 25 - 0
src/main/java/com/ydtech/modules/admin/components/request/UnionPayRequest.java

@@ -0,0 +1,25 @@
+package com.ydtech.modules.admin.components.request;
+
+
+import lombok.Data;
+
+@Data
+public class UnionPayRequest {
+
+    /**
+     * 姓名
+     */
+    private String name;
+    /**
+     * 证件号
+     */
+    private String certNo;
+    /**
+     * 手机号
+     */
+    private String phoneNo;
+    /**
+     * 银行卡号
+     */
+    private String cardNo;
+}

+ 24 - 0
src/main/java/com/ydtech/modules/admin/components/response/UnionPayResponse.java

@@ -0,0 +1,24 @@
+package com.ydtech.modules.admin.components.response;
+
+import lombok.Data;
+
+@Data
+public class UnionPayResponse {
+
+    /**
+     * 应答码
+     */
+    private String respCode;
+    /**
+     * 应答码解释
+     */
+    private String respMsg;
+    /**
+     * 细分应答码
+     */
+    private String detailRespCode;
+    /**
+     * 细分应答码解释
+     */
+    private String detailRespMsg;
+}

+ 109 - 0
src/main/java/com/ydtech/utils/SM2Utils.java

@@ -0,0 +1,109 @@
+package com.ydtech.utils;
+
+
+import com.ydtech.modules.admin.components.UnionPayRequestApiComponent;
+import org.bouncycastle.crypto.engines.SM2Engine;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.crypto.util.PrivateKeyFactory;
+import org.bouncycastle.crypto.util.PublicKeyFactory;
+import org.bouncycastle.jce.ECNamedCurveTable;
+import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
+import org.bouncycastle.jce.spec.ECNamedCurveSpec;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+import org.bouncycastle.util.encoders.Hex;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.ECPrivateKeySpec;
+import java.util.Base64;
+
+public class SM2Utils {
+    private final static Logger LOGGER = LoggerFactory.getLogger(SM2Utils.class);
+
+
+    /**
+     * 公钥转换
+     * @param publicKeyString 公钥字符串
+     * @return ECPublicKeyParameters
+     * @throws Exception
+     */
+    public static ECPublicKeyParameters getPublicKeyFromString (String publicKeyString) throws Exception {
+        byte[] keyBytes = Base64.getDecoder().decode(publicKeyString);
+        ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("sm2p256v1");
+        ECPublicKeySpec publicKeySpec = new ECPublicKeySpec(ecSpec.getCurve().decodePoint(keyBytes), ecSpec);
+        PublicKey publicKey =  KeyFactory.getInstance("EC","BC").generatePublic(publicKeySpec);
+        return (ECPublicKeyParameters) PublicKeyFactory.createKey(publicKey.getEncoded());
+    }
+
+    /**
+     * 私钥转换
+     * @param privateKeyString 私钥字符串
+     * @return ECPrivateKeyParameters
+     * @throws Exception
+     */
+    public static ECPrivateKeyParameters getPrivateKeyFromString (String privateKeyString) throws Exception{
+        byte[] keyBytes = Base64.getDecoder().decode(privateKeyString);
+        ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("sm2p256v1");
+        ECNamedCurveSpec param =  new ECNamedCurveSpec("sm2p256v1", ecSpec.getCurve(), ecSpec.getG(), ecSpec.getN());
+        KeyFactory keyFactory = KeyFactory.getInstance("EC","BC");
+        PrivateKey privateKey = keyFactory.generatePrivate(new ECPrivateKeySpec(new BigInteger(1, keyBytes) , param));
+        return (ECPrivateKeyParameters) PrivateKeyFactory.createKey(privateKey.getEncoded());
+    }
+
+
+    /**
+     * SM2加密算法
+     *
+     * @param publicKeyString 公钥字符串
+     * @param data      明文数据
+     * @return
+     */
+    public static String encrypt(String data, String publicKeyString) throws Exception{
+        try {
+            byte[] bytes = data.getBytes();
+            ECPublicKeyParameters publicKey = getPublicKeyFromString(publicKeyString);
+
+            SM2Engine engine = new SM2Engine();
+            engine.init(true, new ParametersWithRandom(publicKey));
+
+            byte[] arrayOfBytes =engine.processBlock(bytes,0, bytes.length);
+            return Hex.toHexString(arrayOfBytes);
+        } catch (Exception e) {
+            LOGGER.error("SM2加密时出现异常:",e);
+            throw new Exception("SM2加密时出现异常", e);
+        }
+    }
+
+
+
+    /**
+     * SM2解密算法
+     * @param data    hex格式密文
+     * @param privateKeyString    密钥PrivateKey型
+     * @return              明文
+     */
+    public static String decrypt(String data, String privateKeyString)  throws Exception{
+        try {
+            byte[] bytes = data.getBytes();
+            ECPrivateKeyParameters privateKeyParameters = getPrivateKeyFromString(privateKeyString);
+
+            SM2Engine sm2Engine = new SM2Engine();
+            // 设置sm2为解密模式
+            sm2Engine.init(false, privateKeyParameters);
+            byte[] arrayOfBytes = sm2Engine.processBlock(bytes, 0, bytes.length);
+            return new String(arrayOfBytes);
+        } catch (Exception e) {
+            LOGGER.error("SM2解密时出现异常:" ,e);
+            throw new Exception("SM2解密时出现异常", e);
+        }
+    }
+
+
+}

+ 10 - 0
src/main/resources/application-pre.yml

@@ -20,6 +20,16 @@ baidu:
   vehicle: https://aip.baidubce.com/rest/2.0/ocr/v1/vehicle_license
   idcard: https://aip.baidubce.com/rest/2.0/ocr/v1/idcard
 
+#银联API参数配置
+unionPay:
+  appId: 111
+  appKey: 1111
+  accessTokenUrl: https://test-api-open.chinaums.com/v2/token/acces
+  2FactorVerifyUrl: https://test-api-open.chinaums.com/v1/datacenter/smartverification/encrypted/mobile/2factor/verify
+  bankCardVerifyUrl: https://test-api-open.chinaums.com/v1/datacenter/smartverification/encrypted/bankcard/verify
+  publicKey: 0414bb7e9c3914bb65b85079b1dc6e1ca2a0ee04dd9bb55b2f8d31704a6dec0cca695739e128f9c931330e1c0f493be4d56244236434c9d344c4716ba64a4470ba
+  privateKey:
+
 aly:
   cbit:
     url_pre: http://223.70.186.115:80/CBIT/API/insCBIT

+ 10 - 0
src/main/resources/application-prod.yml

@@ -20,6 +20,16 @@ baidu:
   vehicle: https://aip.baidubce.com/rest/2.0/ocr/v1/vehicle_license
   idcard: https://aip.baidubce.com/rest/2.0/ocr/v1/idcard
 
+#银联API参数配置
+unionPay:
+  appId: 111
+  appKey: 1111
+  accessTokenUrl: https://test-api-open.chinaums.com/v2/token/acces
+  2FactorVerifyUrl: https://test-api-open.chinaums.com/v1/datacenter/smartverification/encrypted/mobile/2factor/verify
+  bankCardVerifyUrl: https://test-api-open.chinaums.com/v1/datacenter/smartverification/encrypted/bankcard/verify
+  publicKey: 0414bb7e9c3914bb65b85079b1dc6e1ca2a0ee04dd9bb55b2f8d31704a6dec0cca695739e128f9c931330e1c0f493be4d56244236434c9d344c4716ba64a4470ba
+  privateKey:
+
 aly:
   cbit:
     url_pre: http://223.70.186.115:80/CBIT/API/insCBIT

+ 10 - 0
src/main/resources/application-test.yml

@@ -22,6 +22,16 @@ baidu:
   vehicle: https://aip.baidubce.com/rest/2.0/ocr/v1/vehicle_license
   idcard: https://aip.baidubce.com/rest/2.0/ocr/v1/idcard
 
+#银联API参数配置
+unionPay:
+  appId: 111
+  appKey: 1111
+  accessTokenUrl: https://test-api-open.chinaums.com/v2/token/acces
+  2FactorVerifyUrl: https://test-api-open.chinaums.com/v1/datacenter/smartverification/encrypted/mobile/2factor/verify
+  bankCardVerifyUrl: https://test-api-open.chinaums.com/v1/datacenter/smartverification/encrypted/bankcard/verify
+  publicKey: 0414bb7e9c3914bb65b85079b1dc6e1ca2a0ee04dd9bb55b2f8d31704a6dec0cca695739e128f9c931330e1c0f493be4d56244236434c9d344c4716ba64a4470ba
+  privateKey:
+
 aly:
   cbit:
     #================ 阿里云部署对接保信服务器==============#