美文网首页服务端开发实战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