第一步 引入依赖pom.xml:
org.redisson
redisson
3.16.3
com.fasterxml.jackson.core
jackson-databind
2.11.1
com.fasterxml.jackson.core
jackson-core
2.11.1
com.fasterxml.jackson.core
jackson-annotations
2.11.1
第二步 增加配置:
redis:
host: localhost # Redis服务器地址
database: 4 # Redis数据库索引(默认为0)
port: 6379 # Redis服务器连接端口
password: w23456
timeout: 30000ms # 连接超时时间(毫秒)
第三步 增加自定义注解:
package com.hxnwm.ny.diancan.common.annotaiton;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @ClassName SubmitLimit
* @Description TODO
* @Author wdj
* @Date 2023/7/25 18:01
* @Version
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface SubmitLimit {
/**
* 指定时间内不可重复提交(仅相对上一次发起请求时间差),单位毫秒
* @return
*/
int waitTime() default 1000;
/**
* 指定请求头部key,可以组合生成签名
* @return
*/
String[] customerHeaders() default {};
/**
* 自定义重复提交提示语
* @return
*/
String customerTipMsg() default "";
}
package com.hxnwm.ny.diancan.common.aspect;
import com.hxnwm.ny.diancan.common.annotaiton.SubmitLimit;
import com.hxnwm.ny.diancan.common.lock.DistributedLockService;
import com.hxnwm.ny.diancan.common.result.Result;
import com.hxnwm.ny.diancan.common.utils.JacksonUtils;
import org.apache.commons.codec.digest.DigestUtils;
import org.aspectj.lang.JoinPoint;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.ExtendedServletRequestDataBinder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Objects;
/**
* @ClassName SubmitLimitAspect
* @Description TODO
* @Author wdj
* @Date 2023/7/27 9:07
* @Version
*/
@Order(1)
@Aspect
@Component
public class SubmitLimitAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(SubmitLimitAspect.class);
/**
* redis分割符
*/
private static final String REDIS_SEPARATOR = ":";
/**
* 默认重复提交提示语
*/
private static final String DEFAULT_TIP_MSG = "服务正在处理,请勿重复提交!";
@Autowired
private DistributedLockService distributedLockService;
/**
* 方法调用环绕拦截
*/
@Around(value = "@annotation(com.hxnwm.ny.diancan.common.annotaiton.SubmitLimit)")
public Object doAround(ProceedingJoinPoint joinPoint){
HttpServletRequest request = getHttpServletRequest();
if(Objects.isNull(request)){
return Result.error(5001,"请求参数不能为空!");
}
//获取注解配置的参数
SubmitLimit submitLimit = getSubmitLimit(joinPoint);
//组合生成key,通过key实现加锁和解锁
String lockKey = buildSubmitLimitKey(joinPoint, request, submitLimit.customerHeaders());
//尝试在指定的时间内加锁
boolean lock = distributedLockService.acquireLock(lockKey, submitLimit.waitTime());
if(!lock){
String tipMsg = StringUtils.isEmpty(submitLimit.customerTipMsg()) ? DEFAULT_TIP_MSG : submitLimit.customerTipMsg();
return Result.error(5001,tipMsg);
}
try {
//继续执行后续流程
return execute(joinPoint);
} finally {
//执行完毕之后,手动将锁释放
distributedLockService.releaseLock();
}
}
/**
* 执行任务
* @param joinPoint
* @return
*/
private Object execute(ProceedingJoinPoint joinPoint){
try {
return joinPoint.proceed();
} catch (Exception e) {
return Result.error(5001,e.getMessage());
} catch (Throwable e) {
LOGGER.error("业务处理发生异常,错误信息:",e);
return Result.error(5001,"业务处理发生异常");
}
}
/**
* 获取请求对象
* @return
*/
private HttpServletRequest getHttpServletRequest(){
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes)ra;
HttpServletRequest request = sra.getRequest();
return request;
}
/**
* 获取注解值
* @param joinPoint
* @return
*/
private SubmitLimit getSubmitLimit(JoinPoint joinPoint){
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
SubmitLimit submitLimit = method.getAnnotation(SubmitLimit.class);
return submitLimit;
}
/**
* 组合生成lockKey
* 生成规则:接口名+方法名+请求参数签名(对请求头部参数+请求body参数,取SHA1值)
* @param joinPoint
* @param request
* @param customerHeaders
* @return
*/
private String buildSubmitLimitKey(JoinPoint joinPoint, HttpServletRequest request, String[] customerHeaders){
//请求参数=请求头部+请求body
String requestHeader = getRequestHeader(request, customerHeaders);
String requestBody = getRequestBody(joinPoint.getArgs());
String requestParamSign = DigestUtils.sha1Hex(requestHeader + requestBody);
String submitLimitKey = new StringBuilder()
.append(joinPoint.getSignature().getDeclaringType().getSimpleName())
.append(REDIS_SEPARATOR)
.append(joinPoint.getSignature().getName())
.append(REDIS_SEPARATOR)
.append(requestParamSign)
.toString();
return submitLimitKey;
}
/**
* 获取指定请求头部参数
* @param request
* @param customerHeaders
* @return
*/
private String getRequestHeader(HttpServletRequest request, String[] customerHeaders){
if (Objects.isNull(customerHeaders)) {
return "";
}
StringBuilder sb = new StringBuilder();
for (String headerKey : customerHeaders) {
sb.append(request.getHeader(headerKey));
}
return sb.toString();
}
/**
* 获取请求body参数
* @param args
* @return
*/
private String getRequestBody(Object[] args){
if (Objects.isNull(args)) {
return "";
}
StringBuilder sb = new StringBuilder();
for (Object arg : args) {
if (arg instanceof HttpServletRequest
|| arg instanceof HttpServletResponse
|| arg instanceof MultipartFile
|| arg instanceof BindResult
|| arg instanceof MultipartFile[]
|| arg instanceof ModelMap
|| arg instanceof Model
|| arg instanceof ExtendedServletRequestDataBinder
|| arg instanceof byte[]) {
continue;
}
sb.append(JacksonUtils.toJson(arg));
}
return sb.toString();
}
}
第四步 增加分布式服务:
package com.hxnwm.ny.diancan.common.lock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class DistributedLockService {
// 锁的过期时间,单位秒
private static final int LOCK_EXPIRE = 30;
@Autowired
private RedissonClient redissonClient;
private RLock lock;
public boolean acquireLock(String lockKey,int lockExpire) {
if (lockExpire<=0){lockExpire=LOCK_EXPIRE;}
try {
lock= redissonClient.getLock(lockKey);
// 尝试获取锁,设置锁的自动过期时间为 LOCK_EXPIRE 秒
boolean tryLock = lock.tryLock(lockExpire, TimeUnit.SECONDS);
return tryLock;
} catch (Exception e) {
e.printStackTrace();
Thread.currentThread().interrupt();
return false;
}
}
public void releaseLock() {
lock.unlock();
}
}
最后一步:使用。在控制层增加@SubmitLimit(customerHeaders = {"token"}, customerTipMsg = "正在加紧为您处理,请勿重复下单!")
在不改变原有逻辑上增加额外的功能
/**
* 下单详情
*
* @author knight
* @time 2022/3/22 13:51
*/
@SubmitLimit(customerHeaders = {"token"}, customerTipMsg = "正在加紧为您处理,请勿重复下单!")
@RequestMapping(value = "submit/detail", method = RequestMethod.POST)
public Result submitDetail(@Validated @RequestBody CartOrderInfo cartOrderInfo) {
return Result.handlerData(this.orderManage.submitDetail(cartOrderInfo));
}