• 若依的放接口表单数据重复提交疑问


    关注 码龄 粉丝数 原力等级 -- 被采纳 被点赞 采纳率 Ⅲ1024 2024-04-20 20:05 采纳率: 62.5% 浏览 8 首页/ 编程语言 / 若依的放接口表单数据重复提交疑问 javaluaredis 若依防止接口重复提交下面是若依的放接口表单数据重复提交核心代码,在拦截器preHandle内调用的 疑问的点是 将用户的登录标识 访问路径 和参数 组成key存到redis中5秒钟之内是否一样 如果有则视为重复提交 但是这里个人感觉还是有并发问题 如果有人恶意用连点器大量发提交请求 因为判断逻辑是没有用原子性操作或加锁的 比如lua redisson之类的 那不是就存在问题吗 求指教 public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) { String nowParams = ""; if (request instanceof RepeatedlyRequestWrapper) { RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request; //现在传递表单参数 nowParams = HttpHelper.getBodyString(repeatedlyRequest); } // body参数为空,获取Parameter的数据 if (StringUtils.isEmpty(nowParams)) { nowParams = JSON.toJSONString(request.getParameterMap()); } Map nowDataMap = new HashMap(); nowDataMap.put(REPEAT_PARAMS, nowParams); nowDataMap.put(REPEAT_TIME, System.currentTimeMillis()); // 请求地址(作为存放cache的key值) String url = request.getRequestURI(); // 唯一值(没有消息头则使用请求地址) String submitKey = StringUtils.trimToEmpty(request.getHeader(header)); // 唯一标识(指定key + url + 消息头) String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY + url + submitKey; Object sessionObj = redisCache.getCacheObject(cacheRepeatKey); if (sessionObj != null) { Map sessionMap = (Map) sessionObj; if (sessionMap.containsKey(url)) { Map preDataMap = (Map) sessionMap.get(url); if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval())) { return true; } } } Map cacheMap = new HashMap(); cacheMap.put(url, nowDataMap); redisCache.setCacheObject(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS); return false; } 拦截器内代码 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class); if (annotation != null) { if (this.isRepeatSubmit(request, annotation)) { AjaxResult ajaxResult = AjaxResult.error(annotation.message()); ServletUtils.renderString(response, JSON.toJSONString(ajaxResult)); return false; } } return true; } else { return true; } } 展开全部 收起 写回答 好问题 0 提建议 追加酬金 关注问题 微信扫一扫 点击复制链接 分享 邀请回答 编辑 收藏 删除 结题 收藏 举报 追加酬金 (90%的用户在追加酬金后获得了解决方案) 当前问题酬金 ¥ 0 (可追加 ¥500) ¥ 15¥ 20¥ 50¥ 100¥ 200 支付方式 扫码支付 二维码出错 点击刷新 支付金额 15 元 提供问题酬金的用户不参与问题酬金结算和分配 支付即为同意 《付费问题酬金结算规则》 结题 再想想 删除 再等等

    3条回答 默认 最新

    • 专家-赤兔[在线] 全栈领域优质创作者 2024-04-20 20:05
      关注

      引自免费微信小程序:皆我百晓生

      针对您提到的问题,确实存在并发情况下可能的重复提交风险。为了提高并发控制并确保原子性,可以采用Redisson提供的分布式锁机制来解决这个问题。Redisson是一个基于Jedis的高性能Java Redis客户端,它提供了丰富的数据结构和分布式服务,包括分布式锁。

      首先,你需要在项目中引入Redisson依赖。如果您使用的是Maven,可以在pom.xml文件中添加以下依赖:

      
          org.redisson
          redisson
          3.16.7
      
      

      接下来,我们需要创建一个Redisson客户端,并配置它连接到您的Redis服务器。在Spring Boot应用中,可以创建一个RedissonConfig类:

      @Configuration
      public class RedissonConfig {
      
          @Value("${spring.redis.host}")
          private String host;
      
          @Value("${spring.redis.port}")
          private int port;
      
          @Bean
          public Config config() {
              Config config = new Config();
              config.useSingleServer()
                   .setAddress("redis://" + host + ":" + port);
              return config;
          }
      
          @Bean
          public RedissonClient redissonClient(Config config) {
              return Redisson.create(config);
          }
      }
      

      然后,修改isRepeatSubmit方法,利用Redisson的分布式锁来实现线程安全的判断:

      import org.redisson.api.RLock;
      import org.redisson.api.RedissonClient;
      import ...
      
      @Service
      public class YourService {
      
          @Autowired
          private RedissonClient redissonClient;
      
          ...
      
          public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) {
              ...
              
              // 使用Redisson的分布式锁
              RLock lock = redissonClient.getLock(cacheRepeatKey);
      
              try {
                  // 获取锁,如果获取失败,则表示其他线程正在处理同一请求,返回true
                  if (!lock.tryLock()) {
                      return true;
                  }
      
                  // 在这里执行原来的逻辑
                  Object sessionObj = redisCache.getCacheObject(cacheRepeatKey);
                  if (sessionObj != null) {
                      ...
                      if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval())) {
                          return true;
                      }
                  }
                  ...
      
                  // 设置缓存并释放锁
                  redisCache.setCacheObject(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS);
                  lock.unlock();
              } catch (Exception e) {
                  log.error("Error occurred while handling repeat submission", e);
              } finally {
                  // 无论结果如何,都要尝试释放锁
                  lock.unlock();
              }
      
              return false;
          }
      }
      

      通过这种方式,当多个请求同时到达时,只有一个请求能成功获取到锁并进行后续的检查与缓存操作,其余请求将被阻塞直到锁被释放。这大大降低了并发环境下重复提交的风险。

      注意:这个解决方案依赖于Spring框架和Redisson库。如果你的项目中没有使用Spring,那么你需要自行管理Redisson客户端的生命周期,以及使用对应的Java API来替代Spring提供的便利功能。

      展开全部

      评论 编辑记录
    • Kwan的解忧杂货铺 Java领域优质创作者 2024-04-20 20:05
      关注

      晚上好🌙🌙🌙
      本答案参考ChatGPT-3.5

      根据代码中的判断逻辑,如果用户在指定时间内,多次请求相同的接口地址和参数,则会拦截请求,返回“请勿重复提交”的错误信息。但是,由于代码中缺乏并发控制,存在恶意用户可能会利用连点器大量发起请求,导致判断逻辑失效的风险。

      为了避免这个问题,可以考虑使用原子性操作或加锁控制并发请求。有两种解决方案:

      1.使用Redisson分布式锁控制并发请求

      Redisson是一个高性能的分布式Java对象和服务锁定库。它基于Redis,实现了许多分布式锁及其相关的服务。可以在拦截器中尝试使用Redisson进行分布式锁控制,避免多个线程同时访问Redis缓存,保证判断逻辑的原子性操作。

      具体实现方式如下:

      1)引入Redisson依赖

      org.redisson redisson 3.12.5

      2)使用Redisson获取分布式锁

      // 引入Redisson配置类 @Autowired private RedissonClient redissonClient;

      // 获取分布式锁 RLock lock = redissonClient.getLock(cacheRepeatKey); lock.lock(annotation.interval(), TimeUnit.MILLISECONDS);

      // 判断重复提交逻辑 Object sessionObj = redisCache.getCacheObject(cacheRepeatKey); if (sessionObj != null) { // ... }

      // 释放锁 lock.unlock();

      2.使用Lua脚本操作Redis缓存

      Redis支持使用Lua脚本在单个Redis实例上执行原子性操作,这可以确保同时请求只能互斥地更新相同的缓存数据。在拦截器中,可以使用Lua脚本操作Redis缓存,避免多个线程同时访问Redis缓存导致判断逻辑失效。

      具体实现方式如下:

      1)在Redis客户端中定义Lua脚本

      local key = KEYS[1] local data = KEYS[2] local submitTime = KEYS[3] local expire = ARGV[1]

      if redis.call('exists', key) == 0 then redis.call('hset', key, data, submitTime) redis.call('expire', key, expire) return false end

      if tonumber(redis.call('hget', key, data)) == tonumber(submitTime) then redis.call('hset', key, data, submitTime) redis.call('expite', key, expire) return false end

      return true

      2)在拦截器中使用Lua脚本操作Redis缓存

      // 执行Lua脚本,加锁控制并发 String luaScript = "local key = KEYS[1]\n"+ "local data = KEYS[2]\n" + "local submitTime = KEYS[3]\n" + "local expire = ARGV[1]\n" + "if redis.call('exists', key) == 0 then\n" + " redis.call('hset', key, data, submitTime)\n" + " redis.call('expire', key, expire)\n" + " return false\n" + "end\n" + "if tonumber(redis.call('hget', key, data)) == tonumber(submitTime) then\n" + " redis.call('hset', key, data, submitTime)\n" + " redis.call('expite', key, expire)\n" + " return false\n" + "end\n" + "return true"; List> keysValues = Lists.newArrayList( KeyValue.pair("REPEAT_SUBMIT_KEY", cacheRepeatKey), KeyValue.pair("url", url), KeyValue.pair("nowParams", nowParams), KeyValue.pair("nowTime", String.valueOf(System.currentTimeMillis()))); Boolean result = template .execute(new DefaultRedisScript(luaScript, Boolean.class), keysValues.stream().map(KeyValue::getKey).collect(Collectors.toList()), keysValues.stream().map(KeyValue::getValue).collect(Collectors.toList()));

      // 判断重复提交逻辑 if (result) { // ... }

      上述两种方案都可以避免并发控制问题,按需选择即可。

      展开全部

      评论
    • 码农阿豪 Java领域优质创作者 2024-04-20 20:05
      关注
      让阿豪来帮你解答,本回答参考chatgpt3.5编写提供,如果还有疑问可以评论或留言
      问题:如何解决若依防止接口重复提交的并发问题? 解决方案:
      1. 使用类级别的同步锁 在 isRepeatSubmit() 方法上添加 synchronized 关键字,使得同一时间只有一个线程可以执行该方法。这种方式的缺陷是在高并发情况下,性能会受到影响。代码示例: public synchronized boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) { ... }
      2. 使用 Redis 分布式锁 使用 Redis 的分布式锁,保证同一时刻只有一个客户端可以执行 isRepeatSubmit() 方法。需要注意的是,锁的粒度需要控制好,过细的锁可能导致性能问题。代码示例: //获取锁 RLock lock = redisson.getLock(lockKey); //尝试加锁,最多等待3秒,上锁以后10秒自动解锁 boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS); if (locked) { //执行重复提交检查 try { ... } finally { //解锁 lock.unlock(); } }
      3. 使用 Redis 原子操作 Redis 提供了一些原子操作,例如 SETNX、INCR 等,可以避免多线程并发访问带来的问题。可以使用 Redis 的 SETNX 命令来保证同一时刻只有一个客户端可以执行 isRepeatSubmit() 方法。代码示例: //尝试设置缓存,如果设置成功则表示没有其他线程在检查重复提交 Boolean success = redisCache.setIfAbsent(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS); if (success) { try { ... } finally { //删除缓存,释放锁 redisCache.delete(cacheRepeatKey); } } 总结: 在高并发情况下,防止接口重复提交是一个常见的需求。为了保证程序的正确性和性能,需要考虑锁的粒度、锁的类型等因素。切勿过度依赖锁,需要注意锁的开销和并发冲突的可能性。建议使用 Redis 来实现分布式锁,或者使用 Redis 的原子操作来实现并发控制。
      评论
    编辑
    预览

    报告相同问题?

  • 相关阅读:
    设计模式3-分类
    Selenium+Java 环境搭建
    iHRM 人力资源管理系统_第11章_刷脸登录
    LeetCode 387 字符串中的第一个唯一字符 简单
    优雅而高效的JavaScript——防抖和节流
    用于旅行商问题的离散布谷鸟算法
    消息总线 —— SpringCloud Bus
    RabbitMq安装过程
    【嵌入式linux】Ubuntu 修改用户名
    python 生成器
  • 原文地址:https://ask.csdn.net/questions/8091990