commit 1dcefee9ddea0694899ab8f9eb9c750c51775e5c Author: niushuai233 Date: Mon Dec 19 11:48:44 2022 +0800 feat: 初次提交 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..d0e3ff5 --- /dev/null +++ b/pom.xml @@ -0,0 +1,71 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.6.2 + + + cc.niushuai.projects.demo + aspect-lock + 0.0.1-SNAPSHOT + aspect-lock + 使用切面的方式来对$加锁 + + 1.8 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-aop + + + + cn.hutool + hutool-all + 5.3.8 + + + + org.apache.poi + poi-excelant + 4.1.1 + + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/src/main/java/cc/niushuai/projects/demo/aspectlock/AspectLockApplication.java b/src/main/java/cc/niushuai/projects/demo/aspectlock/AspectLockApplication.java new file mode 100644 index 0000000..dc17fdc --- /dev/null +++ b/src/main/java/cc/niushuai/projects/demo/aspectlock/AspectLockApplication.java @@ -0,0 +1,19 @@ +package cc.niushuai.projects.demo.aspectlock; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 主应用 启动类 + * + * @author niushuai + * @date: 2021/12/22 9:59 + */ +@SpringBootApplication +public class AspectLockApplication { + + public static void main(String[] args) { + SpringApplication.run(AspectLockApplication.class, args); + } + +} diff --git a/src/main/java/cc/niushuai/projects/demo/aspectlock/common/anno/AopLock.java b/src/main/java/cc/niushuai/projects/demo/aspectlock/common/anno/AopLock.java new file mode 100644 index 0000000..c48fc7e --- /dev/null +++ b/src/main/java/cc/niushuai/projects/demo/aspectlock/common/anno/AopLock.java @@ -0,0 +1,59 @@ +package cc.niushuai.projects.demo.aspectlock.common.anno; + +import java.lang.annotation.*; + +/** + * AopLock 注解 + * + * @author niushuai + * @date 2021/12/22 10:01 + */ +@Target(value = ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +public @interface AopLock { + + /** + * 是否启用 默认false + *
+     *  true 表示要利用反射来获取对象属性值作为key和value
+     *  false 表示直接将key作为key, value作为value
+     * 
+ */ + boolean enableReflect() default false; + + /** + * key 多个数据会组合到一起 + *
+     * 若为基本类型或其包装类型 直接 paramName 即可
+     * 若为对象请用 paramName.fieldName 的形式
+     * 
+     */
+    String[] key();
+
+    /**
+     * value 多个数据会组合到一起
+     */
+    String[] value() default "aopLock_default_value";
+
+    /**
+     * key前缀
+     */
+    String prefix() default "";
+
+    /**
+     * key 数组element分隔符
+     */
+    String keySeparator() default "";
+
+    /**
+     * value 数组element分隔符
+     */
+    String valueSeparator() default "";
+
+    /**
+     * 有效期 默认3000ms
+     */
+    long period() default 3000L;
+}
diff --git a/src/main/java/cc/niushuai/projects/demo/aspectlock/common/anno/Lock.java b/src/main/java/cc/niushuai/projects/demo/aspectlock/common/anno/Lock.java
new file mode 100644
index 0000000..af79b06
--- /dev/null
+++ b/src/main/java/cc/niushuai/projects/demo/aspectlock/common/anno/Lock.java
@@ -0,0 +1,25 @@
+package cc.niushuai.projects.demo.aspectlock.common.anno;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+/**
+ * lock 参数实体类
+ *
+ * @author niushuai
+ * @date 2021/12/22 10:21
+ */
+@Data
+@Accessors(chain = true)
+public class Lock {
+
+    private String key;
+    private String value;
+    private long period;
+    private long expireTime;
+
+    public boolean isExpire() {
+        // 预计过期时间
+        return expireTime < System.currentTimeMillis();
+    }
+}
diff --git a/src/main/java/cc/niushuai/projects/demo/aspectlock/common/anno/LockAspect.java b/src/main/java/cc/niushuai/projects/demo/aspectlock/common/anno/LockAspect.java
new file mode 100644
index 0000000..e7e7d4c
--- /dev/null
+++ b/src/main/java/cc/niushuai/projects/demo/aspectlock/common/anno/LockAspect.java
@@ -0,0 +1,196 @@
+package cc.niushuai.projects.demo.aspectlock.common.anno;
+
+import cc.niushuai.projects.demo.aspectlock.common.exception.LockException;
+import cc.niushuai.projects.demo.aspectlock.common.util.AopLockUtil;
+import cn.hutool.core.util.ReflectUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.extern.slf4j.Slf4j;
+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.stereotype.Component;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * LockAspect 锁切面
+ *
+ * @author niushuai
+ * @date 2021/12/22 10:17
+ */
+@Slf4j
+@Aspect
+@Component
+public class LockAspect {
+
+    private static final String POINT = ".";
+    private static final String REGEX_POINT = "\\.";
+
+    private static final Class[] BASE_TYPE_CLASS =
+            new Class[]{
+                    Byte.class,
+                    Character.class,
+                    Short.class, Integer.class, Long.class,
+                    Float.class, Double.class,
+                    String.class,
+                    Boolean.class,
+                    Date.class
+            };
+
+    @Pointcut(value = "@annotation(cc.niushuai.projects.demo.aspectlock.common.anno.AopLock)")
+    public void lockPointcut() {
+    }
+
+    @Around(value = "lockPointcut()")
+    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
+
+        Lock lock = parseLock(joinPoint);
+        try {
+            // 加锁
+            AopLockUtil.lock(lock);
+            // 执行方法
+            return joinPoint.proceed();
+        } finally {
+            // 解锁
+            AopLockUtil.unlock(lock);
+        }
+    }
+
+    /**
+     * 封装出一个lock对象
+     *
+     * @param joinPoint 切入点
+     * @author niushuai
+     * @date: 2021/12/22 17:42
+     * @return: {@link Lock} 封装过的lock对象
+     */
+    private Lock parseLock(ProceedingJoinPoint joinPoint) {
+        Lock lock = new Lock();
+        StringBuffer key = new StringBuffer(), value = new StringBuffer();
+
+        // 实际参数值
+        Object[] args = joinPoint.getArgs();
+        MethodSignature method = (MethodSignature) joinPoint.getSignature();
+
+        // 方法签名上的参数类型
+        // Class[] parameterTypes = method.getParameterTypes();
+        // 方法签名上的参数名称
+        // String[] parameterNames = new String[]{};
+
+        // 确定key的顺序 便于后续反射取值
+        Map paramOrderMap = transferParamOrderMap(method.getParameterNames());
+        Map classOrderMap = transferClassOrderMap(method.getParameterTypes());
+
+        // 方法上的注解 携带有具体的内容
+        AopLock annotation = method.getMethod().getAnnotation(AopLock.class);
+        String[] keyArr = annotation.key();
+        String[] valueArr = annotation.value();
+        // 根据是否启用反射取值来进行不同的处理
+        log.debug("启用反射取值 - {}", annotation.enableReflect());
+        if (annotation.enableReflect()) {
+            // key
+            reflectVal(key, args, paramOrderMap, classOrderMap, keyArr);
+            // value
+            reflectVal(value, args, paramOrderMap, classOrderMap, valueArr);
+        } else {
+            // 未启用反射 直接拼接key和value
+            key.append(StrUtil.concat(true, annotation.prefix(), StrUtil.join(annotation.keySeparator(), keyArr)));
+            value.append(StrUtil.join(annotation.valueSeparator(), valueArr));
+        }
+        log.debug("锁值 - key: {}, value: {}", key, value);
+
+        lock.setKey(key.toString()).setValue(value.toString()).setPeriod(annotation.period());
+
+        return lock;
+    }
+
+    private void reflectVal(StringBuffer key, Object[] args, Map paramOrderMap, Map classOrderMap, String[] keyArr) {
+        for (String tmpKey : keyArr) {
+            // student.age
+            // 要先判断 . 是否存在
+            boolean baseType = false;
+            if (tmpKey.contains(POINT)) {
+                // 含有点
+            } else if (judgeBaseType(tmpKey, paramOrderMap, classOrderMap)) {
+                // 基础类型
+                baseType = true;
+            } else {
+                throw new LockException("取值 - 在启用反射获取值时请按照paramName.fieldName的方式来做 [" + tmpKey + "]");
+            }
+
+
+
+            if (baseType) {
+                // 基础类型不用反射  直接拼接即可
+                key.append(args[paramOrderMap.get(tmpKey)]);
+                continue;
+            }
+
+            // 获取paramName.fieldName的具体值
+            String[] split = tmpKey.split(REGEX_POINT);
+            String paramName = split[0];
+            String fieldName = split[1];
+
+            if (!paramOrderMap.containsKey(paramName)) {
+                throw new LockException("取值 - 不存在形参名为[" + paramName + "]的参数");
+            }
+
+            try {
+                // 非基础类型 通过反射取舍  像list map啊啥的 么想好怎么搞
+                // 感觉可以通过类型判断来对于这些特定类型进行按照特定的方式进行取值
+                // 最后才会使用到反射来取值  一般应用场景的话 形参对象居多 先不考虑吧
+                Object fieldValue = ReflectUtil.getFieldValue(args[paramOrderMap.get(paramName)], fieldName);
+                key.append(fieldValue);
+            } catch (Exception e) {
+                throw new LockException("取值 - 不存在形参名为[" + paramName + "], 且字段名称为[" + fieldName + "]的参数, 完整参数: [" + tmpKey + "]");
+            }
+
+        }
+    }
+
+    /**
+     * 判断既定key是否为基础类型
+     *
+     * @param tmpKey        既定key
+     * @param paramOrderMap 参数顺序
+     * @param classOrderMap clazz顺序
+     * @author niushuai
+     * @date: 2021/12/23 10:03
+     * @return: {@link boolean}
+     */
+    private boolean judgeBaseType(String tmpKey, Map paramOrderMap, Map classOrderMap) {
+        // 进入到此处时均为不带点的参数
+        if (!paramOrderMap.containsKey(tmpKey)) {
+            // 由于基础类型不需要. 因为tmpKey应于key保持一致 所以在paramOrderKey中含有则对 若不含有
+            throw new LockException("取值 - [" + tmpKey + "]在形参中不存在");
+        }
+
+        Class clazz = classOrderMap.get(paramOrderMap.get(tmpKey));
+        // 此处不可能为null 匹配既定的任一类型
+        return Arrays.stream(BASE_TYPE_CLASS).anyMatch(item -> item.isAssignableFrom(clazz));
+    }
+
+    private Map transferParamOrderMap(String[] parameterNames) {
+        Map map = new HashMap<>();
+
+        for (int i = 0; i < parameterNames.length; i++) {
+            map.put(parameterNames[i], i);
+        }
+        return map;
+    }
+
+    private Map transferClassOrderMap(Class[] clazz) {
+        Map map = new HashMap<>();
+
+        for (int i = 0; i < clazz.length; i++) {
+            map.put(i, clazz[i]);
+        }
+        return map;
+    }
+
+}
diff --git a/src/main/java/cc/niushuai/projects/demo/aspectlock/common/exception/GlobalExceptionHandler.java b/src/main/java/cc/niushuai/projects/demo/aspectlock/common/exception/GlobalExceptionHandler.java
new file mode 100644
index 0000000..1a3eccc
--- /dev/null
+++ b/src/main/java/cc/niushuai/projects/demo/aspectlock/common/exception/GlobalExceptionHandler.java
@@ -0,0 +1,32 @@
+package cc.niushuai.projects.demo.aspectlock.common.exception;
+
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * TODO
+ *
+ * @author niushuai
+ * @date 2021/12/23 10:18
+ */
+@RestControllerAdvice
+public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
+
+    @ExceptionHandler(value = LockException.class)
+    public Map lockExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {
+
+        Map map = new HashMap<>();
+        map.put("code", 500);
+        map.put("message", e.getMessage());
+        map.put("data", null);
+        return map;
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/cc/niushuai/projects/demo/aspectlock/common/exception/LockException.java b/src/main/java/cc/niushuai/projects/demo/aspectlock/common/exception/LockException.java
new file mode 100644
index 0000000..b6db5b7
--- /dev/null
+++ b/src/main/java/cc/niushuai/projects/demo/aspectlock/common/exception/LockException.java
@@ -0,0 +1,29 @@
+package cc.niushuai.projects.demo.aspectlock.common.exception;
+
+/**
+ * 锁异常
+ *
+ * @author niushuai
+ * @date 2021/12/22 10:42
+ */
+public class LockException extends RuntimeException {
+
+    public LockException() {
+    }
+
+    public LockException(String message) {
+        super(message);
+    }
+
+    public LockException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public LockException(Throwable cause) {
+        super(cause);
+    }
+
+    public LockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+}
diff --git a/src/main/java/cc/niushuai/projects/demo/aspectlock/common/util/AopLockUtil.java b/src/main/java/cc/niushuai/projects/demo/aspectlock/common/util/AopLockUtil.java
new file mode 100644
index 0000000..43df87f
--- /dev/null
+++ b/src/main/java/cc/niushuai/projects/demo/aspectlock/common/util/AopLockUtil.java
@@ -0,0 +1,129 @@
+package cc.niushuai.projects.demo.aspectlock.common.util;
+
+import cc.niushuai.projects.demo.aspectlock.common.anno.Lock;
+import cc.niushuai.projects.demo.aspectlock.common.exception.LockException;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 公共工具类
+ *
+ * @author niushuai
+ * @date 2021/12/22 10:19
+ */
+public class AopLockUtil {
+
+    /**
+     * 锁柜
+     */
+    private static final Map LOCK_MAP = new HashMap<>(16);
+
+    private AopLockUtil() {
+    }
+
+    /**
+     * 从锁柜中根据钥匙获取到相应的锁
+     *
+     * @param key 钥匙
+     * @author niushuai
+     * @date: 2021/12/22 11:24
+     * @return: {@link Lock}
+     */
+    private static Lock getLock(String key) {
+        return LOCK_MAP.get(key);
+    }
+
+    /**
+     * 加锁
+     *
+     * @param lock 锁
+     * @author niushuai
+     * @date: 2021/12/22 11:25
+     * @return: void
+     */
+    public static void lock(Lock lock) {
+
+        // 加锁时 钥匙不能为空
+        validate(lock.getKey());
+
+        // 要求锁不存在
+        if (isLocked(lock.getKey())) {
+            throw new LockException("加锁失败 - 锁[" + lock.getKey() + "]已存在");
+        }
+
+        // 重置key过期时间
+        lock.setExpireTime(getExpireTime(lock.getPeriod()));
+        // 放入锁柜
+        LOCK_MAP.put(lock.getKey(), lock);
+    }
+
+
+    /**
+     * 解锁
+     *
+     * @param lock 锁
+     * @author niushuai
+     * @date: 2021/12/22 11:29
+     * @return: void
+     */
+    public static void unlock(Lock lock) {
+        // 要求锁存在
+        if (ObjectUtil.isNull(getLock(lock.getKey()))) {
+            throw new LockException("解锁失败 - 锁[" + lock.getKey() + "]不存在");
+        }
+        // 无需关心过期问题 直接移除锁柜
+        LOCK_MAP.remove(lock.getKey());
+    }
+
+    /**
+     * 是否可锁
+     * 为空 可锁
+     * 锁已过期 可锁
+     *
+     * @param key 钥匙
+     * @author niushuai
+     * @date: 2021/12/22 11:06
+     * @return: {@link boolean}
+     */
+    public static boolean lockable(String key) {
+        Lock lock = getLock(key);
+
+        return ObjectUtil.isEmpty(lock) || lock.isExpire();
+    }
+
+    /**
+     * 非空 且未过期 即为true
+     * 非空 已过期 为false
+     * 空  为false
+     *
+     * @param key 钥匙
+     * @author niushuai
+     * @date: 2021/12/22 11:04
+     * @return: {@link boolean} 是否可锁
+     */
+    public static boolean isLocked(String key) {
+        return !lockable(key);
+    }
+
+
+    private static void validate(String key) {
+        if (StrUtil.isEmpty(key)) {
+            throw new LockException("锁[" + key + "]不能为空");
+        }
+    }
+
+    /**
+     * 根据有效时间 获取基于当前时间的有效期
+     *
+     * @param period
+     * @author niushuai
+     * @date: 2021/12/22 10:40
+     * @return: {@link long}
+     */
+    private static long getExpireTime(long period) {
+        return System.currentTimeMillis() + period;
+    }
+}
diff --git a/src/main/java/cc/niushuai/projects/demo/aspectlock/student/controller/StudentController.java b/src/main/java/cc/niushuai/projects/demo/aspectlock/student/controller/StudentController.java
new file mode 100644
index 0000000..bdf6106
--- /dev/null
+++ b/src/main/java/cc/niushuai/projects/demo/aspectlock/student/controller/StudentController.java
@@ -0,0 +1,31 @@
+package cc.niushuai.projects.demo.aspectlock.student.controller;
+
+import cc.niushuai.projects.demo.aspectlock.common.anno.AopLock;
+import cc.niushuai.projects.demo.aspectlock.student.entity.Student;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * TODO
+ *
+ * @author niushuai
+ * @date 2021/12/22 11:44
+ */
+@RestController
+@RequestMapping("/students")
+public class StudentController {
+
+    @AopLock(enableReflect = true, key = {"student.namex", "student.age"}, keySeparator = "_", value = "age")
+    @GetMapping("/query/{age}")
+    public Student query(@PathVariable Long age, Student student) {
+        return new Student();
+    }
+
+    @GetMapping("/query2/{age}")
+    public Student query2(@PathVariable Long age, Student student) {
+
+        return new Student();
+    }
+}
diff --git a/src/main/java/cc/niushuai/projects/demo/aspectlock/student/entity/Student.java b/src/main/java/cc/niushuai/projects/demo/aspectlock/student/entity/Student.java
new file mode 100644
index 0000000..1247089
--- /dev/null
+++ b/src/main/java/cc/niushuai/projects/demo/aspectlock/student/entity/Student.java
@@ -0,0 +1,18 @@
+package cc.niushuai.projects.demo.aspectlock.student.entity;
+
+import lombok.Data;
+
+/**
+ * TODO
+ *
+ * @author niushuai
+ * @date 2021/12/22 11:44
+ */
+@Data
+public class Student {
+
+    private String id;
+    private String stuNo;
+    private String name;
+    private Long age;
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..afc9c26
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,8 @@
+server:
+  port: 8301
+  servlet:
+    context-path: /
+
+logging:
+  level:
+    cc.niushuai.projects.demo.aspectlock: debug
\ No newline at end of file
diff --git a/src/test/java/cc/niushuai/projects/demo/aspectlock/AspectLockApplicationTests.java b/src/test/java/cc/niushuai/projects/demo/aspectlock/AspectLockApplicationTests.java
new file mode 100644
index 0000000..98cdefa
--- /dev/null
+++ b/src/test/java/cc/niushuai/projects/demo/aspectlock/AspectLockApplicationTests.java
@@ -0,0 +1,13 @@
+package cc.niushuai.projects.demo.aspectlock;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class AspectLockApplicationTests {
+
+    @Test
+    void contextLoads() {
+    }
+
+}
diff --git a/src/test/java/cc/niushuai/projects/demo/aspectlock/OptionalTest.java b/src/test/java/cc/niushuai/projects/demo/aspectlock/OptionalTest.java
new file mode 100644
index 0000000..a3342f3
--- /dev/null
+++ b/src/test/java/cc/niushuai/projects/demo/aspectlock/OptionalTest.java
@@ -0,0 +1,21 @@
+package cc.niushuai.projects.demo.aspectlock;
+
+import java.util.Arrays;
+
+/**
+ * TODO
+ *
+ * @author niushuai
+ * @date 2021/12/22 10:50
+ */
+public class OptionalTest {
+
+    public static void main(String[] args) throws InterruptedException {
+
+
+        String[] split = "student.name".split("\\.");
+        System.out.println(Arrays.toString(split));
+
+
+    }
+}