当前位置:   article > 正文

接口重复提交解决方案

getrequest() can return not null
  1. /**
  2. * 防止重复提交的注解
  3. */
  4. @Retention(RetentionPolicy.RUNTIME)
  5. @Target({ElementType.METHOD})
  6. public @interface AvoidRepeatSubmit {
  7. long lockTime() default 1000;
  8. }

  

  1. import com.mushi.anno.AvoidRepeatSubmit;
  2. import com.mushi.config.ResultGenerator;
  3. import com.mushi.redis.service.RedisDistributedLock;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.aspectj.lang.ProceedingJoinPoint;
  6. import org.aspectj.lang.annotation.Around;
  7. import org.aspectj.lang.annotation.Aspect;
  8. import org.aspectj.lang.annotation.Pointcut;
  9. import org.springframework.beans.factory.annotation.Autowired;
  10. import org.springframework.stereotype.Component;
  11. import org.springframework.util.Assert;
  12. import org.springframework.web.context.request.RequestContextHolder;
  13. import org.springframework.web.context.request.ServletRequestAttributes;
  14. import javax.servlet.http.HttpServletRequest;
  15. import java.util.UUID;
  16. /**
  17. * 防止重复提交的切面
  18. */
  19. @Aspect
  20. @Component
  21. @Slf4j
  22. public class RepeatSubmitAspect {
  23. @Autowired
  24. private RedisDistributedLock redisDistributedLock;
  25. /**
  26. * 切点
  27. *
  28. * @param avoidRepeatSubmit
  29. */
  30. @Pointcut("@annotation(avoidRepeatSubmit)")
  31. public void pointCut(AvoidRepeatSubmit avoidRepeatSubmit) {
  32. }
  33. /**
  34. * 利用环绕通知进行处理重复提交问题
  35. *
  36. * @param pjp
  37. * @param avoidRepeatSubmit
  38. * @return
  39. * @throws Throwable
  40. */
  41. @Around("pointCut(avoidRepeatSubmit)")
  42. public Object around(ProceedingJoinPoint pjp, AvoidRepeatSubmit avoidRepeatSubmit) throws Throwable {
  43. /**
  44. * 获取锁的时间
  45. */
  46. long lockSeconds = avoidRepeatSubmit.lockTime();
  47. //获得request对象
  48. HttpServletRequest request = httpServletRequest();
  49. Assert.notNull(request, "request can not null");
  50. // 此处可以用token或者JSessionId
  51. String token = request.getHeader("token");
  52. String path = request.getServletPath();
  53. String key = getKey(token, path);
  54. String clientId = getClientId();
  55. //锁定多少秒
  56. boolean isSuccess = redisDistributedLock.setLock(key, clientId, lockSeconds);
  57. if (isSuccess) {
  58. log.info("tryLock success, key = [{}], clientId = [{}]", key, clientId);
  59. // 获取锁成功, 执行进程
  60. Object result;
  61. try {
  62. result = pjp.proceed();
  63. } finally {
  64. //解锁
  65. redisDistributedLock.releaseLock(key, clientId);
  66. log.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);
  67. }
  68. return result;
  69. } else {
  70. // 获取锁失败,认为是重复提交的请求
  71. log.info("tryLock fail, key = [{}]", key);
  72. return ResultGenerator.genRepeatSubmitResult("重复请求,请稍后再试");
  73. }
  74. }
  75. /**
  76. * 获得request对象
  77. *
  78. * @return
  79. */
  80. private HttpServletRequest httpServletRequest() {
  81. ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  82. return requestAttributes.getRequest();
  83. }
  84. /**
  85. * 获得请求key
  86. *
  87. * @param token
  88. * @param path
  89. * @return
  90. */
  91. private String getKey(String token, String path) {
  92. return token + path;
  93. }
  94. /**
  95. * 获得uuid
  96. *
  97. * @return
  98. */
  99. private String getClientId() {
  100. return UUID.randomUUID().toString();
  101. }
  102. }
  1. import org.slf4j.Logger;
  2. import org.slf4j.LoggerFactory;
  3. import org.springframework.data.redis.core.RedisCallback;
  4. import org.springframework.data.redis.core.RedisTemplate;
  5. import org.springframework.stereotype.Component;
  6. import org.springframework.util.StringUtils;
  7. import redis.clients.jedis.Jedis;
  8. import redis.clients.jedis.JedisCluster;
  9. import redis.clients.jedis.JedisCommands;
  10. import javax.annotation.Resource;
  11. import java.util.ArrayList;
  12. import java.util.List;
  13. @Component
  14. public class RedisDistributedLock {
  15. @Resource
  16. private RedisTemplate<String, Object> redisTemplate;
  17. public static final String UNLOCK_LUA;
  18. static {
  19. StringBuilder sb = new StringBuilder();
  20. sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
  21. sb.append("then ");
  22. sb.append(" return redis.call(\"del\",KEYS[1]) ");
  23. sb.append("else ");
  24. sb.append(" return 0 ");
  25. sb.append("end ");
  26. UNLOCK_LUA = sb.toString();
  27. }
  28. private final Logger logger = LoggerFactory.getLogger(RedisDistributedLock.class);
  29. public boolean setLock(String key, String clientId, long expire) {
  30. try {
  31. RedisCallback<String> callback = (connection) -> {
  32. JedisCommands commands = (JedisCommands) connection.getNativeConnection();
  33. return commands.set(key, clientId, "NX", "PX", expire);
  34. };
  35. String result = redisTemplate.execute(callback);
  36. return !StringUtils.isEmpty(result);
  37. } catch (Exception e) {
  38. logger.error("set redis occured an exception", e);
  39. }
  40. return false;
  41. }
  42. public String get(String key) {
  43. try {
  44. RedisCallback<String> callback = (connection) -> {
  45. JedisCommands commands = (JedisCommands) connection.getNativeConnection();
  46. return commands.get(key);
  47. };
  48. String result = redisTemplate.execute(callback);
  49. return result;
  50. } catch (Exception e) {
  51. logger.error("get redis occured an exception", e);
  52. }
  53. return "";
  54. }
  55. public boolean releaseLock(String key, String requestId) {
  56. // 释放锁的时候,有可能因为持锁之后方法执行时间大于锁的有效期,此时有可能已经被另外一个线程持有锁,所以不能直接删除
  57. try {
  58. List<String> keys = new ArrayList<>();
  59. keys.add(key);
  60. List<String> args = new ArrayList<>();
  61. args.add(requestId);
  62. // 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
  63. // spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本
  64. RedisCallback<Long> callback = (connection) -> {
  65. Object nativeConnection = connection.getNativeConnection();
  66. // 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
  67. // 集群模式
  68. if (nativeConnection instanceof JedisCluster) {
  69. return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
  70. }
  71. // 单机模式
  72. else if (nativeConnection instanceof Jedis) {
  73. return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
  74. }
  75. return 0L;
  76. };
  77. Long result = redisTemplate.execute(callback);
  78. return result != null && result > 0;
  79. } catch (Exception e) {
  80. logger.error("release lock occured an exception", e);
  81. } finally {
  82. // 清除掉ThreadLocal中的数据,避免内存溢出
  83. //lockFlag.remove();
  84. }
  85. return false;
  86. }
  87. }

  

  

 

然后再需要接口防重的接口上加上AvoidRepeatSubmit注解 

 

转载于:https://www.cnblogs.com/java-le/p/11056635.html

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/234253
推荐阅读
  

闽ICP备14008679号