Idempotent non-intrusive implementation

Get into the habit of writing together! This is the 13th day of my participation in the "Nuggets Daily New Plan · April Update Challenge", click to view the event details .

idempotency

What are we going to talk about today 幂等性?

百度百科The parsing of the reference is as follows:

Idempotent (idempotent, idempotence) is a mathematical and computer science concept commonly used in abstract algebra .

The characteristic of an idempotent operation in programming is that any multiple executions of it have the same effect as a single execution. An idempotent function, or idempotent method, is a function that can take the same parameters 重复执行and get something 相同结果. These functions do not affect the system state, and there is no need to worry that repeated execution will cause changes to the system. For example, the "setTrue()" function is an idempotent function, no matter how many times it is executed, the result is the same. More complex operations are guaranteed to be idempotent using a unique transaction number (serial number).

This analysis is indeed a little bit , and everyone has a look at it! ! ! (●'◡'●)

For us 程序员, we are more concerned with the following issues:

Where and in what scenarios do you need to use idempotency?

Idempotent, what do we need to do, how to achieve idempotence?

image.png

In what scenarios do you need to use idempotency

  • Front-end form repeated submission problem
  • User order payment problem
  • Bank business handling number issue
  • The user maliciously adjusts the interface problem
  • The interface times out and repeatedly submits the problem
  • Repeated consumption of MQ messages
  • ...

Of course, there are many scenarios that will be used 幂等, so we won't list them one by one here.

So how do we design an idempotent function, and still 代码非侵入式?

代码非侵入式It means that our business logic code does not need to deal with 幂等the logic of verification.

Business functions are not processed? Who will handle it? Don't worry, listen to the buddies one by one. ^_^

image.png

