refactor(mybatis): 重构 Mybatis 相关代码
-移动 AmountBig 类到新的包结构 - 新增 AmountTypeHandler 类用于处理 AmountBig 类型- 更新 application.yaml 配置- 重构 DBRouter 相关代码,优化切面逻辑 - 新增 LeetCode 相关解题代码 - 更新 Mybatis配置和测试代码
This commit is contained in:
parent
0341ebc835
commit
501357cbd0
24
ForJdk17/src/main/java/cn/whaifree/interview/td/p1.java
Normal file
24
ForJdk17/src/main/java/cn/whaifree/interview/td/p1.java
Normal file
@ -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<DelayedTask> 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);
|
||||||
|
}
|
||||||
|
}
|
@ -101,6 +101,12 @@ public class LeetCode215 {
|
|||||||
class Solution2 {
|
class Solution2 {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 构建大根堆
|
||||||
|
*
|
||||||
|
* 构建每个小子树都是一个大根堆,再以子堆的父节点往上换
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
* 构建大根堆
|
* 构建大根堆
|
||||||
* 把根移动到最后
|
* 把根移动到最后
|
||||||
* 移动k次,顶部就是了
|
* 移动k次,顶部就是了
|
||||||
@ -138,6 +144,7 @@ public class LeetCode215 {
|
|||||||
if (index != large) {
|
if (index != large) {
|
||||||
swap(index, large);
|
swap(index, large);
|
||||||
build(large, rightEdge); // large换完后large已经是小的了,小的下沉到合适的位置
|
build(large, rightEdge); // large换完后large已经是小的了,小的下沉到合适的位置
|
||||||
|
// 大的不断换上去,直到不换就退出
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
207
Spring/pom.xml
Normal file
207
Spring/pom.xml
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.4.0</version>
|
||||||
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
|
</parent>
|
||||||
|
<groupId>cn.whaifree</groupId>
|
||||||
|
<artifactId>Spring</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<name>Spring</name>
|
||||||
|
<description>Spring</description>
|
||||||
|
<url/>
|
||||||
|
<licenses>
|
||||||
|
<license/>
|
||||||
|
</licenses>
|
||||||
|
<developers>
|
||||||
|
<developer/>
|
||||||
|
</developers>
|
||||||
|
<scm>
|
||||||
|
<connection/>
|
||||||
|
<developerConnection/>
|
||||||
|
<tag/>
|
||||||
|
<url/>
|
||||||
|
</scm>
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- mybatis-spring-boot-starter-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mybatis.spring.boot</groupId>
|
||||||
|
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||||
|
<version>3.0.3</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- <!– https://mvnrepository.com/artifact/cn.bugstack.middleware/db-router-spring-boot-starter –>-->
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>cn.bugstack.middleware</groupId>-->
|
||||||
|
<!-- <artifactId>db-router-spring-boot-starter</artifactId>-->
|
||||||
|
<!-- <version>1.0.2</version>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
|
||||||
|
<!--security-->
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>org.springframework.boot</groupId>-->
|
||||||
|
<!-- <artifactId>spring-boot-starter-security</artifactId>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.poi</groupId>
|
||||||
|
<artifactId>poi</artifactId>
|
||||||
|
<version>5.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.poi</groupId>
|
||||||
|
<artifactId>poi-ooxml</artifactId>
|
||||||
|
<version>5.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-integration</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.integration</groupId>
|
||||||
|
<artifactId>spring-integration-ip</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- <!– openAPI包,替换 Swagger 的 SpringFox –>-->
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>org.springdoc</groupId>-->
|
||||||
|
<!-- <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>-->
|
||||||
|
<!-- <version>2.2.0</version>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
<!-- caffeine 缓存使用姿势 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||||
|
<artifactId>caffeine</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.xiaoymin</groupId>
|
||||||
|
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||||
|
<version>4.4.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.redisson</groupId>
|
||||||
|
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||||
|
<version>3.23.5</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>org.redisson</groupId>-->
|
||||||
|
<!-- <artifactId>redisson</artifactId>-->
|
||||||
|
<!-- <version>3.16.8</version>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
<!-- rabbitMQ-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!--minio-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.minio</groupId>
|
||||||
|
<artifactId>minio</artifactId>
|
||||||
|
<version>8.4.3</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- 其他依赖 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||||
|
<artifactId>jackson-dataformat-xml</artifactId>
|
||||||
|
<version>2.13.0</version> <!-- 根据实际情况选择最新稳定版本 -->
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
<version>31.0.1-jre</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--FastJson-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>fastjson</artifactId>
|
||||||
|
<version>2.0.36</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--hutool-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-all</artifactId>
|
||||||
|
<version>5.8.13</version>
|
||||||
|
</dependency>
|
||||||
|
<!--aspect-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-aop</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
|
<!--jdbc-->
|
||||||
|
<groupId>mysql</groupId>
|
||||||
|
<version>8.0.33</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jdbc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>org.springframework.amqp</groupId>-->
|
||||||
|
<!-- <artifactId>spring-rabbit-test</artifactId>-->
|
||||||
|
<!-- <scope>test</scope>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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<Object, Object> redisTemplate;
|
||||||
|
|
||||||
|
private RedisScript<Long> limitScript;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setRedisTemplate(RedisTemplate<Object, Object> redisTemplate) {
|
||||||
|
this.redisTemplate = redisTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setLimitScript(RedisScript<Long> 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<Object> 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
@ -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<String> getCacheNames() {
|
||||||
|
// return Arrays.asList("myCache"); // 设定缓存的名称
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//}
|
@ -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<SelfFilter> customFilterRegistration() {
|
||||||
|
FilterRegistrationBean<SelfFilter> registration = new FilterRegistrationBean<>();
|
||||||
|
registration.setFilter(new SelfFilter());
|
||||||
|
registration.addUrlPatterns("/*"); // 指定过滤器应用的 URL 模式
|
||||||
|
registration.setName("customFilter");
|
||||||
|
registration.setOrder(1); // 设置过滤器的顺序
|
||||||
|
return registration;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
@ -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<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||||
|
RedisTemplate<Object, Object> 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<Long> limitScript() {
|
||||||
|
DefaultRedisScript<Long> 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);";
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
// }
|
||||||
|
}
|
@ -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("/"));
|
||||||
|
// }
|
||||||
|
//}
|
@ -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<Object>() {
|
||||||
|
// @Override
|
||||||
|
// public Object doInTransaction(TransactionStatus status) {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//class Tran{
|
||||||
|
//
|
||||||
|
//}
|
@ -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:";
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
@ -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;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//}
|
@ -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<String, SseEmitter> 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");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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<String, SseEmitter> 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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";
|
||||||
|
|
||||||
|
/*
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-integration</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.integration</groupId>
|
||||||
|
<artifactId>spring-integration-ip</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收
|
||||||
|
*
|
||||||
|
* @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<String, Object> 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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<Object> msg = MessageBuilder.withPayload(object).build();
|
||||||
|
sender.handleMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package cn.whaifree.springdemo.controller.TS.common;
|
||||||
|
|
||||||
|
public interface ProcessStrategy {
|
||||||
|
void process(byte[] frame);
|
||||||
|
|
||||||
|
void process(Object o);
|
||||||
|
}
|
@ -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<Integer, ProcessStrategy> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -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<Integer, TreeMap<Integer, byte[]>> 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<Integer, byte[]> 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("图像");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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<String> instances, int userId) {
|
||||||
|
// 导入容器节点到集群中
|
||||||
|
return addToCluster(instances, "clusterId", userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param instances 导入实例的ID
|
||||||
|
* @param userID
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String addToCluster(List<String> 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<String> loadTo(List<String> instances, String clusterId) {
|
||||||
|
|
||||||
|
CountDownLatch countDownLatch = new CountDownLatch(instances.size());
|
||||||
|
|
||||||
|
for (String instance : instances) {
|
||||||
|
executor.submit(new Callable<String>() {
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
@ -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<Object, Object> guavaCache = CacheBuilder.newBuilder()
|
||||||
|
.maximumSize(1000)
|
||||||
|
.expireAfterWrite(Duration.ofDays(1))
|
||||||
|
.initialCapacity(10)
|
||||||
|
.build(new com.google.common.cache.CacheLoader<Object, Object>() {
|
||||||
|
@Override
|
||||||
|
public Object load(Object key) throws Exception {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
private com.github.benmanes.caffeine.cache.LoadingCache<Object, Object> caffeineCache = Caffeine.newBuilder()
|
||||||
|
.maximumSize(1000)
|
||||||
|
.expireAfterWrite(Duration.ofDays(1))
|
||||||
|
.build(new com.github.benmanes.caffeine.cache.CacheLoader<Object, Object>() {
|
||||||
|
@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<Object, Object> 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<Object, Object>() {
|
||||||
|
@Override
|
||||||
|
public @Nullable Object load(Object key) throws Exception {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://blog.csdn.net/zhangyunfeihhhh/article/details/108105928">...</a>
|
||||||
|
*
|
||||||
|
* 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<V> get(K key, Function<? super K, ? extends V> mappingFunction) {
|
||||||
|
// requireNonNull(mappingFunction);
|
||||||
|
// return get(key, (k1, executor) -> CompletableFuture.supplyAsync(
|
||||||
|
// () -> mappingFunction.apply(key), executor));
|
||||||
|
// }
|
||||||
|
|
||||||
|
@RequestMapping("/asyncGet")
|
||||||
|
public String asyncGet(String key) {
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
@PostMapping("query")
|
||||||
|
public ResVo query() {
|
||||||
|
List<Map<String, Object>> 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 '创建时间'
|
||||||
|
* );
|
||||||
|
* <p>
|
||||||
|
* 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<Long> tDefaultRedisScript = new DefaultRedisScript<>(script, Long.class);
|
||||||
|
|
||||||
|
Long execute = redisTemplate.execute(tDefaultRedisScript, Arrays.asList(key), tokenV);
|
||||||
|
if (execute == 0) {
|
||||||
|
throw new RuntimeException("未通过");
|
||||||
|
}
|
||||||
|
return ResVo.success("通过");
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
@ -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只能有一个有效期,
|
||||||
|
* 所以要根据有效期进行分组
|
||||||
|
* <p>
|
||||||
|
* 【exprieTime,【keyOfIp,time】】
|
||||||
|
* <p>
|
||||||
|
* <p>
|
||||||
|
* 对不同时间用不同cache实现
|
||||||
|
*/
|
||||||
|
Map<Integer, Cache<String, AtomicInteger>> 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<String, AtomicInteger> 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<String, AtomicInteger> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
|
||||||
|
}
|
@ -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<String, String> imgReplaceCache = CacheBuilder.newBuilder().maximumSize(300).expireAfterWrite(5, TimeUnit.MINUTES).build(new CacheLoader<String, String>() {
|
||||||
|
@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<MediaType> 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);
|
||||||
|
}
|
@ -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<T> 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<T> implements ApplicationListener<NotifyMsgEvent<T>> {
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(NotifyMsgEvent<T> 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<Integer, NotifyTypeEnum> 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());
|
||||||
|
}
|
||||||
|
}
|
@ -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<String, Object> 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<String> event = JSON.parseObject(message.getBody(), NotifyMsgEvent.class);
|
||||||
|
|
||||||
|
// 构造活跃度增加的通知
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final String todayRankKey = DateUtil.format(DateUtil.date(), "yyyyMMdd");
|
||||||
|
@PostMapping("/activity")
|
||||||
|
public ResVo getRank(int k) {
|
||||||
|
// 获取前k个
|
||||||
|
Set<String> execute = redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
|
||||||
|
Set<String> collect = connection.zRange(todayRankKey.getBytes(), 0, k - 1).stream().map(new Function<byte[], String>() {
|
||||||
|
@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<Object>) 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<Double>() {
|
||||||
|
@Override
|
||||||
|
public Double doInRedis(RedisConnection connection) throws DataAccessException {
|
||||||
|
return connection.zScore(todayRankKey.getBytes(), userId.getBytes());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object execute = redisTemplate.execute(new RedisCallback<Object>() {
|
||||||
|
@Override
|
||||||
|
public Object doInRedis(RedisConnection connection) throws DataAccessException {
|
||||||
|
return connection.zScore(monthRankKey.getBytes(), userId.getBytes());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
redisTemplate.execute((RedisCallback<Object>) connection -> {
|
||||||
|
connection.expire(todayRankKey.getBytes(), 31 * 24 * 60 * 60); // 保存一个月
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
redisTemplate.execute((RedisCallback<Object>) 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";
|
||||||
|
}
|
@ -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<String, Object> redPacket = new HashMap<>();
|
||||||
|
redPacket.put("amount", amount);
|
||||||
|
redPacket.put("id", System.currentTimeMillis()); // 使用当前时间戳作为红包 ID
|
||||||
|
|
||||||
|
// 存入 Redis 红包列表
|
||||||
|
RBucket<Object> redPacketList = redissonClient.getBucket("red_packet_list");
|
||||||
|
redPacketList.set(redPacket);
|
||||||
|
|
||||||
|
return "Distributed successfully";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping("rob")
|
||||||
|
public String rob(String userId) {
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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<User> getUserList() {
|
||||||
|
List<User> 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<User> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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<User> 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<User> 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<User2> 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<User> getUserList() {
|
||||||
|
List<User> 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<User2> getUser2List() {
|
||||||
|
List<User2> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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<String, SseEmitter> verifyCodeCache;
|
||||||
|
/**
|
||||||
|
* key = 设备 value = 验证码
|
||||||
|
*/
|
||||||
|
private LoadingCache<String, String> deviceCodeCache;
|
||||||
|
|
||||||
|
public WxQrLoginController(@Qualifier("redisTemplate") RedisTemplate redisTemplate) {
|
||||||
|
this.redisTemplate = redisTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
// 验证码,SSE
|
||||||
|
verifyCodeCache = CacheBuilder.newBuilder().build(new CacheLoader<String, SseEmitter>() {
|
||||||
|
@Override
|
||||||
|
public SseEmitter load(String key) throws Exception {
|
||||||
|
// 如果缓存未命中,则抛出异常,提示缓存未命中
|
||||||
|
throw new RuntimeException("no val: " + key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 设备,验证码
|
||||||
|
deviceCodeCache = CacheBuilder.newBuilder().build(new CacheLoader<String, String>() {
|
||||||
|
@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
|
||||||
|
* '<xml>
|
||||||
|
* <URL><![CDATA[https://hhui.top]]></URL>
|
||||||
|
* <ToUserName><![CDATA[一灰灰blog]]></ToUserName>
|
||||||
|
* <FromUserName><![CDATA[demoUser1234]]></FromUserName>
|
||||||
|
* <CreateTime>1655700579</CreateTime>
|
||||||
|
* <MsgType><![CDATA[text]]></MsgType>
|
||||||
|
* <Content><![CDATA[login]]></Content>
|
||||||
|
* <MsgId>11111111</MsgId>
|
||||||
|
* </xml>' -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();
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>Spring Bean生命周期</h1>
|
||||||
|
* <p>Spring Bean 的生命周期包括创建、初始化、使用、销毁等过程。</p>
|
||||||
|
* <p>Spring Bean 的生命周期</p>
|
||||||
|
*
|
||||||
|
* <li>实例化阶段:Bean 被实例化,Bean 实例化后,Bean 处于未初始化状态。</li>
|
||||||
|
* <li>初始化阶段:Bean 完成实例化后,Bean 处于初始化状态,Bean 实例变量可以被赋值。</li>
|
||||||
|
* <ul>
|
||||||
|
* <li><code>BeanNameAware.setBeanName</code></li>
|
||||||
|
* <li><code>BeanFactoryAware.setBeanFactory</code></li>
|
||||||
|
* <li><code>ApplicationContextAware.setApplicationContext</code></li>
|
||||||
|
* </ul>
|
||||||
|
* <ul>
|
||||||
|
* <li><code>@PostConstruct</code></li>
|
||||||
|
* <li><code>InitializingBean.afterPropertiesSet</code></li>
|
||||||
|
* <li><code>BeanPostProcessor.postProcessBeforeInitialization</code></li>
|
||||||
|
* <li>初始化</li>
|
||||||
|
* <li><code>BeanPostProcessor.postProcessAfterInitialization</code></li>
|
||||||
|
* </ul>
|
||||||
|
* <li>使用阶段:Bean 完成初始化后,Bean 处于可使用状态,Bean 可以被使用。</li>
|
||||||
|
* <li>销毁阶段:Bean 不再被使用时,Bean 处于销毁状态,Bean 实例将被销毁。</li>
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li><code>@PreDestroy</code></li>
|
||||||
|
* <li><code>DisposableBean.destroy</code></li>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* <h2>相关Aware注入:让 Bean 能够**感知**到它们所运行的环境或依赖的资源e</h2>
|
||||||
|
* <h2>为什么要叫Aware</h2>
|
||||||
|
* 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;
|
||||||
|
// }
|
||||||
|
//}
|
@ -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);
|
||||||
|
// }
|
||||||
|
//}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
196
Spring/src/main/java/cn/whaifree/springdemo/utils/ResVo.java
Normal file
196
Spring/src/main/java/cn/whaifree/springdemo/utils/ResVo.java
Normal file
@ -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<String, Object>
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
2
Spring/src/main/resources/META-INF/spring.factories
Normal file
2
Spring/src/main/resources/META-INF/spring.factories
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||||
|
cn.whaifree.springdemo.config.MyAutoConfiguration;
|
78
Spring/src/main/resources/application.yaml
Normal file
78
Spring/src/main/resources/application.yaml
Normal file
@ -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
|
27
Spring/src/main/resources/lua/RobEnvelope.lua
Normal file
27
Spring/src/main/resources/lua/RobEnvelope.lua
Normal file
@ -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
|
42
Spring/src/main/resources/redisson.yaml
Normal file
42
Spring/src/main/resources/redisson.yaml
Normal file
@ -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: !<org.redisson.codec.JsonJacksonCodec> {}
|
||||||
|
## 传输模式
|
||||||
|
#transportMode : "NIO"
|
||||||
|
#
|
BIN
Spring/src/main/resources/template/empty.xlsx
Normal file
BIN
Spring/src/main/resources/template/empty.xlsx
Normal file
Binary file not shown.
BIN
Spring/src/main/resources/template/project_member_workHours.xlsx
Normal file
BIN
Spring/src/main/resources/template/project_member_workHours.xlsx
Normal file
Binary file not shown.
@ -0,0 +1,13 @@
|
|||||||
|
package cn.whaifree.spring;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class ApplicationTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void contextLoads() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,146 @@
|
|||||||
|
package cn.whaifree.springdemo.RedisData;
|
||||||
|
|
||||||
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
|
import cn.whaifree.springdemo.utils.BroomFilter.BroomFilter;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.data.geo.*;
|
||||||
|
import org.springframework.data.redis.connection.BitFieldSubCommands;
|
||||||
|
import org.springframework.data.redis.connection.RedisGeoCommands;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @version 1.0
|
||||||
|
* @Author whai文海
|
||||||
|
* @Date 2024/10/14 22:06
|
||||||
|
* @注释
|
||||||
|
*/
|
||||||
|
@SpringBootTest
|
||||||
|
@Slf4j
|
||||||
|
public class RedisDataTest {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> 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<RedisGeoCommands.GeoLocation<Object>> radius =
|
||||||
|
redisTemplate.opsForGeo().radius(key, new Circle(point, new Distance(2000, RedisGeoCommands.DistanceUnit.KILOMETERS)), // 圆形区域, 半径为1000米
|
||||||
|
RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeCoordinates().sortAscending());
|
||||||
|
|
||||||
|
|
||||||
|
List<GeoResult<RedisGeoCommands.GeoLocation<Object>>> content = radius.getContent();
|
||||||
|
for (GeoResult<RedisGeoCommands.GeoLocation<Object>> 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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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 是多余的
|
||||||
|
}
|
@ -11,6 +11,7 @@
|
|||||||
<groupId>cn.whaifree</groupId>
|
<groupId>cn.whaifree</groupId>
|
||||||
<artifactId>SpringDemo</artifactId>
|
<artifactId>SpringDemo</artifactId>
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<packaging>pom</packaging>
|
||||||
<name>SpringDemo</name>
|
<name>SpringDemo</name>
|
||||||
<description>SpringDemo</description>
|
<description>SpringDemo</description>
|
||||||
<url/>
|
<url/>
|
||||||
@ -30,6 +31,16 @@
|
|||||||
<java.version>17</java.version>
|
<java.version>17</java.version>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/cn.bugstack.middleware/db-router-spring-boot-starter -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.bugstack.middleware</groupId>
|
||||||
|
<artifactId>db-router-spring-boot-starter</artifactId>
|
||||||
|
<version>1.0.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!--security-->
|
<!--security-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@ -70,30 +81,33 @@
|
|||||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||||
<artifactId>caffeine</artifactId>
|
<artifactId>caffeine</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.baomidou</groupId>
|
|
||||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
|
||||||
<version>3.5.9</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.xiaoymin</groupId>
|
<groupId>com.github.xiaoymin</groupId>
|
||||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||||
<version>4.4.0</version>
|
<version>4.4.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- <!– https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter –>-->
|
<!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
|
||||||
<!-- <dependency>-->
|
|
||||||
<!-- <groupId>org.redisson</groupId>-->
|
|
||||||
<!-- <artifactId>redisson-spring-boot-starter</artifactId>-->
|
|
||||||
<!-- <version>3.23.5</version>-->
|
|
||||||
<!-- </dependency>-->
|
|
||||||
<!--redisson-->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.redisson</groupId>
|
<groupId>org.redisson</groupId>
|
||||||
<artifactId>redisson</artifactId>
|
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||||
<version>3.16.8</version>
|
<version>3.23.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||||
|
<version>3.5.9</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>org.redisson</groupId>-->
|
||||||
|
<!-- <artifactId>redisson</artifactId>-->
|
||||||
|
<!-- <version>3.16.8</version>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
<!-- rabbitMQ-->
|
<!-- rabbitMQ-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@ -125,6 +139,7 @@
|
|||||||
<artifactId>fastjson</artifactId>
|
<artifactId>fastjson</artifactId>
|
||||||
<version>2.0.36</version>
|
<version>2.0.36</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!--hutool-->
|
<!--hutool-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
@ -194,6 +209,14 @@
|
|||||||
</excludes>
|
</excludes>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>17</source>
|
||||||
|
<target>17</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
@ -31,13 +31,11 @@ import java.util.Objects;
|
|||||||
public class RateLimitAspect {
|
public class RateLimitAspect {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(RateLimitAspect.class);
|
private static final Logger log = LoggerFactory.getLogger(RateLimitAspect.class);
|
||||||
|
|
||||||
private RedisTemplate<Object, Object> redisTemplate;
|
private RedisTemplate<Object, Object> redisTemplate;
|
||||||
|
|
||||||
private RedisScript<Long> limitScript;
|
private RedisScript<Long> limitScript;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public void setRedisTemplate1(RedisTemplate<Object, Object> redisTemplate) {
|
public void setRedisTemplate(RedisTemplate<Object, Object> redisTemplate) {
|
||||||
this.redisTemplate = redisTemplate;
|
this.redisTemplate = redisTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,11 +46,8 @@ public class RateLimitAspect {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* key 对应方法,value 已经被访问的次数
|
* key 对应方法,value 已经被访问的次数
|
||||||
*
|
|
||||||
* lua的逻辑:
|
* lua的逻辑:
|
||||||
*
|
*
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param point
|
* @param point
|
||||||
* @param rateLimiter
|
* @param rateLimiter
|
||||||
* @throws Throwable
|
* @throws Throwable
|
||||||
@ -62,7 +57,6 @@ public class RateLimitAspect {
|
|||||||
int time = rateLimiter.time(); // 多长时间
|
int time = rateLimiter.time(); // 多长时间
|
||||||
int count = rateLimiter.count(); // 允许次数
|
int count = rateLimiter.count(); // 允许次数
|
||||||
|
|
||||||
|
|
||||||
String combineKey = getCombineKey(rateLimiter, point); // 组合key, Class-Method
|
String combineKey = getCombineKey(rateLimiter, point); // 组合key, Class-Method
|
||||||
List<Object> keys = Collections.singletonList(combineKey);
|
List<Object> keys = Collections.singletonList(combineKey);
|
||||||
try {
|
try {
|
||||||
@ -82,6 +76,7 @@ public class RateLimitAspect {
|
|||||||
// if (rateLimiter.limitType() == LimitType.IP) {
|
// if (rateLimiter.limitType() == LimitType.IP) {
|
||||||
// stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append("-");
|
// stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append("-");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if (rateLimiter.limitType() == LimitType.USER) {
|
if (rateLimiter.limitType() == LimitType.USER) {
|
||||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||||
String username = attributes.getRequest().getParameter("userId");
|
String username = attributes.getRequest().getParameter("userId");
|
||||||
|
@ -10,6 +10,7 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
* @注释
|
* @注释
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
|
//@MapperScan("cn.whaifree.springdemo.mybatis.mapper")
|
||||||
@MapperScan("cn.whaifree.springdemo.mybatis.mapper")
|
@MapperScan("cn.whaifree.springdemo.mybatis.mapper")
|
||||||
public class MybatisConfig {
|
public class MybatisConfig {
|
||||||
|
|
||||||
|
@ -15,12 +15,12 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
@Configuration
|
@Configuration
|
||||||
public class RedissonConfig {
|
public class RedissonConfig {
|
||||||
|
|
||||||
@Bean
|
// @Bean
|
||||||
public RedissonClient redissonClient() {
|
// public RedissonClient redissonClient() {
|
||||||
Config config = new Config();
|
// Config config = new Config();
|
||||||
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
|
// config.useSingleServer().setAddress("redis://127.0.0.1:6379");
|
||||||
config.setThreads(10);
|
// config.setThreads(10);
|
||||||
RedissonClient redissonClient = Redisson.create(config);
|
// RedissonClient redissonClient = Redisson.create(config);
|
||||||
return redissonClient;
|
// return redissonClient;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ import java.util.concurrent.*;
|
|||||||
@RestController
|
@RestController
|
||||||
public class TestController {
|
public class TestController {
|
||||||
@PostMapping("/test")
|
@PostMapping("/test")
|
||||||
@RateLimiter(key = "test:", time = 10, count = 1) // 10s只能1次
|
@RateLimiter(key = "test:", time = 10, count = 10) // 10s只能1次
|
||||||
public String test() {
|
public String test() {
|
||||||
return "test";
|
return "test";
|
||||||
}
|
}
|
||||||
|
@ -1,46 +1,32 @@
|
|||||||
package cn.whaifree.springdemo.controller.interceptRetry.aspect;
|
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.BuinessException;
|
||||||
import cn.whaifree.springdemo.controller.interceptRetry.ErrorType;
|
import cn.whaifree.springdemo.controller.interceptRetry.ErrorType;
|
||||||
import com.github.benmanes.caffeine.cache.Cache;
|
import com.github.benmanes.caffeine.cache.Cache;
|
||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
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.PostConstruct;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.Getter;
|
|
||||||
import org.apache.catalina.util.RequestUtil;
|
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
import org.aspectj.lang.Signature;
|
|
||||||
import org.aspectj.lang.annotation.Around;
|
import org.aspectj.lang.annotation.Around;
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
import org.aspectj.lang.reflect.MethodSignature;
|
import org.aspectj.lang.reflect.MethodSignature;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
|
||||||
import org.springframework.core.annotation.AnnotationUtils;
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.bind.ServletRequestUtils;
|
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
import org.springframework.web.util.WebUtils;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
极海
|
||||||
* @version 1.0
|
* @version 1.0
|
||||||
* @Author whai文海
|
* @Author whai文海
|
||||||
* @Date 2024/11/9 21:57
|
* @Date 2024/11/9 21:57
|
||||||
@ -66,6 +52,10 @@ public class RetryAspect {
|
|||||||
@Lazy
|
@Lazy
|
||||||
private ApplicationContext applicationContext;
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化方法,用于扫描所有Bean的方法,寻找带有RetryLimit注解的方法
|
||||||
|
* 并根据注解的限制时间创建相应的缓存对象
|
||||||
|
*/
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
|
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
|
||||||
@ -86,7 +76,11 @@ public class RetryAspect {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int expireTime = retryLimit.limitTime();
|
int expireTime = retryLimit.limitTime();
|
||||||
Cache<String, AtomicInteger> build = Caffeine.newBuilder().expireAfterAccess(Duration.ofMinutes(expireTime)).maximumSize(1000).build();
|
Cache<String, AtomicInteger> build =
|
||||||
|
Caffeine.newBuilder()
|
||||||
|
.expireAfterAccess(Duration.ofMinutes(expireTime))
|
||||||
|
.maximumSize(1000)
|
||||||
|
.build();
|
||||||
cacheMap.put(expireTime, build);
|
cacheMap.put(expireTime, build);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@ -99,6 +93,14 @@ public class RetryAspect {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 环绕通知,拦截带有RetryLimit注解的方法
|
||||||
|
* 检查方法的重试次数是否超过限制,如果超过则抛出异常,否则继续执行方法
|
||||||
|
*
|
||||||
|
* @param joinPoint 切入点对象,包含被拦截方法的信息
|
||||||
|
* @return 被拦截方法的返回值
|
||||||
|
* @throws Throwable 被拦截方法抛出的异常
|
||||||
|
*/
|
||||||
@Around("@annotation(cn.whaifree.springdemo.controller.interceptRetry.aspect.RetryLimit)")
|
@Around("@annotation(cn.whaifree.springdemo.controller.interceptRetry.aspect.RetryLimit)")
|
||||||
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||||
// 获取方法签名,设置方法可以访问
|
// 获取方法签名,设置方法可以访问
|
||||||
|
@ -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> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 该方法用于将 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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<Orders> {
|
|
||||||
|
|
||||||
@Select("SELECT * FROM orders")
|
|
||||||
List<Orders> selectByExample();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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<Orders> {
|
|
||||||
|
|
||||||
}
|
|
@ -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<OrdersMapper, Orders>
|
|
||||||
implements OrdersService{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -6,16 +6,16 @@ my:
|
|||||||
coreSize: 12
|
coreSize: 12
|
||||||
maxSize: 24
|
maxSize: 24
|
||||||
|
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
datasource:
|
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
|
username: root
|
||||||
password: 123456
|
password: 123456
|
||||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
|
host: 192.168.50.248
|
||||||
host: localhost
|
|
||||||
port: 6379
|
port: 6379
|
||||||
# 选择db1
|
# 选择db1
|
||||||
database: 3
|
database: 3
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE mapper
|
|
||||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|
||||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
|
||||||
<mapper namespace="cn.whaifree.springdemo.mybatis.mapper.OrdersMapper">
|
|
||||||
|
|
||||||
<resultMap id="BaseResultMap" type="cn.whaifree.springdemo.mybatis.domain.Orders">
|
|
||||||
<id property="id" column="id" jdbcType="INTEGER"/>
|
|
||||||
<result property="orderNo" column="order_no" jdbcType="VARCHAR"/>
|
|
||||||
<result property="userId" column="user_id" jdbcType="INTEGER"/>
|
|
||||||
<result property="totalAmount" column="total_amount" jdbcType="DECIMAL"/>
|
|
||||||
<result property="status" column="status" jdbcType="TINYINT"/>
|
|
||||||
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
|
|
||||||
<result property="version" column="version" jdbcType="INTEGER"/>
|
|
||||||
</resultMap>
|
|
||||||
|
|
||||||
<sql id="Base_Column_List">
|
|
||||||
id,order_no,user_id,
|
|
||||||
total_amount,status,create_time,
|
|
||||||
version
|
|
||||||
</sql>
|
|
||||||
</mapper>
|
|
@ -18,7 +18,7 @@ singleServerConfig:
|
|||||||
# 客户端名称
|
# 客户端名称
|
||||||
#clientName: axin
|
#clientName: axin
|
||||||
# # 节点地址
|
# # 节点地址
|
||||||
address: redis://127.0.0.1:6379
|
address: redis://192.168.50.248:6379
|
||||||
# 发布和订阅连接的最小空闲连接数
|
# 发布和订阅连接的最小空闲连接数
|
||||||
subscriptionConnectionMinimumIdleSize: 1
|
subscriptionConnectionMinimumIdleSize: 1
|
||||||
# 发布和订阅连接池大小
|
# 发布和订阅连接池大小
|
||||||
|
@ -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> orders = ordersMapper.selectList(null);
|
|
||||||
System.out.println(orders);
|
|
||||||
List<Orders> orders1 = ordersMapper.selectByExample();
|
|
||||||
System.out.println(orders1);
|
|
||||||
}
|
|
||||||
}
|
|
101
mybatisplus/pom.xml
Normal file
101
mybatisplus/pom.xml
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>2.3.5.RELEASE</version>
|
||||||
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
|
</parent>
|
||||||
|
<groupId>com.whai.springcloud.mybatis</groupId>
|
||||||
|
<artifactId>mybatisplus</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<name>mybatisplus</name>
|
||||||
|
<description>mybatisplus</description>
|
||||||
|
<url/>
|
||||||
|
<licenses>
|
||||||
|
<license/>
|
||||||
|
</licenses>
|
||||||
|
<developers>
|
||||||
|
<developer/>
|
||||||
|
</developers>
|
||||||
|
<scm>
|
||||||
|
<connection/>
|
||||||
|
<developerConnection/>
|
||||||
|
<tag/>
|
||||||
|
<url/>
|
||||||
|
</scm>
|
||||||
|
<properties>
|
||||||
|
<java.version>8</java.version>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.whai.middleware</groupId>
|
||||||
|
<artifactId>db-router-spring-boot-starter</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--web-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
|
<!--jdbc-->
|
||||||
|
<groupId>mysql</groupId>
|
||||||
|
<version>8.0.33</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||||
|
<version>3.5.1</version>
|
||||||
|
</dependency>
|
||||||
|
<!--aspect-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-aop</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/cn.bugstack.middleware/db-router-spring-boot-starter -->
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>cn.bugstack.middleware</groupId>-->
|
||||||
|
<!-- <artifactId>db-router-spring-boot-starter</artifactId>-->
|
||||||
|
<!-- <version>1.0.2</version>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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> {
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * 该方法用于将 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;
|
||||||
|
// }
|
||||||
|
//}
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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 {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package cn.whaifree.springdemo.mybatis.domain;
|
package com.whai.springcloud.mybatis.mybatisplus.mybatis.domain;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @version 1.0
|
* @version 1.0
|
@ -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;
|
||||||
|
}
|
@ -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<User> {
|
||||||
|
|
||||||
|
@Select("select * from user")
|
||||||
|
@DBRouter(key = "id")
|
||||||
|
List<User> getlist(User user);
|
||||||
|
|
||||||
|
@Select("select * from user")
|
||||||
|
@DBRouter(key = "id")
|
||||||
|
List<User> getlist1(long l);
|
||||||
|
}
|
73
mybatisplus/src/main/resources/application.yaml
Normal file
73
mybatisplus/src/main/resources/application.yaml
Normal file
@ -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
|
9
mybatisplus/src/main/resources/mapper/OrdersMapper.xml
Normal file
9
mybatisplus/src/main/resources/mapper/OrdersMapper.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper
|
||||||
|
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
|
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.whai.springcloud.mybatis.mybatisplus.mybatis.mapper.UserMapper">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</mapper>
|
@ -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<User> userList = userMapper.selectList(null);
|
||||||
|
userList.forEach(System.out::println);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Resource
|
||||||
|
// private OrdersMapper ordersMapper;
|
||||||
|
// @Test
|
||||||
|
// public void test() {
|
||||||
|
//
|
||||||
|
// List<Orders> orders = ordersMapper.selectList(null);
|
||||||
|
// System.out.println(orders);
|
||||||
|
//// List<Orders> orders1 = ordersMapper.selectByExample();
|
||||||
|
//// System.out.println(orders1);
|
||||||
|
// }
|
||||||
|
}
|
@ -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<User> list = userMapper.selectList(null);
|
||||||
|
// list.forEach(System.out::println);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user