提供三方API接口、调用第三方接口API接口、模拟API接口(四)使用AOP切面编程实现防止未登录提交业务及避免重复提交

背景 : 依托着之前的 提供三方API接口、调用第三方接口API接口、模拟API接口(二)通过token实现防止业务接口的重复调用 我们为了使得代码更精简,通过AOP的方式来实现登录后才能提交业务的验证、防止重复提交验证。

接下来,开搞!

自定义注解

package com.atguigu.signcenter.annotation;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 *    原文链接:https://blog.csdn.net/weixin_47560078/article/details/118222785
 * 检查表单携带的 token 是否在缓存中
 */
@Target(ElementType.METHOD)  // 表示此注解用在方法上
@Retention(RetentionPolicy.RUNTIME)   // 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
public @interface ExistsApiToken  {
    /**
     * 参数类型 head 、 form
     */
    String type();
}

aop拦截器

package com.atguigu.signcenter.component;

import com.atguigu.signcenter.annotation.ExistsApiToken;
import com.atguigu.signcenter.constant.ReqParameterConstant;
import com.atguigu.signcenter.util.ApiUtil;
import com.atguigu.signcenter.util.TokenUtil;
import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

/**
 * 原文链接:https://blog.csdn.net/weixin_47560078/article/details/118222785
 * 先定义一个切面,myPointCut,这个切面主要针对 controller 层的方法。
 * 前置通知:before,主要用于获取 token。
 * 环绕通知:aroundAOP,主要用于判断 token 是否重复。
 *
 * @author: jd
 * @create: 2024-08-04
 */
@Slf4j
@Component
@Aspect
public class ExtApiAop {

    @Autowired
    private TokenUtil tokenUtil;
    /**
     * 定义切入面:使用 AOP 环绕通知拦截所有访问,拦截的是 controller 层的所有public方法,任何参数数量都被拦截 并执行环绕通知中的校验逻辑
     */
    @Pointcut("execution(public * com.atguigu.signcenter.controller.*.*(..))")
    public void myPointCut() {

    }

    /**
     * 环绕通知,主要用于业务逻辑的方法上
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("myPointCut()")
    @Synchronized
    public Object aroundAOP(ProceedingJoinPoint joinPoint) throws Throwable {
        // 判断方法上是否有注解 @ExistsApiToken
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        ExistsApiToken existsApiToken = methodSignature.getMethod().getDeclaredAnnotation(ExistsApiToken.class);
        String paramToken = null;
        // 如果方法上有此注解,则拦截
        if (null != existsApiToken) {
            //获取请求在哪里
            String type = existsApiToken.type();
            log.info("type:{}", type);
            // 获取上下文的请求
            HttpServletRequest request = getRequest();
            if (ReqParameterConstant.HEAD.equals(type)) {
                paramToken = request.getHeader("token");
            } else if (type.equals(ReqParameterConstant.FORM)) {
                paramToken = request.getParameter("token");
            }
            if (StringUtils.isBlank(paramToken)) {
                response("请求失效,请勿重复提交!");
                return null;
            }
            log.info("请求token:{}",paramToken);
            // 判断缓存里是否有该 token,如果有,且相同,则执行业务;如果没有,则返回无token,请携带token,如果有,且不同,则返回token无效,请重新提交业务。

            String redisToken  = tokenUtil.findToken(paramToken);
            if(StringUtils.isBlank(redisToken)){
                response("未携带token,请携带token后重新提交业务");
               log.info("未携带token,请携带token后重新提交业务!");
            }
            if(!redisToken.equals(paramToken)){
                response("携带token和redis存储的token,不一致,请重新获取token后提交业务");
                log.info("携带token和redis存储的token,不一致,请重新获取token后提交业务!");
            }
            if(redisToken.equals(paramToken)){
                response("请求有效,删除token[],无法再重复提交");
            }

        }
        // 继续往后执行
        Object proceed = joinPoint.proceed();
        return proceed;
    }

    /**
     * 获取容器上下文请求
     *
     * @return
     */
    public HttpServletRequest getRequest() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        return request;
    }


    /**
     * 相应错误响应信息
     *
     * @param msg
     */
    private void response(String msg) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletResponse response = requestAttributes.getResponse();
        response.setHeader("Content-type", "text/html;charset=UTF-8");
        try {
            PrintWriter writer = response.getWriter();
            writer.print(msg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

控制器添加token校验注解


    /**
     * 模拟高并发重复提交,根据 token 缓存防止重复提交
     * 添加自定义 @ExistsApiToken 注解通过 aop 切面进行表单 token 认证
     * type 类型为 head ,token 存储在 header 中
     * @param data
     * @return
     */
    @ExistsApiToken(type = ReqParameterConstant.HEAD)
    @PostMapping("/form/repeatSubmitTestForAOP")
    public Map<String, Object> repeatSubmitTestForAOP(@RequestParam Map<String, String> data) {
        Map<String, Object> result = new HashMap<>();

        // 模拟提交表单信息
        // TODO Something
        log.info("做一些业务,业务模拟");
        result.put("code", 0);
        result.put("msg", "success");

        return result;
    }

测试
模拟前端获取token: 调用获取token的链接
在这里插入图片描述
获取token的代码:

tokenController


    /**
     *
     *
     * 获取 token ,这一步骤会向redis中写写入token
     * 这个可以理解成登录操作,如果登录了则会向redis中写入一个token,然后下面方法中每一次做业务,都会验证携带的token是否和redis中的一样,一样才可以操作,否则不可以做业务,需要重新登录后再做。
     * @return
     */
    @GetMapping("/getToken")
    public String getToken(){
        return tokenUtil.getToken();
    }


TokenUtil

   /**
     * 生成 token
     * @return
     */
    public String getToken(){
        StringBuilder token = new StringBuilder("token_");
        token.append(UUID.randomUUID().toString().replaceAll("-",""));
        redisTemplateUtil.setString(token.toString(),token.toString(),TIMEOUT);
        return token.toString();
    }

获取到token,其实就是向redis中投放了一个特定的键值对

重头戏来了,我们访问我们的有token验证的测试接口
我们首先只传参数,但是不带token信息,
验证结果:不通过,提示 :请求失效,请勿重复提交!
在这里插入图片描述

先调用获取token接口,并把最新的token携带到token验证的请求头中(日常开发中前台获取token的接口,然后携带这个token做其他业务。)

在这里插入图片描述
调用:接口,验证通过
在这里插入图片描述
控制台输出了做业务的提示:
在这里插入图片描述

当我们携带的token。错误时,则给出提示:
在这里插入图片描述
码字不易,不足之处还请大家多多指教~

参考链接:https://blog.csdn.net/weixin_47560078/article/details/118222785

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

执键行天涯

码你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值