这里,要实现代码非侵入式的幂等校验,我们就要使用到切面编程了(@Aspect

幂等的实现原理

在系统中一些接口需要增加幂等处理,幂等的概念是一个业务请求只能执行一次。类似银行业务办理,首先需要取一个号,然后用户使用这个号去柜台办理业务。这个号只能使用一次,如果过期或者已办理这个号就无效了。

我们的幂等也是使用这种原理。

  • 1.首先客户端调用通过我们的系统获取一个号,我们称之为幂等号,这个号已经存在我们的系统中。
  • 2.客户端使用这个号,调用我们的接口。
  • 3.我们系统判断这个号在我们的系统中已经存在,如果存在则允许业务办理,如果不存在,则表示这是一个非法的号,我们直接抛出异常。
  • 4.当业务处理完成,我们会将这个号从我们的系统中删除掉。

好了,这实现步骤,也是十分清晰了呀!!!^_^

那么我们下面就来看代码如何实现了

幂等的代码实现

  • 定义一个幂等处理接口
public interface Idempotence {
    /**
     * 检查是否存在幂等号
     * @param idempotenceId 幂等号
     * @return 是否存在
     */
    boolean check(String idempotenceId);

    /**
     * 记录幂等号
     * @param idempotenceId 幂等号
     */
    void record(String idempotenceId);

    /**
     * 记录幂等号
     * @param idempotenceId 幂等号
     * @param time 过期时间
     */
    void record(String idempotenceId, Integer time);

    /**
     * 删除幂等号
     * @param idempotenceId 幂等号
     */
    void delete(String idempotenceId);

}
复制代码
  • 定义一个幂等处理接口实现类
@Component
public class RedisIdempotence implements Idempotence {
    @Autowired
    private RedisRepository redisRepository;

    @Override
    public boolean check(String idempotenceId) {
        return redisRepository.exists(idempotenceId);
    }

    @Override
    public void record(String idempotenceId) {
        redisRepository.set(idempotenceId,"1");
    }

    @Override
    public void record(String idempotenceId,Integer time) {
        redisRepository.setExpire(idempotenceId,"1",time);
    }

    @Override
    public void delete(String idempotenceId) {
        redisRepository.del(idempotenceId);
    }
}
复制代码

这个实现类,咱们就用redis存储这个幂等号 实现4个方法:

检查是否存在幂等号

记录幂等号

记录幂等号(带过期时间)

删除幂等号

  • 幂等工具类
@Component
public class IdempotenceUtil {
    @Autowired
    private RedisRepository redisRepository;
    /**
     * 生成幂等号
     * @return
     */
    public String generateId() {
        String uuid = UUID.randomUUID().toString();
        String uId=Base64Util.encode(uuid).toLowerCase();
        redisRepository.setExpire(uId,"1",1800);
        return uId;
    }

    /**
     * 从Header里面获取幂等号
     * @return
     */
    public String getHeaderIdempotenceId(){
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String idempotenceId=request.getHeader("idempotenceId");
        return idempotenceId;
    }
}
复制代码

这个工具类,提供两个方法。

1.生成一个幂等号,咱们就用uuid

2.从Header里面获取幂等号

  • 定义一个注解
/**
 * 接口增加幂等性
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IdempotenceRequired {

}
复制代码
  • 切面
@Aspect
@Slf4j
@Component
public class IdempotenceSupportAdvice {
    @Autowired
    private Idempotence idempotence;
    @Autowired
    IdempotenceUtil idempotenceUtil;

    /**
     * 拦截有@IdempotenceRequired 注解 的方法。
     */
    @Pointcut("@annotation(xxx.xxx.IdempotenceRequired)")
    public void idempotenceMethod(){}

    @AfterThrowing(value = "idempotenceMethod()()",throwing = "e")
    public void afterThrowing(Throwable e){
        if(!(e instanceof IdempotencyException)) {
            // 从HTTP header中获取幂等号idempotenceId
            String idempotenceId = idempotenceUtil.getHeaderIdempotenceId();
            idempotence.record(idempotenceId, 1800);
        }
    }

    @Around(value = "idempotenceMethod()")
    public Object around(ProceedingJoinPoint  joinPoint) throws Throwable {
        // 从HTTP header中获取幂等号idempotenceId
        String idempotenceId = idempotenceUtil.getHeaderIdempotenceId();
        if(StringUtils.isEmpty(idempotenceId)){
            //不存在幂等号则不进行额外操作
            return joinPoint.proceed();
        }
        // 前置操作 幂等号是否存在
        boolean existed = idempotence.check(idempotenceId);
        if (!existed) {
            throw new IdempotencyException("{success:false,message:"操作重复,请重新输入幂等号重试!",data:-2}");
        }
        //删除幂等号
        idempotence.delete(idempotenceId);
        Object result = joinPoint.proceed();

        return result;
    }
}
复制代码
  • 定义个controller
@RequestMapping("/idempotence")
public class IdempotenceController {
    /**
     * 生成幂等号
     * @return
     */
    @GetMapping("/generateId")
    public JsonResult generateId(){
        IdempotenceUtil idempotenceUtil=SpringUtil.getBean(IdempotenceUtil.class);
        String uId=idempotenceUtil.generateId();
        return JsonResult.success("成功生成!").setData(uId);
    }
}
复制代码

好了,实现的代码,就是这些了,理解起来也是比较简单,没有过多复杂的逻辑。

接下来,就是如何使用的问题了,

image.png

这个使用,也是十分的简单啦!!!

幂等的使用

服务端:

不是所有的方法都需要切面拦截 ,只有 IdempotenceRequired 注解的方法才会被拦截。

例如下面接口:

    @IdempotenceRequired
    @PostMapping("/getUsers")
    public JsonResult getUsers(){
  
        //执行正常业务逻辑
        ...
    }
复制代码

When developing 幂等an interface, you only need to simply add an  IdempotenceRequired annotation to the method.

That's basically 代码非侵入式it ! ! !

image.png

Client:

After the server is processed, the following steps need to be performed when the client accesses the interface:

  • need to get first幂等号
  • and then 幂等号add it to the request header
  • 1. Get幂等号

http://service address/idempotence/generateId

  • 2. Request call

Add an idempotent sign to the header

image.png

Well, the idempotent implementation here is complete! ! ! ^_^

Then we can happily write code! ! ! ^_^

image.png

Guess you like

Origin juejin.im/post/7085944825085689864