服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - Java结合redis实现接口防重复提交

Java结合redis实现接口防重复提交

2022-01-11 00:50帆非凡 Java教程

本文主要介绍了Java结合redis实现接口防重复提交,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

redis 接口防重

技术点:redis/aop

说明:

简易版本实现防止重复提交,适用范围为所有接口适用,采用注解方式,在需要防重的接口上使用注解,可以设置防重时效。

场景:

在系统中,经常会有一些接口会莫名其妙的被调用两次,可能在幂等接口中不会存在太大的问题,但是非幂等接口的处理就会导致出现脏数据,甚至影响系统的正确性。

选型参考:

在常见的防重处理分为多种,粗分为前端处理,后端处理

前端处理分为:

  • 在按钮触发后便将按钮置灰,设置为不可用,在接口调用成功后回调处理,将按钮恢复
  • 发送请求时,设置一个状态,在接口请求时去获取状态,查看在保护期是否已经有调用,思路与第一条类似

后端处理分为:

  • 版本号,在数据表中增加version字段,在我们需要进行防重的接口请求到达后端后,sql处理时增加版本号条件(切记:每次在修改操作后需要对版本号进行加1哦),如果不一致则不进行处理。这也是乐观锁实现的一种思路。
  • redis,即本文所述方式

选型原因

  • 在系统安全中,防重复提交也是比较重要的一个指标,也就是接口幂等性。所以我们在日常的系统开发中,一般使用的是简化版的放重复。也就是仅仅通过前端来进行防重控制,但是这样也是具有风险性的。如果在涉及比较重要的数据的时候,可能往往会有热心同行来帮我们找bug,对于他们可以直接通过抓报文的方式拿到我们的交互信息,以此来进行各种骚操作(羊毛党做派,当然了,如果要避免接口攻击,我们还要设置ip请求限制,小黑屋,防DDOS等各种防御工作,此处只讲防重咯)。所以我们在重要数据处理时在后端也是同样需要进行防重处理的。
  • 防重提交的方式非常非常多,如上提出了四种方式,也只是冰山一角了。针对于后端侧防重,如上简述了两种方式。个人觉得在不同的时机可以进行不同的选择。如果我们在项目初期,个人觉得使用版本号处理更为合适一点,这样会降低对第三方工具的依赖,因为我们在每加入一个新东西的时候都是会增加系统的复杂性的。我们在考虑性能,安全,可靠的时候就会多出一个事项,有点给自己找事做的样子。但是如果我们的系统已经较为平稳了,那么此时对数据库进行增加字段虽然也不会太难,但是会改动一些代码。惊喜总是从这些地方来的。使用redis我觉得就要合适一些了,我们只需要面向切面进行编程,一处编写,处处可用。从代码和扩展来讲redis就更为合适了。

以上内容都是瞎BB的,其实我也是个菜鸟,欢迎各位大佬提建议或者意见,大家共同进步,共同完善,让java圈充满激情四射的爱。

代码样例

?
1
2
3
4
5
6
@PostMapping("/user/update")
@ApiOperation(value = "修改用户信息", notes = "修改用户信息", tags = "user module")
@AvoidReSubmit(expireTime = 1000 * 3)
public void update(@RequestBody User user){
 userMapper.updateById(user);
}

具体代码实现

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 定义自定义注解,设置注解参数默认值
package top.withu.gaof.freehope.annotate;
 
import java.lang.annotation.*;
 
 
/**
 * @author Gaofan
 * @date 2019年10月12日 下午2:54:45
 * @describe 防止重复提交
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AvoidReSubmit {
 
    /**
     * 失效时间,即可以第二次提交间隔时长
     * @return
     */
    long expireTime() default 30 * 1000L;
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// 定义切面进行处理
package top.withu.gaof.freehope.aspect;
 
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import top.withu.gaof.freehope.annotate.AvoidReSubmit;
 
import javax.annotation.Resource;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.TimeUnit;
 
/**
 * @Description: TODO
 * @Author: gaofan
 * @Date: 2019/10/12 16:10
 * @Copyright: 2019 www.blog.freehope.top Inc. All rights reserved.
 **/
@Aspect
@Component
public class AvoidResumitAspect {
 
    @Resource
    private RedisTemplate redisTemplate;
 
    /**
     * 定义匹配规则,以便于后续拦截直接拦截submit方法,不用重复表达式
     */
    @Pointcut(value = "@annotation(top.withu.gaof.freehope.annotate.AvoidReSubmit)")
    public void submit() {
    }
 
