From 26de11e567b9d138e94fcfe808ccef5231ba70ad Mon Sep 17 00:00:00 2001 From: dzc <2388224167@qq.com> Date: Wed, 30 Apr 2025 17:31:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E7=AC=AC=E4=B8=89=E6=96=B9=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/pay/PayGetWalletInfoDto.java | 13 ++++++ .../thirdparty/vo/pay/PayGetWalletInfoVo.java | 14 ++++++ .../controller/pay/ThirdPayApiController.java | 11 +++-- .../service/ThirdKeyManageService.java | 8 ++++ .../service/ThirdPayApiService.java | 5 +- .../impl/ThirdKeyManageServiceImpl.java | 43 ++++++++++++++++++ .../impl/pay/ThirdPayApiServiceImpl.java | 24 ++++++++-- .../src/main/resources/doc/第三方支付文档.wps | Bin 0 -> 39936 bytes 8 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/pay/PayGetWalletInfoDto.java create mode 100644 pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/resources/doc/第三方支付文档.wps diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/pay/PayGetWalletInfoDto.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/pay/PayGetWalletInfoDto.java new file mode 100644 index 000000000..477a35e24 --- /dev/null +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/dto/pay/PayGetWalletInfoDto.java @@ -0,0 +1,13 @@ +package com.bsd.pjcloud.thirdparty.dto.pay; + +import lombok.Data; + +@Data +public class PayGetWalletInfoDto { + + /** + * sm4加密后的手机号 + */ + private String signPhone; + +} diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/vo/pay/PayGetWalletInfoVo.java b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/vo/pay/PayGetWalletInfoVo.java index 237fb61d9..1b8126cc6 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/vo/pay/PayGetWalletInfoVo.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-api/src/main/java/com/bsd/pjcloud/thirdparty/vo/pay/PayGetWalletInfoVo.java @@ -5,6 +5,20 @@ import lombok.Data; @Data public class PayGetWalletInfoVo { + /** + * sm4加密后的手机号 + */ + private String signPhone; + + /** + * 用户真实姓名 + */ + private String username; + + /** + * 用户余额(分) + */ + private int balance; } diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/controller/pay/ThirdPayApiController.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/controller/pay/ThirdPayApiController.java index 48310118b..4970873c3 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/controller/pay/ThirdPayApiController.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/controller/pay/ThirdPayApiController.java @@ -2,16 +2,20 @@ package com.bsd.pjcloud.thirdparty.controller.pay; import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.thirdparty.dto.pay.PayGetWalletInfoDto; import com.bsd.pjcloud.thirdparty.service.ThirdPayApiService; import com.bsd.pjcloud.thirdparty.service.ThirdPayLogService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; +import java.util.Map; + /** * 第三方支付日志 */ @@ -25,9 +29,10 @@ public class ThirdPayApiController { private final ThirdPayApiService thirdPayApiService; @Operation(summary = "通过手机号查询用户钱包信息", description = "通过手机号查询用户钱包信息") - @GetMapping("/{signPhone}") - public R getWalletInfo(@PathVariable("signPhone") String signPhone) { - return R.ok(thirdPayApiService.getWalletInfo(signPhone)); + @GetMapping("/user-wallet") + public R getWalletInfo(HttpEntity httpEntity, @RequestHeader Map headers) { + String body = httpEntity.getBody(); + return R.ok(thirdPayApiService.getWalletInfo(body, headers)); } diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdKeyManageService.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdKeyManageService.java index eab9de9ca..25b9ea4df 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdKeyManageService.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdKeyManageService.java @@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.extension.service.IService; import com.bsd.pjcloud.thirdparty.constant.ThirdKeyTypeEnum; import com.bsd.pjcloud.thirdparty.entity.ThirdKeyManageEntity; +import java.util.Map; + public interface ThirdKeyManageService extends IService { /** @@ -20,4 +22,10 @@ public interface ThirdKeyManageService extends IService { * 获取第三方key配置 */ ThirdKeyManageEntity get(ThirdKeyTypeEnum type); + + /** + * 验签并且返回配置 + */ + ThirdKeyManageEntity checkSignAndReturnConfig(String json, Map headers); + } \ No newline at end of file diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdPayApiService.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdPayApiService.java index 449ab9e39..0054b89eb 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdPayApiService.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/ThirdPayApiService.java @@ -2,11 +2,14 @@ package com.bsd.pjcloud.thirdparty.service; import com.bsd.pjcloud.thirdparty.dto.pay.PayDto; +import com.bsd.pjcloud.thirdparty.dto.pay.PayGetWalletInfoDto; import com.bsd.pjcloud.thirdparty.dto.pay.RefundDto; import com.bsd.pjcloud.thirdparty.vo.pay.PayGetWalletInfoVo; import com.bsd.pjcloud.thirdparty.vo.pay.PayVo; import com.bsd.pjcloud.thirdparty.vo.pay.RefundVo; +import java.util.Map; + public interface ThirdPayApiService { /** @@ -22,5 +25,5 @@ public interface ThirdPayApiService { /** * 根据加密的手机号查询用户钱包信息 */ - PayGetWalletInfoVo getWalletInfo(String phone); + PayGetWalletInfoVo getWalletInfo(String json, Map headers); } diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/ThirdKeyManageServiceImpl.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/ThirdKeyManageServiceImpl.java index e11d72253..a018dd209 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/ThirdKeyManageServiceImpl.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/ThirdKeyManageServiceImpl.java @@ -9,9 +9,13 @@ import com.bsd.pjcloud.thirdparty.mapper.ThirdKeyManageMapper; import com.bsd.pjcloud.thirdparty.service.ThirdKeyManageService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.DigestUtils; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; +import java.util.LinkedHashMap; +import java.util.Map; + /** * 第三方密钥管理 */ @@ -52,6 +56,45 @@ public class ThirdKeyManageServiceImpl extends ServiceImpl().eq(ThirdKeyManageEntity::getBizType, type.getType())); } + /** + * 验签并且返回配置 + */ + @Override + public ThirdKeyManageEntity checkSignAndReturnConfig(String json, Map headers) { + Map transitionHeader = new LinkedHashMap<>(); + for (Map.Entry entry : headers.entrySet()) { + transitionHeader.put(entry.getKey().toUpperCase(), entry.getValue()); + } + // 验签 + String appKey = transitionHeader.get("APP-KEY"); + String bizType = transitionHeader.get("BIZ-TYPE"); + String timestamp = transitionHeader.get("TIMESTAMP"); + String sign = transitionHeader.get("SIGN"); + if (!ObjectUtil.isAllNotEmpty(appKey, bizType, timestamp, sign)) { + return null; + } + // 判断bizType + ThirdKeyTypeEnum keyType = ThirdKeyTypeEnum.valueOfByType(bizType); + if (ObjectUtil.isEmpty(keyType)) { + return null; + } + // 查询配置 + ThirdKeyManageEntity keyManage = get(keyType); + if (ObjectUtil.isEmpty(keyManage)) { + return null; + } + if (ObjectUtil.notEqual(appKey, keyManage.getAppKey())) { + return null; + } + // 计算签名 + String signStr = String.format("%s%s%s%s%s", keyManage.getAppKey(), keyManage.getAppSecret(), keyManage.getBizType(), timestamp, json); + String successSignMd5 = DigestUtils.md5Hex(signStr); + if (ObjectUtil.notEqual(successSignMd5.toLowerCase(), sign.toLowerCase())) { + return null; + } + return keyManage; + } + /** * 第三方类型不能重复 */ diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/pay/ThirdPayApiServiceImpl.java b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/pay/ThirdPayApiServiceImpl.java index b5f2cc3b9..0832e2299 100644 --- a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/pay/ThirdPayApiServiceImpl.java +++ b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/java/com/bsd/pjcloud/thirdparty/service/impl/pay/ThirdPayApiServiceImpl.java @@ -1,10 +1,14 @@ package com.bsd.pjcloud.thirdparty.service.impl.pay; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.json.JSONUtil; import com.bsd.pjcloud.thirdparty.constant.pay.ThirdPayApiBizTypeEnum; import com.bsd.pjcloud.thirdparty.dto.pay.PayDto; +import com.bsd.pjcloud.thirdparty.dto.pay.PayGetWalletInfoDto; import com.bsd.pjcloud.thirdparty.dto.pay.RefundDto; +import com.bsd.pjcloud.thirdparty.entity.ThirdKeyManageEntity; import com.bsd.pjcloud.thirdparty.entity.api.ThirdPayLogEntity; +import com.bsd.pjcloud.thirdparty.service.ThirdKeyManageService; import com.bsd.pjcloud.thirdparty.service.ThirdPayApiService; import com.bsd.pjcloud.thirdparty.service.ThirdPayLogService; import com.bsd.pjcloud.thirdparty.utils.lb.tool.LbRetailSignUtils; @@ -17,6 +21,7 @@ import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import java.util.Arrays; +import java.util.Map; /** * 第三方支付 @@ -29,7 +34,8 @@ public class ThirdPayApiServiceImpl implements ThirdPayApiService { private final String TAG = "第三方支付"; - private final ThirdPayLogService thirdPayLogService; + private final ThirdKeyManageService keyManageService; + private final ThirdPayLogService payLogService; /** * 扣款 @@ -47,7 +53,7 @@ public class ThirdPayApiServiceImpl implements ThirdPayApiService { logObj.setError(error); } finally { logObj.setTimeConsuming(Long.toString(System.currentTimeMillis() - startTime)); - thirdPayLogService.save(logObj); + payLogService.save(logObj); } return vo; } @@ -68,7 +74,7 @@ public class ThirdPayApiServiceImpl implements ThirdPayApiService { logObj.setError(error); } finally { logObj.setTimeConsuming(Long.toString(System.currentTimeMillis() - startTime)); - thirdPayLogService.save(logObj); + payLogService.save(logObj); } return vo; } @@ -77,9 +83,17 @@ public class ThirdPayApiServiceImpl implements ThirdPayApiService { * 根据加密的手机号查询用户钱包信息 */ @Override - public PayGetWalletInfoVo getWalletInfo(String signPhone) { + public PayGetWalletInfoVo getWalletInfo(String json, Map headers) { + ThirdKeyManageEntity keySetting = keyManageService.checkSignAndReturnConfig(json, headers); + if (ObjectUtil.isEmpty(keySetting)) { + + return null; + } + PayGetWalletInfoDto dto = JSONUtil.toBean(json, PayGetWalletInfoDto.class); // 手机号解密 - String phone = LbRetailSignUtils.sm4Decrypt(signPhone, ""); + String phone = LbRetailSignUtils.sm4Decrypt(dto.getSignPhone(), keySetting.getFieldKey()); + return null; } + } diff --git a/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/resources/doc/第三方支付文档.wps b/pjcloud-thirdparty/pjcloud-thirdparty-biz/src/main/resources/doc/第三方支付文档.wps new file mode 100644 index 0000000000000000000000000000000000000000..07d533bc13a8bd9d86135a7dca9fa41a4d45e313 GIT binary patch literal 39936 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