? ? ? ? 注解用于接口方法、接口参数、和请求实体的属性上。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author dll
*/
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiLock {
/** lock的key前缀,不填时用会用方法路径作为key前缀*/
String prefix() default "";
/** 服务名,非必填,多服务时用*/
String serverName() default "";
}
import com.navigation.berth.common.util.LocalCache;
import com.navigation.common.core.exception.BizException;
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.stereotype.Component;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Objects;
@Aspect
@Component
public class ApiLockAspect {
/** 接口数据锁
*
* 1,只有方法上存在DataLock注解,没有key ,用 package+类名+方法名 组合成一个lock的key
* 2,只有方法上存在DataLock注解,有key ,用key做lock的key
* 3,若参数有注解,且注解是基本类型或String,Number,则用1,2,的key+参数做lock的key
* 4,若参数是实体类有注解,则用1,2,的key+实体类带注解的属性 做lock的key
*/
final static String SEPARATOR = ":";
final static Long LOCK_ACTION_TIME = 6000L * 60;
@Around("@annotation(com.navigation.berth.aop.ApiLock)")
public Object dataLockAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
String lockKey = getLockKey(joinPoint);
System.out.println("lockKey = " + lockKey);
if (LocalCache.LOCK.containsKey(lockKey)) {
throw new BizException("正在处理...");
}
// 加锁 多服务时改成redis锁,
try {
LocalCache.lock(lockKey, "1", LOCK_ACTION_TIME);
return joinPoint.proceed();
} catch (Throwable e) {
throw e;
} finally {
LocalCache.unLock(lockKey);
}
}
private String getLockKey(ProceedingJoinPoint joinPoint) throws IllegalAccessException {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
StringBuilder lockKey = new StringBuilder();
// 获取方法上的DataLock注解的key属性值
String serverName = method.getAnnotation(ApiLock.class).serverName();
String prefix = method.getAnnotation(ApiLock.class).prefix();
if (!serverName.trim().isEmpty()) {
lockKey.append(serverName).append(SEPARATOR);
}
if (!prefix.trim().isEmpty()) {
lockKey.append(prefix);
} else {
// 获取方法所在的类名和方法名
String className = signature.getDeclaringTypeName().replace(".", SEPARATOR);
String methodName = method.getName();
lockKey.append(className).append(SEPARATOR).append(methodName);
}
Object[] args = joinPoint.getArgs();
Annotation[][] pa = method.getParameterAnnotations();
for (int i = 0; i < args.length; i++) {
for (Annotation annotation : pa[i]) {
if (annotation instanceof ApiLock) {
// 参数是基本类型或String,Number,Boolean包装类型,则只到这一层
if (args[i].getClass().isPrimitive() || args[i] instanceof Number
|| args[i] instanceof String || args[i] instanceof Boolean) {
lockKey.append(SEPARATOR).append(args[i]);
break;
}
// 实体类再遍历属性
Field[] fields = args[i].getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(ApiLock.class)) {
field.setAccessible(true);
Object value = field.get(args[i]);
if (!Objects.isNull(value)) {
lockKey.append(SEPARATOR).append(value);
}
}
}
}
}
}
return lockKey.toString();
}
}
import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.TimedCache;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author dll
*/
public class LocalCache {
// 超过一分钟缓存自动删除
public static final TimedCache<String, String> LOCK = CacheUtil.newTimedCache(1000*60);
static {
/** 每100ms检查一次过期 */
LOCK.schedulePrune(100);
}
public static void put(TimedCache<String, String> cache,String key, String value, Long timeout) {
/** 设置消逝时间 */
cache.put(key, value, timeout);
}
static Lock rtLock = new ReentrantLock();
public static boolean lock(String key, String value, Long timeout) {
if (rtLock.tryLock()) {
try {
if (LOCK.containsKey(key)) {
return false;
} else {
LocalCache.put(LOCK, key, "1", timeout);
return true;
}
} finally {
rtLock.unlock();
}
} else {
return false;
}
}
public static void unLock(String key) {
LOCK.remove(key);
}
}
public class BizException extends RuntimeException {
public BizException(String message) {
super(message);
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 业务异常处理
*/
@ExceptionHandler(BizException.class)
public Result handleBizException(HttpServletRequest request, BizException ex) {
log.error("请求地址URL: " + request.getRequestURL());
log.error(SeExceptionUtils.getStackTrace(ex), ex);
return Result.error(ex.getMessage());
}
}
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
/**
* @author dll
*/
@Data
@SuperBuilder
@NoArgsConstructor
@Accessors(chain = true)
public class MyQueryVO {
@ApiLock
private Integer abc;
private String recordNo;
@ApiLock
private String deviceSn;
@ApiLock
private String spaceNo;
@ApiLock
private int num ;
@ApiLock
private boolean bool ;
}
import cn.hutool.core.thread.ThreadUtil;
import com.navigation.berth.aop.ApiLock;
import com.navigation.berth.aop.MyQueryVO;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
@RequestMapping("lock")
public class ApiLockTestController {
final static String prefix = "test:api:list";
@ApiOperation(value = "查询停车记录", notes = "查询停车记录")
@PostMapping("list/{id}/{b}")
@ApiLock(prefix = prefix)
public String list(@PathVariable int id, @PathVariable @ApiLock boolean b, @RequestBody @ApiLock MyQueryVO vo) {
// 休眠10秒,模拟业务操作
ThreadUtil.sleep(10000);
return "ok";
}
@ApiOperation(value = "查询停车记录-根据 id 查询", notes = "查询停车记录-根据 id 查询")
@GetMapping("{id}")
@ApiLock(serverName = "testSystem") // prefix没赋值会用方法路径赋值,prefix只在方法上有效 在参数和属性上没用
public String get(@PathVariable @ApiLock Long id) {
return "ok";
}
}