完成实名认证的功能定义,使用数据宝的二元实名认证接口(仅验证人名、身份证)
This commit is contained in:
parent
888374a84f
commit
7abbe96a36
@ -180,6 +180,14 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据宝实名认证加密 -->
|
||||
<dependency>
|
||||
<groupId>com.cdp.product</groupId>
|
||||
<artifactId>cdp-common-security</artifactId>
|
||||
<version>11.0.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
<profiles>
|
||||
<!--开发环境-->
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
package com.cdzy.orders.config;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 实名认证配置类。
|
||||
* 使用数据宝进行实名认证。
|
||||
*
|
||||
* @author dingchao
|
||||
* @date 2025/5/29
|
||||
* @modified by:
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "real-name")
|
||||
public class RealNameVerifyConfig {
|
||||
|
||||
/**
|
||||
* 数据宝api序列号。
|
||||
*/
|
||||
private String apiKey;
|
||||
/**
|
||||
* 数据宝客户端加密私钥。
|
||||
*/
|
||||
private String clientPrivateKey;
|
||||
/**
|
||||
* 数据宝服务端加密公钥。
|
||||
*/
|
||||
private String serverPublicKey;
|
||||
}
|
||||
@ -1,10 +1,8 @@
|
||||
package com.cdzy.orders.config;
|
||||
|
||||
import com.cdzy.orders.uitls.WechatUtil;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
|
||||
@ -3,12 +3,13 @@ package com.cdzy.orders.controller;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.cdzy.common.model.JsonResult;
|
||||
import com.cdzy.orders.model.dto.req.ReqEbikeUserDto;
|
||||
import com.cdzy.orders.model.dto.req.ReqUserValidateDto;
|
||||
import com.cdzy.orders.model.dto.req.ReqWechatInfoDto;
|
||||
import com.cdzy.orders.model.dto.res.EbikeUserDto;
|
||||
import com.cdzy.orders.model.entity.EbikeUser;
|
||||
import com.cdzy.orders.model.entity.EbikeUserOrders;
|
||||
import com.cdzy.orders.service.UserOrdersService;
|
||||
import com.cdzy.orders.uitls.WechatUtil;
|
||||
import com.cdzy.orders.uitls.VerifyUtil;
|
||||
import com.mybatisflex.core.paginate.Page;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@ -30,7 +31,7 @@ public class EbikeUserController {
|
||||
@Autowired
|
||||
private UserOrdersService userOrdersService;
|
||||
@Resource
|
||||
private WechatUtil wechatUtil;
|
||||
private VerifyUtil wechatUtil;
|
||||
|
||||
/**
|
||||
* 用户微信无感登录。
|
||||
@ -73,6 +74,17 @@ public class EbikeUserController {
|
||||
return JsonResult.success("解密成功", jsonObject.getString("phoneNumber"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户实名
|
||||
*
|
||||
* @param reqUserValidateDto 验证用户实名请求
|
||||
* @return 验证结果
|
||||
*/
|
||||
@PostMapping("/verifyRealName")
|
||||
public JsonResult<?> verifyRealName(@RequestBody ReqUserValidateDto reqUserValidateDto) {
|
||||
return wechatUtil.verifyRealName(reqUserValidateDto);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加用户信息。
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
package com.cdzy.orders.model.dto.req;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 用户实名验证dto
|
||||
*
|
||||
* @author dingchao
|
||||
* @date 2025/5/27
|
||||
* @modified by:
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ReqUserValidateDto implements Serializable {
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
private String userId;
|
||||
/**
|
||||
* 姓名
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 身份证号
|
||||
*/
|
||||
private String idCard;
|
||||
|
||||
}
|
||||
218
ebike-orders/src/main/java/com/cdzy/orders/uitls/VerifyUtil.java
Normal file
218
ebike-orders/src/main/java/com/cdzy/orders/uitls/VerifyUtil.java
Normal file
@ -0,0 +1,218 @@
|
||||
package com.cdzy.orders.uitls;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.cdp.product.security.context.SecurityContext;
|
||||
import com.cdp.product.security.exception.DecryptFailureException;
|
||||
import com.cdp.product.security.exception.EncryptFailureException;
|
||||
import com.cdp.product.security.exception.SignFailureException;
|
||||
import com.cdp.product.security.strategyImpl.RSAStrategyImpl;
|
||||
import com.cdzy.common.model.JsonResult;
|
||||
import com.cdzy.orders.config.RealNameVerifyConfig;
|
||||
import com.cdzy.orders.config.WechatConfig;
|
||||
import com.cdzy.orders.model.dto.req.ReqUserValidateDto;
|
||||
import com.cdzy.orders.model.entity.EbikeUser;
|
||||
import com.cdzy.orders.service.EbikeUserService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.*;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
/**
|
||||
* 验证工具类
|
||||
*
|
||||
* @author dingchao
|
||||
* @date 2025/4/9
|
||||
* @modified by:
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class VerifyUtil {
|
||||
/**
|
||||
* 微信登录API地址
|
||||
*/
|
||||
private static final String WECHAT_LOGIN_URL = "https://api.weixin.qq.com/sns/jscode2session";
|
||||
|
||||
/**
|
||||
* 数据宝实名认证API地址
|
||||
*/
|
||||
private static final String REALNAME_VERIFY_URL = "https://api.chinadatapay.com/communication/personal/1882";
|
||||
|
||||
/**
|
||||
* OkHttpClient实例
|
||||
*/
|
||||
private static OkHttpClient client = null;
|
||||
|
||||
@Resource
|
||||
private WechatConfig wechatConfig;
|
||||
@Resource
|
||||
private RealNameVerifyConfig realNameVerifyConfig;
|
||||
@Resource
|
||||
private EbikeUserService ebikeUserService;
|
||||
|
||||
/**
|
||||
* 微信工具类构造函数。
|
||||
*
|
||||
*/
|
||||
public VerifyUtil() throws IOException {
|
||||
client = new OkHttpClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取openId。
|
||||
*
|
||||
* @param code 微信登录返回的code
|
||||
* @return openId
|
||||
*/
|
||||
public JSONObject wechatAuthority(String code){
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("appid", wechatConfig.getAppId());
|
||||
params.put("secret", wechatConfig.getAppSecret());
|
||||
params.put("js_code", code);
|
||||
params.put("grant_type", "authorization_code");
|
||||
return httpGet("获取openId", WECHAT_LOGIN_URL, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密微信用户信息。
|
||||
*
|
||||
* @param encryptedData 加密数据
|
||||
* @param sessionKey 会话密钥
|
||||
* @param iv 初始向量
|
||||
* @return 解密后的用户信息
|
||||
*/
|
||||
public String decryptData(String encryptedData, String sessionKey, String iv) {
|
||||
try {
|
||||
// 添加 BouncyCastle 安全提供器
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
|
||||
// Base64 解码参数
|
||||
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
|
||||
byte[] sessionKeyBytes = Base64.getDecoder().decode(sessionKey);
|
||||
byte[] ivBytes = Base64.getDecoder().decode(iv);
|
||||
|
||||
// 初始化 AES-CBC 解密器
|
||||
SecretKeySpec keySpec = new SecretKeySpec(sessionKeyBytes, "AES");
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
|
||||
|
||||
// 执行解密
|
||||
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
|
||||
return new String(decryptedBytes, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
log.error("解密失败", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户实名
|
||||
*
|
||||
* @param reqUserValidateDto 验证用户实名请求
|
||||
* @return 验证结果
|
||||
*/
|
||||
public JsonResult<?> verifyRealName(ReqUserValidateDto reqUserValidateDto) {
|
||||
EbikeUser ebikeUser = ebikeUserService.getById(reqUserValidateDto.getUserId());
|
||||
if (ebikeUser == null) {
|
||||
log.error("用户不存在");
|
||||
return JsonResult.failed("用户不存在");
|
||||
}
|
||||
try {
|
||||
SecurityContext securityContext = new SecurityContext();
|
||||
//设置加解密上下⽂类调⽤RSA加解密类
|
||||
securityContext.setSecurityImpl(new RSAStrategyImpl());
|
||||
//设置加解密上下⽂类调⽤RSA加解密类加签
|
||||
securityContext.setSign(new RSAStrategyImpl());
|
||||
|
||||
// 1. 加密数据
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("name", securityContext.encrypt(reqUserValidateDto.getName(), realNameVerifyConfig.getServerPublicKey()));
|
||||
params.put("idcard", securityContext.encrypt(reqUserValidateDto.getIdCard(), realNameVerifyConfig.getServerPublicKey()));
|
||||
params.put("timestamp", securityContext.encrypt(String.valueOf(System.currentTimeMillis()), realNameVerifyConfig.getServerPublicKey()));
|
||||
String sign = securityContext.sign(params, realNameVerifyConfig.getClientPrivateKey());
|
||||
params.put("sign", sign);
|
||||
params.put("key", realNameVerifyConfig.getApiKey());
|
||||
|
||||
// 2. 验证用户实名
|
||||
JSONObject result = httpGet("验证用户实名", REALNAME_VERIFY_URL, params);
|
||||
if (result == null) {
|
||||
log.error("验证用户实名失败");
|
||||
return JsonResult.failed("验证用户实名失败");
|
||||
}
|
||||
if ("10000".equals(result.getString("code"))){
|
||||
// 3. 解密结果
|
||||
String data = securityContext.decrypt(result.getString("data"), realNameVerifyConfig.getClientPrivateKey());
|
||||
JSONObject jsonData = JSON.parseObject(data);
|
||||
if("10000".equals(jsonData.getString("code"))&&"1".equals(jsonData.getJSONObject("data").getString("state"))){
|
||||
// 4. 更新用户实名信息
|
||||
|
||||
return JsonResult.success("验证用户实名成功");
|
||||
}
|
||||
}else{
|
||||
log.error("验证用户实名失败,{} {}", result.getString("code"), result.getString("message"));
|
||||
return JsonResult.failed(result.getString("code") +", "+ result.getString("message"));
|
||||
}
|
||||
}catch (EncryptFailureException e) {
|
||||
log.error("加密失败", e);
|
||||
return JsonResult.failed("加密失败");
|
||||
}catch (SignFailureException e) {
|
||||
log.error("签名失败", e);
|
||||
return JsonResult.failed("签名失败");
|
||||
} catch (DecryptFailureException e) {
|
||||
log.error("结果解密失败", e);
|
||||
return JsonResult.failed("结果解密失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送HTTP GET请求。
|
||||
*
|
||||
* @param func_ 功能描述
|
||||
* @param url 请求URL
|
||||
* @param params 请求参数
|
||||
* @return 响应结果
|
||||
*/
|
||||
private JSONObject httpGet(String func_, String url, Map<String, String> params) {
|
||||
StringJoiner paramJoiner = new StringJoiner("&");
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
paramJoiner.add(entry.getKey() + "=" + entry.getValue());
|
||||
}
|
||||
String requestUrl = url + "?" + paramJoiner.toString();
|
||||
Request request = new Request.Builder()
|
||||
.url(requestUrl)
|
||||
.method("GET", null)
|
||||
.build();
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
if (response.isSuccessful()) {
|
||||
if (response.body() != null) {
|
||||
return JSON.parseObject(response.body().string());
|
||||
}
|
||||
log.error("{}, 请求{}无返回结果", func_, url);
|
||||
return null;
|
||||
}
|
||||
if (response.body() != null) {
|
||||
log.error("{}, 请求{}失败, {}", func_, url, response.body().string());
|
||||
}else {
|
||||
log.error("{}, 请求{}失败", func_, url);
|
||||
}
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
log.error("{}, 请求{}失败", func_, url, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,112 +0,0 @@
|
||||
package com.cdzy.orders.uitls;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.cdzy.orders.config.WechatConfig;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.*;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* 微信工具类
|
||||
*
|
||||
* @author dingchao
|
||||
* @date 2025/4/9
|
||||
* @modified by:
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WechatUtil {
|
||||
/**
|
||||
* 微信登录API地址
|
||||
*/
|
||||
private static final String WECHAT_LOGIN_URL = "https://api.weixin.qq.com/sns/jscode2session";
|
||||
|
||||
/**
|
||||
* OkHttpClient实例
|
||||
*/
|
||||
private static OkHttpClient client = null;
|
||||
|
||||
@Resource
|
||||
private WechatConfig wechatConfig;
|
||||
|
||||
/**
|
||||
* 微信工具类构造函数。
|
||||
*
|
||||
*/
|
||||
public WechatUtil() throws IOException {
|
||||
client = new OkHttpClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取openId。
|
||||
*
|
||||
* @param code 微信登录返回的code
|
||||
* @return openId
|
||||
*/
|
||||
public JSONObject wechatAuthority(String code){
|
||||
Request request = new Request.Builder()
|
||||
.url(WECHAT_LOGIN_URL + "?appid=" + wechatConfig.getAppId() + "&secret=" + wechatConfig.getAppSecret() + "&js_code=" + code + "&grant_type=authorization_code")
|
||||
.build();
|
||||
try(Response response = client.newCall(request).execute()) {
|
||||
if(response.isSuccessful()) {
|
||||
if (response.body()!= null) {
|
||||
String result = response.body().string();
|
||||
JSONObject jsonObject = JSONObject.parseObject(result);
|
||||
log.info("result: {}", jsonObject);
|
||||
return jsonObject;
|
||||
}
|
||||
log.error("获取openId失败");
|
||||
return null;
|
||||
}
|
||||
log.error("获取openId失败");
|
||||
return null;
|
||||
}catch (IOException e){
|
||||
log.error("获取openId失败", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密微信用户信息。
|
||||
*
|
||||
* @param encryptedData 加密数据
|
||||
* @param sessionKey 会话密钥
|
||||
* @param iv 初始向量
|
||||
* @return 解密后的用户信息
|
||||
*/
|
||||
public String decryptData(String encryptedData, String sessionKey, String iv) {
|
||||
try {
|
||||
// 添加 BouncyCastle 安全提供器
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
|
||||
// Base64 解码参数
|
||||
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
|
||||
byte[] sessionKeyBytes = Base64.getDecoder().decode(sessionKey);
|
||||
byte[] ivBytes = Base64.getDecoder().decode(iv);
|
||||
|
||||
// 初始化 AES-CBC 解密器
|
||||
SecretKeySpec keySpec = new SecretKeySpec(sessionKeyBytes, "AES");
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
|
||||
|
||||
// 执行解密
|
||||
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
|
||||
return new String(decryptedBytes, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("解密失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -7,7 +7,7 @@ spring:
|
||||
name: ebike-orders
|
||||
cloud:
|
||||
nacos:
|
||||
server-addr: 192.168.2.226:8848 # nacos
|
||||
server-addr: 127.0.0.1:8848 # nacos
|
||||
username: nacos
|
||||
password: nacos
|
||||
jackson:
|
||||
@ -92,7 +92,10 @@ minio:
|
||||
access-key: eQtGmQBEsGxNHrTd7AkJ # 访问密钥
|
||||
secret-key: Zg6X6j0kgUT1fGsGSgoCZWu6fgL8F3Kw1FfoX4yJ # 私有密钥
|
||||
bucket-name: test
|
||||
|
||||
real-name:
|
||||
api-key: 123456789
|
||||
client-private-pey: 123456789
|
||||
server-public-key: 123456789
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
|
||||
@ -202,7 +202,7 @@ public class EbikeWxPaymentController {
|
||||
/**
|
||||
* 退款申请用户交易记录
|
||||
*
|
||||
* @param reqTradeRecordDto
|
||||
* @param reqTradeRecordDto 查询条件
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/refundApplyTradeRecord")
|
||||
@ -214,7 +214,7 @@ public class EbikeWxPaymentController {
|
||||
/**
|
||||
* 退款申请用户退款记录
|
||||
*
|
||||
* @param reqRefundRecordDto
|
||||
* @param reqRefundRecordDto 查询条件
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/refundApplyRefundRecord")
|
||||
|
||||
@ -20,6 +20,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||
* @modified by:
|
||||
*/
|
||||
public interface WxPayService {
|
||||
|
||||
/**
|
||||
* 关闭订单
|
||||
*
|
||||
|
||||
@ -17,6 +17,7 @@ import com.cdzy.payment.model.enums.RefoundApplySource;
|
||||
import com.cdzy.payment.model.enums.RefundProcessState;
|
||||
import com.cdzy.payment.service.EbikePaymentService;
|
||||
import com.cdzy.payment.service.EbikeRefundService;
|
||||
import com.cdzy.payment.service.EbikeUserService;
|
||||
import com.cdzy.payment.service.WxPayService;
|
||||
import com.cdzy.payment.utils.HttpServletUtils;
|
||||
import com.cdzy.payment.utils.MapUtils;
|
||||
@ -85,6 +86,8 @@ public class WxPayServiceImpl implements WxPayService {
|
||||
@Resource
|
||||
private EbikeRefundService ebikeRefundService;
|
||||
@Resource
|
||||
private EbikeUserService ebikeUserService;
|
||||
@Resource
|
||||
private OrdersFeignClient ordersFeignClient;
|
||||
@Resource
|
||||
private MaintenanceFeignClient maintenanceFeignClient;
|
||||
|
||||
@ -66,9 +66,6 @@ task-scheduler-pool:
|
||||
threadNamePrefix: task-scheduled-
|
||||
waitForTasksToCompleteOnShutdown: true
|
||||
awaitTerminationSeconds: 30
|
||||
geo-coding:
|
||||
api-url: https://apis.map.qq.com/ws/geocoder/v1
|
||||
access-key: BECBZ-EJIEQ-LUU5N-B5ISQ-3TLMZ-BXFLG
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user