From 501357cbd0489f86434e591da7e85511c5186b1d Mon Sep 17 00:00:00 2001 From: whai Date: Wed, 27 Nov 2024 21:46:41 +0800 Subject: [PATCH] =?UTF-8?q?refactor(mybatis):=20=E9=87=8D=E6=9E=84=20Mybat?= =?UTF-8?q?is=20=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -移动 AmountBig 类到新的包结构 - 新增 AmountTypeHandler 类用于处理 AmountBig 类型- 更新 application.yaml 配置- 重构 DBRouter 相关代码,优化切面逻辑 - 新增 LeetCode 相关解题代码 - 更新 Mybatis配置和测试代码 --- .../java/cn/whaifree/interview/td/p1.java | 24 ++ .../redo/redo_all_241016/LeetCode215.java | 7 + .../redo/redo_all_241121/LeetCode165.java | 50 +++ .../redo/redo_all_241121/LeetCode215.java | 65 ++++ .../redo/redo_all_241121/LeetCode28.java | 31 ++ .../redo/redo_all_241121/LeetCode7.java | 37 +++ .../redo/redo_all_241121/LeetCode9.java | 45 +++ Spring/pom.xml | 207 +++++++++++++ .../springdemo/SpringDemoApplication.java | 15 + .../springdemo/aspect/RateLimitAspect.java | 100 ++++++ .../aspect/annotation/RateLimiter.java | 38 +++ .../springdemo/config/CacheConfig.java | 44 +++ .../springdemo/config/FilterConfig.java | 32 ++ .../config/GlobalExceptionHandler.java | 23 ++ .../config/MyAutoConfiguration.java | 84 ++++++ .../springdemo/config/MybatisConfig.java | 17 ++ .../springdemo/config/RedisConfig.java | 78 +++++ .../springdemo/config/RedissonConfig.java | 26 ++ .../springdemo/config/SwaggerConfig.java | 42 +++ .../springdemo/config/TransactionConfig.java | 97 ++++++ .../springdemo/constant/CacheConstants.java | 11 + .../springdemo/constant/LimitType.java | 13 + .../CacheDecoratorController.java | 67 +++++ .../springdemo/controller/SSE/SSEEmitter.java | 49 +++ .../controller/TS/HTTP/QBController.java | 58 ++++ .../controller/TS/SSE/SSEEmitterDemo.java | 55 ++++ .../controller/TS/UDP/UDPConfig.java | 197 ++++++++++++ .../controller/TS/UDP/UDPSendUtils.java | 30 ++ .../controller/TS/common/ProcessStrategy.java | 7 + .../controller/TS/common/ProcessTarget.java | 47 +++ .../controller/TS/common/TargetDown.java | 27 ++ .../controller/TS/common/TargetImage.java | 78 +++++ .../controller/TS/common/TargetStorage.java | 26 ++ .../springdemo/controller/TestController.java | 111 +++++++ .../controller/WhiteListController.java | 23 ++ .../cacheComparator/CacheComparatorDemo.java | 132 ++++++++ .../idempotence/IdempotenceController.java | 135 +++++++++ .../interceptRetry/BuinessException.java | 22 ++ .../controller/interceptRetry/ErrorType.java | 18 ++ .../interceptRetry/RetryController.java | 24 ++ .../interceptRetry/aspect/RetryAspect.java | 148 +++++++++ .../interceptRetry/aspect/RetryLimit.java | 26 ++ .../controller/minio/MinioController.java | 196 ++++++++++++ .../rabbitMqEvent/EventController.java | 106 +++++++ .../rabbitMqEvent/RabbitMQController.java | 268 +++++++++++++++++ .../redEnvelope/RedEnvelopeController.java | 63 ++++ .../workBook/WorkBookController.java | 103 +++++++ .../workBook/export/ExportAutoController.java | 154 ++++++++++ .../export/WorkBookResponseUtils.java | 85 ++++++ .../wxQrLogin/WxQrLoginController.java | 284 ++++++++++++++++++ .../springdemo/entity/UserService.java | 201 +++++++++++++ .../springdemo/tech/CircleRelation.java | 138 +++++++++ .../utils/BroomFilter/BroomFilter.java | 57 ++++ .../springdemo/utils/Filter/SelfFilter.java | 34 +++ .../cn/whaifree/springdemo/utils/ResVo.java | 196 ++++++++++++ .../utils/constants/HttpStatus.java | 95 ++++++ .../main/resources/META-INF/spring.factories | 2 + Spring/src/main/resources/application.yaml | 78 +++++ Spring/src/main/resources/lua/RobEnvelope.lua | 27 ++ Spring/src/main/resources/redisson.yaml | 42 +++ Spring/src/main/resources/template/empty.xlsx | Bin 0 -> 9818 bytes .../template/project_member_workHours.xlsx | Bin 0 -> 10108 bytes .../cn/whaifree/spring/ApplicationTests.java | 13 + .../springdemo/RedisData/RedisDataTest.java | 146 +++++++++ .../SpringDemoApplicationTests.java | 72 +++++ .../springdemo/tech/AbstractClass.java | 44 +++ SpringDemo/pom.xml | 53 +++- .../springdemo/aspect/RateLimitAspect.java | 9 +- .../springdemo/config/MybatisConfig.java | 1 + .../springdemo/config/RedissonConfig.java | 16 +- .../springdemo/controller/TestController.java | 2 +- .../interceptRetry/aspect/RetryAspect.java | 34 ++- .../springdemo/mybatis/AmountTypeHandler.java | 52 ---- .../springdemo/mybatis/domain/Orders.java | 111 ------- .../mybatis/mapper/OrdersMapper.java | 26 -- .../mybatis/service/OrdersService.java | 13 - .../service/impl/OrdersServiceImpl.java | 22 -- .../src/main/resources/application.yaml | 6 +- .../main/resources/mapper/OrdersMapper.xml | 22 -- SpringDemo/src/main/resources/redisson.yaml | 2 +- .../springdemo/MybatisTest/MybatisTest.java | 34 --- mybatisplus/pom.xml | 101 +++++++ .../mybatisplus/MybatisplusApplication.java | 15 + .../mybatis/AmountTypeHandler.java | 52 ++++ .../mybatis/aspect/RateLimitAspect.java | 34 +++ .../aspect/annotation/RateLimiter.java | 17 ++ .../mybatis/domain/AmountBig.java | 2 +- .../mybatisplus/mybatis/domain/User.java | 20 ++ .../mybatis/mapper/UserMapper.java | 31 ++ .../src/main/resources/application.yaml | 73 +++++ .../main/resources/mapper/OrdersMapper.xml | 9 + .../mybatisplus/MybatisTest/MybatisTest.java | 48 +++ .../MybatisplusApplicationTests.java | 27 ++ 93 files changed, 5270 insertions(+), 332 deletions(-) create mode 100644 ForJdk17/src/main/java/cn/whaifree/interview/td/p1.java create mode 100644 ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241121/LeetCode165.java create mode 100644 ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241121/LeetCode215.java create mode 100644 ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241121/LeetCode28.java create mode 100644 ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241121/LeetCode7.java create mode 100644 ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241121/LeetCode9.java create mode 100644 Spring/pom.xml create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/SpringDemoApplication.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/aspect/RateLimitAspect.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/aspect/annotation/RateLimiter.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/config/CacheConfig.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/config/FilterConfig.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/config/GlobalExceptionHandler.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/config/MyAutoConfiguration.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/config/MybatisConfig.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/config/RedisConfig.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/config/RedissonConfig.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/config/SwaggerConfig.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/config/TransactionConfig.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/constant/CacheConstants.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/constant/LimitType.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/CacheDecoratorDemo/CacheDecoratorController.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/SSE/SSEEmitter.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/TS/HTTP/QBController.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/TS/SSE/SSEEmitterDemo.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/TS/UDP/UDPConfig.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/TS/UDP/UDPSendUtils.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/TS/common/ProcessStrategy.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/TS/common/ProcessTarget.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/TS/common/TargetDown.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/TS/common/TargetImage.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/TS/common/TargetStorage.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/TestController.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/WhiteListController.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/cacheComparator/CacheComparatorDemo.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/idempotence/IdempotenceController.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/BuinessException.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/ErrorType.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/RetryController.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/aspect/RetryAspect.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/aspect/RetryLimit.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/minio/MinioController.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/rabbitMqEvent/EventController.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/rabbitMqEvent/RabbitMQController.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/redEnvelope/RedEnvelopeController.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/workBook/WorkBookController.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/workBook/export/ExportAutoController.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/workBook/export/WorkBookResponseUtils.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/controller/wxQrLogin/WxQrLoginController.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/entity/UserService.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/tech/CircleRelation.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/utils/BroomFilter/BroomFilter.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/utils/Filter/SelfFilter.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/utils/ResVo.java create mode 100644 Spring/src/main/java/cn/whaifree/springdemo/utils/constants/HttpStatus.java create mode 100644 Spring/src/main/resources/META-INF/spring.factories create mode 100644 Spring/src/main/resources/application.yaml create mode 100644 Spring/src/main/resources/lua/RobEnvelope.lua create mode 100644 Spring/src/main/resources/redisson.yaml create mode 100644 Spring/src/main/resources/template/empty.xlsx create mode 100644 Spring/src/main/resources/template/project_member_workHours.xlsx create mode 100644 Spring/src/test/java/cn/whaifree/spring/ApplicationTests.java create mode 100644 Spring/src/test/java/cn/whaifree/springdemo/RedisData/RedisDataTest.java create mode 100644 Spring/src/test/java/cn/whaifree/springdemo/SpringDemoApplicationTests.java create mode 100644 Spring/src/test/java/cn/whaifree/springdemo/tech/AbstractClass.java delete mode 100644 SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/AmountTypeHandler.java delete mode 100644 SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/domain/Orders.java delete mode 100644 SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/mapper/OrdersMapper.java delete mode 100644 SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/service/OrdersService.java delete mode 100644 SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/service/impl/OrdersServiceImpl.java delete mode 100644 SpringDemo/src/main/resources/mapper/OrdersMapper.xml delete mode 100644 SpringDemo/src/test/java/cn/whaifree/springdemo/MybatisTest/MybatisTest.java create mode 100644 mybatisplus/pom.xml create mode 100644 mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/MybatisplusApplication.java create mode 100644 mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/AmountTypeHandler.java create mode 100644 mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/aspect/RateLimitAspect.java create mode 100644 mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/aspect/annotation/RateLimiter.java rename {SpringDemo/src/main/java/cn/whaifree/springdemo => mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus}/mybatis/domain/AmountBig.java (66%) create mode 100644 mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/domain/User.java create mode 100644 mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/mapper/UserMapper.java create mode 100644 mybatisplus/src/main/resources/application.yaml create mode 100644 mybatisplus/src/main/resources/mapper/OrdersMapper.xml create mode 100644 mybatisplus/src/test/java/com/whai/springcloud/mybatis/mybatisplus/MybatisTest/MybatisTest.java create mode 100644 mybatisplus/src/test/java/com/whai/springcloud/mybatis/mybatisplus/MybatisplusApplicationTests.java diff --git a/ForJdk17/src/main/java/cn/whaifree/interview/td/p1.java b/ForJdk17/src/main/java/cn/whaifree/interview/td/p1.java new file mode 100644 index 0000000..0631ad1 --- /dev/null +++ b/ForJdk17/src/main/java/cn/whaifree/interview/td/p1.java @@ -0,0 +1,24 @@ +package cn.whaifree.interview.td; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/26 18:57 + * @注释 + */ +public class p1 { + + // 快慢指针 + + + public static void main(String[] args) { + +// DelayQueue delayQueue = new DelayQueue<>(); +// +// // 创建并添加任务到队列 +// DelayedTask task1 = new DelayedTask("Task 1", 1000); // 1秒后执行 +// DelayedTask task2 = new DelayedTask("Task 2", 2000); // 2秒后执行 +// delayQueue.put(task1); +// delayQueue.put(task2); + } +} diff --git a/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241016/LeetCode215.java b/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241016/LeetCode215.java index fc28bc5..3d300af 100644 --- a/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241016/LeetCode215.java +++ b/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241016/LeetCode215.java @@ -101,6 +101,12 @@ public class LeetCode215 { class Solution2 { /** + * 构建大根堆 + * + * 构建每个小子树都是一个大根堆,再以子堆的父节点往上换 + * + * + * * 构建大根堆 * 把根移动到最后 * 移动k次,顶部就是了 @@ -138,6 +144,7 @@ public class LeetCode215 { if (index != large) { swap(index, large); build(large, rightEdge); // large换完后large已经是小的了,小的下沉到合适的位置 + // 大的不断换上去,直到不换就退出 } } diff --git a/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241121/LeetCode165.java b/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241121/LeetCode165.java new file mode 100644 index 0000000..0567a17 --- /dev/null +++ b/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241121/LeetCode165.java @@ -0,0 +1,50 @@ +package cn.whaifree.redo.redo_all_241121; + +import org.junit.Test; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/24 11:42 + * @注释 + */ +public class LeetCode165 { + + @Test + public void test() { + String s1 = "1.0"; + String s2 = "1.0.0.0"; + System.out.println(new Solution().compareVersion(s1, s2)); + } + + class Solution { + public int compareVersion(String version1, String version2) { + // 双指针,遇到.要退出 + int index1 = 0; + int index2 = 0; + while (index1 < version1.length() || index2 < version2.length()) { + + int A = 0; + while (index1 < version1.length() && version1.charAt(index1) != '.') { + A *= 10; + A += version1.charAt(index1) - '0'; + index1++; + } + index1++; + + int B = 0; + while (index2 < version2.length() && version2.charAt(index2) != '.') { + B *= 10; + B += version2.charAt(index2) - '0'; + index2++; + } + index2++; + + if (A != B) { + return A > B ? 1 : -1; + } + } + return 0; + } + } +} diff --git a/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241121/LeetCode215.java b/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241121/LeetCode215.java new file mode 100644 index 0000000..9043cfd --- /dev/null +++ b/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241121/LeetCode215.java @@ -0,0 +1,65 @@ +package cn.whaifree.redo.redo_all_241121; + +import org.junit.Test; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/24 12:30 + * @注释 + */ +public class LeetCode215 { + + @Test + public void test() { + int[] nums = {3,2,3,1,2,4,5,5,6}; + int k = 4; + System.out.println(new Solution().findKthLargest(nums, k)); + } + + class Solution { + public int findKthLargest(int[] nums, int k) { + heap = nums; + // 构建大根堆,最上面的最大 + for (int i = heap.length / 2 - 1; i >= 0; i--) { + build(i, heap.length - 1); + } + + // 从非叶子节点不断向下递归 + + + // 把堆顶最大swap最后,最后再有新的边界从堆顶开始向下换 + for (int i = heap.length - 1; i > heap.length - k; i--) { + swap(0, i); + build(0, i - 1); + } + return heap[0]; + } + + int[] heap = null; + + public void build(int startToDown, int edge) { + int left = startToDown * 2 + 1; + int right = startToDown * 2 + 2; + int maxIndex = startToDown; + if (left <= edge && heap[maxIndex] < heap[left]) { + maxIndex = left; + } + if (right <= edge && heap[maxIndex] < heap[right]) { + maxIndex = right; + } + + if (maxIndex != startToDown) { + swap(startToDown, maxIndex); + build(maxIndex, edge); + } + } + + public void swap(int i, int j) { + int temp = heap[i]; + heap[i] = heap[j]; + heap[j] = temp; + } + } + +} diff --git a/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241121/LeetCode28.java b/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241121/LeetCode28.java new file mode 100644 index 0000000..8834e96 --- /dev/null +++ b/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241121/LeetCode28.java @@ -0,0 +1,31 @@ +package cn.whaifree.redo.redo_all_241121; + +import org.junit.Test; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/24 17:14 + * @注释 + */ +public class LeetCode28 { + + @Test + public void test() { + Solution solution = new Solution(); + System.out.println(solution.strStr("hell", "ll")); + } + + class Solution { + public int strStr(String haystack, String needle) { + for (int i = 0; i <= haystack.length() - needle.length(); i++) { + String substring = haystack.substring(i, i + needle.length()); + if (substring.equals(needle)) { + return i; + } + } + return -1; + } + } + +} diff --git a/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241121/LeetCode7.java b/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241121/LeetCode7.java new file mode 100644 index 0000000..634fd55 --- /dev/null +++ b/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241121/LeetCode7.java @@ -0,0 +1,37 @@ +package cn.whaifree.redo.redo_all_241121; + +import org.junit.Test; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/24 13:57 + * @注释 + */ +public class LeetCode7 { + + @Test + public void test() { + Solution solution = new Solution(); + System.out.println(solution.reverse(123)); + } + + class Solution { + public int reverse(int x) { // 123 + int res = 0; + while (x != 0) { // 正负都可以进入 + if (res > Integer.MAX_VALUE / 10) { + return 0; + } + if (res < Integer.MIN_VALUE / 10) { + return 0; + } + int retail = x % 10; // 余数 3 + x /= 10; // 12 + res = res * 10 + retail; // + } + return res; + } + } + +} diff --git a/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241121/LeetCode9.java b/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241121/LeetCode9.java new file mode 100644 index 0000000..ef2a59e --- /dev/null +++ b/ForJdk17/src/main/java/cn/whaifree/redo/redo_all_241121/LeetCode9.java @@ -0,0 +1,45 @@ +package cn.whaifree.redo.redo_all_241121; + +import org.junit.Test; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/24 17:06 + * @注释 + */ +public class LeetCode9 { + + @Test + public void test() { + Solution solution = new Solution(); + System.out.println(solution.isPalindrome(123)); + } + + class Solution { + /** + * 12321 + * @param x + * @return + */ + public boolean isPalindrome(int x) { + if (x < 0) { + return false; + } + // 反转后相等? + return reverse(x) == x; + } + + public int reverse(int x) { + + int res = 0; + while (x != 0) { + res *= 10; + res += x % 10; + x /= 10; + } + return res; + } + } + +} diff --git a/Spring/pom.xml b/Spring/pom.xml new file mode 100644 index 0000000..3bcdc8d --- /dev/null +++ b/Spring/pom.xml @@ -0,0 +1,207 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.4.0 + + + cn.whaifree + Spring + 0.0.1-SNAPSHOT + Spring + Spring + + + + + + + + + + + + + + + 17 + + + + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 3.0.3 + + + + + + + + + + + + + + + + + org.apache.poi + poi + 5.0.0 + + + + org.apache.poi + poi-ooxml + 5.0.0 + + + + org.springframework.boot + spring-boot-starter-integration + + + + + org.springframework.integration + spring-integration-ip + + + + + + + + + + + com.github.ben-manes.caffeine + caffeine + + + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + 4.4.0 + + + + + org.redisson + redisson-spring-boot-starter + 3.23.5 + + + + + + + + + + org.springframework.boot + spring-boot-starter-amqp + + + + io.minio + minio + 8.4.3 + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.13.0 + + + + + com.google.guava + guava + 31.0.1-jre + + + + + com.alibaba + fastjson + 2.0.36 + + + + + cn.hutool + hutool-all + 5.8.13 + + + + org.springframework.boot + spring-boot-starter-aop + + + mysql-connector-java + + mysql + 8.0.33 + + + org.springframework.boot + spring-boot-starter-amqp + + + org.springframework.boot + spring-boot-starter-data-jdbc + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter-web + + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/Spring/src/main/java/cn/whaifree/springdemo/SpringDemoApplication.java b/Spring/src/main/java/cn/whaifree/springdemo/SpringDemoApplication.java new file mode 100644 index 0000000..a164470 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/SpringDemoApplication.java @@ -0,0 +1,15 @@ +package cn.whaifree.springdemo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +@SpringBootApplication +@EnableAspectJAutoProxy +public class SpringDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringDemoApplication.class, args); + } + +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/aspect/RateLimitAspect.java b/Spring/src/main/java/cn/whaifree/springdemo/aspect/RateLimitAspect.java new file mode 100644 index 0000000..6b9f81b --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/aspect/RateLimitAspect.java @@ -0,0 +1,100 @@ +package cn.whaifree.springdemo.aspect; + +import cn.whaifree.springdemo.aspect.annotation.RateLimiter; +import cn.whaifree.springdemo.constant.LimitType; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/9 17:46 + * @注释 + */ +@Aspect +@Component +public class RateLimitAspect { + + private static final Logger log = LoggerFactory.getLogger(RateLimitAspect.class); + + private RedisTemplate redisTemplate; + + private RedisScript limitScript; + + @Autowired + public void setRedisTemplate(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + @Autowired + public void setLimitScript(RedisScript limitScript) { + this.limitScript = limitScript; + } + + /** + * key 对应方法,value 已经被访问的次数 + * + * lua的逻辑: + * + * + * + * @param point + * @param rateLimiter + * @throws Throwable + */ + @Before("@annotation(rateLimiter)") + public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable { + int time = rateLimiter.time(); // 多长时间 + int count = rateLimiter.count(); // 允许次数 + + + String combineKey = getCombineKey(rateLimiter, point); // 组合key, Class-Method + List keys = Collections.singletonList(combineKey); + try { + Long number = redisTemplate.execute(limitScript, keys, count, time); + if (Objects.isNull(number) || number.intValue() > count) { // 如果超过限额,报错 + throw new Exception("访问过于频繁,请稍候再试"); + } + log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), combineKey); + } catch (Exception e) { + + throw e; + } + } + + public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) { + StringBuffer stringBuffer = new StringBuffer(rateLimiter.key()); // 获取key +// if (rateLimiter.limitType() == LimitType.IP) { +// stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append("-"); +// } + if (rateLimiter.limitType() == LimitType.USER) { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + String username = attributes.getRequest().getParameter("userId"); + if (username != null) { + stringBuffer.append(username).append("-"); + }else { + throw new RuntimeException("用户id为空"); // 抛出异常,禁止继续执行,防止缓存穿透,缓存穿透指缓存中不存在该key,但是数据库中存在该ke + } + } + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + Class targetClass = method.getDeclaringClass(); + stringBuffer.append(targetClass.getName()).append("-").append(method.getName()); + return stringBuffer.toString(); + } +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/aspect/annotation/RateLimiter.java b/Spring/src/main/java/cn/whaifree/springdemo/aspect/annotation/RateLimiter.java new file mode 100644 index 0000000..d1bd2f7 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/aspect/annotation/RateLimiter.java @@ -0,0 +1,38 @@ +package cn.whaifree.springdemo.aspect.annotation; + +import cn.whaifree.springdemo.constant.CacheConstants; +import cn.whaifree.springdemo.constant.LimitType; + +import java.lang.annotation.*; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/9 17:43 + * @注释 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter { + + /** + * 限流key + */ + public String key() default CacheConstants.RATE_LIMIT_KEY; + + /** + * 限流时间,单位秒 + */ + public int time() default 60; + + /** + * 限流次数 + */ + public int count() default 100; + + /** + * 限流类型 + */ + public LimitType limitType() default LimitType.DEFAULT; +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/config/CacheConfig.java b/Spring/src/main/java/cn/whaifree/springdemo/config/CacheConfig.java new file mode 100644 index 0000000..efe5369 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/config/CacheConfig.java @@ -0,0 +1,44 @@ +//package cn.whaifree.springdemo.config; +// +//import org.springframework.cache.Cache; +//import org.springframework.cache.CacheManager; +//import org.springframework.cache.annotation.EnableCaching; +//import org.springframework.cache.concurrent.ConcurrentMapCache; +//import org.springframework.cache.transaction.TransactionAwareCacheDecorator; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.transaction.annotation.EnableTransactionManagement; +// +//import java.util.Arrays; +//import java.util.Collection; +// +///** +// * @version 1.0 +// * @Author whai文海 +// * @Date 2024/10/25 22:44 +// * @注释 +// */ +//@Configuration +//@EnableCaching() +//@EnableTransactionManagement() +//public class CacheConfig { +// @Bean +// public CacheManager cacheManager() { +// return new CustomCacheManager(); +// } +// +// // 自定义的 CacheManager,装饰每个缓存为 TransactionAwareCacheDecorator +// static class CustomCacheManager implements CacheManager { +// @Override +// public Cache getCache(String name) { +// Cache cache = new ConcurrentMapCache(name); // 使用 ConcurrentMapCache 实现缓存 +// return new TransactionAwareCacheDecorator(cache); // 包装为 TransactionAwareCacheDecorator +// } +// +// @Override +// public Collection getCacheNames() { +// return Arrays.asList("myCache"); // 设定缓存的名称 +// } +// } +// +//} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/config/FilterConfig.java b/Spring/src/main/java/cn/whaifree/springdemo/config/FilterConfig.java new file mode 100644 index 0000000..7c7bd62 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/config/FilterConfig.java @@ -0,0 +1,32 @@ +package cn.whaifree.springdemo.config; + +import cn.whaifree.springdemo.utils.Filter.SelfFilter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/12 22:15 + * @注释 + */ +@Slf4j +@Configuration +public class FilterConfig { + + + + @Bean + public FilterRegistrationBean customFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setFilter(new SelfFilter()); + registration.addUrlPatterns("/*"); // 指定过滤器应用的 URL 模式 + registration.setName("customFilter"); + registration.setOrder(1); // 设置过滤器的顺序 + return registration; + } + + +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/config/GlobalExceptionHandler.java b/Spring/src/main/java/cn/whaifree/springdemo/config/GlobalExceptionHandler.java new file mode 100644 index 0000000..9f73033 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/config/GlobalExceptionHandler.java @@ -0,0 +1,23 @@ +package cn.whaifree.springdemo.config; + +import cn.whaifree.springdemo.utils.ResVo; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/11 16:28 + * @注释 + */ +@RestControllerAdvice +@Slf4j +public class GlobalExceptionHandler{ + @ExceptionHandler(Exception.class) + public ResVo handleException(Exception e) { + log.error("系统异常", e); + return ResVo.error(e.getMessage()); + } + +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/config/MyAutoConfiguration.java b/Spring/src/main/java/cn/whaifree/springdemo/config/MyAutoConfiguration.java new file mode 100644 index 0000000..2353936 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/config/MyAutoConfiguration.java @@ -0,0 +1,84 @@ +package cn.whaifree.springdemo.config; + +import jakarta.annotation.Resource; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.*; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.stereotype.Component; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/24 16:38 + * @注释 + */ +@Configuration +public class MyAutoConfiguration { + + + @Resource + private MyProperties myProperties; + + + @Bean(name = "whaiThreadPool") + @Conditional(value = {MyCondition.class}) + public ThreadPoolExecutor myService(MyProperties myProperties) { + return new ThreadPoolExecutor( + myProperties.getCoreSize(), + myProperties.getMaxSize(), + 10, + TimeUnit.SECONDS, + new ArrayBlockingQueue<>(10)); + } + +} + + +@Component +@ConfigurationProperties(prefix = "my.thread") +class MyProperties{ + String name = "whai"; + int coreSize = 1; + int maxSize = 2; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getCoreSize() { + return coreSize; + } + + public void setCoreSize(int coreSize) { + this.coreSize = coreSize; + } + + public int getMaxSize() { + return maxSize; + } + + public void setMaxSize(int maxSize) { + this.maxSize = maxSize; + } +} + +class MyCondition implements Condition { + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + String property = context.getEnvironment().getProperty("my.condition"); + if ("true".equals(property)) { + return true; + } + System.out.println("未加载"); + return false; + } + +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/config/MybatisConfig.java b/Spring/src/main/java/cn/whaifree/springdemo/config/MybatisConfig.java new file mode 100644 index 0000000..0418ec3 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/config/MybatisConfig.java @@ -0,0 +1,17 @@ +package cn.whaifree.springdemo.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/1 12:41 + * @注释 + */ +@Configuration +//@MapperScan("cn.whaifree.springdemo.mybatis.mapper") +@MapperScan("cn.whaifree.springdemo.mybatis.mapper") +public class MybatisConfig { + +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/config/RedisConfig.java b/Spring/src/main/java/cn/whaifree/springdemo/config/RedisConfig.java new file mode 100644 index 0000000..2351966 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/config/RedisConfig.java @@ -0,0 +1,78 @@ +package cn.whaifree.springdemo.config; + +import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/9 17:47 + * @注释 + */ +@Configuration +public class RedisConfig { + + @Bean + @SuppressWarnings(value = {"unchecked", "rawtypes"}) + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + FastJsonRedisSerializer jsonRedisSerializer = new FastJsonRedisSerializer(Object.class); + + // 使用StringRedisSerializer来序列化和反序列化redis的key值 + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(jsonRedisSerializer); + + // Hash的key也采用StringRedisSerializer的序列化方式 + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(jsonRedisSerializer); + + template.afterPropertiesSet(); + return template; + } + + + /** + * RedisScript是Redis官方Java客户端Jedis中的一个类,用于执行Redis脚本(Lua脚本)。 + * + * 在Redis中,Lua脚本是一种强大的工具,可以用于执行复杂的操作,如事务、发布/订阅、锁等。通过使用Lua脚本,你可以将多个操作封装在一个脚本中,然后一次性发送给Redis服务器执行,这样可以减少网络延迟和服务器负载。 + * + * RedisScript类提供了一些方法,可以用于设置脚本的返回类型、执行脚本、获取脚本的返回值等。 + * + * @return + */ + @Bean + public DefaultRedisScript limitScript() { + DefaultRedisScript redisScript = new DefaultRedisScript<>(); + redisScript.setScriptText(limitScriptText()); + redisScript.setResultType(Long.class); + return redisScript; + } + + /** + * 限流脚本 + */ + private String limitScriptText() { + + // 传入的参数有 time多长时间 count多少次 + + return "local key = KEYS[1]\n" + //test:cn.whaifree.springdemo.controller.TestController-test + "local count = tonumber(ARGV[1])\n" + // 传入的次数, 比如1次 + "local time = tonumber(ARGV[2])\n" + // 传入的时间,比如2s + "local current = redis.call('get', key);\n" + // 获取当前的次数 + "if current and tonumber(current) > count then\n" + + " return tonumber(current);\n" + // 当前的次数超过count 表示当前的次数超过限额,直接返回,表示拒绝 + "end\n" + + "current = redis.call('incr', key)\n" + // 如果没有超过限额,对value,即current增加 + "if tonumber(current) == 1 then\n" + // 如果是第一次,增加过期时间 + " redis.call('expire', key, time)\n" + + "end\n" + + "return tonumber(current);"; + } +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/config/RedissonConfig.java b/Spring/src/main/java/cn/whaifree/springdemo/config/RedissonConfig.java new file mode 100644 index 0000000..48dc317 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/config/RedissonConfig.java @@ -0,0 +1,26 @@ +package cn.whaifree.springdemo.config; + +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/12 20:56 + * @注释 + */ +@Configuration +public class RedissonConfig { + +// @Bean +// public RedissonClient redissonClient() { +// Config config = new Config(); +// config.useSingleServer().setAddress("redis://127.0.0.1:6379"); +// config.setThreads(10); +// RedissonClient redissonClient = Redisson.create(config); +// return redissonClient; +// } +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/config/SwaggerConfig.java b/Spring/src/main/java/cn/whaifree/springdemo/config/SwaggerConfig.java new file mode 100644 index 0000000..22fa38f --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/config/SwaggerConfig.java @@ -0,0 +1,42 @@ +//package cn.whaifree.springdemo.config; +// +//import io.swagger.v3.oas.models.ExternalDocumentation; +//import io.swagger.v3.oas.models.OpenAPI; +//import io.swagger.v3.oas.models.info.Info; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +// +// +//import java.util.ArrayList; +// +///** +// * Created with IntelliJ IDEA. +// * +// * @author : 村雨遥 +// * @version : 1.0 +// * @project : springboot-swagger3-demo +// * @package : com.cunyu.springbootswagger3demo.config +// * @className : SwaggerConfig +// * @createTime : 2022/1/6 14:19 +// * @email : 747731461@qq.com +// * @微信 : cunyu1024 +// * @公众号 : 村雨遥 +// * @网站 : https://cunyu1943.github.io +// * @description : +// */ +// +//@Configuration +//public class SwaggerConfig { +// @Bean +// public OpenAPI openAPI() { +// return new OpenAPI() +// .info(new Info() +// .title("接口文档标题") +// .description("SpringBoot3 集成 Swagger3接口文档") +// .version("v1")) +// .externalDocs(new ExternalDocumentation() +// .description("项目API文档") +// .url("/")); +// } +//} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/config/TransactionConfig.java b/Spring/src/main/java/cn/whaifree/springdemo/config/TransactionConfig.java new file mode 100644 index 0000000..7be3782 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/config/TransactionConfig.java @@ -0,0 +1,97 @@ +//package cn.whaifree.springdemo.config; +// +//import jakarta.annotation.Resource; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.jdbc.datasource.DataSourceTransactionManager; +//import org.springframework.transaction.PlatformTransactionManager; +//import org.springframework.transaction.TransactionDefinition; +//import org.springframework.transaction.TransactionManager; +//import org.springframework.transaction.TransactionStatus; +//import org.springframework.transaction.annotation.TransactionManagementConfigurer; +//import org.springframework.transaction.annotation.Transactional; +//import org.springframework.transaction.support.DefaultTransactionDefinition; +//import org.springframework.transaction.support.TransactionCallback; +//import org.springframework.transaction.support.TransactionTemplate; +// +//import javax.sql.DataSource; +// +///** +// * @version 1.0 +// * @Author whai文海 +// * @Date 2024/11/15 16:37 +// * @注释 +// */ +//@Configuration +//@Slf4j +//public class TransactionConfig implements TransactionManagementConfigurer { +// +// +// @Autowired +// private TransactionTemplate transactionTemplate; +// +// //配置事务管理器 +// @Bean +// public TransactionManager transactionManager(DataSource dataSource) { +// DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource); +// // 打印参数 +// log.info("transactionManager: {}", transactionManager); +// log.info("dataSource: {}", dataSource); +// return transactionManager; +// } +// +// @Resource(name="txManager1") +// private PlatformTransactionManager txManager1; +// +// // 创建事务管理器1 +// @Bean(name = "txManager1") +// public PlatformTransactionManager txManager(DataSource dataSource) { +// return new DataSourceTransactionManager(dataSource); +// } +// +// @Override +// public TransactionManager annotationDrivenTransactionManager() { +// return txManager1; +// } +// +// @Transactional(value="txManager1") +// public void addUser() { +// +// } +// +// public void addUser2() { +// DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition(); +// transactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); +// transactionDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); +// TransactionStatus transaction = txManager1.getTransaction(transactionDefinition); +// try { +// txManager1.commit(transaction); +// } catch (Exception e) { +// txManager1.rollback(transaction); +// } +// } +// +// /** +// * 编程事务 +// * TransactionTemplate +// * PlatformTransactionManager +// * DataSourceTransactionManager +// */ +// +// public void adduser3() { +// Object execute = transactionTemplate.execute(new TransactionCallback() { +// @Override +// public Object doInTransaction(TransactionStatus status) { +// return null; +// } +// }); +// } +// +// +//} +// +//class Tran{ +// +//} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/constant/CacheConstants.java b/Spring/src/main/java/cn/whaifree/springdemo/constant/CacheConstants.java new file mode 100644 index 0000000..990b18f --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/constant/CacheConstants.java @@ -0,0 +1,11 @@ +package cn.whaifree.springdemo.constant; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/9 17:44 + * @注释 + */ +public class CacheConstants { + public static final String RATE_LIMIT_KEY = "rate_limit:"; +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/constant/LimitType.java b/Spring/src/main/java/cn/whaifree/springdemo/constant/LimitType.java new file mode 100644 index 0000000..de3c45e --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/constant/LimitType.java @@ -0,0 +1,13 @@ +package cn.whaifree.springdemo.constant; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/9 17:45 + * @注释 + */ +public enum LimitType { + IP, + USER, + DEFAULT; +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/CacheDecoratorDemo/CacheDecoratorController.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/CacheDecoratorDemo/CacheDecoratorController.java new file mode 100644 index 0000000..9ed5299 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/CacheDecoratorDemo/CacheDecoratorController.java @@ -0,0 +1,67 @@ +//package cn.whaifree.springdemo.controller.CacheDecoratorDemo; +// +//import cn.hutool.extra.spring.SpringUtil; +//import jakarta.annotation.Resource; +//import org.springframework.cache.Cache; +//import org.springframework.cache.annotation.CachePut; +//import org.springframework.cache.annotation.Cacheable; +//import org.springframework.data.redis.cache.RedisCacheManager; +//import org.springframework.transaction.annotation.Transactional; +//import org.springframework.web.bind.annotation.RequestMapping; +//import org.springframework.web.bind.annotation.RestController; +// +///** +// * @version 1.0 +// * @Author whai文海 +// * @Date 2024/10/25 22:43 +// * @注释 +// */ +//@RestController +//public class CacheDecoratorController { +// +// @Resource +// private final RedisCacheManager cacheManager; +// +// +// public CacheDecoratorController(RedisCacheManager cacheManager) { +// this.cacheManager = cacheManager; +// } +// +// @RequestMapping("/getData") +// public String getData(Long id) { +// // 先从缓存中获取数据 +// String data = SpringUtil.getBean(CacheDecoratorController.class).getDataById(id); +// return data; +// } +// +// @RequestMapping("/update") +// public void update(Long id, String newValue) { +// // 更新数据 +// SpringUtil.getBean(CacheDecoratorController.class).updateData(id, newValue); +// } +// +// @Cacheable(value = "myCache", key = "#id") +// public String getDataById(Long id) { +// // 模拟获取数据操作 +// return "Data for ID " + id; +// } +// +// @Transactional +// @CachePut(value = "myCache", key = "#id", condition = "#result != null") +// public String updateData(Long id, String newValue) { +// // 更新数据库操作 +// // ... +// // 返回的新值会在事务提交后自动更新到缓存中 +// // return newValue; +// +// +// // 或者手动操作缓存 +// Cache myCache = cacheManager.getCache("myCache"); +// if (myCache != null) { +// myCache.put(id, newValue); +// return null; +// } +// return null; +// } +// +//} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/SSE/SSEEmitter.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/SSE/SSEEmitter.java new file mode 100644 index 0000000..4f38f89 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/SSE/SSEEmitter.java @@ -0,0 +1,49 @@ +//package cn.whaifree.springdemo.controller.SSE; +// +//import cn.hutool.core.util.StrUtil; +//import org.springframework.http.MediaType; +//import org.springframework.web.bind.annotation.GetMapping; +//import org.springframework.web.bind.annotation.PostMapping; +//import org.springframework.web.bind.annotation.RestController; +//import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; +// +//import java.io.IOException; +//import java.util.Map; +// +///** +// * @version 1.0 +// * @Author whai文海 +// * @Date 2024/10/22 21:44 +// * @注释 +// */ +//@RestController +//public class SSEEmitter { +// +// Map sseEmitterMap = new java.util.HashMap<>(); +// +// @GetMapping(value = "/sseStart", produces = MediaType.TEXT_EVENT_STREAM_VALUE) +// public SseEmitter sse(String key) { +// System.out.println(key); +// if (!sseEmitterMap.containsKey(key)) { +// SseEmitter sseEmitter = new SseEmitter(); +// sseEmitterMap.put(key, sseEmitter); +// } +// +// return sseEmitterMap.get(key); +// } +// +// @PostMapping("sendSSE") +// public void send(String key, String message) { +// if (sseEmitterMap.containsKey(key)) { +// SseEmitter sseEmitter = sseEmitterMap.get(key); +// try { +// System.out.println(StrUtil.format("send message to {}:{}", key, message)); +// sseEmitter.send(message); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// }else { +// throw new IllegalArgumentException("No such key"); +// } +// } +//} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/HTTP/QBController.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/HTTP/QBController.java new file mode 100644 index 0000000..27e9434 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/HTTP/QBController.java @@ -0,0 +1,58 @@ +package cn.whaifree.springdemo.controller.TS.HTTP; + +import cn.whaifree.springdemo.controller.TS.common.ProcessStrategy; +import cn.whaifree.springdemo.controller.TS.common.ProcessTarget; +import cn.whaifree.springdemo.controller.TS.common.TargetDown; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.imageio.ImageIO; +import javax.imageio.ImageWriter; +import javax.imageio.plugins.jpeg.JPEGImageWriteParam; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/22 21:21 + * @注释 + */ + +@RestController +public class QBController { + + @RequestMapping("/TargetDown") + public void targetDown(String msg) { + + ProcessStrategy processStrategy = ProcessTarget.getProcessStrategy(ProcessTarget.TARGET_DOWN); + processStrategy.process(msg); + } + + + public void compressImage(InputStream inputStream, float quality, String fileFullName) { + try { + BufferedImage read = ImageIO.read(inputStream); + File out = new File(fileFullName); + ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next(); + OutputStream os = new java.io.FileOutputStream(out); + JPEGImageWriteParam param = new JPEGImageWriteParam(null); + param.setCompressionMode(JPEGImageWriteParam.MODE_EXPLICIT); + param.setCompressionQuality(quality); // 压缩质量 + writer.setOutput(ImageIO.createImageOutputStream(os)); + writer.write(null, new javax.imageio.IIOImage(read, null, null), param); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @RequestMapping("/TargetFind") + public void targetFind(String msg) { + ProcessStrategy processStrategy = ProcessTarget.getProcessStrategy(ProcessTarget.TARGET_FIND); + processStrategy.process(msg); + } +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/SSE/SSEEmitterDemo.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/SSE/SSEEmitterDemo.java new file mode 100644 index 0000000..f79a63b --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/SSE/SSEEmitterDemo.java @@ -0,0 +1,55 @@ +package cn.whaifree.springdemo.controller.TS.SSE; + +import cn.hutool.core.util.StrUtil; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/22 21:44 + * @注释 + */ +@RestController +public class SSEEmitterDemo { + + Map sseEmitterMap = new ConcurrentHashMap<>(); + + /** + * 签约 + * @param key + * @return + */ + @GetMapping(value = "/sseStart", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public SseEmitter sse(String key) { + System.out.println(key); + if (!sseEmitterMap.containsKey(key)) { + SseEmitter sseEmitter = new SseEmitter(); + sseEmitterMap.put(key, sseEmitter); + } + return sseEmitterMap.get(key); + } + + + @PostMapping("sendSSE") + public void send(String key, String message) { + if (sseEmitterMap.containsKey(key)) { + SseEmitter sseEmitter = sseEmitterMap.get(key); + try { + System.out.println(StrUtil.format("send message to {}:{}", key, message)); + sseEmitter.send(message); + } catch (IOException e) { + e.printStackTrace(); + } + }else { + throw new IllegalArgumentException("No such key"); + } + } +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/UDP/UDPConfig.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/UDP/UDPConfig.java new file mode 100644 index 0000000..7213d51 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/UDP/UDPConfig.java @@ -0,0 +1,197 @@ +package cn.whaifree.springdemo.controller.TS.UDP; + +import cn.whaifree.springdemo.controller.TS.common.ProcessStrategy; +import cn.whaifree.springdemo.controller.TS.common.ProcessTarget; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.integration.annotation.BridgeFrom; +import org.springframework.integration.annotation.Transformer; +import org.springframework.integration.channel.DirectChannel; +import org.springframework.integration.channel.PublishSubscribeChannel; +import org.springframework.integration.dispatcher.LoadBalancingStrategy; +import org.springframework.integration.dsl.*; +import org.springframework.integration.handler.MessageProcessor; +import org.springframework.integration.ip.udp.UnicastReceivingChannelAdapter; +import org.springframework.integration.ip.udp.UnicastSendingMessageHandler; +import org.springframework.integration.transformer.AbstractMessageProcessingTransformer; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.MessageHandler; +import org.springframework.messaging.MessagingException; +import org.springframework.messaging.handler.annotation.Headers; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Component; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.SecretKeySpec; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.Map; + + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/22 20:32 + * @注释 + */ +@Configuration +public class UDPConfig { + + static final String encoderNumber = "123"; + + /* + + org.springframework.boot + spring-boot-starter-integration + + + org.springframework.integration + spring-integration-ip + + +*/ + + /** + * 接收 + * + * @param udpClient + * @return + */ + @Bean + public IntegrationFlow processUniCastUdpMessage(@Qualifier("UDPGet") MessageHandler udpClient) { + UnicastReceivingChannelAdapter channelAdapter = new UnicastReceivingChannelAdapter(9030); + channelAdapter.setReceiveBufferSize(4096); + channelAdapter.setLengthCheck(false); + return IntegrationFlow + .from(channelAdapter) + .handle(udpClient) +// .transform(this, "encoderTransformer") +// .channel("udpChannel") + .get(); + } + + class Encryptor { + +// public static String encrypt(Object msg, String key) { +// try { +// SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES"); +// Cipher cipher = Cipher.getInstance("AES"); +// cipher.init(Cipher.ENCRYPT_MODE, secretKey); +// byte[] encrypted = cipher.doFinal((byte[]) msg); +// return Base64.getEncoder().encodeToString(encrypted); +// } catch (Exception e) { +// throw new RuntimeException("Encryption failed", e); +// } +// } +// +// public static byte[] decrypt(byte[] encryptedMsg, String key) { +// try { +// SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES"); +// Cipher cipher = Cipher.getInstance("AES"); +// cipher.init(Cipher.DECRYPT_MODE, secretKey); +// return cipher.doFinal(Base64.getDecoder().decode(encryptedMsg)); +// } catch (Exception e) { +// throw new RuntimeException(e); +// } +// } + + private static final String ALGORITHM = "AES"; + private static byte[] SECRET_KEY = null; // 替换为你自己的密钥,密钥长度必须符合算法要求 + + public static byte[] encrypt(byte[] data) throws Exception { + Cipher cipher = Cipher.getInstance(ALGORITHM); + SecretKeySpec secretKey = new SecretKeySpec(SECRET_KEY, ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + return cipher.doFinal(data); + } + + public static byte[] decrypt(byte[] encryptedData) throws Exception { + Cipher cipher = Cipher.getInstance(ALGORITHM); + SecretKeySpec secretKey = new SecretKeySpec(SECRET_KEY, ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, secretKey); + return cipher.doFinal(encryptedData); + } + + } + +// +// /** +// * 转换器 在消息通道(Channel)之间传递消息时进行数据格式的转换 +// * +// * @param payload +// * @param headers +// */ +// @Transformer(inputChannel = "channelAdapter", outputChannel = "udpChannel") +// public byte[] encoderTransformer(@Payload byte[] payload, @Headers Map headers) { +// return Encryptor.decrypt(payload, encoderNumber); +// } + +// @Bean("udpChannel") +// public MessageChannel udpChannel() { +// return new DirectChannel(); +// } + + +// /** +// * 一对一 +// * +// * @return +// */ +// @Bean +// @BridgeFrom("messageChannel2") +// public MessageChannel directChannel2() { +// return new DirectChannel(); +// } +// +// /** +// * 一对一 +// * +// * @return +// */ +// @Bean +// @BridgeFrom("messageChannel2") +// public MessageChannel directChannel() { +// return MessageChannels.direct().getObject(); +// } +// +// /** +// * 发布订阅 一对多 +// */ +// @Bean +// public MessageChannel messageChannel2() { +// return MessageChannels.publishSubscribe().getObject(); +// } + + + /** + * 发送 + * + * @return + */ + @Bean + public UnicastSendingMessageHandler sending() { + return new UnicastSendingMessageHandler("localhost", 9030, false); + } + +} + + +@Component("UDPGet") +@Slf4j +class UdpGet implements MessageHandler { + @Override + public void handleMessage(Message message) throws MessagingException { + byte[] frame = (byte[]) message.getPayload(); + StringBuilder result = new StringBuilder("16进制表示:"); + for (byte aByte : frame) { + result.append(String.format("%02x ", aByte)); + } + log.info(result.toString()); + ProcessStrategy processStrategy = ProcessTarget.getProcessStrategy(frame); + processStrategy.process(frame); + } +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/UDP/UDPSendUtils.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/UDP/UDPSendUtils.java new file mode 100644 index 0000000..6e52a47 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/UDP/UDPSendUtils.java @@ -0,0 +1,30 @@ +package cn.whaifree.springdemo.controller.TS.UDP; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.ip.udp.UnicastSendingMessageHandler; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.messaging.Message; +import org.springframework.stereotype.Component; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/22 20:24 + * @注释 + */ +@Component +public class UDPSendUtils { + + private final UnicastSendingMessageHandler sender; + + @Autowired + public UDPSendUtils(UnicastSendingMessageHandler sender) { + this.sender = sender; + } + + public void send(Object object) { + Message msg = MessageBuilder.withPayload(object).build(); + sender.handleMessage(msg); + } + +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/common/ProcessStrategy.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/common/ProcessStrategy.java new file mode 100644 index 0000000..739cf98 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/common/ProcessStrategy.java @@ -0,0 +1,7 @@ +package cn.whaifree.springdemo.controller.TS.common; + +public interface ProcessStrategy { + void process(byte[] frame); + + void process(Object o); +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/common/ProcessTarget.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/common/ProcessTarget.java new file mode 100644 index 0000000..7756d9c --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/common/ProcessTarget.java @@ -0,0 +1,47 @@ +package cn.whaifree.springdemo.controller.TS.common; + +import cn.hutool.extra.spring.SpringUtil; +import jakarta.annotation.PostConstruct; +import org.springframework.stereotype.Component; + +import java.util.HashMap; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/22 20:48 + * @注释 + */ +@Component +public class ProcessTarget { + + public static final int TARGET_FIND = processToInt(new byte[]{0x00, 0x00, 0x00, 0x00}); + public static final int TARGET_DOWN = processToInt(new byte[]{0x00, 0x00, 0x00, 0x01}); + + static HashMap processStrategyHashMap = new HashMap<>(); + + @PostConstruct + public void init() { + processStrategyHashMap.put(TARGET_FIND, SpringUtil.getBean(TargetStorage.class)); + processStrategyHashMap.put(TARGET_DOWN, SpringUtil.getBean(TargetDown.class)); + } + + private static int processToInt(byte[] heads) { + if (heads.length < 4) { + return -1; + } + // 获取前4个byte转为int + return (heads[0] & 0xFF) << 24 | (heads[1] & 0xFF) << 16 | (heads[2] & 0xFF) << 8 | (heads[3] & 0xFF); + } + + public static ProcessStrategy getProcessStrategy(byte[] heads) { + return processStrategyHashMap.get(processToInt(heads)); + } + + public static ProcessStrategy getProcessStrategy(int code) { + return processStrategyHashMap.get(code); + } + +} + + diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/common/TargetDown.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/common/TargetDown.java new file mode 100644 index 0000000..ab8abff --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/common/TargetDown.java @@ -0,0 +1,27 @@ +package cn.whaifree.springdemo.controller.TS.common; + +import org.springframework.stereotype.Component; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/22 21:22 + * @注释 + */ +@Component +public class TargetDown implements ProcessStrategy { + + @Override + public void process(byte[] frame) { + + + // 封装成object + process(frame); + } + + @Override + public void process(Object o) { + System.out.println("TargetDown"); + + } +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/common/TargetImage.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/common/TargetImage.java new file mode 100644 index 0000000..aa55bb5 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/common/TargetImage.java @@ -0,0 +1,78 @@ +package cn.whaifree.springdemo.controller.TS.common; + +import org.springframework.stereotype.Component; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/22 21:22 + * @注释 + */ +@Component +public class TargetImage implements ProcessStrategy { + + private final Map> imageMap = new ConcurrentHashMap<>(); + + /** + * @param frame + */ + @Override + public void process(byte[] frame) { + try { + + // 读取第5-8个字节作为id + int id = getIntByByteArray(frame, 5, 8); + int seq = getIntByByteArray(frame, 9, 12); + int sumGramSize = getIntByByteArray(frame, 13, 16); // 数据报数量 + if (!imageMap.containsKey(id)) { + imageMap.put(id, new TreeMap<>()); + } + TreeMap treeMap = imageMap.get(id); + treeMap.put(seq, frame); + if (treeMap.size() == sumGramSize) { + // 满了 + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + for (int i = 1; i <= sumGramSize; i++) { + byte[] fragment = treeMap.get(i); + if (fragment != null) { + // 假设图像数据从第17字节开始 + outputStream.write(fragment, 17, fragment.length - 17); + } + } + outputStream.write(frame); + // 输入到Minio + // minioClient.putObject("test", "image.jpg", outputStream.toByteArray(), null); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + // 封装成object + process(frame); + } + + public int getIntByByteArray(byte[] frame, int left, int right) { + int result = 0; + for (int i = left; i <= right; i++) { + result = result | (frame[i] & 0xFF) << (right - i) * 8; + } + return result; + } + + + @Override + public void process(Object o) { + System.out.println("图像"); + + } + +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/common/TargetStorage.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/common/TargetStorage.java new file mode 100644 index 0000000..1164fae --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/TS/common/TargetStorage.java @@ -0,0 +1,26 @@ +package cn.whaifree.springdemo.controller.TS.common; + +import org.springframework.stereotype.Component; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/22 21:21 + * @注释 + */ +@Component +public class TargetStorage implements ProcessStrategy { + + @Override + public void process(byte[] frame) { + // 封装 + process(frame); + } + + @Override + public void process(Object o) { + System.out.println("TargetStorage"); + } +} + + diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/TestController.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/TestController.java new file mode 100644 index 0000000..4857365 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/TestController.java @@ -0,0 +1,111 @@ +package cn.whaifree.springdemo.controller; + +import cn.whaifree.springdemo.aspect.annotation.RateLimiter; +import cn.whaifree.springdemo.constant.LimitType; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.concurrent.*; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/9 17:40 + * @注释 + */ +@RestController +public class TestController { + @PostMapping("/test") + @RateLimiter(key = "test:", time = 10, count = 1) // 10s只能1次 + public String test() { + return "test"; + } + + /** + * + * BG:重复导入多个容器,有一定几率触发15s响应超时 + * 1. 限频率,如果重复导入直接返回已经在导入中 + * 2. 控制导入的数量,对导入的数量分片+线程池处理 + * + * @return + */ + @PostMapping("addContainer") + @RateLimiter(key = "addContainer:", limitType = LimitType.USER, time = 5, count = 1) // 10s只能1次 + @Transactional(rollbackFor = Exception.class) + public String addContainerInstanceToCluster(@RequestBody List instances, int userId) { + // 导入容器节点到集群中 + return addToCluster(instances, "clusterId", userId); + } + + + + /** + * + * @param instances 导入实例的ID + * @param userID + * @return + */ + public String addToCluster(List instances,String clusterId, int userID) { + return new MockK8sAPI().loadTo(instances, clusterId).toString(); + } + + ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10); + + + class CallableRun implements Callable { + + String id; + + public CallableRun(String id, CountDownLatch countDownLatch) { + this.id = id; + } + + @Override + public Object call(){ + + return null; + } + } + + class MockK8sAPI{ + + /** + * + * @param instances + * @param clusterId + * @return + */ + public List loadTo(List instances, String clusterId) { + + CountDownLatch countDownLatch = new CountDownLatch(instances.size()); + + for (String instance : instances) { + executor.submit(new Callable() { + @Override + public String call() throws Exception { + try { + System.out.println(instance); + }finally { + countDownLatch.countDown(); + } + return instance; + } + }); + } + try { + countDownLatch.await(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + System.out.println("超时"); + throw new RuntimeException(e); + } + + return instances; + } + } + + + +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/WhiteListController.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/WhiteListController.java new file mode 100644 index 0000000..d6c6163 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/WhiteListController.java @@ -0,0 +1,23 @@ +package cn.whaifree.springdemo.controller; + +import jakarta.annotation.Resource; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/15 21:29 + * @注释 + */ +@RestController +public class WhiteListController { + + @Resource + private RedisTemplate redisTemplate; + @PostMapping("/queryIn") + public boolean query(String userId) { + return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember("whiteList", userId)); + } +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/cacheComparator/CacheComparatorDemo.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/cacheComparator/CacheComparatorDemo.java new file mode 100644 index 0000000..aafba48 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/cacheComparator/CacheComparatorDemo.java @@ -0,0 +1,132 @@ +package cn.whaifree.springdemo.controller.cacheComparator; + + +import com.github.benmanes.caffeine.cache.AsyncLoadingCache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.google.common.cache.CacheBuilder; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.time.Duration; +import java.util.concurrent.ForkJoinPool; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/24 22:27 + * @注释 + */ +@RestController +public class CacheComparatorDemo { + + private com.google.common.cache.LoadingCache guavaCache = CacheBuilder.newBuilder() + .maximumSize(1000) + .expireAfterWrite(Duration.ofDays(1)) + .initialCapacity(10) + .build(new com.google.common.cache.CacheLoader() { + @Override + public Object load(Object key) throws Exception { + return null; + } + }); + + + private com.github.benmanes.caffeine.cache.LoadingCache caffeineCache = Caffeine.newBuilder() + .maximumSize(1000) + .expireAfterWrite(Duration.ofDays(1)) + .build(new com.github.benmanes.caffeine.cache.CacheLoader() { + @Override + public @Nullable Object load(Object key) throws Exception { + return null; + } + }); + + + + @PostMapping("/guavaPut") + public String guava(String key, String value) { + guavaCache.put(key, value); + return "guava put success"; + } + @GetMapping("/guavaGet") + public String guavaGet(String key) { + return (String) guavaCache.getIfPresent(key); + } + + @PostMapping("/caffeinePut") + public String caffeine(String key, String value) { + caffeineCache.put(key, value); + return "caffeine put success"; + } + @GetMapping("/caffeineGet") + public String caffeineGet(String key) { + return (String) caffeineCache.getIfPresent(key); + } + + /** + * AsyncLoadingCache是继承自LoadingCache类的, + * 异步加载使用Executor去调用方法并返回一个CompletableFuture, + * 相比于同步填充模式,在load数据时, + * 使用异步线程来执行load方法, + * 默认使用ForkJoinPool.commonPool()来执行异步线程, + * 我们可以通过Caffeine.executor(Executor) 方法来替换线程池。 + */ + private AsyncLoadingCache cache = Caffeine.newBuilder() + .recordStats() + .maximumSize(1000) + .expireAfterWrite(Duration.ofDays(1)) // 缓存有效期1天,write后1天过期 + .expireAfterAccess(Duration.ofDays(1)) // 缓存有效期1天,访问后1天过期 + .executor(ForkJoinPool.commonPool()) // 默认使用ForkJoinPool.commonPool()来执行异步线程 + .buildAsync( + new com.github.benmanes.caffeine.cache.CacheLoader() { + @Override + public @Nullable Object load(Object key) throws Exception { + return null; + } + } + ); + + + /** + * ... + * + * guava提供了两种回收策略 + * - 但Guava室友在获取值的时候进行回收,如果一直没get,会导致缓存一直在占用内存 + * 并不会导致数据直接失效,而是在get时,去load新的值 + * - 一旦一个kv写入缓存,设置过期时间无法去除(没有get就不会对其清除,但如果过期了,去load新的值如果不是null,就会一直占用内存) + * - 所以最好设置MaxSize,设置缓存大小,不然可能内存泄露 + * + * caffine和guava相同的两种过期策略也是惰性删除(在get时去进行过期判断过期),和guava基本一致 + * + * + * caffeine chache通过W-TinyLFU算法进行数据驱逐: + * + Caffeine 使用 W-TinyLFU 作为其默认的频率计数器,以支持高效的缓存淘汰策略。以下是一些关键点: + 频率计数器: Caffeine 使用一个小型的高频计数器数组来记录每个键的访问频率。 + 权重因子: 通过权重因子调整频率计数,使得频繁访问的键更容易保留。 + 淘汰策略: 当缓存达到容量上限时,根据频率计数和权重因子决定哪些键应该被淘汰。 + * + * + * 通过对guava cache 和caffeine 从性能到算法及使用的对比中,可以发现Caffeine基本是在Guava的基础上进行优化而来的,提供的功能基本一致,但是通过对算法和部分逻辑的优化,完成了对性能极大的提升,而且我们可以发现,两者切换几乎没有成本,毕竟caffeine就是以替换guava cache为目的而来的。 + * + * @param key + * @return + */ + +// @Override +// default CompletableFuture get(K key, Function mappingFunction) { +// requireNonNull(mappingFunction); +// return get(key, (k1, executor) -> CompletableFuture.supplyAsync( +// () -> mappingFunction.apply(key), executor)); +// } + + @RequestMapping("/asyncGet") + public String asyncGet(String key) { + + return ""; + } + +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/idempotence/IdempotenceController.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/idempotence/IdempotenceController.java new file mode 100644 index 0000000..f4265dd --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/idempotence/IdempotenceController.java @@ -0,0 +1,135 @@ +package cn.whaifree.springdemo.controller.idempotence; + +import cn.whaifree.springdemo.utils.ResVo; +import com.alibaba.fastjson.JSON; +import jakarta.annotation.Resource; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.sql.PreparedStatement; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * 幂等性测试 + * + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/11 16:06 + * @注释 + */ +@RestController +@RequestMapping("/idempotence") +public class IdempotenceController { + + @Resource + JdbcTemplate jdbcTemplate; + @Resource + RedisTemplate redisTemplate; + + @PostMapping("query") + public ResVo query() { + List> maps = jdbcTemplate.queryForList("select * from orders"); + return ResVo.success(JSON.toJSONString(maps)); + } + + /** + * CREATE TABLE `orders` ( + * `id` INT AUTO_INCREMENT PRIMARY KEY, + * `order_no` VARCHAR(100) UNIQUE NOT NULL COMMENT '订单号', -- 唯一 + * `user_id` INT NOT NULL COMMENT '用户ID', + * `total_amount` DECIMAL(10,2) NOT NULL COMMENT '订单总金额', + * `status` TINYINT DEFAULT 0 COMMENT '订单状态: 0-未支付, 1-已支付', + * `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' + * ); + *

+ * java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '1' for key 'order_no' + * at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:118) ~[mysql-connector-j-8.0.33.jar:8.0.33] + * at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-j-8.0.33.jar:8.0.33] + * + * @param orderId + * @param userId + * @param totalAmount + * @return + */ + @PostMapping("insertByMySQL") + public ResVo insertMySQL(String orderId, String userId, Integer totalAmount) { + + int update = jdbcTemplate.update(con -> { + PreparedStatement preparedStatement = con.prepareStatement( + "INSERT INTO orders (order_no, user_id, total_amount) " + + "VALUES (?, ?, ?);" + ); + preparedStatement.setString(1, orderId); + preparedStatement.setString(2, userId); + preparedStatement.setString(3, String.valueOf(totalAmount)); + return preparedStatement; + }); + + return update > 0 ? ResVo.success("插入成功") : ResVo.error("插入失败"); + } + + /** + * 适用于更新操作 + * + * @return + */ + @PostMapping("upadteCAS") + public ResVo updateCAS(String orderId, Integer totalAmount, Integer originVersion) { + + int update = jdbcTemplate.update(con -> { + PreparedStatement preparedStatement = con.prepareStatement( + "UPDATE orders " + + "SET total_amount = ?, version=version+1 " + + "WHERE order_no = ? AND version = ?"); + preparedStatement.setInt(1, totalAmount); + preparedStatement.setString(2, orderId); + preparedStatement.setInt(3, originVersion); + return preparedStatement; + }); + + + return update > 0 ? ResVo.success("更新成功") : ResVo.error("更新失败"); + } + + + final String tokenV = "tokenV"; + + /** + * Token防刷 + */ + @PostMapping("token") + public ResVo generateToken() { + // 生成UUID + String s = UUID.randomUUID().toString(); + String key = "token:" + s; + redisTemplate.opsForValue().set(key, tokenV, 60, TimeUnit.SECONDS); + return ResVo.success(key); + } + + @PostMapping("tokenCheck") + public ResVo tokenCheck(String key) { + String script = + // 有这个token则通过,并且删除,只能一次性使用 + "if redis.call('get',KEYS[1]) == ARGV[1] " + // key argv为参数 + "then " + + "return redis.call('del',KEYS[1]) " + + "else " + + "return 0 end"; + + DefaultRedisScript tDefaultRedisScript = new DefaultRedisScript<>(script, Long.class); + + Long execute = redisTemplate.execute(tDefaultRedisScript, Arrays.asList(key), tokenV); + if (execute == 0) { + throw new RuntimeException("未通过"); + } + return ResVo.success("通过"); + } +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/BuinessException.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/BuinessException.java new file mode 100644 index 0000000..d8f93a4 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/BuinessException.java @@ -0,0 +1,22 @@ +package cn.whaifree.springdemo.controller.interceptRetry; + +import lombok.Getter; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/10 15:26 + * @注释 + */ +public class BuinessException extends RuntimeException { + @Getter + private ErrorType type; + private Integer code; + + + public BuinessException(ErrorType type, Integer code) { + this.type = type; + this.code = code; + } +} + diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/ErrorType.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/ErrorType.java new file mode 100644 index 0000000..9040b34 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/ErrorType.java @@ -0,0 +1,18 @@ +package cn.whaifree.springdemo.controller.interceptRetry; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/10 15:26 + * @注释 + */ +public enum ErrorType { + RetryType("Retry Too Many", 503); + + private String type; + private Integer code; + ErrorType (String type, Integer code) { + this.type = type; + this.code = code; + } +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/RetryController.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/RetryController.java new file mode 100644 index 0000000..d34b204 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/RetryController.java @@ -0,0 +1,24 @@ +package cn.whaifree.springdemo.controller.interceptRetry; + +import cn.whaifree.springdemo.controller.interceptRetry.aspect.RetryLimit; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/9 21:56 + * @注释 + */ +@RestController +public class RetryController { + + @PostMapping("/try") + @RetryLimit(limitCount = 3, limitTime = 1, limitKey = "ip", resMsg = "retry请求频繁") + public String tryMethod(int success) { + if (success == 1) { + throw new BuinessException(ErrorType.RetryType, 500); + } + return "tryMethod"; + } +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/aspect/RetryAspect.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/aspect/RetryAspect.java new file mode 100644 index 0000000..2a98769 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/aspect/RetryAspect.java @@ -0,0 +1,148 @@ +package cn.whaifree.springdemo.controller.interceptRetry.aspect; + +import cn.whaifree.springdemo.controller.interceptRetry.BuinessException; +import cn.whaifree.springdemo.controller.interceptRetry.ErrorType; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +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.context.ApplicationContext; +import org.springframework.context.annotation.Lazy; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.lang.reflect.Method; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + 极海 + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/9 21:57 + * @注释 + */ +@Aspect +@Component +@Order(100) +public class RetryAspect { + + /** + * 一个Cache只能有一个有效期, + * 所以要根据有效期进行分组 + *

+ * 【exprieTime,【keyOfIp,time】】 + *

+ *

+ * 对不同时间用不同cache实现 + */ + Map> cacheMap = new HashMap<>(); + + @Resource + @Lazy + private ApplicationContext applicationContext; + + /** + * 初始化方法,用于扫描所有Bean的方法,寻找带有RetryLimit注解的方法 + * 并根据注解的限制时间创建相应的缓存对象 + */ + @PostConstruct + public void init() { + String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); + for (String beanDefinitionName : beanDefinitionNames) { + // 这里有疑问,可能拿到RetryAspect走getBean导致循环依赖,RetryAspect循环依赖RetryAspect + if (beanDefinitionName.equalsIgnoreCase(this.getClass().getSimpleName())) { + continue; + } + Object bean = applicationContext.getBean(beanDefinitionName); + + Method[] methods = bean.getClass().getDeclaredMethods(); + for (Method method : methods) { + try { + // 手动用反射获取不到注解 + RetryLimit retryLimit = AnnotationUtils.findAnnotation(method, RetryLimit.class); +// RetryLimit retryLimit = method.getAnnotation(RetryLimit.class); + if (retryLimit == null) { + continue; + } + int expireTime = retryLimit.limitTime(); + Cache build = + Caffeine.newBuilder() + .expireAfterAccess(Duration.ofMinutes(expireTime)) + .maximumSize(1000) + .build(); + cacheMap.put(expireTime, build); + } catch (Exception e) { + e.printStackTrace(); + } + + } + } + System.out.println(cacheMap); + } + + + + /** + * 环绕通知,拦截带有RetryLimit注解的方法 + * 检查方法的重试次数是否超过限制,如果超过则抛出异常,否则继续执行方法 + * + * @param joinPoint 切入点对象,包含被拦截方法的信息 + * @return 被拦截方法的返回值 + * @throws Throwable 被拦截方法抛出的异常 + */ + @Around("@annotation(cn.whaifree.springdemo.controller.interceptRetry.aspect.RetryLimit)") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + // 获取方法签名,设置方法可以访问 + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + method.setAccessible(true); + + // 获取注解 + RetryLimit retryAn = AnnotationUtils.findAnnotation(method, RetryLimit.class); + if (retryAn == null) { + return joinPoint.proceed(); + } + + // 如果包含注解,放入缓存,key为ip或者其他限流key,value为次数 + Cache cache = cacheMap.get(retryAn.limitTime()); + String limitKey = retryAn.limitKey(); + if (limitKey == null || limitKey.equals("ip")) { + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = requestAttributes.getRequest(); + limitKey = request.getRemoteAddr(); + }else if (limitKey.equals("userId")) { + // 其他策略 + } + // 如果缓存中没有这个ip访问过,初始化为0 + AtomicInteger atomicInteger = cache.get(limitKey, s -> new AtomicInteger(0)); + if (atomicInteger.intValue() >= retryAn.limitCount()) { + throw new RuntimeException(retryAn.resMsg()); + } + + try { + return joinPoint.proceed(); + } catch (BuinessException e) { + // 如果不是验证错误,向上抛出 + if (!e.getType().equals(ErrorType.RetryType)) { + throw e; + } + // 如果验证错误,对atomic++ + atomicInteger.incrementAndGet(); + String msg = retryAn.resMsg() + ",重试次数:" + atomicInteger.intValue(); + throw new RuntimeException(msg); + } + } + + +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/aspect/RetryLimit.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/aspect/RetryLimit.java new file mode 100644 index 0000000..69ab419 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/aspect/RetryLimit.java @@ -0,0 +1,26 @@ +package cn.whaifree.springdemo.controller.interceptRetry.aspect; + +import cn.whaifree.springdemo.controller.interceptRetry.ErrorType; +import cn.whaifree.springdemo.utils.ResVo; +import org.springframework.data.redis.connection.ReturnType; + +import java.lang.annotation.*; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/9 21:57 + * @注释 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RetryLimit { + + int limitCount() default 3; + int limitTime() default 60; + String limitKey() default "ip"; + String resMsg() default "请求过于频繁"; + ErrorType errorType() default ErrorType.RetryType; + +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/minio/MinioController.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/minio/MinioController.java new file mode 100644 index 0000000..62200ad --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/minio/MinioController.java @@ -0,0 +1,196 @@ +package cn.whaifree.springdemo.controller.minio; + +import cn.hutool.crypto.digest.MD5; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import io.minio.MinioClient; +import io.minio.ObjectWriteResponse; +import io.minio.PutObjectArgs; +import jakarta.annotation.Resource; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Headers; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import org.springframework.util.StreamUtils; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.URI; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/16 9:38 + * @注释 + */ +@RestController +@Slf4j +public class MinioController { + @Resource + private ImageUploader imageUploader; + /** + * 外网图片转存缓存 + */ + private LoadingCache imgReplaceCache = CacheBuilder.newBuilder().maximumSize(300).expireAfterWrite(5, TimeUnit.MINUTES).build(new CacheLoader() { + @Override + public String load(String img) { + try { + InputStream stream = null; + if (img.startsWith("http")) { + // 下载变输入 + HttpRequest get = HttpUtil.createGet(img); + get.header("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"); + HttpResponse response = get.execute(); + stream = response.bodyStream(); + }else { + return ""; + } + URI uri = URI.create(img); + String path = uri.getPath(); + int index = path.lastIndexOf("."); + String fileType = null; + if (index > 0) { + // 从url中获取文件类型 + fileType = path.substring(index + 1); + } + return imageUploader.upload(stream, fileType); + } catch (Exception e) { + log.error("外网图片转存异常! img:{}", img, e); + return ""; + } + } + }); + + + @PostMapping("/changeLink") + public String changeLink(String url) throws ExecutionException { + return imgReplaceCache.get(url); + } +} +@Slf4j +@ConditionalOnExpression(value = "#{'minio'.equals(environment.getProperty('image.oss.type'))}") +@Component +class MinioOssWrapper implements ImageUploader, InitializingBean { + + private MinioClient minioClient; + @Autowired + @Setter + @Getter + private ImageProperties properties; + + + @Override + public String upload(InputStream inputStream, String fileType) { + try { + byte[] bytes = StreamUtils.copyToByteArray(inputStream); + // 计算md5作为文件名,避免重复上传 + String fileName = MD5.create().digestHex(bytes); + ByteArrayInputStream input = new ByteArrayInputStream(bytes); + + fileName = fileName + "." + fileType; + log.info("上传文件名:{}", fileName); + + PutObjectArgs args = PutObjectArgs.builder() + .bucket(properties.getOss().getBucket()) + .object(fileName) + .stream(input, input.available(), -1) + .contentType(fileType) + .build(); + + ObjectWriteResponse response = minioClient.putObject(args); + + // 获取response状态码 + Headers headers = response.headers(); + log.info(headers.toString()); + + + StringBuilder sb = new StringBuilder(); + sb.append(properties.getOss().getEndpoint()).append("/").append(response.bucket()).append("/").append(response.object()); + + return sb.toString(); + + } catch (Exception e) { + log.error(e.getMessage()); + return ""; + } + } + + @Override + public boolean uploadIgnore(String fileUrl) { + return false; + } + + @Override + public void afterPropertiesSet() throws Exception { + // 创建OSSClient实例。 + log.info("init ossClient"); + minioClient = MinioClient.builder() + .credentials( + properties.getOss().getAk(), + properties.getOss().getSk() + ) + .endpoint(properties.getOss().getEndpoint()) + .build(); + } +} + +@Setter +@Getter +@Component +@ConfigurationProperties(prefix = "image") +class ImageProperties { + private String absTmpPath; // 存储绝对路径 + private String webImgPath; // 存储相对路径 + private String tmpUploadPath; // 上传文件的临时存储目录 + private String cdnHost; // 访问图片的host + private OssProperties oss; +} + +@Data +class OssProperties { + private String prefix; // 上传文件前缀路径 + private String type; // oss类型 + //下面几个是oss的配置参数 + private String endpoint; + private String ak; + private String sk; + private String bucket; + private String host; +} + + +interface ImageUploader { + String DEFAULT_FILE_TYPE = "txt"; +// Set STATIC_IMG_TYPE = new HashSet<>(Arrays.asList(MediaType.ImagePng, MediaType.ImageJpg, MediaType.ImageWebp, MediaType.ImageGif)); + + /** + * 文件上传 + * + * @param input + * @param fileType + * @return + */ + String upload(InputStream input, String fileType); + + /** + * 判断外网图片是否依然需要处理 + * + * @param fileUrl + * @return true 表示忽略,不需要转存 + */ + boolean uploadIgnore(String fileUrl); +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/rabbitMqEvent/EventController.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/rabbitMqEvent/EventController.java new file mode 100644 index 0000000..36aa3c0 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/rabbitMqEvent/EventController.java @@ -0,0 +1,106 @@ +package cn.whaifree.springdemo.controller.rabbitMqEvent; + +import cn.hutool.extra.spring.SpringUtil; +import cn.whaifree.springdemo.utils.ResVo; +import lombok.Getter; +import lombok.ToString; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/15 19:12 + * @注释 + */ +@RestController +public class EventController { + + /** + * 根据不同的青桔 + * + * @param msg + * @return + */ + @PostMapping("/sendMsg") + public ResVo sendMsgNotify(NotifyTypeEnum notifyType, String msg) { + // 发送异步消息 + SpringUtil.publishEvent(new NotifyMsgEvent<>(this, notifyType, msg)); + return ResVo.success(); + } +} + +@Getter +@ToString +class NotifyMsgEvent extends ApplicationEvent { + private NotifyTypeEnum notifyType; + private T content; + public NotifyMsgEvent(Object source, NotifyTypeEnum notifyType, T content) { + super(source); + this.notifyType = notifyType; + this.content = content; + } +} + + +@Service +class NotifyMsgListener implements ApplicationListener> { + @Override + public void onApplicationEvent(NotifyMsgEvent event) { + System.out.println(event); // 获取到发送的消息,做下一步处理 + } +} + + + +@Getter +enum NotifyTypeEnum { + COMMENT(1, "评论"), + REPLY(2, "回复"), + PRAISE(3, "点赞"), + COLLECT(4, "收藏"), + FOLLOW(5, "关注消息"), + SYSTEM(6, "系统消息"), + DELETE_COMMENT(1, "删除评论"), + DELETE_REPLY(2, "删除回复"), + CANCEL_PRAISE(3, "取消点赞"), + CANCEL_COLLECT(4, "取消收藏"), + CANCEL_FOLLOW(5, "取消关注"), + + // 注册、登录添加系统相关提示消息 + REGISTER(6, "用户注册"), + LOGIN(6, "用户登录"), + ; + + + private int type; + private String msg; + + private static Map mapper; + + static { + mapper = new HashMap<>(); + for (NotifyTypeEnum type : values()) { + mapper.put(type.type, type); + } + } + + NotifyTypeEnum(int type, String msg) { + this.type = type; + this.msg = msg; + } + + public static NotifyTypeEnum typeOf(int type) { + return mapper.get(type); + } + + public static NotifyTypeEnum typeOf(String type) { + return valueOf(type.toUpperCase().trim()); + } +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/rabbitMqEvent/RabbitMQController.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/rabbitMqEvent/RabbitMQController.java new file mode 100644 index 0000000..3d380ca --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/rabbitMqEvent/RabbitMQController.java @@ -0,0 +1,268 @@ +package cn.whaifree.springdemo.controller.rabbitMqEvent; + +import cn.hutool.core.date.DateUtil; +import cn.whaifree.springdemo.utils.ResVo; +import com.alibaba.fastjson.JSON; +import com.rabbitmq.client.Channel; +import jakarta.annotation.Resource; +import lombok.Data; +import lombok.experimental.Accessors; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.BooleanUtils; +import org.springframework.amqp.core.*; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.dao.DataAccessException; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/15 19:20 + * @注释 + */ +@RestController +@Slf4j +public class RabbitMQController { + @Resource + private RabbitTemplate rabbitTemplate; + @Resource + private RedisTemplate redisTemplate; + + @PostMapping("/send") + public String send(NotifyTypeEnum type, String msg) { + rabbitTemplate.convertAndSend(RabbitMQConstants.EXCHANGE, RabbitMQConstants.ROUTER, new NotifyMsgEvent<>(null, type, msg)); + return "success"; + } + + @RabbitListener(queues = RabbitMQConstants.QUEUE) + void synBlogConsumer(Message msg , Channel channel) throws IOException { + try { + log.info("synBlogConsumer 接收到消息:{}", msg.toString()); + consumer(msg, "user1"); // 某个用户 + + channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false); + } catch (Exception e) { + log.error("synBlogConsumer 接收消息失败:{}", e.getMessage()); + channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true); + } + } + + private final String oprKey = "oprLog"; + + /** + * 消息重复消费?幂等性 + * @param message + * @return + */ + public void consumer(Message message,String user) { + // 转为NotifyMsgEvent + NotifyMsgEvent event = JSON.parseObject(message.getBody(), NotifyMsgEvent.class); + + // 构造活跃度增加的通知 + } + + + final String todayRankKey = DateUtil.format(DateUtil.date(), "yyyyMMdd"); + @PostMapping("/activity") + public ResVo getRank(int k) { + // 获取前k个 + Set execute = redisTemplate.execute((RedisCallback>) connection -> { + Set collect = connection.zRange(todayRankKey.getBytes(), 0, k - 1).stream().map(new Function() { + @Override + public String apply(byte[] bytes) { + return new String(bytes); + } + }).collect(Collectors.toSet()); + return collect; + }); + return ResVo.success(execute); + } + + public void incrDecrByActivity(ActivityScoreBo activityScore,String userId) { + + if (userId == null) { + return; + } + + String field; + int score = 0; + if (activityScore.getPath() != null) { // 关于页面 + field = "path_" + activityScore.getPath(); + score = 1; + } else if (activityScore.getArticleId() != null) { // 关于文章 + field = activityScore.getArticleId() + "_"; + if (activityScore.getPraise() != null) { + field += "praise"; + score = BooleanUtils.isTrue(activityScore.getPraise()) ? 2 : -2; + } else if (activityScore.getCollect() != null) { + field += "collect"; + score = BooleanUtils.isTrue(activityScore.getCollect()) ? 2 : -2; + } else if (activityScore.getRate() != null) { + // 评论回复 + field += "rate"; + score = BooleanUtils.isTrue(activityScore.getRate()) ? 3 : -3; + } else if (BooleanUtils.isTrue(activityScore.getPublishArticle())) { + // 发布文章 + field += "publish"; + score += 10; + } + } else if (activityScore.getFollowedUserId() != null) { // 关于关注 + field = activityScore.getFollowedUserId() + "_follow"; + score = BooleanUtils.isTrue(activityScore.getFollow()) ? 2 : -2; + } else { + return; + } + + + // 幂等性 + final String userActionKey = "ActivityCore:" + userId + DateUtil.format(DateUtil.date(), ":yyyyMMdd"); + + // {user:{action1,action2}} + Integer opr = (Integer) redisTemplate.opsForHash().get(userActionKey, field); + if (opr == null) { // 某个用户在之前是否做过field这个操作 + // 没有操作过 + // 加记录 + redisTemplate.opsForHash().put(userActionKey, field, score); + redisTemplate.execute((RedisCallback) connection -> { + connection.expire(userActionKey.getBytes(), 31 * 24 * 60 * 60); // 保存一个月 + return null; + }); + + // 加分 + // 更新当天和当月的活跃度排行榜 + final String todayRankKey = DateUtil.format(DateUtil.date(), "yyyyMMdd"); + final String monthRankKey = DateUtil.format(DateUtil.date(), "yyyyMM"); + + + Double newAns = redisTemplate.execute(new RedisCallback() { + @Override + public Double doInRedis(RedisConnection connection) throws DataAccessException { + return connection.zScore(todayRankKey.getBytes(), userId.getBytes()); + } + }); + Object execute = redisTemplate.execute(new RedisCallback() { + @Override + public Object doInRedis(RedisConnection connection) throws DataAccessException { + return connection.zScore(monthRankKey.getBytes(), userId.getBytes()); + } + }); + + redisTemplate.execute((RedisCallback) connection -> { + connection.expire(todayRankKey.getBytes(), 31 * 24 * 60 * 60); // 保存一个月 + return null; + }); + redisTemplate.execute((RedisCallback) connection -> { + connection.expire(monthRankKey.getBytes(), 31 * 24 * 60 * 60 * 12); // 保存一年 + return null; + }); + } else if (opr > 0 && score < 0) { + // 减分 + Long delete = redisTemplate.opsForHash().delete(userActionKey, field); + if (delete == 1) { + // 减分成功 + // 更新日、月排行榜 + final String todayRankKey = DateUtil.format(DateUtil.date(), "yyyyMMdd"); + final String monthRankKey = DateUtil.format(DateUtil.date(), "yyyyMM"); + + redisTemplate.opsForHash().increment(todayRankKey, userId, -score); + redisTemplate.opsForHash().increment(monthRankKey, userId, -score); + } + + } + + } + + @Data + @Accessors(chain = true) + public class ActivityScoreBo { + /** + * 访问页面增加活跃度 + */ + private String path; + + /** + * 目标文章 + */ + private Long articleId; + + /** + * 评论增加活跃度 + */ + private Boolean rate; + + /** + * 点赞增加活跃度 + */ + private Boolean praise; + + /** + * 收藏增加活跃度 + */ + private Boolean collect; + + /** + * 发布文章增加活跃度 + */ + private Boolean publishArticle; + + /** + * 被关注的用户 + */ + private Long followedUserId; + + /** + * 关注增加活跃度 + */ + private Boolean follow; + } + + +} + + + +@Configuration +class RabbitMQConfig { + @Bean + Queue aqueue() { + return QueueBuilder.durable(RabbitMQConstants.QUEUE) + .ttl(1000).maxLength(5) + .deadLetterExchange(RabbitMQConstants.EXCHANGE).deadLetterRoutingKey(RabbitMQConstants.FAIL_ROUTER) + .build(); + } + @Bean + Queue failQueue() { + return QueueBuilder.durable(RabbitMQConstants.FAIL_QUEUE) + .build(); + } + + @Bean + Exchange commentExchange() { + return ExchangeBuilder.directExchange(RabbitMQConstants.EXCHANGE).durable(true).build(); + } + + @Bean + Binding commentBinding() { + return BindingBuilder.bind(aqueue()).to(commentExchange()).with(RabbitMQConstants.ROUTER).noargs(); + } +} + +class RabbitMQConstants{ + public static final String QUEUE = "queue"; + public static final String FAIL_QUEUE = "fail_queue"; + public static final String EXCHANGE = "exchange"; + public static final String ROUTER = "router"; + public static final String FAIL_ROUTER = "fail_router"; +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/redEnvelope/RedEnvelopeController.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/redEnvelope/RedEnvelopeController.java new file mode 100644 index 0000000..2068f3f --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/redEnvelope/RedEnvelopeController.java @@ -0,0 +1,63 @@ +package cn.whaifree.springdemo.controller.redEnvelope; + +import jakarta.annotation.Resource; +import org.redisson.api.RBucket; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/12 20:31 + * @注释 + */ +@RestController +@RequestMapping("/redEnvelope") +public class RedEnvelopeController { + + + + @Resource + private RedissonClient redissonClient; + + + @GetMapping(value = "/redisson/{key}") + public String redissonTest(@PathVariable("key") String lockKey) { + RLock lock = redissonClient.getLock(lockKey); + try { + lock.lock(); + Thread.sleep(10000); + } catch (Exception e) { + + } finally { + lock.unlock(); + } + return "已解锁"; + } + + @PostMapping("distribute") + public String rob(Double amount) { + // 构造红包数据 + Map redPacket = new HashMap<>(); + redPacket.put("amount", amount); + redPacket.put("id", System.currentTimeMillis()); // 使用当前时间戳作为红包 ID + + // 存入 Redis 红包列表 + RBucket redPacketList = redissonClient.getBucket("red_packet_list"); + redPacketList.set(redPacket); + + return "Distributed successfully"; + } + + + @PostMapping("rob") + public String rob(String userId) { + + return ""; + } + +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/workBook/WorkBookController.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/workBook/WorkBookController.java new file mode 100644 index 0000000..20d017d --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/workBook/WorkBookController.java @@ -0,0 +1,103 @@ +package cn.whaifree.springdemo.controller.workBook; + +import cn.hutool.http.server.HttpServerResponse; +import cn.hutool.poi.excel.WorkbookUtil; +import com.google.common.collect.Lists; +import jakarta.servlet.http.HttpServletResponse; +import lombok.Data; +import lombok.Setter; +import org.apache.poi.ss.formula.functions.T; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.*; +import java.lang.reflect.Field; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/20 16:35 + * @注释 + */ +@RestController +@RequestMapping("/workBook") +public class WorkBookController { + + + + + @GetMapping(value = "/exportUser") + public void export(HttpServletResponse response) throws UnsupportedEncodingException { + // WorkBook + response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder + .encode("导出成员工时.xlsx", "UTF-8")); + response.setContentType("application/vnd.ms-excel;charset=utf-8"); + try ( + InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("template/project_member_workHours.xlsx"); + OutputStream out = response.getOutputStream() + ) { + Workbook query = query(inputStream); + query.write(out); + out.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + public List getUserList() { + List userList = new ArrayList<>(); + for (int i = 0; i < 30; i++) { + User user = new User(); + user.setNickName("张三" + i); + user.setDepartment("研发部"); + user.setFullName("张三" + i); + userList.add(user); + } + return userList; + } + + + public Workbook query(InputStream inputStream) { + Workbook workbook = WorkbookUtil.createSXSSFBook(inputStream); + Sheet sheet = workbook.getSheetAt(0); // 获取第一个工作表 + List userList = getUserList(); + for (int i = 0; i < userList.size(); i++) { + Row row = sheet.createRow(i + 1); + + Cell cell = row.createCell(0); + cell.setCellValue(userList.get(i).getNickName()); + + cell = row.createCell(1); + cell.setCellValue(userList.get(i).getFullName()); + + cell = row.createCell(2); + cell.setCellValue(userList.get(i).getDepartment()); + } + return workbook; + } + + @Data + @Setter + static class User { + private String nickName; + private String fullName; + private String department; + } + +} + + diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/workBook/export/ExportAutoController.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/workBook/export/ExportAutoController.java new file mode 100644 index 0000000..9ec5567 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/workBook/export/ExportAutoController.java @@ -0,0 +1,154 @@ +package cn.whaifree.springdemo.controller.workBook.export; + +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.poi.excel.WorkbookUtil; +import cn.whaifree.springdemo.controller.workBook.WorkBookController; +import jakarta.servlet.http.HttpServletResponse; +import lombok.Data; +import lombok.Setter; +import org.apache.poi.ss.formula.functions.T; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/20 16:35 + * @注释 + */ +@RestController +@RequestMapping("/workBookAuto") +public class ExportAutoController { + + @GetMapping(value = "/exportUser1") + public void export1(HttpServletResponse response) { + try (OutputStream out = response.getOutputStream()) { + List userList = getUserList(); + Workbook query = WorkBookResponseUtils.getWorkBook(userList, User.class, response, "导出成员工时"); + query.write(out); + out.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + @GetMapping(value = "/exportUser") + public void export(HttpServletResponse response) throws UnsupportedEncodingException { + // WorkBook + response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder + .encode("导出成员工时.xlsx", "UTF-8")); + response.setContentType("application/vnd.ms-excel;charset=utf-8"); + try ( + InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("template/project_member_workHours.xlsx"); + OutputStream out = response.getOutputStream() + ) { + Workbook query = query(inputStream); + query.write(out); + out.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + public Workbook query(InputStream inputStream) { + Workbook workbook = WorkbookUtil.createSXSSFBook(inputStream); + Sheet sheet = workbook.getSheetAt(0); // 获取第一个工作表 + List userList = getUserList(); + for (int i = 0; i < userList.size(); i++) { + Row row = sheet.createRow(i + 1); + + Cell cell = row.createCell(0); + cell.setCellValue(userList.get(i).getNickName()); + + cell = row.createCell(1); + cell.setCellValue(userList.get(i).getFullName()); + + cell = row.createCell(2); + cell.setCellValue(userList.get(i).getDepartment()); + } + return workbook; + } + + public Workbook query2(InputStream inputStream) { + Workbook workbook = WorkbookUtil.createSXSSFBook(inputStream); + Sheet sheet = workbook.getSheetAt(0); // 获取第一个工作表 + List userList = getUser2List(); + for (int i = 0; i < userList.size(); i++) { + Row row = sheet.createRow(i + 1); + + Cell cell = row.createCell(0); + cell.setCellValue(userList.get(i).getNickName()); + + cell = row.createCell(1); + cell.setCellValue(userList.get(i).getFullName()); + + cell = row.createCell(2); + cell.setCellValue(userList.get(i).getDepartment()); + } + return workbook; + } + + @Data + @Setter + static class User { + private String nickName; + private String fullName; + private String department; + + } + + @Data + @Setter + static class User2 { + private String nickName; + private String fullName; + private String department; + } + + + public static List getUserList() { + List userList = new ArrayList<>(); + for (int i = 0; i < 30; i++) { + User user = new User(); + user.setNickName("张三" + i); + user.setDepartment("研发部"); + user.setFullName("张三" + i); + userList.add(user); + } + return userList; + } + + public static List getUser2List() { + List userList = new ArrayList<>(); + for (int i = 0; i < 30; i++) { + User2 user = new User2(); + user.setNickName("张三" + i); + user.setDepartment("研发部"); + user.setFullName("张三" + i); + userList.add(user); + } + return userList; + } + +} + + diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/workBook/export/WorkBookResponseUtils.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/workBook/export/WorkBookResponseUtils.java new file mode 100644 index 0000000..978ab11 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/workBook/export/WorkBookResponseUtils.java @@ -0,0 +1,85 @@ +package cn.whaifree.springdemo.controller.workBook.export; + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.ReflectUtil; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; +import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Optional; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/22 16:56 + * @注释 + */ +public class WorkBookResponseUtils { + + public static void setHead(HttpServletResponse response, String fileName) throws UnsupportedEncodingException { + // WorkBook + fileName = Optional.ofNullable(fileName).orElse("workBook.xlsx"); + + response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder + .encode(fileName, StandardCharsets.UTF_8)); + response.setContentType("application/vnd.ms-excel;charset=utf-8"); + } + + public static Workbook getWorkBook(List list, Class clazz, HttpServletResponse response, String fileName) { + try { + setHead(response, fileName); + return getWorkBook(list, clazz); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + public static Workbook getWorkBook(List list, Class clazz) { + try (InputStream inputStream = ExportAutoController.class.getClassLoader().getResourceAsStream("template/empty.xlsx")) { + Workbook workbook = new XSSFWorkbook(inputStream); + Sheet sheetAt = workbook.getSheetAt(0); + Field[] fields = ReflectUtil.getFields(clazz); + Row row = sheetAt.getRow(0); + if (row == null) { + row = sheetAt.createRow(0); + } + for (int i = 0; i < fields.length; i++) { + Cell cell = row.createCell(i); + cell.setCellValue(fields[i].getName()); + } + int length = fields.length; // 列数 + for (int i = 0; i < list.size(); i++) { + Object o = list.get(i); + row = sheetAt.createRow(i + 1); + for (int j = 0; j < length; j++) { + Cell cell = row.createCell(j); + Field field = fields[j]; + field.setAccessible(true); + try { + Object value = field.get(o); // 获取字段值 + if (value != null) { + cell.setCellValue(value.toString()); // 将字段值设置到单元格中 + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + return workbook; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/controller/wxQrLogin/WxQrLoginController.java b/Spring/src/main/java/cn/whaifree/springdemo/controller/wxQrLogin/WxQrLoginController.java new file mode 100644 index 0000000..e29f7ec --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/controller/wxQrLogin/WxQrLoginController.java @@ -0,0 +1,284 @@ +package cn.whaifree.springdemo.controller.wxQrLogin; + +import cn.hutool.core.util.RandomUtil; +import cn.hutool.jwt.JWT; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import jakarta.annotation.PostConstruct; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.util.Date; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/15 17:07 + * @注释 + */ +@Slf4j +@RestController +@RequestMapping("/wxQrLogin") +public class WxQrLoginController { + + /** + * 1. 客户端请求网站,/subscribe,响应SSEEmitter + * 获取客户端deviceId + * 生成验证码numCode + * 存放Cache < deviceId,numCode > + * 存放Cache < numCode, SSEEmitter> + * SSE发送验证码给客户端 + * + * 2. 客户端扫描公众号,发送numCode到公众号 + * wx会请求/callback接口,带有numCode + * 使用numCode找到deviceId,再找到SSEEmitter,发送生成的最新Token + * + */ + + + /** + * sse的超时时间,默认15min + */ + private final static Long SSE_EXPIRE_TIME = 15 * 60 * 1000L; + private final RedisTemplate redisTemplate; + + + /** + * key = 验证码, value = 长连接 + */ + private LoadingCache verifyCodeCache; + /** + * key = 设备 value = 验证码 + */ + private LoadingCache deviceCodeCache; + + public WxQrLoginController(@Qualifier("redisTemplate") RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + + @PostConstruct + public void init() { + // 验证码,SSE + verifyCodeCache = CacheBuilder.newBuilder().build(new CacheLoader() { + @Override + public SseEmitter load(String key) throws Exception { + // 如果缓存未命中,则抛出异常,提示缓存未命中 + throw new RuntimeException("no val: " + key); + } + }); + // 设备,验证码 + deviceCodeCache = CacheBuilder.newBuilder().build(new CacheLoader() { + @Override + public String load(String key) throws Exception { + // 生成id + int cnt = 0; + while (true) { + String code = "deviceId#" + cnt++; // 可以是其他生成算法 + // 如果verifyCodeCache中已经有这个缓存,证明这个Code已经被使用了 + if (!verifyCodeCache.asMap().containsKey(code)) { + return code; + } + } + } + }); + } + + + /** + * deviceId code + * code sse + * @return + * @throws IOException + */ + @GetMapping(path = "subscribe", produces = {org.springframework.http.MediaType.TEXT_EVENT_STREAM_VALUE}) + public SseEmitter subscribe() throws IOException { + String deviceId = String.valueOf(RandomUtil.randomInt(100)); // 随机生成一个UUID + + String realCode = deviceCodeCache.getUnchecked(deviceId) ; + // 生成验证码 + + + // fixme 设置15min的超时时间, 超时时间一旦设置不能修改;因此导致刷新验证码并不会增加连接的有效期 + SseEmitter sseEmitter = new SseEmitter(SSE_EXPIRE_TIME); + SseEmitter oldSse = verifyCodeCache.getIfPresent(realCode); // 是否已经存在旧的半长连接 + + if (oldSse != null) { + oldSse.complete(); // 旧的长连接 + } + + + verifyCodeCache.put(realCode, sseEmitter); + sseEmitter.onTimeout(() -> { + log.info("sse 超时中断 --> {}", realCode); + verifyCodeCache.invalidate(realCode); + sseEmitter.complete(); + }); + sseEmitter.onError((e) -> { + log.warn("sse error! --> {}", realCode, e); + verifyCodeCache.invalidate(realCode); + sseEmitter.complete(); + }); + // 若实际的验证码与前端显示的不同,则通知前端更新 + sseEmitter.send("initCode!"); + sseEmitter.send("init#" + realCode); + return sseEmitter; + } + + + /** + * fixme: 需要做防刷校验 + * + * + * 微信的响应返回 + * 本地测试访问: + * curl -X POST 'http://localhost:8080/wx/callback' + * -H 'content-type:application/xml' -d + * ' + * + * + * + * 1655700579 + * + * + * 11111111 + * ' -i + * + * @param msg + * @return 返回给微信,微信会给客户端 + */ + @PostMapping(path = "callback", + consumes = {"application/xml", "text/xml"}, + produces = "application/xml;charset=utf-8") + public BaseWxMsgResVo callBack(@RequestBody WxTxtMsgReqVo msg) throws IOException { + BaseWxMsgResVo res = new BaseWxMsgResVo(); + res.setToUserName(msg.getFromUserName()); + res.setFromUserName(msg.getToUserName()); + res.setCreateTime(System.currentTimeMillis()); + + String content = msg.getContent(); + if ("subscribe".equals(msg.getEvent()) || "scan".equalsIgnoreCase(msg.getEvent())) { + String key = msg.getEventKey(); + if (StringUtils.isNotBlank(key) || key.startsWith("qrscene_")) { + + // 带参数的二维码,扫描、关注事件拿到之后,直接登录,省却输入验证码这一步 + // fixme 带参数二维码需要 微信认证,个人公众号无权限 + String code = key.substring("qrscene_".length()); + + + // TODO sessionService.autoRegisterWxUserInfo(msg.getFromUserName()); + // 自动注册一个用户,获得用户ID + + // 找到对应的SSE,实现登录 + SseEmitter sseEmitter = verifyCodeCache.getIfPresent(code); + + // 生成Token + String session = genSession(100L); + // 登录成功,写入session + sseEmitter.send(session); + + // 设置cookie的路径 + sseEmitter.send("login#Session=" + session + ";path=/;"); // session告诉前端,跳转到/根目录 + + + return res; + } + } + return res; + } + + + + public String genSession(Long userId) { + // 1.生成jwt格式的会话,内部持有有效期,用户信息 + String session = String.valueOf(userId); + String token = JWT.create() + .setIssuer("issuer") + .setPayload("session", session) + .setExpiresAt(new Date(System.currentTimeMillis() + 2592000000L)).sign(); + + // 2.使用jwt生成的token时,后端可以不存储这个session信息, 完全依赖jwt的信息 + // 但是需要考虑到用户登出,需要主动失效这个token,而jwt本身无状态,所以再这里的redis做一个简单的token -> userId的缓存,用于双重判定 + redisTemplate.opsForValue().set("UserId:Session:" + session, token); + return token; + } + + + @Data + @JacksonXmlRootElement(localName = "xml") + public class WxTxtMsgReqVo { + @JacksonXmlProperty(localName = "ToUserName") + private String toUserName; + @JacksonXmlProperty(localName = "FromUserName") + private String fromUserName; + @JacksonXmlProperty(localName = "CreateTime") + private Long createTime; + @JacksonXmlProperty(localName = "MsgType") + private String msgType; + @JacksonXmlProperty(localName = "Event") + private String event; + @JacksonXmlProperty(localName = "EventKey") + private String eventKey; + @JacksonXmlProperty(localName = "Ticket") + private String ticket; + @JacksonXmlProperty(localName = "Content") + private String content; + @JacksonXmlProperty(localName = "MsgId") + private String msgId; + @JacksonXmlProperty(localName = "MsgDataId") + private String msgDataId; + @JacksonXmlProperty(localName = "Idx") + private String idx; + } + + + @Data + @JacksonXmlRootElement(localName = "xml") + public class BaseWxMsgResVo { + + @JacksonXmlProperty(localName = "ToUserName") + private String toUserName; + @JacksonXmlProperty(localName = "FromUserName") + private String fromUserName; + @JacksonXmlProperty(localName = "CreateTime") + private Long createTime; + @JacksonXmlProperty(localName = "MsgType") + private String msgType; + } + + +// +// /** +// * 初始化设备id +// * +// * @return +// */ +// private String getOrInitDeviceId(HttpServletRequest request, HttpServletResponse response) { +// String deviceId = request.getParameter("deviceId"); +// if (StringUtils.isNotBlank(deviceId) && !"null".equalsIgnoreCase(deviceId)) { +// return deviceId; +// } +// +// Cookie device = SessionUtil.findCookieByName(request, LoginOutService.USER_DEVICE_KEY); +// if (device == null) { +// deviceId = UUID.randomUUID().toString(); +// if (response != null) { +// response.addCookie(SessionUtil.newCookie(LoginOutService.USER_DEVICE_KEY, deviceId)); +// } +// return deviceId; +// } +// return device.getValue(); +// } + + + +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/entity/UserService.java b/Spring/src/main/java/cn/whaifree/springdemo/entity/UserService.java new file mode 100644 index 0000000..c16165d --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/entity/UserService.java @@ -0,0 +1,201 @@ +package cn.whaifree.springdemo.entity; + + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.*; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/19 22:12 + * @注释 + */ +@Configuration +@Order(-1) +class Config { + +} + +/** + *

Spring Bean生命周期

+ *

Spring Bean 的生命周期包括创建、初始化、使用、销毁等过程。

+ *

Spring Bean 的生命周期

+ * +*
  • 实例化阶段:Bean 被实例化,Bean 实例化后,Bean 处于未初始化状态。
  • +*
  • 初始化阶段:Bean 完成实例化后,Bean 处于初始化状态,Bean 实例变量可以被赋值。
  • +*
      +*
    • BeanNameAware.setBeanName
    • +*
    • BeanFactoryAware.setBeanFactory
    • +*
    • ApplicationContextAware.setApplicationContext
    • +*
    +*
      +*
    • @PostConstruct
    • +*
    • InitializingBean.afterPropertiesSet
    • +*
    • BeanPostProcessor.postProcessBeforeInitialization
    • +*
    • 初始化
    • +*
    • BeanPostProcessor.postProcessAfterInitialization
    • +*
    +*
  • 使用阶段:Bean 完成初始化后,Bean 处于可使用状态,Bean 可以被使用。
  • +*
  • 销毁阶段:Bean 不再被使用时,Bean 处于销毁状态,Bean 实例将被销毁。
  • +* +*
      +*
    • @PreDestroy
    • +*
    • DisposableBean.destroy
    • +* +* +* +*
    +* + * + * + * + * + *

    相关Aware注入:让 Bean 能够**感知**到它们所运行的环境或依赖的资源e

    + *

    为什么要叫Aware

    + * Aware是一个接口, + * 指示 Bean 有资格通过 Callback 样式方法由 Spring 容器通知特定框架对象。 + * 实际的方法签名由各个子接口确定,但通常应仅包含一个接受单个参数的返回 void 的方法 + */ +//@Component +public class UserService implements InitializingBean, DisposableBean, BeanNameAware, BeanFactoryAware, ApplicationContextAware, BeanPostProcessor, AutoCloseable { + + + /** + * 执行 BeanFactoryAware.setBeanFactory + * 执行 ApplicationContextAware.setApplicationContext + * 执行 @PostConstruct + * UserService afterPropertiesSet + * 执行 BeanPostProcessor.postProcessBeforeInitialization + * 执行 BeanPostProcessor.postProcessAfterInitialization + * 执行 @PreDestroy + * UserService destroy + * + * @param args + */ + public static void main(String[] args) { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("cn.whaifree.springdemo.entity"); + UserService userService = context.getBean("userService", UserService.class); + System.out.println(userService); + // 执行 DisposableBean + + context.close(); + } + + private String beanName; + private BeanFactory beanFactory; + private ApplicationContext applicationContext; + + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + System.out.println("执行 BeanPostProcessor.postProcessBeforeInitialization"); + return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName); + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + System.out.println("执行 BeanPostProcessor.postProcessAfterInitialization"); + return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName); + } + + @Override + public void afterPropertiesSet() throws Exception { + System.out.println("UserService afterPropertiesSet"); + } + + @Override + public void destroy() throws Exception { + System.out.println("UserService destroy"); + } + + /** + * org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#invokeAwareMethods(java.lang.String, java.lang.Object) + * + * if (bean instanceof BeanFactoryAware beanFactoryAware) { // 如果实现了BeanFactoryAware接口,就执行setBeanFactory + * beanFactoryAware.setBeanFactory(AbstractAutowireCapableBeanFactory.this); + * } + * @param beanFactory owning BeanFactory (never {@code null}). + * The bean can immediately call methods on the factory. + * @throws BeansException + */ + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = beanFactory; + System.out.println("执行 BeanFactoryAware.setBeanFactory"); + } + + + + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + System.out.println("ApplicationContextAware.setApplicationContext(ApplicationContext applicationContext) 可以获取applicantionContext,便于获取context,可以提取变成一个公共的component,每次从这个component获取Bean"); + } + + + @PostConstruct + public void init() { + System.out.println("执行 @PostConstruct"); + } + + @PreDestroy + public void destroyMethod() { + System.out.println("执行 @PreDestroy"); + } + + @Override + public void close() throws Exception { + + } + + @Override + public void setBeanName(String name) { + System.out.println("执行 BeanNameAware.setBeanName"); + // 修改原来的BeanName + } + + + /** + * org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#invokeAwareMethods(java.lang.String, java.lang.Object) + * + * if (bean instanceof Aware) { + * if (bean instanceof BeanNameAware beanNameAware) { + * beanNameAware.setBeanName(beanName); + * } + * if (bean instanceof BeanClassLoaderAware beanClassLoaderAware) { + * ClassLoader bcl = getBeanClassLoader(); + * if (bcl != null) { + * beanClassLoaderAware.setBeanClassLoader(bcl); + * } + * } + * if (bean instanceof BeanFactoryAware beanFactoryAware) { + * beanFactoryAware.setBeanFactory(AbstractAutowireCapableBeanFactory.this); + * } * } + */ + + +} + +/** + * 模拟工具类,其实就是可以随时获取本Context + */ +//@Component +//class SpringContextUtil implements ApplicationContextAware { +// static ApplicationContext applicationContext; +// @Override +// public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { +// this.applicationContext = applicationContext; +// } +// public static ApplicationContext getApplicationContext(){ +// return applicationContext; +// } +//} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/tech/CircleRelation.java b/Spring/src/main/java/cn/whaifree/springdemo/tech/CircleRelation.java new file mode 100644 index 0000000..352c4f7 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/tech/CircleRelation.java @@ -0,0 +1,138 @@ +//package cn.whaifree.springdemo.tech; +// +//import jakarta.annotation.Resource; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.context.annotation.AnnotationConfigApplicationContext; +//import org.springframework.context.annotation.Lazy; +//import org.springframework.context.annotation.Scope; +//import org.springframework.stereotype.Component; +// +///** +// * @version 1.0 +// * @Author whai文海 +// * @Date 2024/10/31 13:57 +// * @注释 +// */ +//public class CircleRelation { +// +// +//} +// +// +//// ================================================================ +// +//@Component +////@Scope("prototype") +//class A1{ +//// @Resource +//// public B1 b1; +// +// public static void main(String[] args) { +// AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("cn.whaifree.springdemo.tech"); +//// A1 bean = context.getBean(A1.class); +//// System.out.println(bean.b1.a1); +// +//// LazyA1 lazyA1 = context.getBean(LazyA1.class); +//// System.out.println(lazyA1.b1); +// } +//} +// +///** +// * BeanCurrentlyInCreationException: +// * Error creating bean with name 'a1': +// * Requested bean is currently in creation: Is there an unresolvable circular reference? +// * +// * A 实例创建后,populateBean 时,会触发 B 的加载。 +// * B 实例创建后,populateBean 时,会触发 A 的加载。由于 A 的 scope=prototype,需要的时候都会创建一个全新的 A。 +// * 这样,就会进入一个死循环。Spring 肯定是解决不了这种情况下的循环依赖的。所以,提前进行了 check,并抛出了异常。 +// * +// * A需要一个新的B,B需要一个新的A,不能用为初始化的A活着B +// */ +//@Component +////@Scope("prototype") +//class B1{ +// @Resource +// public A1 a1; +//} +// +// +//@Component +//@Scope("prototype") +//class LazyA1{ +// +// @Autowired +// @Lazy +// public LazyB1 b1; +//} +///** +// * +// */ +//@Component +//@Scope("prototype") +//class LazyB1{ +// @Autowired +// public LazyA1 a1; +//} +//// ================================================================ +// +// +//@Component +//class ConstructA { +// ConstructB b; +// public ConstructA(@Lazy ConstructB b) { +// this.b = b; +// } +//} +//@Component +//class ConstructB { +// ConstructA a; +// public ConstructB(ConstructA a) { +// this.a = a; +// } +// public static void main(String[] args) { +// /** +// * A 实例在创建时(createBeanInstance),由于是构造注入,这时会触发 B 的加载。 +// * B 实例在创建时(createBeanInstance),又会触发 A 的加载,此时,A 还没有添加到三级缓存(工厂)中,所以就会创建一个全新的 A。 +// * 这样,就会进入一个死循环。Spring 是解决不了这种情况下的循环依赖的。所以,提前进行了 check,并抛出了异常。 +// * +// * 解决:@Lazy放在构造器上,这样,A 实例在创建时,不会触发 B 的加载。 +// * +// * 构造函数注入要求在创建bean实例时必须提供所有依赖 +// * +// */ +// AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("cn.whaifree.springdemo.tech"); +// ConstructA bean = context.getBean(ConstructA.class); +// System.out.println(bean.b); +// // Unsatisfied dependency expressed through constructor parameter 0: +// // Error creating bean with name 'constructA': Requested bean is currently in creation: Is there an unresolvable circular reference? +// } +//} +// +// +// +//@Component +//class SetA { +// SetB b; +// +// @Autowired +// public void setB(SetB b) { +// this.b = b; +// } +//} +//@Component +//class SetB { +// +// SetA a; +// +// @Autowired +// public void setA(SetA a) { +// this.a = a; +// } +// +// public static void main(String[] args) { +// +// AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("cn.whaifree.springdemo.tech"); +// SetA bean = context.getBean(SetA.class); +// System.out.println(bean.b); +// } +//} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/utils/BroomFilter/BroomFilter.java b/Spring/src/main/java/cn/whaifree/springdemo/utils/BroomFilter/BroomFilter.java new file mode 100644 index 0000000..1a07b40 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/utils/BroomFilter/BroomFilter.java @@ -0,0 +1,57 @@ +package cn.whaifree.springdemo.utils.BroomFilter; + +import cn.hutool.extra.spring.SpringUtil; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/30 21:01 + * @注释 + */ +@Component +public class BroomFilter{ + RedisTemplate redisTemplate = SpringUtil.getBean("redisTemplate", RedisTemplate.class); + + private int hashCount; // 哈希函数数量 + private int size; // 位图大小 + private String key; // Redis 中的 Bitmap 键 + + public BroomFilter() { + this.hashCount = 10; + this.size = 1000000; + this.key = "broomFilter"; + } + + public boolean contains(Object o) { + // 计算Hashcode + int[] hash = getHash(o); + for (int i : hash) { + // 检查位图中对应位置是否为true + if (!redisTemplate.opsForValue().getBit(key, i)) { + return false; + } + } + return true; + } + + public void addToBroom(Object o) { + + // 计算Hashcode + int[] hash = getHash(o); + for (int i : hash) { + // 设置位图中对应位置为true + redisTemplate.opsForValue().setBit(key, i, true); + } + } + + // 生成哈希值 + private int[] getHash(Object value) { + int[] result = new int[hashCount]; + for (int i = 0; i < hashCount; i++) { + result[i] = Math.abs(value.hashCode() + i * 123456) % size; + } + return result; + } +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/utils/Filter/SelfFilter.java b/Spring/src/main/java/cn/whaifree/springdemo/utils/Filter/SelfFilter.java new file mode 100644 index 0000000..8e86cb6 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/utils/Filter/SelfFilter.java @@ -0,0 +1,34 @@ +package cn.whaifree.springdemo.utils.Filter; + +import jakarta.servlet.*; +import jakarta.servlet.annotation.WebFilter; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/12 22:13 + * @注释 + */ +@Component +@WebFilter(filterName = "SelfFilter", urlPatterns = "/*") +public class SelfFilter implements Filter { + @Override + public void init(FilterConfig filterConfig) throws ServletException { + Filter.super.init(filterConfig); + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { +// System.out.println("SelfFilter doFilter"); + filterChain.doFilter(servletRequest, servletResponse); +// System.out.println("SelfFilter doFilter end"); + } + + @Override + public void destroy() { + Filter.super.destroy(); + } +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/utils/ResVo.java b/Spring/src/main/java/cn/whaifree/springdemo/utils/ResVo.java new file mode 100644 index 0000000..0231bd5 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/utils/ResVo.java @@ -0,0 +1,196 @@ +package cn.whaifree.springdemo.utils; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/11 16:30 + * @注释 + */ + + +import cn.whaifree.springdemo.utils.constants.HttpStatus; + +import java.util.HashMap; + +/** + * 操作消息提醒 + * + * @author / + */ +public class ResVo extends HashMap +{ + private static final long serialVersionUID = 1L; + + /** 状态码 */ + public static final String CODE_TAG = "code"; + + /** 返回内容 */ + public static final String MSG_TAG = "msg"; + + /** 数据对象 */ + public static final String DATA_TAG = "data"; + + /** + * 初始化一个新创建的 ResVo 对象,使其表示一个空消息。 + */ + public ResVo() + { + } + + /** + * 初始化一个新创建的 ResVo 对象 + * + * @param code 状态码 + * @param msg 返回内容 + */ + public ResVo(int code, String msg) + { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + } + + public boolean isEmpty(){ + return super.get(CODE_TAG).equals(HttpStatus.FOUND); + } + + /** + * 初始化一个新创建的 ResVo 对象 + * + * @param code 状态码 + * @param msg 返回内容 + * @param data 数据对象 + */ + public ResVo(int code, String msg, Object data) + { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + if (data != null) { + super.put(DATA_TAG, data); + } + } + + /** + * 返回成功消息 + * + * @return 成功消息 + */ + public static ResVo success() + { + return ResVo.success("操作成功"); + } + + /** + * 返回成功数据 + * + * @return 成功消息 + */ + public static ResVo success(Object data) + { + return ResVo.success("操作成功", data); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @return 成功消息 + */ + public static ResVo success(String msg) + { + return ResVo.success(msg, null); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 成功消息 + */ + public static ResVo success(String msg, Object data) + { + return new ResVo(HttpStatus.SUCCESS, msg, data); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @return 警告消息 + */ + public static ResVo warn(String msg) + { + return ResVo.warn(msg, null); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 警告消息 + */ + public static ResVo warn(String msg, Object data) + { + return new ResVo(HttpStatus.WARN, msg, data); + } + + /** + * 返回错误消息 + * + * @return 错误消息 + */ + public static ResVo error() + { + return ResVo.error("操作失败"); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @return 错误消息 + */ + public static ResVo error(String msg) + { + return ResVo.error(msg, null); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 错误消息 + */ + public static ResVo error(String msg, Object data) + { + return new ResVo(HttpStatus.ERROR, msg, data); + } + + /** + * 返回错误消息 + * + * @param code 状态码 + * @param msg 返回内容 + * @return 错误消息 + */ + public static ResVo error(int code, String msg) + { + return new ResVo(code, msg, null); + } + + /** + * 方便链式调用 + * + * @param key 键 + * @param value 值 + * @return 数据对象 + */ + @Override + public ResVo put(String key, Object value) + { + super.put(key, value); + return this; + } +} diff --git a/Spring/src/main/java/cn/whaifree/springdemo/utils/constants/HttpStatus.java b/Spring/src/main/java/cn/whaifree/springdemo/utils/constants/HttpStatus.java new file mode 100644 index 0000000..c119d94 --- /dev/null +++ b/Spring/src/main/java/cn/whaifree/springdemo/utils/constants/HttpStatus.java @@ -0,0 +1,95 @@ +package cn.whaifree.springdemo.utils.constants; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/11 16:32 + * @注释 + */ +public class HttpStatus +{ + /** + * 操作成功 + */ + public static final int SUCCESS = 0; + + /** + * 对象创建成功 + */ + public static final int CREATED = 201; + + /** + * 请求已经被接受 + */ + public static final int ACCEPTED = 202; + + /** + * 操作已经执行成功,但是没有返回数据 + */ + public static final int NO_CONTENT = 204; + + /** + * 资源已被移除 + */ + public static final int MOVED_PERM = 301; + + /** + * 重定向 + */ + public static final int SEE_OTHER = 303; + + /** + * 资源没有被修改 + */ + public static final int NOT_MODIFIED = 304; + + /** + * 参数列表错误(缺少,格式不匹配) + */ + public static final int BAD_REQUEST = 400; + + /** + * 未授权 + */ + public static final int UNAUTHORIZED = 401; + + /** + * 访问受限,授权过期 + */ + public static final int FORBIDDEN = 403; + + /** + * 资源,服务未找到 + */ + public static final int FOUND = 40004; + + /** + * 不允许的http方法 + */ + public static final int BAD_METHOD = 405; + + /** + * 资源冲突,或者资源被锁 + */ + public static final int CONFLICT = 409; + + /** + * 不支持的数据,媒体类型 + */ + public static final int UNSUPPORTED_TYPE = 415; + + /** + * 系统内部错误 + */ + public static final int ERROR = 500; + + /** + * 接口未实现 + */ + public static final int NOT_IMPLEMENTED = 501; + + /** + * 系统警告消息 + */ + public static final int WARN = 601; +} diff --git a/Spring/src/main/resources/META-INF/spring.factories b/Spring/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..75ca659 --- /dev/null +++ b/Spring/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ + cn.whaifree.springdemo.config.MyAutoConfiguration; diff --git a/Spring/src/main/resources/application.yaml b/Spring/src/main/resources/application.yaml new file mode 100644 index 0000000..a9deefd --- /dev/null +++ b/Spring/src/main/resources/application.yaml @@ -0,0 +1,78 @@ + +my: + condition: true + thread: + name: myThread + coreSize: 12 + maxSize: 24 + +spring: + datasource: + url: jdbc:mysql://192.168.50.248:3306/springTest?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai + username: root + password: 123456 + driver-class-name: com.mysql.cj.jdbc.Driver + data: + redis: + host: 192.168.50.248 + port: 6379 + # 选择db1 + database: 3 + redis: + redisson: + file: classpath:redisson.yaml + + rabbitmq: + host: localhost + port: 5672 + username: whai + password: whai + publisher-confirm-type: correlated + # 不可达到 返回给生产者 + # 当启用publisher-returns时,如果发送者发送的消息无法被消费者确认,消息会返回发送者。否则发送者是不知道的 + template: + mandatory: true + publisher-returns: true + listener: + simple: + acknowledge-mode: manual + direct: + acknowledge-mode: manual + +mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + global-config: + db-config: + logic-delete-field: 1 + logic-not-delete-value: 0 + mapper-locations: "classpath:/mapper/**.xml" + + + +# springdoc-openapi项目配置 +springdoc: + swagger-ui: + path: /swagger-ui.html + tags-sorter: alpha + operations-sorter: alpha + api-docs: + path: /v3/api-docs + group-configs: + - group: 'default' + paths-to-match: '/**' + packages-to-scan: cn.whaifree.springdemo.controller +# knife4j的增强配置,不需要增强可以不配 +knife4j: + enable: true + setting: + language: zh_cn +server: + port: 8080 +image: + oss: + type: minio + bucket: picgo + ak: wOSfawBbzug2S3qz9u6W + sk: CCxIopdXdBRNPloaFV7l8XplKpVLPzjSnMxlKcru + endpoint: http://42.192.130.83:9000 diff --git a/Spring/src/main/resources/lua/RobEnvelope.lua b/Spring/src/main/resources/lua/RobEnvelope.lua new file mode 100644 index 0000000..7bf4d56 --- /dev/null +++ b/Spring/src/main/resources/lua/RobEnvelope.lua @@ -0,0 +1,27 @@ +local userHashKey = KEYS[1] -- 用户领取记录 Hash 表键 +local redPacketListKey = KEYS[2] -- 红包列表键 +local userRecordListKey = KEYS[3] -- 用户领取记录列表键 +local userId = ARGV[1] -- 用户 ID + +-- 检查用户是否已经领取过红包 +local exists = redis.call('HEXISTS', userHashKey, userId) +if exists == 1 then + return nil +end + +-- 从红包列表中取出一条红包数据 +local redPacketData = redis.call('RPOP', redPacketListKey) +if not redPacketData then + return nil +end + +-- 解析红包数据 +local redPacket = cjson.decode(redPacketData) + +-- 存储用户领取记录 +redis.call('HSET', userHashKey, userId, redPacketData) + +-- 将红包领取信息存入用户领取记录列表 +redis.call('LPUSH', userRecordListKey, redPacketData) + +return redPacket diff --git a/Spring/src/main/resources/redisson.yaml b/Spring/src/main/resources/redisson.yaml new file mode 100644 index 0000000..21e1495 --- /dev/null +++ b/Spring/src/main/resources/redisson.yaml @@ -0,0 +1,42 @@ +# 单节点配置 +singleServerConfig: + # 连接空闲超时,单位:毫秒 + idleConnectionTimeout: 10000 + # 连接超时,单位:毫秒 + connectTimeout: 10000 + # 命令等待超时,单位:毫秒 + timeout: 3000 + # 命令失败重试次数,如果尝试达到 retryAttempts(命令失败重试次数) 仍然不能将命令发送至某个指定的节点时,将抛出错误。 + # 如果尝试在此限制之内发送成功,则开始启用 timeout(命令等待超时) 计时。 + retryAttempts: 3 + # 命令重试发送时间间隔,单位:毫秒 + retryInterval: 1500 + # 密码 + #password: redis.shbeta + # 单个连接最大订阅数量 + subscriptionsPerConnection: 5 + # 客户端名称 + #clientName: axin + # # 节点地址 + address: redis://192.168.50.248:6379 + # 发布和订阅连接的最小空闲连接数 + subscriptionConnectionMinimumIdleSize: 1 + # 发布和订阅连接池大小 + subscriptionConnectionPoolSize: 50 + # 最小空闲连接数 + connectionMinimumIdleSize: 32 + # 连接池大小 + connectionPoolSize: 64 + # 数据库编号 + database: 6 + # DNS监测时间间隔,单位:毫秒 + dnsMonitoringInterval: 5000 +# 线程池数量,默认值: 当前处理核数量 * 2 +#threads: 0 +# Netty线程池数量,默认值: 当前处理核数量 * 2 +#nettyThreads: 0 +# 编码 +#codec: ! {} +## 传输模式 +#transportMode : "NIO" +# diff --git a/Spring/src/main/resources/template/empty.xlsx b/Spring/src/main/resources/template/empty.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..411097ff23333ca07013f6a3da44b9a18bcd3ac8 GIT binary patch literal 9818 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 redisTemplate; + @Autowired + private SpringUtil springUtil; + + @Test + public void testHyperloglog() { + String day1 = "visituser:article1:20211014"; + redisTemplate.opsForHyperLogLog().add(day1, "user1"); + redisTemplate.opsForHyperLogLog().add(day1, "user2"); + redisTemplate.opsForHyperLogLog().add(day1, "user3"); + redisTemplate.opsForHyperLogLog().add(day1, "user4"); + redisTemplate.opsForHyperLogLog().add(day1, "user5"); + // 获取值 + long count = redisTemplate.opsForHyperLogLog().size(day1); + log.info("count day1:{}", count);//5 第一天有5个人访问这个文章 + + String day2 = "visituser:article1:20211015"; + redisTemplate.opsForHyperLogLog().add(day2, "user1"); + redisTemplate.opsForHyperLogLog().add(day2, "user2"); + redisTemplate.opsForHyperLogLog().add(day2, "user3"); + redisTemplate.opsForHyperLogLog().add(day2, "user4"); + redisTemplate.opsForHyperLogLog().add(day2, "user6"); + + long count2 = redisTemplate.opsForHyperLogLog().size(day2); + log.info("count day2:{}", count2); //5 + + + Long union = redisTemplate.opsForHyperLogLog().union(day1, day2); + log.info("union:{}", union);// 这个文章两天内的,有6个人访问 + } + + @Test + public void testBitMap() { + + // 定义一个字符串变量key,用于存储Redis中存储位图的键 + String key = "bitmap:article1"; + + // 在Redis位图中,将key对应的值的第1个位设置为true,这里用于记录文章1的阅读状态 + redisTemplate.opsForValue().setBit(key, 1, true); + // 同样在Redis位图中,将key对应的值的第2个位设置为true,这里用于记录文章2的阅读状态 + redisTemplate.opsForValue().setBit(key, 2, true); + + // 检查Redis位图中key对应的值的第1个位是否为true,打印结果 + System.out.println(redisTemplate.opsForValue().getBit(key, 1)); + + + + + // 创建BitFieldSubCommands对象,用于执行更复杂的位图操作 + BitFieldSubCommands subCommands = BitFieldSubCommands.create(); + // 添加一个指令到subCommands中,获取第11个位(从0开始计数)的64位整型值 + subCommands.get(BitFieldSubCommands.BitFieldType.INT_64).valueAt(11); + // 添加一个指令到subCommands中,将第11个位设置为64位整型值1 + subCommands.set(BitFieldSubCommands.BitFieldType.INT_64).to(11); + // 执行subCommands中所有位图操作指令 + redisTemplate.opsForValue().bitField(key, subCommands); + // 获取Redis位图中key对应的值的第11个位,检查其是否为true + System.out.println(redisTemplate.opsForValue().getBit(key, 11)); + + } + + @Test + public void testGeo() { + String key = "geo:user:location"; + + + redisTemplate.opsForGeo().add(key, new Point(116.407396, 39.904200), "beijin"); + redisTemplate.opsForGeo().add(key, new Point(121.473701, 31.230416), "shanghai"); + redisTemplate.opsForGeo().add(key, new Point(113.264385, 23.129112), "guangzhou"); + Distance distance = redisTemplate.opsForGeo().distance(key, "beijin", "shanghai", RedisGeoCommands.DistanceUnit.KILOMETERS); // 计算两个地点之间的距离 + System.out.println("beijin to shanghai distance: " + distance.getValue() + " " + distance.getUnit()); + + + Point point = new Point(116.404, 39.915); + String meMember = "me"; + redisTemplate.opsForGeo().add(key, point, meMember); // 增加一个点到地理位置信息中 + + GeoResults> radius = + redisTemplate.opsForGeo().radius(key, new Circle(point, new Distance(2000, RedisGeoCommands.DistanceUnit.KILOMETERS)), // 圆形区域, 半径为1000米 + RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeCoordinates().sortAscending()); + + + List>> content = radius.getContent(); + for (GeoResult> geoResult : content) { + System.out.println(geoResult.getContent().getName() + " " + geoResult.getContent().getPoint()); + + // 计算point 到这些位置的距离 + Distance dis = redisTemplate.opsForGeo().distance(key, meMember, geoResult.getContent().getName(), RedisGeoCommands.DistanceUnit.KILOMETERS); + System.out.println(dis.getValue() + " " + dis.getUnit()); + } + } + + @Test + public void testbitMap2() { + + BroomFilter broomFilter = new BroomFilter(); + broomFilter.addToBroom("obj1"); + broomFilter.addToBroom("obj2"); + broomFilter.addToBroom("obj3"); + broomFilter.addToBroom("obj4"); + broomFilter.addToBroom("obj5"); + broomFilter.addToBroom("obj6"); + broomFilter.addToBroom("obj7"); + broomFilter.addToBroom("obj8"); + broomFilter.addToBroom("obj9"); + broomFilter.addToBroom("obj10"); + broomFilter.addToBroom("obj11"); + broomFilter.addToBroom("obj12"); + broomFilter.addToBroom("obj13"); + broomFilter.addToBroom("obj14"); + broomFilter.addToBroom("obj15"); + System.out.println(broomFilter.contains("obj1")); + System.out.println(broomFilter.contains("obj16")); + } + + +} + + diff --git a/Spring/src/test/java/cn/whaifree/springdemo/SpringDemoApplicationTests.java b/Spring/src/test/java/cn/whaifree/springdemo/SpringDemoApplicationTests.java new file mode 100644 index 0000000..52bd1e2 --- /dev/null +++ b/Spring/src/test/java/cn/whaifree/springdemo/SpringDemoApplicationTests.java @@ -0,0 +1,72 @@ +package cn.whaifree.springdemo; + +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.redisson.api.RedissonClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.locks.Lock; + +@SpringBootTest +@Slf4j +class SpringDemoApplicationTests { + + @Resource(name = "whaiThreadPool") + private ThreadPoolExecutor whaiThreadPool; + @Test + void threadPool() { + whaiThreadPool.submit(new Runnable() { + @Override + public void run() { + log.info("hello world"); + } + }); + } + + @Test + void contextLoads() { + } + // 注入 RedissonClient + @Autowired + RedissonClient redissonClient; + + // 计数器 + private int count; + + @Test + public void test() throws InterruptedException { + + CountDownLatch countDownLatch = new CountDownLatch(1000); + + for (int i = 0; i < 1000; i++) { + + new Thread(() -> { + + // 每个线程都创建自己的锁对象 + // 这是基于 Redis 实现的分布式锁 + Lock lock = this.redissonClient.getLock("counterLock"); + + try { + // 上锁 + lock.lock(); + + // 计数器自增 1 + this.count = this.count + 1; + + } finally { + // 释放锁 + lock.unlock(); + } + countDownLatch.countDown(); + }).start(); + } + + countDownLatch.await(); + + log.info("count = {}", this.count); + } +} diff --git a/Spring/src/test/java/cn/whaifree/springdemo/tech/AbstractClass.java b/Spring/src/test/java/cn/whaifree/springdemo/tech/AbstractClass.java new file mode 100644 index 0000000..e8c0315 --- /dev/null +++ b/Spring/src/test/java/cn/whaifree/springdemo/tech/AbstractClass.java @@ -0,0 +1,44 @@ +package cn.whaifree.springdemo.tech; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/20 20:19 + * @注释 + */ +public class AbstractClass { + + void method() { + + } + + abstract class parents{ + + public parents(int n) { + System.out.println(inter.name); + System.out.println(1); + } + + public void method2() { + + } + + abstract void method(); + + } + + class SubClass extends parents { + public SubClass() { + // System.out.println(1); 不能写在前面 + super(1); + } + + @Override + public void method() { + method2(); + } + } +} +interface inter{ + public static final int name = 0; // public static final 是多余的 +} diff --git a/SpringDemo/pom.xml b/SpringDemo/pom.xml index db4fefb..487b2d7 100644 --- a/SpringDemo/pom.xml +++ b/SpringDemo/pom.xml @@ -11,6 +11,7 @@ cn.whaifree SpringDemo 0.0.1-SNAPSHOT + pom SpringDemo SpringDemo @@ -30,6 +31,16 @@ 17 + + + + + + cn.bugstack.middleware + db-router-spring-boot-starter + 1.0.2 + + org.springframework.boot @@ -70,30 +81,33 @@ com.github.ben-manes.caffeine caffeine - - - com.baomidou - mybatis-plus-spring-boot3-starter - 3.5.9 - + + + com.github.xiaoymin knife4j-openapi3-jakarta-spring-boot-starter 4.4.0 - - - - - - - + org.redisson - redisson - 3.16.8 + redisson-spring-boot-starter + 3.23.5 + + + com.baomidou + mybatis-plus-spring-boot3-starter + 3.5.9 + + + + + + + org.springframework.boot @@ -125,6 +139,7 @@ fastjson 2.0.36 + cn.hutool @@ -194,6 +209,14 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + + diff --git a/SpringDemo/src/main/java/cn/whaifree/springdemo/aspect/RateLimitAspect.java b/SpringDemo/src/main/java/cn/whaifree/springdemo/aspect/RateLimitAspect.java index a5f33e8..fe900cd 100644 --- a/SpringDemo/src/main/java/cn/whaifree/springdemo/aspect/RateLimitAspect.java +++ b/SpringDemo/src/main/java/cn/whaifree/springdemo/aspect/RateLimitAspect.java @@ -31,13 +31,11 @@ import java.util.Objects; public class RateLimitAspect { private static final Logger log = LoggerFactory.getLogger(RateLimitAspect.class); - private RedisTemplate redisTemplate; - private RedisScript limitScript; @Autowired - public void setRedisTemplate1(RedisTemplate redisTemplate) { + public void setRedisTemplate(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } @@ -48,11 +46,8 @@ public class RateLimitAspect { /** * key 对应方法,value 已经被访问的次数 - * * lua的逻辑: * - * - * * @param point * @param rateLimiter * @throws Throwable @@ -62,7 +57,6 @@ public class RateLimitAspect { int time = rateLimiter.time(); // 多长时间 int count = rateLimiter.count(); // 允许次数 - String combineKey = getCombineKey(rateLimiter, point); // 组合key, Class-Method List keys = Collections.singletonList(combineKey); try { @@ -82,6 +76,7 @@ public class RateLimitAspect { // if (rateLimiter.limitType() == LimitType.IP) { // stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append("-"); // } + if (rateLimiter.limitType() == LimitType.USER) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); String username = attributes.getRequest().getParameter("userId"); diff --git a/SpringDemo/src/main/java/cn/whaifree/springdemo/config/MybatisConfig.java b/SpringDemo/src/main/java/cn/whaifree/springdemo/config/MybatisConfig.java index 11a362b..0418ec3 100644 --- a/SpringDemo/src/main/java/cn/whaifree/springdemo/config/MybatisConfig.java +++ b/SpringDemo/src/main/java/cn/whaifree/springdemo/config/MybatisConfig.java @@ -10,6 +10,7 @@ import org.springframework.context.annotation.Configuration; * @注释 */ @Configuration +//@MapperScan("cn.whaifree.springdemo.mybatis.mapper") @MapperScan("cn.whaifree.springdemo.mybatis.mapper") public class MybatisConfig { diff --git a/SpringDemo/src/main/java/cn/whaifree/springdemo/config/RedissonConfig.java b/SpringDemo/src/main/java/cn/whaifree/springdemo/config/RedissonConfig.java index 48d5f49..48dc317 100644 --- a/SpringDemo/src/main/java/cn/whaifree/springdemo/config/RedissonConfig.java +++ b/SpringDemo/src/main/java/cn/whaifree/springdemo/config/RedissonConfig.java @@ -15,12 +15,12 @@ import org.springframework.context.annotation.Configuration; @Configuration public class RedissonConfig { - @Bean - public RedissonClient redissonClient() { - Config config = new Config(); - config.useSingleServer().setAddress("redis://127.0.0.1:6379"); - config.setThreads(10); - RedissonClient redissonClient = Redisson.create(config); - return redissonClient; - } +// @Bean +// public RedissonClient redissonClient() { +// Config config = new Config(); +// config.useSingleServer().setAddress("redis://127.0.0.1:6379"); +// config.setThreads(10); +// RedissonClient redissonClient = Redisson.create(config); +// return redissonClient; +// } } diff --git a/SpringDemo/src/main/java/cn/whaifree/springdemo/controller/TestController.java b/SpringDemo/src/main/java/cn/whaifree/springdemo/controller/TestController.java index 4857365..fe663a0 100644 --- a/SpringDemo/src/main/java/cn/whaifree/springdemo/controller/TestController.java +++ b/SpringDemo/src/main/java/cn/whaifree/springdemo/controller/TestController.java @@ -19,7 +19,7 @@ import java.util.concurrent.*; @RestController public class TestController { @PostMapping("/test") - @RateLimiter(key = "test:", time = 10, count = 1) // 10s只能1次 + @RateLimiter(key = "test:", time = 10, count = 10) // 10s只能1次 public String test() { return "test"; } diff --git a/SpringDemo/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/aspect/RetryAspect.java b/SpringDemo/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/aspect/RetryAspect.java index 27640b6..2a98769 100644 --- a/SpringDemo/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/aspect/RetryAspect.java +++ b/SpringDemo/src/main/java/cn/whaifree/springdemo/controller/interceptRetry/aspect/RetryAspect.java @@ -1,46 +1,32 @@ package cn.whaifree.springdemo.controller.interceptRetry.aspect; -import cn.hutool.core.util.ReflectUtil; -import cn.hutool.extra.servlet.ServletUtil; -import cn.hutool.extra.spring.SpringUtil; -import cn.hutool.http.HttpUtil; import cn.whaifree.springdemo.controller.interceptRetry.BuinessException; import cn.whaifree.springdemo.controller.interceptRetry.ErrorType; import com.github.benmanes.caffeine.cache.Cache; - import com.github.benmanes.caffeine.cache.Caffeine; -import com.google.common.collect.Maps; -import com.mysql.cj.util.LogUtils; import jakarta.annotation.PostConstruct; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletRequest; -import lombok.Getter; -import org.apache.catalina.util.RequestUtil; import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Lazy; -import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; -import org.springframework.web.bind.ServletRequestUtils; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import org.springframework.web.util.WebUtils; import java.lang.reflect.Method; import java.time.Duration; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; /** + 极海 * @version 1.0 * @Author whai文海 * @Date 2024/11/9 21:57 @@ -66,6 +52,10 @@ public class RetryAspect { @Lazy private ApplicationContext applicationContext; + /** + * 初始化方法,用于扫描所有Bean的方法,寻找带有RetryLimit注解的方法 + * 并根据注解的限制时间创建相应的缓存对象 + */ @PostConstruct public void init() { String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); @@ -86,7 +76,11 @@ public class RetryAspect { continue; } int expireTime = retryLimit.limitTime(); - Cache build = Caffeine.newBuilder().expireAfterAccess(Duration.ofMinutes(expireTime)).maximumSize(1000).build(); + Cache build = + Caffeine.newBuilder() + .expireAfterAccess(Duration.ofMinutes(expireTime)) + .maximumSize(1000) + .build(); cacheMap.put(expireTime, build); } catch (Exception e) { e.printStackTrace(); @@ -99,6 +93,14 @@ public class RetryAspect { + /** + * 环绕通知,拦截带有RetryLimit注解的方法 + * 检查方法的重试次数是否超过限制,如果超过则抛出异常,否则继续执行方法 + * + * @param joinPoint 切入点对象,包含被拦截方法的信息 + * @return 被拦截方法的返回值 + * @throws Throwable 被拦截方法抛出的异常 + */ @Around("@annotation(cn.whaifree.springdemo.controller.interceptRetry.aspect.RetryLimit)") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { // 获取方法签名,设置方法可以访问 diff --git a/SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/AmountTypeHandler.java b/SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/AmountTypeHandler.java deleted file mode 100644 index ff663fb..0000000 --- a/SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/AmountTypeHandler.java +++ /dev/null @@ -1,52 +0,0 @@ -package cn.whaifree.springdemo.mybatis; - -import cn.whaifree.springdemo.mybatis.domain.AmountBig; -import org.apache.ibatis.type.BaseTypeHandler; -import org.apache.ibatis.type.JdbcType; - -import java.sql.*; - -/** - * @version 1.0 - * @Author whai文海 - * @Date 2024/11/15 10:52 - * @注释 - */ -public class AmountTypeHandler extends BaseTypeHandler { - - /** - * 该方法用于将 AmountBig 枚举类型的值设置到 PreparedStatement 中, - * @param ps - * @param i - * @param parameter - * @param jdbcType - * @throws SQLException - */ - @Override - public void setNonNullParameter(PreparedStatement ps, int i, AmountBig parameter, JdbcType jdbcType) throws SQLException { - if (parameter.equals(AmountBig.BIG)) { - ps.setDouble(i, 1000000000D); - }else { - ps.setDouble(i, 0D); - } - } - - @Override - public AmountBig getNullableResult(ResultSet rs, String columnName) throws SQLException { - double aDouble = rs.getDouble(columnName); - if (aDouble == 1000000000D) { - return AmountBig.BIG; - } - return AmountBig.SMALL; - } - - @Override - public AmountBig getNullableResult(ResultSet rs, int columnIndex) throws SQLException { - return null; - } - - @Override - public AmountBig getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { - return null; - } -} diff --git a/SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/domain/Orders.java b/SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/domain/Orders.java deleted file mode 100644 index 98ab6bf..0000000 --- a/SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/domain/Orders.java +++ /dev/null @@ -1,111 +0,0 @@ -package cn.whaifree.springdemo.mybatis.domain; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableField; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.Data; - -import java.io.Serializable; -import java.math.BigDecimal; -import java.util.Date; - -/** - * - * @TableName orders - */ -@TableName(value ="orders") -@Data -public class Orders implements Serializable { - /** - * - */ - @TableId(type = IdType.AUTO) - private Integer id; - - /** - * 订单号 - */ - private String orderNo; - - /** - * 用户ID - */ - private Integer userId; - - /** - * 订单总金额 - */ - private BigDecimal totalAmount; - - /** - * 订单状态: 0-未支付, 1-已支付 - */ - private Integer status; - - /** - * 创建时间 - */ - private Date createTime; - - /** - * - */ - private Integer version; - - @TableField(exist = false) - private static final long serialVersionUID = 1L; - - @Override - public boolean equals(Object that) { - if (this == that) { - return true; - } - if (that == null) { - return false; - } - if (getClass() != that.getClass()) { - return false; - } - Orders other = (Orders) that; - return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId())) - && (this.getOrderNo() == null ? other.getOrderNo() == null : this.getOrderNo().equals(other.getOrderNo())) - && (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId())) - && (this.getTotalAmount() == null ? other.getTotalAmount() == null : this.getTotalAmount().equals(other.getTotalAmount())) - && (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus())) - && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime())) - && (this.getVersion() == null ? other.getVersion() == null : this.getVersion().equals(other.getVersion())); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((getId() == null) ? 0 : getId().hashCode()); - result = prime * result + ((getOrderNo() == null) ? 0 : getOrderNo().hashCode()); - result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode()); - result = prime * result + ((getTotalAmount() == null) ? 0 : getTotalAmount().hashCode()); - result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode()); - result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode()); - result = prime * result + ((getVersion() == null) ? 0 : getVersion().hashCode()); - return result; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(getClass().getSimpleName()); - sb.append(" ["); - sb.append("Hash = ").append(hashCode()); - sb.append(", id=").append(id); - sb.append(", orderNo=").append(orderNo); - sb.append(", userId=").append(userId); - sb.append(", totalAmount=").append(totalAmount); - sb.append(", status=").append(status); - sb.append(", createTime=").append(createTime); - sb.append(", version=").append(version); - sb.append(", serialVersionUID=").append(serialVersionUID); - sb.append("]"); - return sb.toString(); - } -} diff --git a/SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/mapper/OrdersMapper.java b/SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/mapper/OrdersMapper.java deleted file mode 100644 index 75eff77..0000000 --- a/SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/mapper/OrdersMapper.java +++ /dev/null @@ -1,26 +0,0 @@ -package cn.whaifree.springdemo.mybatis.mapper; - -import cn.whaifree.springdemo.mybatis.domain.Orders; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import org.apache.ibatis.annotations.Mapper; -import org.apache.ibatis.annotations.Select; - -import java.util.List; - -/** -* @author wenhai -* @description 针对表【orders】的数据库操作Mapper -* @createDate 2024-11-01 12:45:47 -* @Entity generator.domain.Orders -*/ -@Mapper -public interface OrdersMapper extends BaseMapper { - - @Select("SELECT * FROM orders") - List selectByExample(); - -} - - - - diff --git a/SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/service/OrdersService.java b/SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/service/OrdersService.java deleted file mode 100644 index ed1f4f0..0000000 --- a/SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/service/OrdersService.java +++ /dev/null @@ -1,13 +0,0 @@ -package cn.whaifree.springdemo.mybatis.service; - -import cn.whaifree.springdemo.mybatis.domain.Orders; -import com.baomidou.mybatisplus.extension.service.IService; - -/** -* @author wenhai -* @description 针对表【orders】的数据库操作Service -* @createDate 2024-11-01 12:45:47 -*/ -public interface OrdersService extends IService { - -} diff --git a/SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/service/impl/OrdersServiceImpl.java b/SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/service/impl/OrdersServiceImpl.java deleted file mode 100644 index 53159d7..0000000 --- a/SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/service/impl/OrdersServiceImpl.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.whaifree.springdemo.mybatis.service.impl; - -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import cn.whaifree.springdemo.mybatis.domain.Orders; -import cn.whaifree.springdemo.mybatis.service.OrdersService; -import cn.whaifree.springdemo.mybatis.mapper.OrdersMapper; -import org.springframework.stereotype.Service; - -/** -* @author wenhai -* @description 针对表【orders】的数据库操作Service实现 -* @createDate 2024-11-01 12:45:47 -*/ -@Service -public class OrdersServiceImpl extends ServiceImpl - implements OrdersService{ - -} - - - - diff --git a/SpringDemo/src/main/resources/application.yaml b/SpringDemo/src/main/resources/application.yaml index 96996c2..29fafa8 100644 --- a/SpringDemo/src/main/resources/application.yaml +++ b/SpringDemo/src/main/resources/application.yaml @@ -6,16 +6,16 @@ my: coreSize: 12 maxSize: 24 + spring: datasource: - url: jdbc:mysql://localhost:3306/springTest?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai + url: jdbc:mysql://192.168.50.248:3306/springTest?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver data: redis: - - host: localhost + host: 192.168.50.248 port: 6379 # 选择db1 database: 3 diff --git a/SpringDemo/src/main/resources/mapper/OrdersMapper.xml b/SpringDemo/src/main/resources/mapper/OrdersMapper.xml deleted file mode 100644 index 57f3789..0000000 --- a/SpringDemo/src/main/resources/mapper/OrdersMapper.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - id,order_no,user_id, - total_amount,status,create_time, - version - - diff --git a/SpringDemo/src/main/resources/redisson.yaml b/SpringDemo/src/main/resources/redisson.yaml index 53f2ba2..21e1495 100644 --- a/SpringDemo/src/main/resources/redisson.yaml +++ b/SpringDemo/src/main/resources/redisson.yaml @@ -18,7 +18,7 @@ singleServerConfig: # 客户端名称 #clientName: axin # # 节点地址 - address: redis://127.0.0.1:6379 + address: redis://192.168.50.248:6379 # 发布和订阅连接的最小空闲连接数 subscriptionConnectionMinimumIdleSize: 1 # 发布和订阅连接池大小 diff --git a/SpringDemo/src/test/java/cn/whaifree/springdemo/MybatisTest/MybatisTest.java b/SpringDemo/src/test/java/cn/whaifree/springdemo/MybatisTest/MybatisTest.java deleted file mode 100644 index 98d221f..0000000 --- a/SpringDemo/src/test/java/cn/whaifree/springdemo/MybatisTest/MybatisTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package cn.whaifree.springdemo.MybatisTest; - -import cn.whaifree.springdemo.mybatis.domain.Orders; -import cn.whaifree.springdemo.mybatis.mapper.OrdersMapper; -import cn.whaifree.springdemo.mybatis.service.OrdersService; -import jakarta.annotation.Resource; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -import java.util.List; - -/** - * @version 1.0 - * @Author whai文海 - * @Date 2024/11/1 12:43 - * @注释 - */ -@SpringBootTest -public class MybatisTest { - - @Resource - private OrdersService ordersService; - - @Resource - private OrdersMapper ordersMapper; - @Test - public void test() { - - List orders = ordersMapper.selectList(null); - System.out.println(orders); - List orders1 = ordersMapper.selectByExample(); - System.out.println(orders1); - } -} diff --git a/mybatisplus/pom.xml b/mybatisplus/pom.xml new file mode 100644 index 0000000..035379d --- /dev/null +++ b/mybatisplus/pom.xml @@ -0,0 +1,101 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.3.5.RELEASE + + + com.whai.springcloud.mybatis + mybatisplus + 0.0.1-SNAPSHOT + mybatisplus + mybatisplus + + + + + + + + + + + + + + + 8 + + + + + + cn.whai.middleware + db-router-spring-boot-starter + 1.0-SNAPSHOT + + + + + org.springframework.boot + spring-boot-starter-web + + + + mysql-connector-java + + mysql + 8.0.33 + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.baomidou + mybatis-plus-boot-starter + 3.5.1 + + + + org.springframework.boot + spring-boot-starter-aop + + + + + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/MybatisplusApplication.java b/mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/MybatisplusApplication.java new file mode 100644 index 0000000..cf25b48 --- /dev/null +++ b/mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/MybatisplusApplication.java @@ -0,0 +1,15 @@ +package com.whai.springcloud.mybatis.mybatisplus; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +@SpringBootApplication +@MapperScan("com.whai.springcloud.mybatis.mybatisplus.mybatis.mapper") +@EnableAspectJAutoProxy(proxyTargetClass = true) +public class MybatisplusApplication { + public static void main(String[] args) { + SpringApplication.run(MybatisplusApplication.class, args); + } + +} diff --git a/mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/AmountTypeHandler.java b/mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/AmountTypeHandler.java new file mode 100644 index 0000000..3a959a8 --- /dev/null +++ b/mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/AmountTypeHandler.java @@ -0,0 +1,52 @@ +//package com.whai.springcloud.mybatis.mybatisplus.mybatis; +// +//import com.whai.springcloud.mybatis.mybatisplus.mybatis.domain.AmountBig; +//import org.apache.ibatis.type.BaseTypeHandler; +//import org.apache.ibatis.type.JdbcType; +// +//import java.sql.*; +// +///** +// * @version 1.0 +// * @Author whai文海 +// * @Date 2024/11/15 10:52 +// * @注释 +// */ +//public class AmountTypeHandler extends BaseTypeHandler { +// +// /** +// * 该方法用于将 AmountBig 枚举类型的值设置到 PreparedStatement 中, +// * @param ps +// * @param i +// * @param parameter +// * @param jdbcType +// * @throws SQLException +// */ +// @Override +// public void setNonNullParameter(PreparedStatement ps, int i, AmountBig parameter, JdbcType jdbcType) throws SQLException { +// if (parameter.equals(AmountBig.BIG)) { +// ps.setDouble(i, 1000000000D); +// }else { +// ps.setDouble(i, 0D); +// } +// } +// +// @Override +// public AmountBig getNullableResult(ResultSet rs, String columnName) throws SQLException { +// double aDouble = rs.getDouble(columnName); +// if (aDouble == 1000000000D) { +// return AmountBig.BIG; +// } +// return AmountBig.SMALL; +// } +// +// @Override +// public AmountBig getNullableResult(ResultSet rs, int columnIndex) throws SQLException { +// return null; +// } +// +// @Override +// public AmountBig getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { +// return null; +// } +//} diff --git a/mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/aspect/RateLimitAspect.java b/mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/aspect/RateLimitAspect.java new file mode 100644 index 0000000..3aed2bb --- /dev/null +++ b/mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/aspect/RateLimitAspect.java @@ -0,0 +1,34 @@ +package com.whai.springcloud.mybatis.mybatisplus.mybatis.aspect; + +import com.whai.springcloud.mybatis.mybatisplus.mybatis.aspect.annotation.RateLimiter; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/9 17:46 + * @注释 + */ +@Aspect +@Component +public class RateLimitAspect { + + + /** + * key 对应方法,value 已经被访问的次数 + * + * lua的逻辑: + * + * + * + * @param point + * @param rateLimiter + * @throws Throwable + */ + public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable { + System.out.println("rateLimitAspect"); + } + +} diff --git a/mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/aspect/annotation/RateLimiter.java b/mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/aspect/annotation/RateLimiter.java new file mode 100644 index 0000000..41ecb4a --- /dev/null +++ b/mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/aspect/annotation/RateLimiter.java @@ -0,0 +1,17 @@ +package com.whai.springcloud.mybatis.mybatisplus.mybatis.aspect.annotation; + +import java.lang.annotation.*; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/10/9 17:43 + * @注释 + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter { + + +} diff --git a/SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/domain/AmountBig.java b/mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/domain/AmountBig.java similarity index 66% rename from SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/domain/AmountBig.java rename to mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/domain/AmountBig.java index 3b1a316..3d92224 100644 --- a/SpringDemo/src/main/java/cn/whaifree/springdemo/mybatis/domain/AmountBig.java +++ b/mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/domain/AmountBig.java @@ -1,4 +1,4 @@ -package cn.whaifree.springdemo.mybatis.domain; +package com.whai.springcloud.mybatis.mybatisplus.mybatis.domain; /** * @version 1.0 diff --git a/mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/domain/User.java b/mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/domain/User.java new file mode 100644 index 0000000..5d0f19e --- /dev/null +++ b/mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/domain/User.java @@ -0,0 +1,20 @@ +package com.whai.springcloud.mybatis.mybatisplus.mybatis.domain; + + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/25 11:22 + * @注释 + */ +@Data +@TableName("user") +public class User { + private Long id; + private String name; + private Integer age; + private String email; +} diff --git a/mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/mapper/UserMapper.java b/mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/mapper/UserMapper.java new file mode 100644 index 0000000..b060c26 --- /dev/null +++ b/mybatisplus/src/main/java/com/whai/springcloud/mybatis/mybatisplus/mybatis/mapper/UserMapper.java @@ -0,0 +1,31 @@ +package com.whai.springcloud.mybatis.mybatisplus.mybatis.mapper; + + +import cn.whai.middleware.db.router.annotation.DBRouter; +import cn.whai.middleware.db.router.annotation.DBRouterStrategy; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.whai.springcloud.mybatis.mybatisplus.mybatis.domain.User; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/25 11:22 + * @注释 + */ + +@Mapper +@DBRouterStrategy(splitTable = true) +public interface UserMapper extends BaseMapper { + + @Select("select * from user") + @DBRouter(key = "id") + List getlist(User user); + + @Select("select * from user") + @DBRouter(key = "id") + List getlist1(long l); +} diff --git a/mybatisplus/src/main/resources/application.yaml b/mybatisplus/src/main/resources/application.yaml new file mode 100644 index 0000000..ef12849 --- /dev/null +++ b/mybatisplus/src/main/resources/application.yaml @@ -0,0 +1,73 @@ + + +spring: + datasource: + url: jdbc:mysql://192.168.50.248:3306/springTest?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai + username: root + password: 123456 + driver-class-name: com.mysql.cj.jdbc.Driver + + +# dbCount \u5206\u51E0\u4E2A\u5E93\uFF0CtbCount \u5206\u51E0\u4E2A\u8868\uFF0C\u4E24\u4E2A\u6570\u7684\u4E58\u79EF\u4E3A2\u7684\u6B21\u5E42\u3002 +# default \u4E3A\u9ED8\u8BA4\u4E0D\u8D70\u5206\u5E93\u5206\u8868\u65F6\u5019\u8DEF\u7531\u5230\u54EA\u4E2A\u5E93\uFF0C\u8FD9\u91CC\u662F\u6211\u4EEC\u9700\u8981\u7684\u914D\u7F6E\u5E93\u3002 +# routerKey \u9ED8\u8BA4\u8D70\u7684\u8DEF\u7531 Key\uFF0C\u4E00\u4E2A\u6570\u636E\u8DEF\u7531\uFF0C\u662F\u9700\u8981\u6709\u4E00\u4E2A\u952E\u7684\uFF0C\u8FD9\u91CC\u9009\u62E9\u7684\u662F\u7528\u6237ID\u4F5C\u4E3A\u8DEF\u7531\u8BA1\u7B97\u952E\u3002 +# list: db01,db02 \u8868\u793A\u5206\u5E93\u5206\u8868\uFF0C\u8D70\u90A3\u5957\u5E93\u3002 +# db0\u3001db1\u3001db2 \u5C31\u662F\u914D\u7F6E\u7684\u6570\u636E\u5E93\u4FE1\u606F\u4E86\u3002\u8FD9\u91CC\u7ED9\u6BCF\u4E2A\u6570\u636E\u5E93\u90FD\u914D\u7F6E\u4E86\u5BF9\u5E94\u7684\u8FDE\u63A5\u6C60\u4FE1\u606F\u3002 + + +# \u591A\u6570\u636E\u6E90\u8DEF\u7531\u914D\u7F6E\uFF0C\u5E93\u6570\u91CF * \u8868\u6570\u91CF \u4E3A2\u7684\u6B21\u5E42\uFF0C\u59822\u5E934\u8868 +# mysql 5.x \u914D\u7F6E driver-class-name: com.mysql.jdbc.Driver mysql-connector-java 5.1.34 +# mysql 8.x \u914D\u7F6E driver-class-name: com.mysql.cj.jdbc.Driver mysql-connector-java 8.0.22 +mini-db-router: + jdbc: + datasource: + dbCount: 2 + tbCount: 4 + default: db00 + routerKey: id # 默认key,如果@RouterK没有就用这个 + list: db01,db02 + db00: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://192.168.50.248:3306/springTest?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai + username: root + password: 123456 + type-class-name: com.zaxxer.hikari.HikariDataSource + pool: + pool-name: Retail_HikariCP + minimum-idle: 15 #\u6700\u5C0F\u7A7A\u95F2\u8FDE\u63A5\u6570\u91CF + idle-timeout: 180000 #\u7A7A\u95F2\u8FDE\u63A5\u5B58\u6D3B\u6700\u5927\u65F6\u95F4\uFF0C\u9ED8\u8BA4600000\uFF0810\u5206\u949F\uFF09 + maximum-pool-size: 25 #\u8FDE\u63A5\u6C60\u6700\u5927\u8FDE\u63A5\u6570\uFF0C\u9ED8\u8BA4\u662F10 + auto-commit: true #\u6B64\u5C5E\u6027\u63A7\u5236\u4ECE\u6C60\u8FD4\u56DE\u7684\u8FDE\u63A5\u7684\u9ED8\u8BA4\u81EA\u52A8\u63D0\u4EA4\u884C\u4E3A,\u9ED8\u8BA4\u503C\uFF1Atrue + max-lifetime: 1800000 #\u6B64\u5C5E\u6027\u63A7\u5236\u6C60\u4E2D\u8FDE\u63A5\u7684\u6700\u957F\u751F\u547D\u5468\u671F\uFF0C\u503C0\u8868\u793A\u65E0\u9650\u751F\u547D\u5468\u671F\uFF0C\u9ED8\u8BA41800000\u537330\u5206\u949F + connection-timeout: 30000 #\u6570\u636E\u5E93\u8FDE\u63A5\u8D85\u65F6\u65F6\u95F4,\u9ED8\u8BA430\u79D2\uFF0C\u537330000 + connection-test-query: SELECT 1 + db01: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://192.168.50.248:3306/springTest?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai + username: root + password: 123456 + type-class-name: com.zaxxer.hikari.HikariDataSource + pool: + pool-name: Retail_HikariCP + minimum-idle: 15 #\u6700\u5C0F\u7A7A\u95F2\u8FDE\u63A5\u6570\u91CF + idle-timeout: 180000 #\u7A7A\u95F2\u8FDE\u63A5\u5B58\u6D3B\u6700\u5927\u65F6\u95F4\uFF0C\u9ED8\u8BA4600000\uFF0810\u5206\u949F\uFF09 + maximum-pool-size: 25 #\u8FDE\u63A5\u6C60\u6700\u5927\u8FDE\u63A5\u6570\uFF0C\u9ED8\u8BA4\u662F10 + auto-commit: true #\u6B64\u5C5E\u6027\u63A7\u5236\u4ECE\u6C60\u8FD4\u56DE\u7684\u8FDE\u63A5\u7684\u9ED8\u8BA4\u81EA\u52A8\u63D0\u4EA4\u884C\u4E3A,\u9ED8\u8BA4\u503C\uFF1Atrue + max-lifetime: 1800000 #\u6B64\u5C5E\u6027\u63A7\u5236\u6C60\u4E2D\u8FDE\u63A5\u7684\u6700\u957F\u751F\u547D\u5468\u671F\uFF0C\u503C0\u8868\u793A\u65E0\u9650\u751F\u547D\u5468\u671F\uFF0C\u9ED8\u8BA41800000\u537330\u5206\u949F + connection-timeout: 30000 #\u6570\u636E\u5E93\u8FDE\u63A5\u8D85\u65F6\u65F6\u95F4,\u9ED8\u8BA430\u79D2\uFF0C\u537330000 + connection-test-query: SELECT 1 + db02: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://192.168.50.248:3306/springTest?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai + username: root + password: 123456 + type-class-name: com.zaxxer.hikari.HikariDataSource + pool: + pool-name: Retail_HikariCP + minimum-idle: 15 #\u6700\u5C0F\u7A7A\u95F2\u8FDE\u63A5\u6570\u91CF + idle-timeout: 180000 #\u7A7A\u95F2\u8FDE\u63A5\u5B58\u6D3B\u6700\u5927\u65F6\u95F4\uFF0C\u9ED8\u8BA4600000\uFF0810\u5206\u949F\uFF09 + maximum-pool-size: 25 #\u8FDE\u63A5\u6C60\u6700\u5927\u8FDE\u63A5\u6570\uFF0C\u9ED8\u8BA4\u662F10 + auto-commit: true #\u6B64\u5C5E\u6027\u63A7\u5236\u4ECE\u6C60\u8FD4\u56DE\u7684\u8FDE\u63A5\u7684\u9ED8\u8BA4\u81EA\u52A8\u63D0\u4EA4\u884C\u4E3A,\u9ED8\u8BA4\u503C\uFF1Atrue + max-lifetime: 1800000 #\u6B64\u5C5E\u6027\u63A7\u5236\u6C60\u4E2D\u8FDE\u63A5\u7684\u6700\u957F\u751F\u547D\u5468\u671F\uFF0C\u503C0\u8868\u793A\u65E0\u9650\u751F\u547D\u5468\u671F\uFF0C\u9ED8\u8BA41800000\u537330\u5206\u949F + connection-timeout: 30000 #\u6570\u636E\u5E93\u8FDE\u63A5\u8D85\u65F6\u65F6\u95F4,\u9ED8\u8BA430\u79D2\uFF0C\u537330000 + connection-test-query: SELECT 1 diff --git a/mybatisplus/src/main/resources/mapper/OrdersMapper.xml b/mybatisplus/src/main/resources/mapper/OrdersMapper.xml new file mode 100644 index 0000000..b17b0cc --- /dev/null +++ b/mybatisplus/src/main/resources/mapper/OrdersMapper.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/mybatisplus/src/test/java/com/whai/springcloud/mybatis/mybatisplus/MybatisTest/MybatisTest.java b/mybatisplus/src/test/java/com/whai/springcloud/mybatis/mybatisplus/MybatisTest/MybatisTest.java new file mode 100644 index 0000000..4cb25ea --- /dev/null +++ b/mybatisplus/src/test/java/com/whai/springcloud/mybatis/mybatisplus/MybatisTest/MybatisTest.java @@ -0,0 +1,48 @@ +package com.whai.springcloud.mybatis.mybatisplus.MybatisTest; + + + + +import com.whai.springcloud.mybatis.mybatisplus.mybatis.domain.User; +import com.whai.springcloud.mybatis.mybatisplus.mybatis.mapper.UserMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +/** + * @version 1.0 + * @Author whai文海 + * @Date 2024/11/1 12:43 + * @注释 + */ +@SpringBootTest +public class MybatisTest { + + + + @Autowired + private UserMapper userMapper; + + @Test + public void testSelect() { + User user = new User(); + user.setId(1L); + userMapper.getlist1(1l).forEach(System.out::println); + System.out.println(("----- selectAll method test ------")); + List userList = userMapper.selectList(null); + userList.forEach(System.out::println); + } + +// @Resource +// private OrdersMapper ordersMapper; +// @Test +// public void test() { +// +// List orders = ordersMapper.selectList(null); +// System.out.println(orders); +//// List orders1 = ordersMapper.selectByExample(); +//// System.out.println(orders1); +// } +} diff --git a/mybatisplus/src/test/java/com/whai/springcloud/mybatis/mybatisplus/MybatisplusApplicationTests.java b/mybatisplus/src/test/java/com/whai/springcloud/mybatis/mybatisplus/MybatisplusApplicationTests.java new file mode 100644 index 0000000..3056ee9 --- /dev/null +++ b/mybatisplus/src/test/java/com/whai/springcloud/mybatis/mybatisplus/MybatisplusApplicationTests.java @@ -0,0 +1,27 @@ +package com.whai.springcloud.mybatis.mybatisplus; + +import com.whai.springcloud.mybatis.mybatisplus.mybatis.mapper.UserMapper; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import javax.annotation.Resource; + +@SpringBootTest +class MybatisplusApplicationTests { + + @Resource + private UserMapper userMapper; + + + @Test + void contextLoads() { + +// userMapper.getlist(1).forEach( +// System.out::println +// ); +// List list = userMapper.selectList(null); +// list.forEach(System.out::println); + + } + +}