diff --git a/ebike-orders/pom.xml b/ebike-orders/pom.xml index 94adc4bd..202cf685 100644 --- a/ebike-orders/pom.xml +++ b/ebike-orders/pom.xml @@ -180,6 +180,14 @@ org.springframework.boot spring-boot-starter-actuator + + + + com.cdp.product + cdp-common-security + 11.0.0-SNAPSHOT + + diff --git a/ebike-orders/src/main/java/com/cdzy/orders/config/RealNameVerifyConfig.java b/ebike-orders/src/main/java/com/cdzy/orders/config/RealNameVerifyConfig.java new file mode 100644 index 00000000..02a660fb --- /dev/null +++ b/ebike-orders/src/main/java/com/cdzy/orders/config/RealNameVerifyConfig.java @@ -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; +} diff --git a/ebike-orders/src/main/java/com/cdzy/orders/config/WechatConfig.java b/ebike-orders/src/main/java/com/cdzy/orders/config/WechatConfig.java index 1668bb54..869ae382 100644 --- a/ebike-orders/src/main/java/com/cdzy/orders/config/WechatConfig.java +++ b/ebike-orders/src/main/java/com/cdzy/orders/config/WechatConfig.java @@ -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; /** diff --git a/ebike-orders/src/main/java/com/cdzy/orders/controller/EbikeUserController.java b/ebike-orders/src/main/java/com/cdzy/orders/controller/EbikeUserController.java index f970c548..cab95f05 100644 --- a/ebike-orders/src/main/java/com/cdzy/orders/controller/EbikeUserController.java +++ b/ebike-orders/src/main/java/com/cdzy/orders/controller/EbikeUserController.java @@ -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); + } + /** * 添加用户信息。 diff --git a/ebike-orders/src/main/java/com/cdzy/orders/model/dto/req/ReqUserValidateDto.java b/ebike-orders/src/main/java/com/cdzy/orders/model/dto/req/ReqUserValidateDto.java new file mode 100644 index 00000000..f5fe4e16 --- /dev/null +++ b/ebike-orders/src/main/java/com/cdzy/orders/model/dto/req/ReqUserValidateDto.java @@ -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; + +} diff --git a/ebike-orders/src/main/java/com/cdzy/orders/uitls/VerifyUtil.java b/ebike-orders/src/main/java/com/cdzy/orders/uitls/VerifyUtil.java new file mode 100644 index 00000000..b5fbd931 --- /dev/null +++ b/ebike-orders/src/main/java/com/cdzy/orders/uitls/VerifyUtil.java @@ -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 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 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 params) { + StringJoiner paramJoiner = new StringJoiner("&"); + for (Map.Entry 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; + } + } +} diff --git a/ebike-orders/src/main/java/com/cdzy/orders/uitls/WechatUtil.java b/ebike-orders/src/main/java/com/cdzy/orders/uitls/WechatUtil.java deleted file mode 100644 index d413f626..00000000 --- a/ebike-orders/src/main/java/com/cdzy/orders/uitls/WechatUtil.java +++ /dev/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); - } - } - -} diff --git a/ebike-orders/src/main/resources/application-dev.yml b/ebike-orders/src/main/resources/application-dev.yml index 3fe773a3..900800a5 100644 --- a/ebike-orders/src/main/resources/application-dev.yml +++ b/ebike-orders/src/main/resources/application-dev.yml @@ -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: diff --git a/ebike-payment/src/main/java/com/cdzy/payment/controller/EbikeWxPaymentController.java b/ebike-payment/src/main/java/com/cdzy/payment/controller/EbikeWxPaymentController.java index bd4a967a..d1d738cf 100644 --- a/ebike-payment/src/main/java/com/cdzy/payment/controller/EbikeWxPaymentController.java +++ b/ebike-payment/src/main/java/com/cdzy/payment/controller/EbikeWxPaymentController.java @@ -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") diff --git a/ebike-payment/src/main/java/com/cdzy/payment/service/WxPayService.java b/ebike-payment/src/main/java/com/cdzy/payment/service/WxPayService.java index 0acbe431..40136b3a 100644 --- a/ebike-payment/src/main/java/com/cdzy/payment/service/WxPayService.java +++ b/ebike-payment/src/main/java/com/cdzy/payment/service/WxPayService.java @@ -20,6 +20,7 @@ import javax.servlet.http.HttpServletRequest; * @modified by: */ public interface WxPayService { + /** * 关闭订单 * diff --git a/ebike-payment/src/main/java/com/cdzy/payment/service/impl/WxPayServiceImpl.java b/ebike-payment/src/main/java/com/cdzy/payment/service/impl/WxPayServiceImpl.java index 7be43c6c..497a1ea7 100644 --- a/ebike-payment/src/main/java/com/cdzy/payment/service/impl/WxPayServiceImpl.java +++ b/ebike-payment/src/main/java/com/cdzy/payment/service/impl/WxPayServiceImpl.java @@ -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; diff --git a/ebike-payment/src/main/resources/application-dev.yml b/ebike-payment/src/main/resources/application-dev.yml index 73df8bd3..16d0308d 100644 --- a/ebike-payment/src/main/resources/application-dev.yml +++ b/ebike-payment/src/main/resources/application-dev.yml @@ -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: