Chiến lược Debouncing trong Java

Debouncing trong nghiệp vụ (chỉ thực hiện lần gọi cuối cùng):

Khi một sự kiện được kích hoạt, hệ thống sẽ trì hoãn việc thực thi callback trong n giây. Nếu trong khoảng thời gian n giây đó sự kiện bị kích hoạt lại, bộ đếm sẽ được thiết lập lại.

Tác dụng

Đối với các sự kiện xảy ra liên tục, trong một khoảng thời gian nhất định, chỉ phản hồi lần gọi cuối cùng. Nếu sự kiện được kích hoạt lại trong khoảng thời gian đó, thời gian chờ sẽ được tính lại.

Ứng dụng

  1. Ngăn chặn người dùng click quá nhanh gây ra quá nhiều yêu cầu (đăng nhập, gửi tin nhắn SMS...)
  2. Khi mạng của máy khách chậm hoặc máy chủ phản hồi chậm, người dùng có thể làm mới trang hoặc gửi lại biểu mẫu nhiều lần, điều này tạo gánh nặng cho máy chủ và có thể dẫn đến lỗi dữ liệu.

Quy trình

Dưới đây là cách thực hiện bằng cách sử dụng khía cạnh (Aspect) và bộ nhớ cache:

<!-- Cấu hình Redis và Redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>${redisson.version}</version>
</dependency>
/**
 * Công cụ Spring Redis
 *
 * @author zhangyu
 **/
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class CacheService {

    @Autowired
    private RedisTemplate redisTemplate;
    @Resource
    private Redisson redisson;

    /**
     * Lưu đối tượng cơ bản vào cache.
     *
     * @param key   khóa cache
     * @param value giá trị cần lưu
     */
    public <T> void storeCacheObject(String key, T value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * Lưu đối tượng cơ bản vào cache với thời gian sống.
     *
     * @param key      khóa cache
     * @param value    giá trị cần lưu
     * @param timeout  thời gian tồn tại
     * @param timeUnit đơn vị thời gian
     */
    public <T> void storeCacheObject(String key, T value, long timeout, TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * Kiểm tra xem khóa có tồn tại hay không.
     *
     * @param key khóa cần kiểm tra
     * @return true nếu tồn tại, false nếu không tồn tại
     */
    public boolean checkKeyExistence(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * Lấy đối tượng từ cache.
     *
     * @param key khóa cache
     * @return giá trị tương ứng với khóa
     */
    public <T> T fetchCacheObject(String key) {
        ValueOperations<String, T> operations = redisTemplate.opsForValue();
        return operations.get(key);
    }

    /**
     * Xóa đối tượng khỏi cache.
     *
     * @param key khóa cần xóa
     * @return true nếu thành công, false nếu thất bại
     */
    public boolean removeCacheObject(String key) {
        return redisTemplate.delete(key);
    }
}
/**
 * Định nghĩa annotation để ngăn chặn submit lặp lại.
 */
@Documented // Sinh tài liệu API
@Target({ElementType.METHOD}) // Áp dụng trên phương thức
@Retention(RetentionPolicy.RUNTIME) // Hiệu lực tại runtime
public @interface PreventDuplicateSubmission {
    /**
     * Tên tùy chọn cho annotation.
     *
     * @return tên mặc định
     */
    String name() default "defaultName";
}
/**
 * Khía cạnh xử lý submit lặp lại.
 */
@Aspect
@Slf4j
@Component
public class DuplicateSubmissionAspect {

    @Resource
    private CacheService cacheService;

    @Pointcut("@annotation(com.example.annotation.PreventDuplicateSubmission)")
    public void pointcut() {}

    @Around("pointcut()")
    public Object handleAround(ProceedingJoinPoint joinPoint) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Map<String, String[]> params = request.getParameterMap();

        // Tạo khóa duy nhất dựa trên tham số đầu vào
        String uniqueKey = "DUPLICATE_SUBMISSION:" + params.get("uniqueParam")[0];

        if (!cacheService.checkKeyExistence(uniqueKey)) {
            try {
                return joinPoint.proceed();
            } finally {
                cacheService.storeCacheObject(uniqueKey, 0, 15L, TimeUnit.SECONDS);
            }
        } else {
            log.warn("Không được phép submit lặp lại!");
            throw new RuntimeException("Hành động đã được thực hiện trước đó.");
        }
    }
}
@RestController
@RequestMapping("/api")
public class TestController {

    @ApiOperation(value = "Kiểm thử debouncing")
    @GetMapping("/debounce-test")
    @PreventDuplicateSubmission(name = "testDebounce")
    public ResponseEntity<?> testDebounce(@RequestParam String uniqueParam) {
        System.out.println("Kiểm thử debouncing...");
        return ResponseEntity.ok("Thành công!");
    }
}

Thẻ: Java Spring AOP Redis

Đăng vào ngày 14 tháng 6 lúc 00:39