    @Before("submit()&&@annotation(avoidReSubmit)")
    public void doBefore(JoinPoint joinPoint, AvoidReSubmit avoidReSubmit) {
 
        // 拼装参数
        StringBuffer sb = new StringBuffer();
        for(Object object : joinPoint.getArgs()){
            sb.append(object);
        }
 
        String key = md5(sb.toString());
        long expireTime = avoidReSubmit.expireTime();
        ValueOperations valueOperations = redisTemplate.opsForValue();
        Object object = valueOperations.get(key);
        if(null != object){
            throw new RuntimeException("您已经提交了请求,请不要重复提交哦!");
        }
        valueOperations.set(key, 1, expireTime, TimeUnit.MILLISECONDS);
    }
 
    @Around("submit()&&@annotation(avoidReSubmit)")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint, AvoidReSubmit avoidReSubmit) throws Throwable {
        System.out.println("环绕通知:");
        Object result = null;
        result = proceedingJoinPoint.proceed();
        return result;
    }
 
    @After("submit()")
    public void doAfter() {
        System.out.println("******拦截后的逻辑******");
    }
 
    private String md5(String str){
        if (str == null || str.length() == 0) {
            throw new IllegalArgumentException("String to encript cannot be null or zero length");
        }
        StringBuffer hexString = new StringBuffer();
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(str.getBytes());
            byte[] hash = md.digest();
            for (int i = 0; i < hash.length; i++) {
                if ((0xff & hash[i]) < 0x10) {
                    hexString.append("0" + Integer.toHexString((0xFF & hash[i])));
                } else {
                    hexString.append(Integer.toHexString(0xFF & hash[i]));
                }
            }
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return hexString.toString();
    }
}

思路:

简单的通过redis实现,估计版本在网上非常多了。这里的一个思路还是mark一下,现在我这代码只有我和上帝知道什么意思,我怕一个月以后就只有上帝知道了。

  • 自定义注解,注解中申明有效时间
  • 使用aop切面拦截自定义注解,获取注解中有效时间参数,此处默认设置保护期为30 * 1000L,单位毫秒
  • 在切面中获取接口请求的参数,将参数拼接成string,然后进行md5,这样操作是因为降低key长度,避免看起来过于恶心。但是这里有一个情况没有进行测试,那就是key碰撞的问题。在大量数据操作下是否会产生相同key值。
  • 使用md5加密后的key值到redis中查询,如果存在记录则表明已经有接口访问且处于保护期,不可继续提交。此处使用异常处理。如果不存在记录,则表明此接口在保护期内没有访问过,则不操作。此处的场景在使用时可以根据自己需求而定。
  • 此处在环绕通知和after通知均没有操作,因为我们只是对于放重复提交处理,业务场景中不存在后处理的情况,故而没有具体实现。

到此这篇关于Java结合redis实现接口防重复提交的文章就介绍到这了,更多相关redis 接口防重复提交内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家! 

原文链接:https://blog.csdn.net/sky_demo/article/details/102525226

延伸 · 阅读

精彩推荐
  • Java教程20个非常实用的Java程序代码片段

    20个非常实用的Java程序代码片段

    这篇文章主要为大家分享了20个非常实用的Java程序片段,对java开发项目有所帮助,感兴趣的小伙伴们可以参考一下 ...

    lijiao5352020-04-06
  • Java教程Java BufferWriter写文件写不进去或缺失数据的解决

    Java BufferWriter写文件写不进去或缺失数据的解决

    这篇文章主要介绍了Java BufferWriter写文件写不进去或缺失数据的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望...

    spcoder14552021-10-18
  • Java教程升级IDEA后Lombok不能使用的解决方法

    升级IDEA后Lombok不能使用的解决方法

    最近看到提示IDEA提示升级,寻思已经有好久没有升过级了。升级完毕重启之后,突然发现好多错误,本文就来介绍一下如何解决,感兴趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    这篇文章主要介绍了Java使用SAX解析xml的示例,帮助大家更好的理解和学习使用Java,感兴趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程xml与Java对象的转换详解

    xml与Java对象的转换详解

    这篇文章主要介绍了xml与Java对象的转换详解的相关资料,需要的朋友可以参考下...

    Java教程网2942020-09-17
  • Java教程Java实现抢红包功能

    Java实现抢红包功能

    这篇文章主要为大家详细介绍了Java实现抢红包功能,采用多线程模拟多人同时抢红包,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙...

    littleschemer13532021-05-16
  • Java教程小米推送Java代码

    小米推送Java代码

    今天小编就为大家分享一篇关于小米推送Java代码,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧...

    富贵稳中求8032021-07-12
  • Java教程Java8中Stream使用的一个注意事项

    Java8中Stream使用的一个注意事项

    最近在工作中发现了对于集合操作转换的神器,java8新特性 stream,但在使用中遇到了一个非常重要的注意点,所以这篇文章主要给大家介绍了关于Java8中S...

    阿杜7482021-02-04