SpringBoot自定義註解+AOP+redis實現防介面冪等性重複提交,從概念到實戰

可愛的小鋒發表於2023-05-11

本文為千鋒教育技術團獨家創作,更多技術類知識乾貨,點個關注持續追更~

介面冪等性是Web開發中非常重要的一個概念,它可以保證多次呼叫同一個介面不會對結果產生影響。如果你想了解更多關於介面冪等性的知識,那麼本文就是一個不錯的起點。

在Web開發中,我們經常需要防止使用者重複提交某個操作,尤其是一些需要保證資料一致性的操作,如支付等。而介面冪等性就是解決這個問題的一種方案。

介面冪等性指的是:無論呼叫多少次同一個介面,最終的結果都是一致的。如果介面不具備冪等性,那麼多次呼叫可能會導致資料的不一致性,甚至產生莫名其妙的錯誤。**

那麼,如何實現介面冪等性呢?

本文小嶽將給大家介紹一種實現方案,即:使用SpringBoot自定義註解+AOP+redis來實現防介面冪等性重複提交。

1. 概念解析

1.1 介面冪等性

介面冪等性是指:同一個介面的多次呼叫,最終的結果都是一致的。這意味著,無論呼叫多少次介面,最終的結果都應該是相同的。這是因為介面的冪等性保證了多次呼叫介面不會對結果產生影響。

在Web開發中,保證介面冪等性非常重要。

例如, 假設我們有一個介面用來修改使用者資訊,那麼該介面應該具備冪等性。如果使用者多次呼叫該介面,那麼最終的結果都應該是一致的,即使用者資訊被修改成功。如果介面不具備冪等性,那麼多次呼叫可能會導致資料的不一致性,甚至產生莫名其妙的錯誤。

為了實現介面的冪等性,我們可以使用一些技術手段,例如使用Token或者在服務端儲存請求的處理狀態。這些技術手段可以確保同一個請求只會被處理一次,從而保證介面的冪等性。

總之,介面冪等性是Web開發中非常重要的一個概念,它可以保證多次呼叫同一個介面不會對結果產生影響。因此,我們在開發過程中需要注意保證介面的冪等性,以確保系統的穩定性和資料的一致性。

1.2 防重複提交

防重複提交是指系統要能夠識別出使用者重複提交某個操作,並且不會再次執行該操作。這是為了避免資料的不一致性和重複操作產生的問題。

在本文中,我們使用自定義註解@Idempotent、AOP和Redis來實現防介面冪等性重複提交。

當一個請求被處理過後,我們會將請求的處理狀態儲存到Redis中,並設定一個過期時間,以保證不會一直佔用Redis的記憶體空間。來看示例程式碼:

@RestController
public class DemoController {

    @Autowired
    private RedisTemplate redisTemplate;

    @GetMapping("/demo")
    @Idempotent(expire = 60)
    public String demo(@RequestParam("id") Long id) {
        if (redisTemplate.hasKey("demo:" + id)) {
            return "請勿重複提交";
        }
        // 處理請求
        redisTemplate.opsForValue().set("demo:" + id, "1", 60, TimeUnit.SECONDS);
        return "success";
    }
}

在上面的程式碼中,我們在demo方法上使用了自定義註解@Idempotent,並設定了過期時間為60秒。當一個請求被處理過後,我們會將請求的處理狀態儲存到Redis中,以保證在60秒內不會再次執行該操作。如果使用者重複提交該操作,那麼系統會返回請勿重複提交的提示。這樣就可以有效地避免介面重複提交產生的問題。

需要注意的是,為了防止多次請求同時到達伺服器,導致多次同時處理,我們需要在Redis中加鎖,可以使用Redis的setnx命令或者分散式鎖來實現。另外,為了保證冪等性,我們需要保證請求是冪等的,即多次請求的結果都是一致的。如果請求不是冪等的,那麼我們需要對請求進行去重處理,以保證只有一個請求被處理。

2. 實現方案

2.1 自定義註解

為了實現介面的冪等性,我們需要先定義一個自定義註解。註解的作用是標記一個方法是否支援冪等性。如果支援冪等性,那麼就需要對該方法進行特殊處理,使得多次呼叫該方法不會對結果產生影響。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
}

2.2 AOP切面

我們可以使用AOP來判斷一個方法是否被標記了@Idempotent註解。如果被標記了註解,那麼就需要對該方法進行特殊處理,以實現冪等性。

@Aspect
@Component
public class IdempotentAspect {

    private final RedisTemplate redisTemplate;

    @Autowired
    public IdempotentAspect(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Around("@annotation(com.example.demo.annotation.Idempotent)")
    public Object idempotent(ProceedingJoinPoint joinPoint) throws Throwable {
        // 獲取請求引數
        Object[] args = joinPoint.getArgs();
        // 獲取請求方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        // 獲取註解資訊
        Idempotent idempotent = method.getAnnotation(Idempotent.class);
        String key = getKey(joinPoint);
        // 判斷是否已經請求過
        if (redisTemplate.hasKey(key)) {
            throw new RuntimeException("請勿重複提交");
        }
        // 標記請求已經處理過
        redisTemplate.opsForValue().set(key, "1", idempotent.expire(), TimeUnit.SECONDS);
        // 處理請求
        return joinPoint.proceed(args);
    }

    /**
     * 獲取redis key
     */
    private String getKey(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String methodName = method.getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        Object[] args = joinPoint.getArgs();
        StringBuilder sb = new StringBuilder();
        sb.append(className).append(":").append(methodName);
        for (Object arg : args) {
            sb.append(":").append(arg.toString());
        }
        return sb.toString();
    }
}

2.3 Redis儲存

我們使用Redis來儲存請求的處理狀態。當一個請求被處理過後,我們會將請求的處理狀態儲存到Redis中,並設定一個過期時間,以保證不會一直佔用Redis的記憶體空間。

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

2.4 示例程式碼

下面是一個示例程式碼,該程式碼演示瞭如何使用@Idempotent註解來實現介面的冪等性。

@RestController
public class DemoController {

    @Autowired
    private RedisTemplate redisTemplate;

    @GetMapping("/demo")
    @Idempotent(expire = 60)
    public String demo(@RequestParam("id") Long id) {
        // 處理請求
        return "success";
    }
}

3. 總結

本文介紹瞭如何使用SpringBoot自定義註解+AOP+redis來實現防介面冪等性重複提交

我們首先定義了一個自定義註解@Idempotent,然後使用AOP來判斷一個方法是否被標記了該註解。如果被標記了該註解,那麼就需要對該方法進行特殊處理,以實現冪等性。最後,我們使用Redis來儲存請求的處理狀態,並設定一個過期時間,以保證不會一直佔用Redis的記憶體空間。


以上就是本文的全部內容,更多技術類乾貨,點我主頁持續追更~ 大家如果有技術類問題,歡迎和我們一起交流討論

祝天天開心!