原创奔跑的小梁梁霖编程工具库2023-06-1116:57发表于广东
收录于合集#项目14个
下单流程设计首先需要生成量化投资接口,token,这个量化投资接口,token作为唯一标识这个用户下的这笔订单其次携带token和订单信息创建订单并完成支付功能。
如何设计才能更加解耦呢?第5步的过程可以通过注解切面来实现判断是否存在token的步骤和下单支付的流程解耦,通过拦截器将用户携带token和订单信息的请求拦截下来,判断是否携带了token,如果是且验证成功则放行,否则不放行。
代码实现编写自定义注解这里的自定义注解的核心作用是【标识】哪个方法需要注入切面类,其验证的类型分为方法参数类型和token类型,即有该自定义注解的方法就需要验证request-token。
/** * 自定义防重提交注解,用于标识是这次request的类型是方法参数还是token类型 * @author lianglin */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface RepeatSubmit { /** * 防重提交类型: * 1、方法参数类型 : key=ip+method+param * 2、token类型:key=accountNo+token */ enum Type { PARAM, TOKEN }
/** * 默认防重提交是方法参数 * @return */ Type limitType() default Type.PARAM;
/** * 加锁过期时间是5秒 * @return */ long LockTime() default 5;}
编写切面类【核心重点】切面类的核心作用就是配合自定义注解@RepeatSubmit,在有自定义注解的地方注入这个切面方法并执行@PointCut代表切入点@Around代表核心切入逻辑
/** * 定义切面类 */@Aspect@Component@Slf4jpublic class RepeatSubmitAspect { @Autowired private StringRedisTemplate stringRedisTemplate; /** * 定义 @Pointcut注解表达式, * 方式一:@annotation:当执行的方法上拥有指定的注解时生效(我们采用这) * 方式二:execution:一般用于指定方法的执行 * * @param repeatSubmit */ @Pointcut("@annotation(repeatSubmit)") public void pointcutNoRepeatSubmit(RepeatSubmit repeatSubmit) {
}
/** * 环绕通知, 围绕着方法执行 * @Around 可以用来在调用一个具体方法前和调用后来完成一些具体的任务。 * * 方式一:单用 @Around("execution(* net.xdclass.controller.*.*(..))")可以 * 方式二:用@Pointcut和@Around联合注解也可以(我们采用这个) * * * 两种方式 * 方式一:加锁 固定时间内不能重复提交 * <p> * 方式二:先请求获取token,这边再删除token,删除成功则是第一次提交 * * @param joinPoint * @param noRepeatSubmit * @return * @throws Throwable * 所有@RepeatSubmit注解的方法都添加该环绕通知 */ @Around("pointcutNoRepeatSubmit(noRepeatSubmit)") public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit noRepeatSubmit) throws Throwable { //获取request请求 HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
boolean res=false;
String type = noRepeatSubmit.limitType().name();
if (type.equals(RepeatSubmit.Type.PARAM.name())) { //方式一方法参数 TODO
} else { //方式二,令牌形式 String requestToken = request.getHeader("request-token"); if (StringUtils.isBlank(requestToken)) { throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL);
} LoginUser loginUser = LoginInterceptor.threadLocal.get(); //"order:submit:%s:%s" String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, loginUser.getAccountNo(),requestToken);
/** * 提交表单的token key * 方式一:不用lua脚本获取再判断,之前是因为 key组成是 order:submit:accountNo, value是对应的token,所以需要先获取值,再判断 * 方式二:可以直接key是 order:submit:accountNo:token,然后直接删除成功则完成 */ res = stringRedisTemplate.delete(key); }
if (!res) { //删除失败,说明redis中没有这个token throw new BizException(BizCodeEnum.ORDER_CONFIRM_REPEAT); }
System.out.println("目标方法执行前"); //被自定义注解标识的方法继续执行 Object object = joinPoint.proceed(); System.out.println("目标方法执行后"); return object; }}
注解方式解决防重提交问题的测试业务controller层测试接口这个接口用@RepeatSubmit标识,spring就会在执行这个方法前先执行切面类中的around逻辑代码,根据around逻辑代码的执行结果来判断是否需要继续执行testRequestToken代码。
/** * 测试下单token的获取 * @return */@GetMapping("test")@RepeatSubmit(limitType = RepeatSubmit.Type.TOKEN)public JsonData testRequestToken(){ return JsonData.buildSuccess("测试成功");}
测试结果不携带request-token的情况结论:下单之前必须先获取下单token,否则校验失败
第一次携带request-token的情况结论:携带了下单token且第一次使用这个token才起作用
第二次携带同一个token的情况结论:多次使用同一个下单token会导致校验失败的,因此避免了重复下单的问题
总结一下自定义注解的实现逻辑自定义注解,需要指定这个注解作用在哪些地方即@Target的功能定义切面类通过@Aspect告诉spring这个是一个切面类并交给springioc容器管理。配合自定义注解指明该切面逻辑怎么执行@Around是核心执行逻辑。
文章为作者独立观点,不代表股票自动交易程序化数据接口观点