实现思路:
- 客户端每次在调用接口的时候,需要在请求头中,传递令牌参数,每次令牌只能用一次,有超时时间限定。
- 一旦使用之后,就会被删除,这样可以有效防止重复提交。
本文目录结构:
l____1.BaseRedisService封装Redis
下面直接上代码
1.BaseRedisService封装Redis
@Component public class BaseRedisService { @Autowired private StringRedisTemplate stringRedisTemplate; public void setString(String key, Object data, Long timeout) { if (data instanceof String) { String value = (String) data; stringRedisTemplate.opsForValue().set(key, value); } if (timeout != null) { stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS); } } public Object getString(String key) { return stringRedisTemplate.opsForValue().get(key); } public void delKey(String key) { stringRedisTemplate.delete(key); } }
2. RedisTokenUtils工具类
@Component 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; } }
3.自定义Api幂等注解和切面
1. 注解:
@Target(value = ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ExtApiIdempotent { String value(); }
2. 切面:
@Aspect @Component public class ExtApiAopIdempotent { @Autowired private RedisTokenUtils redisTokenUtils; @Pointcut("execution(public * com.itmayiedu.controller.*.*(..))") public void rlAop() { } @Around("rlAop()") public Object doBefore(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature(); ExtApiIdempotent extApiIdempotent = signature.getMethod().getDeclaredAnnotation(ExtApiIdempotent.class); if (extApiIdempotent == null) { // 直接执行程序 Object proceed = proceedingJoinPoint.proceed(); return proceed; } // 代码步骤: // 1.获取令牌 存放在请求头中 HttpServletRequest request = getRequest(); String token = request.getHeader("token"); if (StringUtils.isEmpty(token)) { response("参数错误!"); return null; } // 2.判断令牌是否在缓存中有对应的令牌 // 3.如何缓存没有该令牌的话,直接报错(请勿重复提交) // 4.如何缓存有该令牌的话,直接执行该业务逻辑 // 5.执行完业务逻辑之后,直接删除该令牌。 if (!redisTokenUtils.findToken(token)) { response("请勿重复提交!"); return null; } Object proceed = proceedingJoinPoint.proceed(); return proceed; } 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(); } } }
4.幂等注解使用
// 从redis中获取Token @RequestMapping("/redisToken") public String RedisToken() { return redisTokenUtils.getToken(); } // 验证Token @RequestMapping(value = "/addOrderExtApiIdempotent", produces = "application/json; charset=utf-8") @ExtApiIdempotent public String addOrderExtApiIdempotent(@RequestBody OrderEntity orderEntity, HttpServletRequest request) { int result = orderMapper.addOrder(orderEntity); return result > 0 ? "添加成功" : "添加失败" + ""; }
5.封装生成token注解
@Target(value = ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ExtApiToken { }
6.改造ExtApiAopIdempotent
@Aspect @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"); } 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(); } } }
7.API接口保证幂等性
@RestController public class OrderController { @Autowired private OrderMapper orderMapper; @Autowired private RedisTokenUtils redisTokenUtils; // 从redis中获取Token @RequestMapping("/redisToken") public String RedisToken() { return redisTokenUtils.getToken(); } // 验证Token @RequestMapping(value = "/addOrderExtApiIdempotent", produces = "application/json; charset=utf-8") @ExtApiIdempotent(value = ConstantUtils.EXTAPIHEAD) public String addOrderExtApiIdempotent(@RequestBody OrderEntity orderEntity, HttpServletRequest request) { int result = orderMapper.addOrder(orderEntity); return result > 0 ? "添加成功" : "添加失败" + ""; } }
8. 页面防止重复提交
@Controller 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"; } }