Browse Source

添加防重复提交功能

feature-1.1
wuxicheng 3 years ago
parent
commit
59690f7c8c
  1. 9
      bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/R.java
  2. 13
      bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/enums/ResponseEnum.java
  3. 63
      bnyer-services/bnyer-order/src/main/java/com/bnyer/order/annotation/LimitRepeatRequest.java
  4. 150
      bnyer-services/bnyer-order/src/main/java/com/bnyer/order/aop/LimitRepeatRequestAspect.java
  5. 2
      bnyer-services/bnyer-order/src/main/java/com/bnyer/order/controller/VipOrderController.java
  6. 63
      bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/annotation/LimitRepeatRequest.java
  7. 150
      bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/aop/LimitRepeatRequestAspect.java
  8. 9
      bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/controller/UnifiedPayController.java
  9. 6
      bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/UnifiedPayService.java
  10. 10
      bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/UnifiedPayServiceImpl.java

9
bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/R.java

@ -77,6 +77,15 @@ public class R<T> implements Serializable
return restResult(null,responseEnum.getCode(), responseEnum.getMsg()); return restResult(null,responseEnum.getCode(), responseEnum.getMsg());
} }
/**
* 构建重复提交错误消息
* @param time
* @return
*/
public R<T> buildRepeatRequest(int time){
return restResult(null,ResponseEnum.REPEAT_REQUEST_ERROR.getCode(),time+"分钟内"+ResponseEnum.REPEAT_REQUEST_ERROR.getMsg());
}
private static <T> R<T> restResult(T data, int code, String msg) private static <T> R<T> restResult(T data, int code, String msg)
{ {
R<T> apiResult = new R<>(); R<T> apiResult = new R<>();

13
bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/enums/ResponseEnum.java

@ -10,6 +10,7 @@ public enum ResponseEnum {
//======================系统异常========================, //======================系统异常========================,
SERVER_ERROR(500, "系统繁忙,请稍候重试!"), SERVER_ERROR(500, "系统繁忙,请稍候重试!"),
NOT_AUTH(500, "未登录!"), NOT_AUTH(500, "未登录!"),
REPEAT_REQUEST_ERROR(500, "请勿重复请求"),
PARAM_ERROR(400, "参数异常!"), PARAM_ERROR(400, "参数异常!"),
@ -20,15 +21,9 @@ public enum ResponseEnum {
* 订单已过期当前端看到该状态码的时候提示订单信息已过期请重新确认后提交此时用户点击确定前端刷新页面 * 订单已过期当前端看到该状态码的时候提示订单信息已过期请重新确认后提交此时用户点击确定前端刷新页面
*/ */
ORDER_EXPIRED(110003, "订单已过期"), ORDER_EXPIRED(110003, "订单已过期"),
/** ORDER_CANCEL(110004, "该订单已取消,请重新下单!"),
* 请勿重复提交订单 ORDER_REPEAT_PAY(110005, "该订单已支付,请勿重复支付!"),
* 1.当前端遇到该异常时说明前端防多次点击没做好 REFUND_SING_ERROR(110006, "签名错误!"),
* 2.提示用户 订单已发生改变请勿重复下单
*/
REPEAT_ORDER(110004,"请勿重复提交订单"),
ORDER_CANCEL(110005, "该订单已取消,请重新下单!"),
ORDER_REPEAT_PAY(110006, "该订单已支付,请勿重复支付!"),
REFUND_SING_ERROR(110007, "签名错误!"),

63
bnyer-services/bnyer-order/src/main/java/com/bnyer/order/annotation/LimitRepeatRequest.java

@ -0,0 +1,63 @@
package com.bnyer.order.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
/**
* @author :WXC
* @Date :2023/05/12
* @description : 防重复提交
*/
@Target({ METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LimitRepeatRequest {
/**
* 限制当前用户同一个api 不能重复提交
*/
String SELF="self";
/**
* 限制所有用户同一个参数不能重复提交如我新增了用户 张三 那么其他人不能再并发情况下重复添加张三
*/
String ALL_USER="all";
/**
* 当前时间内 api 只能请求一次单位秒
* @return
*/
int time() default 5;
/**
* 对部分参数做重复请求限制
* @return
*/
String[] bodyParam() default {};
/**
* 是否对全部参数做重复请求限制
* @return
*/
boolean bodyAllParam() default false;
/**
* 重复请求限制的用户范围
* LimitRepeatRequest.SELF针对当前登录用户
* LimitRepeatRequest.ALL_USER针对所有用户
*
* @return
*/
String userRange() default SELF;
/**
* 错误提示信息
* @return
*/
String message() default "";
}

150
bnyer-services/bnyer-order/src/main/java/com/bnyer/order/aop/LimitRepeatRequestAspect.java

@ -0,0 +1,150 @@
package com.bnyer.order.aop;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.bnyer.common.core.domain.R;
import com.bnyer.common.core.utils.MD5Util;
import com.bnyer.common.core.utils.StringUtils;
import com.bnyer.common.core.vo.UserInfoVo;
import com.bnyer.common.redis.service.RedisService;
import com.bnyer.common.security.utils.SecurityUtils;
import com.bnyer.order.annotation.LimitRepeatRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* @author :WXC
* @Date :2023/05/12
* @description :重复提交限制aop
*/
@Slf4j
@Aspect
@Component
public class LimitRepeatRequestAspect {
@Autowired
private RedisService redisService;
@Around("@annotation(limitRepeatRequest)")
public Object around(ProceedingJoinPoint joinPoint, LimitRepeatRequest limitRepeatRequest) throws Throwable{
String key = getKey(joinPoint,limitRepeatRequest);
Object cacheObject = redisService.getCacheObject(key);
Object message;
if (Objects.nonNull(cacheObject)){
if (StringUtils.isNotBlank(limitRepeatRequest.message())){
message = limitRepeatRequest.message();
}else {
message = new R().buildRepeatRequest(limitRepeatRequest.time());
}
}else {
redisService.setCacheObject(key,"1",(long)limitRepeatRequest.time(), TimeUnit.SECONDS);
message = joinPoint.proceed();
}
return message;
}
/**
* 获取rediskey
*
* @param joinPoint
* @param limitRepeatRequest
* @return
*/
private String getKey(ProceedingJoinPoint joinPoint, LimitRepeatRequest limitRepeatRequest) {
UserInfoVo userInfo = SecurityUtils.getUserInfo();
Method currentMethod = getCurrentMethod(joinPoint);
//最后拼接好的key
StringBuilder key = new StringBuilder("LimitRepeatRequestAspect#" + currentMethod.getName());
//限制范围
String userRange = limitRepeatRequest.userRange();
if (LimitRepeatRequest.SELF.equals(userRange)){
key.append("#");
key.append(userInfo.getUserClientType());
key.append("#");
key.append(userInfo.getId());
}
//获取请求参数
JSONObject requestParams = getRequestParams(joinPoint);
//部分参数做重复请求限制
if (Objects.nonNull(requestParams)){
String[] bodyParam = limitRepeatRequest.bodyParam();
if (bodyParam != null){
for (String param : bodyParam) {
key.append("#");
Object obj = requestParams.get(param);
if (obj instanceof JSONArray){
JSONArray jsonArray = requestParams.getJSONArray(param);
if (CollUtil.isNotEmpty(jsonArray)){
for (Object o : jsonArray) {
if (o instanceof String){
key.append(o);
}
}
}
}else if(obj instanceof String){
String value = requestParams.containsKey(param)?requestParams.getString(param):"";
key.append(value);
}else if(obj instanceof Integer){
String value = requestParams.containsKey(param)?requestParams.getString(param):"";
key.append(value);
}
}
}
}
//全部参数做重复请求限制
if (limitRepeatRequest.bodyAllParam()){
String jsonStr = JSON.toJSONString(requestParams);
key.append("#");
key.append(jsonStr);
}
return MD5Util.getMD5String(key.toString());
}
/**
* 获取请求参数
* @param joinPoint
* @return
*/
private JSONObject getRequestParams(ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
if (args != null && args.length != 0){
String jsonString = JSON.toJSONString(args);
JSONArray parseArray = JSON.parseArray(jsonString);
return parseArray.getJSONObject(0);
}
return null;
}
/**
* 获取当前方法
* @param joinPoint
* @return
*/
private Method getCurrentMethod(ProceedingJoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
if (!(signature instanceof MethodSignature)){
throw new IllegalArgumentException("该注解只能作用于方法上面");
}
MethodSignature methodSignature = (MethodSignature)signature;
Object target = joinPoint.getTarget();
try {
Method method = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
return method;
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}

2
bnyer-services/bnyer-order/src/main/java/com/bnyer/order/controller/VipOrderController.java

@ -2,6 +2,7 @@ package com.bnyer.order.controller;
import com.bnyer.common.core.domain.R; import com.bnyer.common.core.domain.R;
import com.bnyer.common.core.web.controller.BaseController; import com.bnyer.common.core.web.controller.BaseController;
import com.bnyer.order.annotation.LimitRepeatRequest;
import com.bnyer.order.bean.dto.AddVipOrderDto; import com.bnyer.order.bean.dto.AddVipOrderDto;
import com.bnyer.order.bean.query.VipOrderExtQuery; import com.bnyer.order.bean.query.VipOrderExtQuery;
import com.bnyer.order.bean.query.VipOrderQuery; import com.bnyer.order.bean.query.VipOrderQuery;
@ -38,6 +39,7 @@ public class VipOrderController extends BaseController {
/** /**
* 提交订单 * 提交订单
*/ */
@LimitRepeatRequest(bodyAllParam = true, message = "您的订单已提交,请勿频繁操作")
@PostMapping("/addVipOrder") @PostMapping("/addVipOrder")
@Operation(summary = "生成会员订单,返回订单号" , description = "生成会员订单,返回订单号,通过订单号调用支付接口") @Operation(summary = "生成会员订单,返回订单号" , description = "生成会员订单,返回订单号,通过订单号调用支付接口")
public R<String> addVipOrder(@Valid @RequestBody AddVipOrderDto addVipOrderDto) { public R<String> addVipOrder(@Valid @RequestBody AddVipOrderDto addVipOrderDto) {

63
bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/annotation/LimitRepeatRequest.java

@ -0,0 +1,63 @@
package com.bnyer.pay.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
/**
* @author :WXC
* @Date :2023/05/12
* @description : 防重复提交
*/
@Target({ METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LimitRepeatRequest {
/**
* 限制当前用户同一个api 不能重复提交
*/
String SELF="self";
/**
* 限制所有用户同一个参数不能重复提交如我新增了用户 张三 那么其他人不能再并发情况下重复添加张三
*/
String ALL_USER="all";
/**
* 当前时间内 api 只能请求一次单位秒
* @return
*/
int time() default 5;
/**
* 对部分参数做重复请求限制
* @return
*/
String[] bodyParam() default {};
/**
* 是否对全部参数做重复请求限制
* @return
*/
boolean bodyAllParam() default false;
/**
* 重复请求限制的用户范围
* LimitRepeatRequest.SELF针对当前登录用户
* LimitRepeatRequest.ALL_USER针对所有用户
*
* @return
*/
String userRange() default SELF;
/**
* 错误提示信息
* @return
*/
String message() default "";
}

150
bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/aop/LimitRepeatRequestAspect.java

@ -0,0 +1,150 @@
package com.bnyer.pay.aop;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.bnyer.common.core.domain.R;
import com.bnyer.common.core.utils.MD5Util;
import com.bnyer.common.core.utils.StringUtils;
import com.bnyer.common.core.vo.UserInfoVo;
import com.bnyer.common.redis.service.RedisService;
import com.bnyer.common.security.utils.SecurityUtils;
import com.bnyer.pay.annotation.LimitRepeatRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* @author :WXC
* @Date :2023/05/12
* @description :重复提交限制aop
*/
@Slf4j
@Aspect
@Component
public class LimitRepeatRequestAspect {
@Autowired
private RedisService redisService;
@Around("@annotation(limitRepeatRequest)")
public Object around(ProceedingJoinPoint joinPoint, LimitRepeatRequest limitRepeatRequest) throws Throwable{
String key = getKey(joinPoint,limitRepeatRequest);
Object cacheObject = redisService.getCacheObject(key);
Object message;
if (Objects.nonNull(cacheObject)){
if (StringUtils.isNotBlank(limitRepeatRequest.message())){
message = limitRepeatRequest.message();
}else {
message = new R().buildRepeatRequest(limitRepeatRequest.time());
}
}else {
redisService.setCacheObject(key,"1",(long)limitRepeatRequest.time(), TimeUnit.SECONDS);
message = joinPoint.proceed();
}
return message;
}
/**
* 获取rediskey
*
* @param joinPoint
* @param limitRepeatRequest
* @return
*/
private String getKey(ProceedingJoinPoint joinPoint, LimitRepeatRequest limitRepeatRequest) {
UserInfoVo userInfo = SecurityUtils.getUserInfo();
Method currentMethod = getCurrentMethod(joinPoint);
//最后拼接好的key
StringBuilder key = new StringBuilder("LimitRepeatRequestAspect#" + currentMethod.getName());
//限制范围
String userRange = limitRepeatRequest.userRange();
if (LimitRepeatRequest.SELF.equals(userRange)){
key.append("#");
key.append(userInfo.getUserClientType());
key.append("#");
key.append(userInfo.getId());
}
//获取请求参数
JSONObject requestParams = getRequestParams(joinPoint);
//部分参数做重复请求限制
if (Objects.nonNull(requestParams)){
String[] bodyParam = limitRepeatRequest.bodyParam();
if (bodyParam != null){
for (String param : bodyParam) {
key.append("#");
Object obj = requestParams.get(param);
if (obj instanceof JSONArray){
JSONArray jsonArray = requestParams.getJSONArray(param);
if (CollUtil.isNotEmpty(jsonArray)){
for (Object o : jsonArray) {
if (o instanceof String){
key.append(o);
}
}
}
}else if(obj instanceof String){
String value = requestParams.containsKey(param)?requestParams.getString(param):"";
key.append(value);
}else if(obj instanceof Integer){
String value = requestParams.containsKey(param)?requestParams.getString(param):"";
key.append(value);
}
}
}
}
//全部参数做重复请求限制
if (limitRepeatRequest.bodyAllParam()){
String jsonStr = JSON.toJSONString(requestParams);
key.append("#");
key.append(jsonStr);
}
return MD5Util.getMD5String(key.toString());
}
/**
* 获取请求参数
* @param joinPoint
* @return
*/
private JSONObject getRequestParams(ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
if (args != null && args.length != 0){
String jsonString = JSON.toJSONString(args);
JSONArray parseArray = JSON.parseArray(jsonString);
return parseArray.getJSONObject(0);
}
return null;
}
/**
* 获取当前方法
* @param joinPoint
* @return
*/
private Method getCurrentMethod(ProceedingJoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
if (!(signature instanceof MethodSignature)){
throw new IllegalArgumentException("该注解只能作用于方法上面");
}
MethodSignature methodSignature = (MethodSignature)signature;
Object target = joinPoint.getTarget();
try {
Method method = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
return method;
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}

9
bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/controller/UnifiedPayController.java

@ -3,6 +3,7 @@ package com.bnyer.pay.controller;
import com.bnyer.common.core.domain.R; import com.bnyer.common.core.domain.R;
import com.bnyer.common.core.enums.ResponseEnum; import com.bnyer.common.core.enums.ResponseEnum;
import com.bnyer.common.core.exception.ServiceException; import com.bnyer.common.core.exception.ServiceException;
import com.bnyer.pay.annotation.LimitRepeatRequest;
import com.bnyer.pay.bean.dto.RefundDto; import com.bnyer.pay.bean.dto.RefundDto;
import com.bnyer.pay.bean.dto.UnifiedOrderDto; import com.bnyer.pay.bean.dto.UnifiedOrderDto;
import com.bnyer.pay.bean.dto.QueryOrderDto; import com.bnyer.pay.bean.dto.QueryOrderDto;
@ -37,6 +38,7 @@ public class UnifiedPayController {
@Autowired @Autowired
private UnifiedPayService unifiedPayService; private UnifiedPayService unifiedPayService;
@LimitRepeatRequest(bodyAllParam = true, message = "您的订单已提交,请勿频繁操作")
@PostMapping("/unifiedOrder") @PostMapping("/unifiedOrder")
@Operation(summary = "统一下单,并生成支付订单" , description = "生成支付订单,返回前端支付所需参数") @Operation(summary = "统一下单,并生成支付订单" , description = "生成支付订单,返回前端支付所需参数")
public R<UnifiedOrderVo> unifiedOrder(@Valid @RequestBody UnifiedOrderDto dto, HttpServletRequest request){ public R<UnifiedOrderVo> unifiedOrder(@Valid @RequestBody UnifiedOrderDto dto, HttpServletRequest request){
@ -51,14 +53,15 @@ public class UnifiedPayController {
return R.ok(queryOrderVo); return R.ok(queryOrderVo);
} }
@LimitRepeatRequest(time = 10, message = "请勿频繁操作")
@PostMapping("/refund") @PostMapping("/refund")
@Operation(summary = "统一退款" , description = "统一退款") @Operation(summary = "统一退款" , description = "统一退款")
public R refund(@Valid @RequestBody RefundDto dto){ public R<?> refund(@Valid @RequestBody RefundDto dto){
if (!PaymentRefundUtil.checkSign(dto)){ if (!PaymentRefundUtil.checkSign(dto)){
throw new ServiceException(ResponseEnum.REFUND_SING_ERROR); throw new ServiceException(ResponseEnum.REFUND_SING_ERROR);
} }
ThirdRefundVo unifiedOrderVo = unifiedPayService.refund(dto); unifiedPayService.refund(dto);
return R.ok(unifiedOrderVo); return R.ok();
} }
} }

6
bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/UnifiedPayService.java

@ -31,5 +31,9 @@ public interface UnifiedPayService {
*/ */
QueryOrderVo queryOrder(QueryOrderDto dto); QueryOrderVo queryOrder(QueryOrderDto dto);
ThirdRefundVo refund(RefundDto dto); /**
* 统一退款
* @param dto
*/
void refund(RefundDto dto);
} }

10
bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/UnifiedPayServiceImpl.java

@ -223,11 +223,6 @@ public class UnifiedPayServiceImpl implements UnifiedPayService {
return queryOrderVo; return queryOrderVo;
} }
@Override
public ThirdRefundVo refund(RefundDto dto) {
return null;
}
/** /**
* 通过第三方系统支付状态构建业务系统对应支付状态 * 通过第三方系统支付状态构建业务系统对应支付状态
* @param payType * @param payType
@ -254,4 +249,9 @@ public class UnifiedPayServiceImpl implements UnifiedPayService {
} }
} }
@Override
public void refund(RefundDto dto) {
}
} }

Loading…
Cancel
Save