1.@RedisLock注解
package com.dstcar.common.utils.lock;
import java.lang.annotation.*;
/**
* @author: liu wei ping
* @date: 2022/8/15 16:39
*/
@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {
/** 缓存Key,支持SPEL表达式 **/
String value() default "";
String tips() default "重复请求";
/** 过期时间(毫秒) **/
long expire() default 3000;
/** 最大过期时间(毫秒) **/
long maxExpire() default 300000;
/** 方法执行完释放 **/
boolean quitRelease() default true;
}
2.使用切片类RedisDistributedLockAspect监听@RedisLock主解
package com.dstcar.common.utils.lock;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import java.lang.reflect.Method;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 分布式时锁,用于防重提交,注意:业务内部还是要进行重复校验
配置
@Bean
public RedisDistributedLockAspect createRedisDistributedLockAspect(ObjectProvider<RedisTemplate> redisTemplate){
RedisDistributedLockAspect aspect = new RedisDistributedLockAspect();
aspect.setRedisTemplate(redisTemplate.getIfAvailable());
return aspect;
}
* @author: liu wei ping
* @date: 2022/8/15 16:35
*/
@Aspect
public class RedisDistributedLockAspect {
public static final String KEY_PREFIX = "DUPLICATION_SUBMIT:";
private RedisTemplate redisTemplate;
public void setRedisTemplate(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 切点
* @author: liu wei ping
* @date: 2022/08/15 16:45
**/
@Pointcut("@annotation(com.dstcar.common.utils.lock.RedisLock)")
public void getPointcut(){}
/**
*
**/
@Around("getPointcut()")
public Object doBefore(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
Object result;
if (null != method && method.isAnnotationPresent(RedisLock.class)){
RedisLock redisLock = method.getAnnotation(RedisLock.class);
String lockKey =
KEY_PREFIX+SpELAspectSupport.getKeyValue(point,redisLock.value());
String requestId = UUID.randomUUID().toString();
long expireTime = redisLock.expire();
if (redisLock.quitRelease()){
expireTime = redisLock.maxExpire();
}
boolean res = tryGetDistributedLock(lockKey,requestId,expireTime);
if (!res){
throw new DistributedLockException(redisLock.tips());
}
try {
result = point.proceed();
}finally {
if (redisLock.quitRelease()){
releaseDistributedLock(lockKey);
}
}
}else {
result = point.proceed();
}
return result;
}
/**
* 尝试获取分布式锁
* @param lockKey 锁
* @param expireTime 超期时间
* @return 是否获取成功
*/
public boolean tryGetDistributedLock(String lockKey, String requestId,long
expireTime) {
return redisTemplate.opsForValue().
setIfAbsent(lockKey,requestId,expireTime,TimeUnit.MILLISECONDS);
}
/**
* 释放分布式锁
*
* @param lockKey 锁
* @return 是否释放成功
*/
public boolean releaseDistributedLock(String lockKey) {
return redisTemplate.delete(lockKey);
}
}
3.切面的SPEL表达式工具类SpELAspectSupport
package com.dstcar.common.utils.lock;
import com.dstcar.common.utils.json.JsonUtil;
import com.dstcar.common.utils.string.MD5Helper;
import org.aspectj.lang.JoinPoint;
/**
*
* @author: liu wei ping
* @date: 2022/8/15 16:48
*/
public class SpELAspectSupport {
private static final ExpressionEvaluator evaluator = new ExpressionEvaluator();
public static String getKeyValue(JoinPoint jp,String keyExpression){
Object[] args = jp.getArgs();
String keyValue;
if (null != keyExpression && !"".equals(keyExpression.trim())){
keyValue = evaluator.getKey(keyExpression,jp);
}else if (args != null && args.length > 0 && args[0] != null){
keyValue = MD5Helper.encrypt32(JsonUtil.toJson(args[0]));
}else {
throw new IllegalArgumentException("key或方法入参必须存在一个且不可为null");
}
return keyValue;
}
}
4.SPEL表达式翻译类
package com.dstcar.common.utils.lock;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.context.expression.CachedExpressionEvaluator;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
*
* @author: liu wei ping
* @date: 2022/8/15 16:29
*/
public class ExpressionEvaluator extends CachedExpressionEvaluator {
private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<>(64);
private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>
(64);
/**
* 解析key
* @author: liu wei ping
* @date: 2022/8/15 16:29
**/
public String getKey(String conditionExpression,JoinPoint jp){
Object[] args = jp.getArgs();
Object target = jp.getTarget();
Class<?> targetClass = target.getClass();
MethodSignature signature = (MethodSignature) jp.getSignature();
Method method = signature.getMethod();
EvaluationContext evalContext =
createEvaluationContext(target,targetClass,method,args);
AnnotatedElementKey elementKey = new AnnotatedElementKey(method,targetClass);
return getExpression(this.conditionCache, elementKey,
conditionExpression).getValue(evalContext,String.class);
}
private EvaluationContext createEvaluationContext(Object target, Class<?>
targetClass, Method method, Object[] args) {
Method targetMethod = getTargetMethod(targetClass,method);
ExpressionRootObject rootObject = new ExpressionRootObject(target,args);
return new MethodBasedEvaluationContext(
rootObject, targetMethod, args, getParameterNameDiscoverer());
}
/**
* 获取目标方法
**/
private Method getTargetMethod(Class<?> targetClass,Method method){
AnnotatedElementKey methodKey = new AnnotatedElementKey(method,targetClass);
Method targetMethod = this.targetMethodCache.get(methodKey);
if (targetMethod == null){
targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
if (targetMethod == null) {
targetMethod = method;
}
this.targetMethodCache.put(methodKey, targetMethod);
}
return targetMethod;
}
}
package com.dstcar.common.utils.lock;
/**
*
* @author: liu wei ping
* @date: 2022/08/15 18:13
*/
public class ExpressionRootObject {
private final Object obj;
private final Object[] args;
public ExpressionRootObject(Object obj, Object[] args) {
this.obj = obj;
this.args = args;
}
public Object getObj() {
return obj;
}
public Object[] getArgs() {
return args;
}
}
5.Json工具类JsonUtil
package com.dstcar.common.utils.json;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.SimpleType;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author liu wei ping
* @date 创建时间:2022年8月15日 下午6:18:33
* @version 1.0
*/
@Slf4j
public class JsonUtil {
public final static ObjectMapper mapper = new ObjectMapper();
static {
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 多余的值不解析
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 解析器支持解析单引号
mapper.configure(Feature.ALLOW_SINGLE_QUOTES, true);
// 解析器支持解析结束符
mapper.configure(Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
// mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
mapper.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
// Include.NON_EMPTY 属性为 空(“”) 或者为 NULL 都不序列化
// Include.NON_NULL 属性为NULL 不序列化
mapper.setSerializationInclusion(Include.NON_EMPTY);
}
public static String toJson(Object obj) {
String json = null;
if (obj == null){
return json;
}
try {
json = mapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
log.error("转换JSON异常:{}",e);
}
return json;
}
/**
* 异常要抛出来
*
* @param jsonStr
* @param objClass
* @param <T>
* @return
* @throws IOException
*/
public static <T> T json2Bean(String jsonStr, Class<T> objClass) throws IOException {
if (jsonStr == null) {
return null;
}
return mapper.readValue(jsonStr, objClass);
}
public static <T> T toBean(String jsonStr, Class<T> objClass) {
if (jsonStr == null) {
return null;
}
T t = null;
try {
t = mapper.readValue(jsonStr, objClass);
} catch (Exception e) {
log.error("objClass转换BEAN异常详情:"+jsonStr,e);
}
return t;
}
public static <T> T toBean(Reader reader, Type type) {
T t = null;
try {
t = mapper.readValue(reader, mapper.constructType(type));
} catch (Exception e) {
}finally {
try {
if (reader != null){reader.close();}
}catch (IOException e){}
}
return t;
}
public static <T> T toBean(InputStream in, Type type) {
T t = null;
try {
t = mapper.readValue(in, mapper.constructType(type));
} catch (Exception e) {
} finally {
try {
if (in != null){in.close();}
}catch (IOException e){}
}
return t;
}
private static <T> T toBean(String jsonStr, JavaType type) {
if (jsonStr == null) {
return null;
}
T t = null;
try {
t = mapper.readValue(jsonStr, type);
} catch (Exception e) {
log.error("JavaType转换BEAN异常详情:"+jsonStr,e);
}
return t;
}
public static <T> T toBean(String jsonStr, Class<?> c1, Class<?> c2, Class<?> c3) {
return toBean(jsonStr, mapper.getTypeFactory().constructParametricType(c1,
mapper.getTypeFactory().constructParametricType(c2, c3)));
}
public static <T> T toBean(String jsonStr, Class<?> collectionClass, Class<?>... elementClasses) {
return toBean(jsonStr, mapper.getTypeFactory().constructParametricType(collectionClass, elementClasses));
}
private static <T> T toCollection(String jsonStr, Class<?> collectionClass, Class<?>... elementClasses) {
return toBean(jsonStr, mapper.getTypeFactory().constructParametricType(collectionClass, elementClasses));
}
public static <T> T toSet(String jsonStr, Class<?>... elementClasses) {
return toCollection(jsonStr, Set.class, elementClasses);
}
public static <T> T toList(String jsonStr, Class<?>... elementClasses) {
return toCollection(jsonStr, List.class, elementClasses);
}
public static <T> List<T> toList(File file, Class<T> elementClasses) {
try {
return mapper.readValue(file, mapper.getTypeFactory()
.constructParametricType(List.class, elementClasses));
} catch (IOException e) {}
return null;
}
public static <T> T toMap(String jsonStr) {
return toBean(jsonStr, mapper.getTypeFactory()
.constructMapType(Map.class,
String.class, Object.class));
}
public static <T> T toMap(String jsonStr, Class<?> c1, Class<?> c2, Class<?>... c3) {
JavaType keyType = SimpleType.constructUnsafe(c1);
JavaType valueType = mapper.getTypeFactory().constructParametricType(c2, c3);
return toBean(jsonStr, mapper.getTypeFactory().constructMapType(Map.class,
keyType, valueType));
}
public static <T> T convertValue(Object obj,Class<T> clazz){
return mapper.convertValue(obj,clazz);
}
public static <T> List<T> convertList(Object obj,Class<T> clazz){
return
mapper.convertValue(obj,mapper.getTypeFactory()
.constructCollectionType(List.class,clazz));
}
public static <K,V> Map<K,V> convertMap(Object obj,Class<K> kClazz,Class<V> vClazz){
return mapper.convertValue(obj,mapper.getTypeFactory()
.constructMapType(Map.class,kClazz,vClazz));
}
public static <T> T convertValue(Object obj,Type type){
return mapper.convertValue(obj,mapper.constructType(type));
}
public static byte[] writeValueAsBytes(Object obj) throws JsonProcessingException {
return mapper.writeValueAsBytes(obj);
}
}
6.防止重复提交异常
package com.dstcar.common.utils.lock;
/**
* 防止重复提交异常
* @author: liu wei ping
* @date: 2022/08/15 20:32
*/
public class DistributedLockException extends RuntimeException {
public DistributedLockException(String msg) {
super(msg);
}
}
7.在方法上加上@RedisLock注解
/**
* 盘点记录提交
* @param stockcountRecordSaveDto
* @return
*/
@Transactional
@RedisLock(value = "'StockcountRecordOpenApiController_stockcountSubmit_'
+#stockcountRecordSaveDto.stockcountRecord.id", quitRelease = false,expire = 30000)
public boolean stockcountSubmit(StockcountRecordSaveDto stockcountRecordSaveDto,
SSOUser ssoUser) {
log.info("stockcountSubmit.stockcountRecordSaveDto:{},ssoUser:
{}",toJson(stockcountRecordSaveDto),toJson(ssoUser));
StopWatch stopWatch = new StopWatch();
stopWatch.start("盘点记录提交开始:");
StockcountRecord stockcountRecord =
stockcountRecordSaveDto.getStockcountRecord();
Assert.notNull(stockcountRecord,"非法参数");
//修改盘点记录状态
updateStockcountRecord(stockcountRecordSaveDto,ssoUser);
//生成盘点记录明细数据
createStockcountRecordDetail(stockcountRecordSaveDto,ssoUser);
//生成车辆图片上传
uploadCarFile(stockcountRecordSaveDto,ssoUser);
//生成盘点差异明细
createStockcountRecordDiff(stockcountRecordSaveDto,ssoUser);
//生成盘点车况信息和修改车辆属性值和记录车辆车况和属性变更日志
createCarConditionInfo(stockcountRecordSaveDto,ssoUser);
//修改盘点记录是否有差异
updateStockcountRecordDiff(stockcountRecord);
stopWatch.stop();
log.info(stopWatch.prettyPrint());
return true;
}
8.效果如图: