Vue3+Springboot实现前后端防抖增强的示例代码
作者:BillKu
这篇文章主要为大家详细介绍了Vue3如何结合Springboot实现前后端防抖增强功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
我将基于您提供的防抖切面代码进行改进,主要优化点包括使用Redis实现分布式防抖、增强键生成策略、改进异常处理等。以下是完整的改进方案:
1. 改进的防抖注解 (Debounce.java)
package com.weiyu.anno;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Debounce {
/**
* 防抖时间(毫秒),默认5秒
*/
long value() default 5000;
/**
* 自定义键值(支持SpEL表达式)
*/
String key() default "";
/**
* 防抖键类型
*/
KeyType keyType() default KeyType.METHOD;
/**
* 错误提示信息
*/
String message() default "操作过于频繁,请稍后再试";
enum KeyType {
METHOD, // 方法级别防抖(默认)
USER, // 用户级别防抖(需要用户上下文)
IP, // IP级别防抖
PARAM, // 参数级别防抖(基于参数值)
CUSTOM // 完全自定义键
}
}2. 改进的防抖工具类 (DebounceUtil.java)
package com.weiyu.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.concurrent.TimeUnit;
@Component
public class DebounceUtil {
@Autowired
private StringRedisTemplate redisTemplate;
private static final ExpressionParser parser = new SpelExpressionParser();
/**
* 检查并设置防抖锁
* @param key 防抖键
* @param debounceTime 防抖时间(毫秒)
* @return 是否允许操作(true:允许,false:防抖中)
*/
public boolean checkAndSet(String key, long debounceTime) {
// 使用SETNX+EXPIRE原子操作
Boolean result = redisTemplate.opsForValue().setIfAbsent(
key,
"1",
debounceTime,
TimeUnit.MILLISECONDS
);
return result != null && result;
}
/**
* 生成防抖键
* @param joinPoint 切点
* @param keyType 键类型
* @param customKey 自定义键表达式
* @return 生成的防抖键
*/
public String generateKey(ProceedingJoinPoint joinPoint, Debounce.KeyType keyType, String customKey) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String methodName = signature.getDeclaringTypeName() + "#" + signature.getName();
// 1. 处理自定义SpEL表达式
if (keyType == Debounce.KeyType.CUSTOM && StringUtils.hasText(customKey)) {
return evaluateSpEL(joinPoint, customKey);
}
// 2. 处理其他键类型
switch (keyType) {
case USER:
return "DEBOUNCE:USER:" + getCurrentUserId() + ":" + methodName;
case IP:
return "DEBOUNCE:IP:" + getClientIp() + ":" + methodName;
case PARAM:
return "DEBOUNCE:PARAM:" + methodName + ":" + Arrays.hashCode(joinPoint.getArgs());
case CUSTOM: // 已处理,此处为fallback
case METHOD:
default:
return "DEBOUNCE:METHOD:" + methodName;
}
}
/**
* 解析SpEL表达式
*/
private String evaluateSpEL(ProceedingJoinPoint joinPoint, String expression) {
try {
StandardEvaluationContext context = new StandardEvaluationContext();
// 设置方法参数
context.setVariable("args", joinPoint.getArgs());
// 设置方法签名
context.setVariable("method", joinPoint.getSignature());
// 设置目标对象
context.setVariable("target", joinPoint.getTarget());
Expression exp = parser.parseExpression(expression);
return "DEBOUNCE:CUSTOM:" + exp.getValue(context, String.class);
} catch (Exception e) {
throw new IllegalArgumentException("Invalid SpEL expression: " + expression, e);
}
}
/**
* 获取当前用户ID(需要根据实际认证系统实现)
*/
private String getCurrentUserId() {
// 示例:从Spring Security上下文中获取
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
return authentication.getName();
}
return "ANONYMOUS";
}
/**
* 获取客户端IP
*/
private String getClientIp() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}3. 改进的防抖切面 (DebounceAspect.java)
package com.weiyu.aop;
import com.weiyu.anno.Debounce;
import com.weiyu.exception.DebounceException;
import com.weiyu.utils.DebounceUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import java.lang.reflect.Method;
/**
* 改进的防抖切面(支持Redis分布式锁)
*/
@Aspect
@Component
public class DebounceAspect {
@Autowired
private DebounceUtil debounceUtil;
@Around("@annotation(debounceAnnotation)")
public Object debounce(ProceedingJoinPoint joinPoint, Debounce debounceAnnotation) throws Throwable {
// 获取方法上的注解(处理继承情况)
if (debounceAnnotation == null) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
debounceAnnotation = AnnotationUtils.findAnnotation(method, Debounce.class);
if (debounceAnnotation == null) {
return joinPoint.proceed();
}
}
// 生成防抖键
String key = debounceUtil.generateKey(
joinPoint,
debounceAnnotation.keyType(),
debounceAnnotation.key()
);
// 获取防抖时间
long debounceTime = debounceAnnotation.value();
// 检查并设置防抖锁
if (!debounceUtil.checkAndSet(key, debounceTime)) {
throw new DebounceException(debounceAnnotation.message());
}
// 执行目标方法
try {
return joinPoint.proceed();
} catch (Throwable throwable) {
// 异常时移除防抖锁(可选)
// debounceUtil.removeLock(key);
throw throwable;
}
}
}4. 自定义防抖异常 (DebounceException.java)
package com.weiyu.exception;
/**
* 防抖业务异常
*/
public class DebounceException extends RuntimeException {
private int code = 429; // 使用429状态码表示操作过于频繁
public DebounceException(String message) {
super(message);
}
public int getCode() {
return code;
}
}5. 全局异常处理器 (GlobalExceptionHandler.java)
package com.weiyu.exception;
import com.weiyu.common.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理防抖异常
*/
@ExceptionHandler(DebounceException.class)
public Result handleDebounceException(DebounceException e) {
return Result.error(e.getCode(), e.getMessage());
}
}6. 统一响应对象 (Result.java)
package com.weiyu.common;
public class Result<T> {
private int code;
private String msg;
private T data;
// 成功静态方法
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMsg("操作成功");
result.setData(data);
return result;
}
// 错误静态方法
public static <T> Result<T> error(int code, String msg) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMsg(msg);
return result;
}
// Getters and Setters
}7. 使用示例
@RestController
@RequestMapping("/api/download")
public class DownloadController {
// 基本方法级别防抖(5秒内只能调用一次)
@Debounce
@GetMapping("/basic")
public Result<String> basicDownload() {
// ... 下载逻辑
return Result.success("下载成功");
}
// 用户级别防抖(每个用户5秒内只能下载一次)
@Debounce(keyType = Debounce.KeyType.USER, value = 5000)
@GetMapping("/user")
public Result<String> userSpecificDownload() {
// ... 下载逻辑
return Result.success("下载成功");
}
// 自定义键防抖(基于文件ID)
@Debounce(
keyType = Debounce.KeyType.CUSTOM,
key = "'FILE:' + #fileId", // SpEL表达式
message = "该文件正在下载中,请稍后再试"
)
@GetMapping("/file/{fileId}")
public Result<String> fileDownload(@PathVariable String fileId) {
// ... 下载逻辑
return Result.success("下载成功");
}
// IP级别防抖(每个IP 10秒内只能下载一次)
@Debounce(keyType = Debounce.KeyType.IP, value = 10000)
@GetMapping("/ip-based")
public Result<String> ipBasedDownload() {
// ... 下载逻辑
return Result.success("下载成功");
}
}8. 前端适配方案
// 在响应拦截器中处理防抖异常
instance.interceptors.response.use(
response => {
// ... 正常处理
},
error => {
if (error.response) {
// 识别防抖异常(HTTP 429状态码)
if (error.response.status === 429) {
ElMessage.error(error.response.data?.msg || "操作过于频繁,请稍后再试");
return Promise.reject(error);
}
// 识别业务层防抖异常(code=429)
const res = error.response.data;
if (res && res.code === 429) {
ElMessage.error(res.msg || "操作过于频繁,请稍后再试");
return Promise.reject(error);
}
}
// ... 其他错误处理
}
);改进亮点
1.分布式支持:
- 使用Redis实现分布式防抖锁
- 原子操作(SETNX + EXPIRE)确保并发安全
2.灵活的键策略:
- 支持方法、用户、IP、参数、自定义五种键类型
- 集成SpEL表达式引擎实现动态键生成
3.增强的异常处理:
- 自定义防抖异常类型(DebounceException)
- 统一使用429状态码(Too Many Requests)
- 可自定义错误消息
4.注解增强:
- 支持方法继承的注解查找
- 可配置防抖时间和错误消息
- 支持SpEL表达式自定义键
5.集成Spring生态:
- 自动获取请求上下文(IP、用户信息)
- 通过全局异常处理器统一处理
6.更精确的防抖:
- 精确到毫秒级别的防抖控制
- 避免本地内存防抖的分布式问题
这个改进方案保持了原有代码的结构和风格,同时增加了企业级应用所需的分布式支持和灵活性,特别适合在微服务架构中使用。
以上就是Vue3+Springboot实现前后端防抖增强的示例代码的详细内容,更多关于Vue Springboot防抖的资料请关注脚本之家其它相关文章!
