diff --git a/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/R.java b/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/R.java index 825ea22..035eb42 100644 --- a/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/R.java +++ b/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/domain/R.java @@ -77,6 +77,15 @@ public class R implements Serializable return restResult(null,responseEnum.getCode(), responseEnum.getMsg()); } + /** + * 构建重复提交错误消息 + * @param time + * @return + */ + public R buildRepeatRequest(int time){ + return restResult(null,ResponseEnum.REPEAT_REQUEST_ERROR.getCode(),time+"分钟内"+ResponseEnum.REPEAT_REQUEST_ERROR.getMsg()); + } + private static R restResult(T data, int code, String msg) { R apiResult = new R<>(); diff --git a/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/enums/ResponseEnum.java b/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/enums/ResponseEnum.java index 71d7c0f..e84e13a 100644 --- a/bnyer-common/bnyer-common-core/src/main/java/com/bnyer/common/core/enums/ResponseEnum.java +++ b/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, "系统繁忙,请稍候重试!"), NOT_AUTH(500, "未登录!"), + REPEAT_REQUEST_ERROR(500, "请勿重复请求"), PARAM_ERROR(400, "参数异常!"), @@ -20,15 +21,9 @@ public enum ResponseEnum { * 订单已过期,当前端看到该状态码的时候,提示订单信息已过期,请重新确认后提交,此时用户点击确定,前端刷新页面。 */ ORDER_EXPIRED(110003, "订单已过期"), - /** - * 请勿重复提交订单, - * 1.当前端遇到该异常时,说明前端防多次点击没做好 - * 2.提示用户 订单已发生改变,请勿重复下单 - */ - REPEAT_ORDER(110004,"请勿重复提交订单"), - ORDER_CANCEL(110005, "该订单已取消,请重新下单!"), - ORDER_REPEAT_PAY(110006, "该订单已支付,请勿重复支付!"), - REFUND_SING_ERROR(110007, "签名错误!"), + ORDER_CANCEL(110004, "该订单已取消,请重新下单!"), + ORDER_REPEAT_PAY(110005, "该订单已支付,请勿重复支付!"), + REFUND_SING_ERROR(110006, "签名错误!"), diff --git a/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/annotation/LimitRepeatRequest.java b/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/annotation/LimitRepeatRequest.java new file mode 100644 index 0000000..c27d369 --- /dev/null +++ b/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 ""; + +} diff --git a/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/aop/LimitRepeatRequestAspect.java b/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/aop/LimitRepeatRequestAspect.java new file mode 100644 index 0000000..b13f80b --- /dev/null +++ b/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); + } + } + +} diff --git a/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/controller/VipOrderController.java b/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/controller/VipOrderController.java index 2208272..39611fa 100644 --- a/bnyer-services/bnyer-order/src/main/java/com/bnyer/order/controller/VipOrderController.java +++ b/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.web.controller.BaseController; +import com.bnyer.order.annotation.LimitRepeatRequest; import com.bnyer.order.bean.dto.AddVipOrderDto; import com.bnyer.order.bean.query.VipOrderExtQuery; import com.bnyer.order.bean.query.VipOrderQuery; @@ -38,6 +39,7 @@ public class VipOrderController extends BaseController { /** * 提交订单 */ + @LimitRepeatRequest(bodyAllParam = true, message = "您的订单已提交,请勿频繁操作") @PostMapping("/addVipOrder") @Operation(summary = "生成会员订单,返回订单号" , description = "生成会员订单,返回订单号,通过订单号调用支付接口") public R addVipOrder(@Valid @RequestBody AddVipOrderDto addVipOrderDto) { diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/annotation/LimitRepeatRequest.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/annotation/LimitRepeatRequest.java new file mode 100644 index 0000000..605b466 --- /dev/null +++ b/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 ""; + +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/aop/LimitRepeatRequestAspect.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/aop/LimitRepeatRequestAspect.java new file mode 100644 index 0000000..e3116f0 --- /dev/null +++ b/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); + } + } + +} diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/controller/UnifiedPayController.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/controller/UnifiedPayController.java index 0f8a9a2..1296451 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/controller/UnifiedPayController.java +++ b/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.enums.ResponseEnum; 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.UnifiedOrderDto; import com.bnyer.pay.bean.dto.QueryOrderDto; @@ -37,6 +38,7 @@ public class UnifiedPayController { @Autowired private UnifiedPayService unifiedPayService; + @LimitRepeatRequest(bodyAllParam = true, message = "您的订单已提交,请勿频繁操作") @PostMapping("/unifiedOrder") @Operation(summary = "统一下单,并生成支付订单" , description = "生成支付订单,返回前端支付所需参数") public R unifiedOrder(@Valid @RequestBody UnifiedOrderDto dto, HttpServletRequest request){ @@ -51,14 +53,15 @@ public class UnifiedPayController { return R.ok(queryOrderVo); } + @LimitRepeatRequest(time = 10, message = "请勿频繁操作") @PostMapping("/refund") @Operation(summary = "统一退款" , description = "统一退款") - public R refund(@Valid @RequestBody RefundDto dto){ + public R refund(@Valid @RequestBody RefundDto dto){ if (!PaymentRefundUtil.checkSign(dto)){ throw new ServiceException(ResponseEnum.REFUND_SING_ERROR); } - ThirdRefundVo unifiedOrderVo = unifiedPayService.refund(dto); - return R.ok(unifiedOrderVo); + unifiedPayService.refund(dto); + return R.ok(); } } diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/UnifiedPayService.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/UnifiedPayService.java index 2ec7e84..59ed825 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/UnifiedPayService.java +++ b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/UnifiedPayService.java @@ -31,5 +31,9 @@ public interface UnifiedPayService { */ QueryOrderVo queryOrder(QueryOrderDto dto); - ThirdRefundVo refund(RefundDto dto); + /** + * 统一退款 + * @param dto + */ + void refund(RefundDto dto); } diff --git a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/UnifiedPayServiceImpl.java b/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/UnifiedPayServiceImpl.java index 90ab6d2..ec9fe52 100644 --- a/bnyer-services/bnyer-pay/src/main/java/com/bnyer/pay/service/impl/UnifiedPayServiceImpl.java +++ b/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; } - @Override - public ThirdRefundVo refund(RefundDto dto) { - return null; - } - /** * 通过第三方系统支付状态构建业务系统对应支付状态 * @param payType @@ -254,4 +249,9 @@ public class UnifiedPayServiceImpl implements UnifiedPayService { } } + @Override + public void refund(RefundDto dto) { + + } + }