ref 校验注解改名, 请求参数增加随机数, 增加请求有效时长参数和校验

This commit is contained in:
DaxPay
2024-06-11 20:37:34 +08:00
parent 783164d8e7
commit 27e21470c1
30 changed files with 199 additions and 212 deletions

View File

@@ -3,7 +3,7 @@ package cn.daxpay.single.service.annotation;
import java.lang.annotation.*;
/**
* 支付校验签名标识
* 支付校验校验标识
* 支付方法至少有一个参数并且需要签名支付参数需要放在第一位
* 返回对象必须为 ResResult<T extends PaymentCommonResult> 格式
* @author xxm
@@ -13,5 +13,5 @@ import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface PaymentSign {
public @interface PaymentVerify {
}

View File

@@ -1,21 +0,0 @@
package cn.daxpay.single.service.common.context;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 支付接口信息
* @author xxm
* @since 2023/12/24
*/
@Data
@Accessors(chain = true)
public class ApiInfoLocal {
/** 当前支付接口编码 */
private String apiCode;
/** 回调地址 */
private String noticeUrl;
}

View File

@@ -10,7 +10,7 @@ import lombok.experimental.Accessors;
*/
@Data
@Accessors(chain = true)
public class RequestLocal {
public class ClientLocal {
/** 客户端ip */
private String clientIp;

View File

@@ -1,15 +0,0 @@
package cn.daxpay.single.service.common.context;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 支付同步信息
* @author xxm
* @since 2024/1/24
*/
@Data
@Accessors(chain = true)
public class PaySyncLocal {
}

View File

@@ -15,6 +15,9 @@ public class PaymentContext {
/** 平台全局配置 */
private final PlatformLocal platformInfo = new PlatformLocal();
/** 请求终端信息 */
private final ClientLocal clientInfo = new ClientLocal();
/** 支付相关信息 */
private final PayLocal payInfo = new PayLocal();
@@ -24,12 +27,6 @@ public class PaymentContext {
/** 回调相关信息 */
private final CallbackLocal callbackInfo = new CallbackLocal();
/** 请求相关信息 */
private final RequestLocal requestInfo = new RequestLocal();
/** 支付同步相关信息 */
private final PaySyncLocal paySyncInfo = new PaySyncLocal();
/** 修复相关信息 */
private final RepairLocal repairInfo = new RepairLocal();

View File

@@ -28,6 +28,13 @@ public class PlatformLocal {
/** 是否对请求进行验签 */
private boolean reqSign;
/**
* 请求有效时长(秒)
* 如果传输的请求时间早于当前服务时间, 而且差值超过配置的时长, 将会请求失败
* 如果传输的请求时间比服务时间大于配置的时长(超过一分钟), 将会请求失败
*/
private Integer reqTimeout;
/** 消息通知方式 */
private String noticeType;

View File

@@ -1,14 +0,0 @@
package cn.daxpay.single.service.common.context;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 退款请求上下文
* @author xxm
* @since 2023/12/26
*/
@Data
@Accessors(chain = true)
public class RefundRequestLocal {
}

View File

@@ -167,7 +167,7 @@ public class AllocationSyncService {
.setSyncInfo(syncResult.getSyncInfo())
.setErrorCode(errorCode)
.setErrorMsg(errorMsg)
.setClientIp(PaymentContextLocal.get().getRequestInfo().getClientIp());
.setClientIp(PaymentContextLocal.get().getClientInfo().getClientIp());
paySyncRecordService.saveRecord(paySyncRecord);
}
}

View File

@@ -98,7 +98,7 @@ public class PayCancelService {
*/
private void saveRecord(PayOrder payOrder, boolean closed, String errMsg){
String clientIp = PaymentContextLocal.get()
.getRequestInfo()
.getClientInfo()
.getClientIp();
PayCloseRecord record = new PayCloseRecord()
.setOrderNo(payOrder.getOrderNo())

View File

@@ -105,7 +105,7 @@ public class PayCloseService {
*/
private void saveRecord(PayOrder payOrder, boolean closed, String errMsg){
String clientIp = PaymentContextLocal.get()
.getRequestInfo()
.getClientInfo()
.getClientIp();
PayCloseRecord record = new PayCloseRecord()
.setOrderNo(payOrder.getOrderNo())

View File

@@ -5,8 +5,8 @@ import cn.bootx.platform.common.core.util.ValidationUtil;
import cn.daxpay.single.exception.pay.PayFailureException;
import cn.daxpay.single.param.PaymentCommonParam;
import cn.daxpay.single.result.PaymentCommonResult;
import cn.daxpay.single.service.annotation.PaymentSign;
import cn.daxpay.single.service.core.payment.common.service.PaymentSignService;
import cn.daxpay.single.service.annotation.PaymentVerify;
import cn.daxpay.single.service.core.payment.common.service.PaymentAssistService;
import cn.daxpay.single.util.DaxRes;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -27,11 +27,11 @@ import org.springframework.stereotype.Component;
@Component
@Order()
@RequiredArgsConstructor
public class PaymentVerifySignAop {
private final PaymentSignService paymentSignService;
public class PaymentVerifyAop {
private final PaymentAssistService paymentAssistService;
@Around("@annotation(paymentSign)")
public Object beforeMethod(ProceedingJoinPoint pjp, @SuppressWarnings("unused") PaymentSign paymentSign) throws Throwable {
@Around("@annotation(paymentVerify)")
public Object beforeMethod(ProceedingJoinPoint pjp, @SuppressWarnings("unused") PaymentVerify paymentVerify) throws Throwable {
Object[] args = pjp.getArgs();
if (args.length == 0){
throw new PayFailureException("支付方法至少有一个参数,并且需要签名支付参数需要放在第一位");
@@ -40,8 +40,14 @@ public class PaymentVerifySignAop {
if (param instanceof PaymentCommonParam){
// 参数校验
ValidationUtil.validateParam(param);
// 参数验签
paymentSignService.verifySign((PaymentCommonParam) param);
// 请求上下文初始化
paymentAssistService.initRequest((PaymentCommonParam) param);
// 参数签名校验
paymentAssistService.signVerify((PaymentCommonParam) param);
// 参数请求时间校验
paymentAssistService.reqTimeoutVerify((PaymentCommonParam) param);
} else {
throw new PayFailureException("支付参数需要继承PayCommonParam");
}
@@ -54,14 +60,14 @@ public class PaymentVerifySignAop {
// todo 后期错误码统一管理后进行更改
commonResult.setCode(1);
commonResult.setMsg(ex.getMessage());
paymentSignService.sign(commonResult);
paymentAssistService.sign(commonResult);
return DaxRes.ok(commonResult);
}
// 对返回值进行签名
if (proceed instanceof ResResult){
Object data = ((ResResult<?>) proceed).getData();
if (data instanceof PaymentCommonResult){
paymentSignService.sign((PaymentCommonResult) data);
paymentAssistService.sign((PaymentCommonResult) data);
} else {
throw new PayFailureException("支付方法返回类型需要为 ResResult<T extends PaymentCommonResult> 格式");
}

View File

@@ -1,13 +1,23 @@
package cn.daxpay.single.service.core.payment.common.service;
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
import cn.daxpay.single.code.PaySignTypeEnum;
import cn.daxpay.single.exception.pay.PayFailureException;
import cn.daxpay.single.param.PaymentCommonParam;
import cn.daxpay.single.service.common.context.RequestLocal;
import cn.daxpay.single.result.PaymentCommonResult;
import cn.daxpay.single.service.common.context.ClientLocal;
import cn.daxpay.single.service.common.context.PlatformLocal;
import cn.daxpay.single.service.common.local.PaymentContextLocal;
import cn.daxpay.single.service.core.system.config.service.PlatformConfigService;
import cn.daxpay.single.util.PaySignUtil;
import cn.hutool.core.bean.BeanUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* 支付、退款等各类操作支持服务
* @author xxm
@@ -19,18 +29,90 @@ import org.springframework.stereotype.Service;
public class PaymentAssistService {
private final PlatformConfigService platformConfigService;
/**
* 初始化上下文
*/
public void initContext(PaymentCommonParam paymentCommonParam){
platformConfigService.initPlatform();
this.initRequest(paymentCommonParam);
}
/**
* 初始化请求相关信息上下文
*/
public void initRequest(PaymentCommonParam paymentCommonParam){
RequestLocal request = PaymentContextLocal.get().getRequestInfo();
ClientLocal request = PaymentContextLocal.get().getClientInfo();
request.setClientIp(paymentCommonParam.getClientIp());
}
/**
* 入参签名校验
*/
public void signVerify(PaymentCommonParam param) {
PlatformLocal platformInfo = PaymentContextLocal.get().getPlatformInfo();
// 如果平台配置所有属性为空, 进行初始化
if (BeanUtil.isEmpty(platformInfo)){
platformConfigService.initPlatform();
}
// 判断当前接口是否不需要签名
if (!platformInfo.isReqSign()){
return;
}
// 参数转换为Map对象
PlatformLocal platform = PaymentContextLocal.get().getPlatformInfo();
String signType = platform.getSignType();
if (Objects.equals(PaySignTypeEnum.HMAC_SHA256.getCode(), signType)){
boolean verified = PaySignUtil.verifyHmacSha256Sign(param, platform.getSignSecret(), param.getSign());
if (!verified){
throw new PayFailureException("未通过签名验证");
}
} else if (Objects.equals(PaySignTypeEnum.MD5.getCode(), signType)){
boolean verified = PaySignUtil.verifyMd5Sign(param, platform.getSignSecret(), param.getSign());
if (!verified){
throw new PayFailureException("未通过签名验证");
}
} else {
throw new PayFailureException("签名方式错误");
}
}
/**
* 请求有效时间校验
*/
public void reqTimeoutVerify(PaymentCommonParam param) {
PlatformLocal platformInfo = PaymentContextLocal.get().getPlatformInfo();
// 如果平台配置所有属性为空, 进行初始化
if (BeanUtil.isEmpty(platformInfo)){
platformConfigService.initPlatform();
}
if (Objects.nonNull(platformInfo.getReqTimeout()) ){
LocalDateTime now = LocalDateTime.now();
// 时间差值(秒)
long durationSeconds = Math.abs(LocalDateTimeUtil.between(now, param.getReqTime()).getSeconds());
// 当前时间是否晚于请求时间
if (LocalDateTimeUtil.lt(now, param.getReqTime())){
// 请求时间比服务器时间晚, 超过一分钟直接打回
if (durationSeconds > 60){
throw new PayFailureException("请求时间晚于服务器接收到的时间,请检查");
}
} else {
// 请求时间比服务器时间早, 超过配置时间直接打回
if (durationSeconds > platformInfo.getReqTimeout()){
throw new PayFailureException("请求已过期,请重新发送!");
}
}
}
}
/**
* 对对象进行签名
*/
public void sign(PaymentCommonResult result) {
PlatformLocal platformInfo = PaymentContextLocal.get().getPlatformInfo();
// 如果平台配置所有属性为空, 进行初始化
if (BeanUtil.isEmpty(platformInfo)){
platformConfigService.initPlatform();
}
String signType = platformInfo.getSignType();
if (Objects.equals(PaySignTypeEnum.HMAC_SHA256.getCode(), signType)){
result.setSign(PaySignUtil.hmacSha256Sign(result, platformInfo.getSignSecret()));
} else if (Objects.equals(PaySignTypeEnum.MD5.getCode(), signType)){
result.setSign(PaySignUtil.md5Sign(result, platformInfo.getSignSecret()));
} else {
throw new PayFailureException("未获取到签名方式,请检查");
}
}
}

View File

@@ -1,80 +0,0 @@
package cn.daxpay.single.service.core.payment.common.service;
import cn.daxpay.single.code.PaySignTypeEnum;
import cn.daxpay.single.exception.pay.PayFailureException;
import cn.daxpay.single.param.PaymentCommonParam;
import cn.daxpay.single.result.PaymentCommonResult;
import cn.daxpay.single.service.common.context.PlatformLocal;
import cn.daxpay.single.service.common.local.PaymentContextLocal;
import cn.daxpay.single.service.core.system.config.service.PlatformConfigService;
import cn.daxpay.single.util.PaySignUtil;
import cn.hutool.core.bean.BeanUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Objects;
/**
* 支付签名服务
* @author xxm
* @since 2023/12/24
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class PaymentSignService {
private final PaymentAssistService paymentAssistService;
private final PlatformConfigService platformConfigService;
/**
* 入参签名校验
*/
public void verifySign(PaymentCommonParam param) {
// 先触发上下文的初始化
paymentAssistService.initContext(param);
PlatformLocal platformInfo = PaymentContextLocal.get().getPlatformInfo();
// 判断当前接口是否不需要签名
if (!platformInfo.isReqSign()){
return;
}
// 参数转换为Map对象
PlatformLocal platform = PaymentContextLocal.get().getPlatformInfo();
String signType = platform.getSignType();
if (Objects.equals(PaySignTypeEnum.HMAC_SHA256.getCode(), signType)){
boolean verified = PaySignUtil.verifyHmacSha256Sign(param, platform.getSignSecret(), param.getSign());
if (!verified){
throw new PayFailureException("未通过签名验证");
}
} else if (Objects.equals(PaySignTypeEnum.MD5.getCode(), signType)){
boolean verified = PaySignUtil.verifyMd5Sign(param, platform.getSignSecret(), param.getSign());
if (!verified){
throw new PayFailureException("未通过签名验证");
}
} else {
throw new PayFailureException("签名方式错误");
}
}
/**
* 对对象进行签名
*/
public void sign(PaymentCommonResult result) {
PlatformLocal platformInfo = PaymentContextLocal.get().getPlatformInfo();
// 如果平台配置所有属性为空, 进行初始化
if (BeanUtil.isEmpty(platformInfo)){
platformConfigService.initPlatform();
}
String signType = platformInfo.getSignType();
if (Objects.equals(PaySignTypeEnum.HMAC_SHA256.getCode(), signType)){
result.setSign(PaySignUtil.hmacSha256Sign(result, platformInfo.getSignSecret()));
} else if (Objects.equals(PaySignTypeEnum.MD5.getCode(), signType)){
result.setSign(PaySignUtil.md5Sign(result, platformInfo.getSignSecret()));
} else {
throw new PayFailureException("未获取到签名方式,请检查");
}
}
}

View File

@@ -10,7 +10,7 @@ import cn.daxpay.single.service.core.order.pay.convert.PayOrderConvert;
import cn.daxpay.single.service.core.order.pay.entity.PayOrder;
import cn.daxpay.single.service.core.order.refund.convert.RefundOrderConvert;
import cn.daxpay.single.service.core.order.refund.entity.RefundOrder;
import cn.daxpay.single.service.core.payment.common.service.PaymentSignService;
import cn.daxpay.single.service.core.payment.common.service.PaymentAssistService;
import cn.daxpay.single.service.core.payment.notice.result.AllocDetailNoticeResult;
import cn.daxpay.single.service.core.payment.notice.result.AllocNoticeResult;
import cn.daxpay.single.service.core.payment.notice.result.PayNoticeResult;
@@ -32,7 +32,7 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class ClientNoticeAssistService {
private final PaymentSignService paymentSignService;
private final PaymentAssistService paymentAssistService;
/**
* 构建出支付通知任务对象
@@ -40,7 +40,7 @@ public class ClientNoticeAssistService {
public ClientNoticeTask buildPayTask(PayOrder order){
PayNoticeResult payNoticeResult = PayOrderConvert.CONVERT.convertNotice(order);
payNoticeResult.setAttach(order.getAttach());
paymentSignService.sign(payNoticeResult);
paymentAssistService.sign(payNoticeResult);
return new ClientNoticeTask()
.setUrl(order.getNotifyUrl())
// 时间序列化进行了重写, 所以使用Jackson的序列化工具类
@@ -60,7 +60,7 @@ public class ClientNoticeAssistService {
RefundNoticeResult refundNoticeResult = RefundOrderConvert.CONVERT.convertNotice(order);
refundNoticeResult.setAttach(order.getAttach());
// 签名
paymentSignService.sign(refundNoticeResult);
paymentAssistService.sign(refundNoticeResult);
return new ClientNoticeTask()
.setUrl(order.getNotifyUrl())
// 时间序列化进行了重写
@@ -86,7 +86,7 @@ public class ClientNoticeAssistService {
allocOrderResult.setAttach(order.getAttach())
.setDetails(details);
// 签名
paymentSignService.sign(allocOrderResult);
paymentAssistService.sign(allocOrderResult);
return new ClientNoticeTask()
.setUrl(order.getNotifyUrl())
// 时间序列化进行了重写

View File

@@ -243,7 +243,7 @@ public class PaySyncService {
.setRepair(repair)
.setRepairNo(repairOrderNo)
.setErrorMsg(errorMsg)
.setClientIp(PaymentContextLocal.get().getRequestInfo().getClientIp());
.setClientIp(PaymentContextLocal.get().getClientInfo().getClientIp());
paySyncRecordService.saveRecord(paySyncRecord);
}

View File

@@ -5,7 +5,6 @@ import cn.bootx.platform.common.core.exception.RepetitiveOperationException;
import cn.daxpay.single.code.RefundStatusEnum;
import cn.daxpay.single.code.RefundSyncStatusEnum;
import cn.daxpay.single.exception.pay.PayFailureException;
import cn.daxpay.single.exception.pay.PayUnsupportedMethodException;
import cn.daxpay.single.param.payment.refund.RefundSyncParam;
import cn.daxpay.single.result.sync.RefundSyncResult;
import cn.daxpay.single.service.code.PayRepairSourceEnum;
@@ -23,12 +22,10 @@ import cn.daxpay.single.service.core.record.sync.entity.PaySyncRecord;
import cn.daxpay.single.service.core.record.sync.service.PaySyncRecordService;
import cn.daxpay.single.service.func.AbsRefundSyncStrategy;
import cn.daxpay.single.service.util.PayStrategyFactory;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@@ -203,7 +200,7 @@ public class RefundSyncService {
.setRepair(repair)
.setRepairNo(repairOrderNo)
.setErrorMsg(errorMsg)
.setClientIp(PaymentContextLocal.get().getRequestInfo().getClientIp());
.setClientIp(PaymentContextLocal.get().getClientInfo().getClientIp());
paySyncRecordService.saveRecord(paySyncRecord);
}

View File

@@ -42,6 +42,14 @@ public class PlatformConfig extends MpBaseEntity implements EntityBaseFunction<P
@DbColumn(comment = "是否对请求进行验签")
private boolean reqSign;
/**
* 请求有效时长(秒)
* 如果传输的请求时间早于当前服务时间, 而且差值超过配置的时长, 将会请求失败
* 如果传输的请求时间比服务时间大于配置的时长(超过一分钟), 将会请求失败
*/
@DbColumn(comment = "请求有效时长(秒)")
private Integer reqTimeout;
/**
* 消息通知方式, 目前只支持http
* @see TradeNotifyTypeEnum

View File

@@ -47,12 +47,6 @@ public class PlatformConfigService {
public void initPlatform(){
PlatformConfig config = this.getConfig();
PlatformLocal platform = PaymentContextLocal.get().getPlatformInfo();
platform.setSignType(config.getSignType());
platform.setSignSecret(config.getSignSecret());
platform.setOrderTimeout(config.getOrderTimeout());
platform.setLimitAmount(config.getLimitAmount());
platform.setWebsiteUrl(config.getWebsiteUrl());
platform.setNoticeType(config.getNotifyType());
platform.setNoticeUrl(config.getNotifyUrl());
BeanUtil.copyProperties(config,platform);
}
}

View File

@@ -33,6 +33,14 @@ public class PlatformConfigDto {
@Schema(description = "是否对请求进行验签")
private boolean reqSign;
/**
* 请求有效时长(秒)
* 如果传输的请求时间早于当前服务时间, 而且差值超过配置的时长, 将会请求失败
* 如果传输的请求时间比服务时间大于配置的时长(超过一分钟), 将会请求失败
*/
@Schema(description = "请求有效时长(秒)")
private Integer reqTimeout;
/**
* 消息通知方式, 目前只支持http
* @see TradeNotifyTypeEnum

View File

@@ -23,6 +23,14 @@ public class PlatformConfigParam {
@Schema(description = "是否对请求进行验签")
private boolean reqSign;
/**
* 请求有效时长(秒)
* 如果传输的请求时间早于当前服务时间, 而且差值超过配置的时长, 将会请求失败
* 如果传输的请求时间比服务时间大于配置的时长(超过一分钟), 将会请求失败
*/
@Schema(description = "请求有效时长(秒)")
private Integer reqTimeout;
/**
* @see PaySignTypeEnum
*/