spring项目aop实现接口防止连续点击锁

发布时间:2024年01月20日

aop实现

????????1,注解

? ? ? ? 注解用于接口方法、接口参数、和请求实体的属性上。

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 "";
}

????????2,切面

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();
    }
}

? ? ? ? 3,hutool本地锁,可用redis锁替换

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);
    }

}

自定义异常处理

? ? ? ? 1,自定义异常类

public class BizException extends RuntimeException {

    public BizException(String message) {
        super(message);
    }
}

????????2,全局异常处理


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());
    }
}

测试

????????1,测试接口请求实体

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 ;
}

????????2,测试接口

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";
    }

}

? ? ? ? 3,模拟连续操作

? ? ? ? ? ? ? ? 第一次操作

? ? ? ? ? ? ? ? 10秒内第二次操作

文章来源:https://blog.csdn.net/daizikui/article/details/135716041
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。