美文网首页服务端开发实战php面试题
基于AOP Token机制,防止页面重复提交

基于AOP Token机制,防止页面重复提交

作者: YNZXGWZM | 来源:发表于2018-11-09 09:24 被阅读161次

    Token机制,防止页面重复提交
     业务要求:页面的数据只能被点击提交一次
      发生原因:由于重复点击或者网络重发,或者 nginx 重发等情况会导致数据被重复提交
    解决办法:
    集群环境:采用 token 加 redis(redis 单线程的,处理需要排队)
    单 JVM 环境:采用 token 加 redis 或 token 加 jvm 内存
    处理流程:

    数据提交前要向服务的申请 token,token 放到 redis 或 jvm 内存,token 有效时间
    提交后后台校验 token,同时删除 token,生成新的 token 返回
    token 特点:要申请,一次有效性,可以限流
    基于Token方式防止API接口幂等
    客户端每次在调用接口的时候,需要在请求头中,传递令牌参数,每次令牌只能用一次。
    一旦使用之后,就会被删除,这样可以有效防止重复提交。
    步骤:
    1.生成令牌接口

    1. 接口中获取令牌验证
      生成令牌接口

    RedisTokenUtils工具类

    public class RedisTokenUtils {
        private long timeout = 60 * 60;
        @Autowired
        private BaseRedisService baseRedisService;
    
        // 将token存入在redis
        public String getToken() {
            String token = "token" + System.currentTimeMillis();
            baseRedisService.setString(token, token, timeout);
            return token;
        }
    
        public boolean findToken(String tokenKey) {
            String token = (String) baseRedisService.getString(tokenKey);
            if (StringUtils.isEmpty(token)) {
                return false;
            }
            // token 获取成功后 删除对应tokenMapstoken
            baseRedisService.delKey(token);
            return true;
        }
    
    }
    
    @Target(value = ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ExtApiIdempotent {
        String value();
    }
    
    @Target(value = ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ExtApiToken {
    
    }
    

    自定义Api幂等注解和切面

    @Component
    public class ExtApiAopIdempotent {
        @Autowired
        private RedisTokenUtils redisTokenUtils;
    
        @Pointcut("execution(public * com.itmayiedu.controller.*.*(..))")
        public void rlAop() {
        }
    
        // 前置通知转发Token参数
        @Before("rlAop()")
        public void before(JoinPoint point) {
            MethodSignature signature = (MethodSignature) point.getSignature();
            ExtApiToken extApiToken = signature.getMethod().getDeclaredAnnotation(ExtApiToken.class);
            if (extApiToken != null) {
                extApiToken();
            }
        }
    
        // 环绕通知验证参数
        @Around("rlAop()")
        public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
            ExtApiIdempotent extApiIdempotent = signature.getMethod().getDeclaredAnnotation(ExtApiIdempotent.class);
            if (extApiIdempotent != null) {
                return extApiIdempotent(proceedingJoinPoint, signature);
            }
            // 放行
            Object proceed = proceedingJoinPoint.proceed();
            return proceed;
        }
    
        // 验证Token
        public Object extApiIdempotent(ProceedingJoinPoint proceedingJoinPoint, MethodSignature signature)
                throws Throwable {
            ExtApiIdempotent extApiIdempotent = signature.getMethod().getDeclaredAnnotation(ExtApiIdempotent.class);
            if (extApiIdempotent == null) {
                // 直接执行程序
                Object proceed = proceedingJoinPoint.proceed();
                return proceed;
            }
            // 代码步骤:
            // 1.获取令牌 存放在请求头中
            HttpServletRequest request = getRequest();
            String valueType = extApiIdempotent.value();
            if (StringUtils.isEmpty(valueType)) {
                response("参数错误!");
                return null;
            }
            String token = null;
            if (valueType.equals(ConstantUtils.EXTAPIHEAD)) {
                token = request.getHeader("token");
            } else {
                token = request.getParameter("token");
            }
               // 2.判断令牌是否在缓存中有对应的令牌
            // 3.如何缓存没有该令牌的话,直接报错(请勿重复提交)
            // 4.如何缓存有该令牌的话,直接执行该业务逻辑
            // 5.执行完业务逻辑之后,直接删除该令牌。
    
            if (StringUtils.isEmpty(token)) {
                response("参数错误!");
                return null;
            }
            if (!redisTokenUtils.findToken(token)) {
                response("请勿重复提交!");
                return null;
            }
            Object proceed = proceedingJoinPoint.proceed();
            return proceed;
        }
    
        public void extApiToken() {
            String token = redisTokenUtils.getToken();
            getRequest().setAttribute("token", token);
    
        }
    
        public HttpServletRequest getRequest() {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            return request;
        }
    
        public void response(String msg) throws IOException {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletResponse response = attributes.getResponse();
            response.setHeader("Content-type", "text/html;charset=UTF-8");
            PrintWriter writer = response.getWriter();
            try {
                writer.println(msg);
            } catch (Exception e) {
    
            } finally {
                writer.close();
            }
    
        }
    
    }
    

    页面防止重复提交

    public class OrderPageController {
        @Autowired
        private OrderMapper orderMapper;
    
        @RequestMapping("/indexPage")
        @ExtApiToken
        public String indexPage(HttpServletRequest req) {
            return "indexPage";
        }
    
        @RequestMapping("/addOrderPage")
        @ExtApiIdempotent(value = ConstantUtils.EXTAPIFROM)
        public String addOrder(OrderEntity orderEntity) {
            int addOrder = orderMapper.addOrder(orderEntity);
            return addOrder > 0 ? "success" : "fail";
        }
    
    }
    
    

    相关文章

      网友评论

        本文标题:基于AOP Token机制,防止页面重复提交

        本文链接:https://www.haomeiwen.com/subject/bkjnxqtx.html