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 {
|
||||
|
||||
/**
|
||||
* 构建大根堆
|
||||
*
|
||||
* 构建每个小子树都是一个大根堆,再以子堆的父节点往上换
|
||||
*
|
||||
*
|
||||
*
|
||||
* 构建大根堆
|
||||
* 把根移动到最后
|
||||
* 移动k次,顶部就是了
|
||||
@ -138,6 +144,7 @@ public class LeetCode215 {
|
||||
if (index != large) {
|
||||
swap(index, 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>
|
||||
<artifactId>SpringDemo</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>SpringDemo</name>
|
||||
<description>SpringDemo</description>
|
||||
<url/>
|
||||
@ -30,6 +31,16 @@
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
<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-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@ -70,30 +81,33 @@
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
</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>
|
||||
<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>-->
|
||||
<!--redisson-->
|
||||
<!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
|
||||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson</artifactId>
|
||||
<version>3.16.8</version>
|
||||
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||
<version>3.23.5</version>
|
||||
</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-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@ -125,6 +139,7 @@
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>2.0.36</version>
|
||||
</dependency>
|
||||
|
||||
<!--hutool-->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
@ -194,6 +209,14 @@
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
@ -31,13 +31,11 @@ import java.util.Objects;
|
||||
public class RateLimitAspect {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RateLimitAspect.class);
|
||||
|
||||
private RedisTemplate<Object, Object> redisTemplate;
|
||||
|
||||
private RedisScript<Long> limitScript;
|
||||
|
||||
@Autowired
|
||||
public void setRedisTemplate1(RedisTemplate<Object, Object> redisTemplate) {
|
||||
public void setRedisTemplate(RedisTemplate<Object, Object> redisTemplate) {
|
||||
this.redisTemplate = redisTemplate;
|
||||
}
|
||||
|
||||
@ -48,11 +46,8 @@ public class RateLimitAspect {
|
||||
|
||||
/**
|
||||
* key 对应方法,value 已经被访问的次数
|
||||
*
|
||||
* lua的逻辑:
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param point
|
||||
* @param rateLimiter
|
||||
* @throws Throwable
|
||||
@ -62,7 +57,6 @@ public class RateLimitAspect {
|
||||
int time = rateLimiter.time(); // 多长时间
|
||||
int count = rateLimiter.count(); // 允许次数
|
||||
|
||||
|
||||
String combineKey = getCombineKey(rateLimiter, point); // 组合key, Class-Method
|
||||
List<Object> keys = Collections.singletonList(combineKey);
|
||||
try {
|
||||
@ -82,6 +76,7 @@ public class RateLimitAspect {
|
||||
// if (rateLimiter.limitType() == LimitType.IP) {
|
||||
// stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append("-");
|
||||
// }
|
||||
|
||||
if (rateLimiter.limitType() == LimitType.USER) {
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
String username = attributes.getRequest().getParameter("userId");
|
||||
|
@ -10,6 +10,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
* @注释
|
||||
*/
|
||||
@Configuration
|
||||
//@MapperScan("cn.whaifree.springdemo.mybatis.mapper")
|
||||
@MapperScan("cn.whaifree.springdemo.mybatis.mapper")
|
||||
public class MybatisConfig {
|
||||
|
||||
|
@ -15,12 +15,12 @@ import org.springframework.context.annotation.Configuration;
|
||||
@Configuration
|
||||
public class RedissonConfig {
|
||||
|
||||
@Bean
|
||||
public RedissonClient redissonClient() {
|
||||
Config config = new Config();
|
||||
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
|
||||
config.setThreads(10);
|
||||
RedissonClient redissonClient = Redisson.create(config);
|
||||
return redissonClient;
|
||||
}
|
||||
// @Bean
|
||||
// public RedissonClient redissonClient() {
|
||||
// Config config = new Config();
|
||||
// config.useSingleServer().setAddress("redis://127.0.0.1:6379");
|
||||
// config.setThreads(10);
|
||||
// RedissonClient redissonClient = Redisson.create(config);
|
||||
// return redissonClient;
|
||||
// }
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import java.util.concurrent.*;
|
||||
@RestController
|
||||
public class TestController {
|
||||
@PostMapping("/test")
|
||||
@RateLimiter(key = "test:", time = 10, count = 1) // 10s只能1次
|
||||
@RateLimiter(key = "test:", time = 10, count = 10) // 10s只能1次
|
||||
public String test() {
|
||||
return "test";
|
||||
}
|
||||
|
@ -1,46 +1,32 @@
|
||||
package cn.whaifree.springdemo.controller.interceptRetry.aspect;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.whaifree.springdemo.controller.interceptRetry.BuinessException;
|
||||
import cn.whaifree.springdemo.controller.interceptRetry.ErrorType;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.mysql.cj.util.LogUtils;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.Getter;
|
||||
import org.apache.catalina.util.RequestUtil;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.Signature;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.bind.ServletRequestUtils;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
极海
|
||||
* @version 1.0
|
||||
* @Author whai文海
|
||||
* @Date 2024/11/9 21:57
|
||||
@ -66,6 +52,10 @@ public class RetryAspect {
|
||||
@Lazy
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
/**
|
||||
* 初始化方法,用于扫描所有Bean的方法,寻找带有RetryLimit注解的方法
|
||||
* 并根据注解的限制时间创建相应的缓存对象
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
|
||||
@ -86,7 +76,11 @@ public class RetryAspect {
|
||||
continue;
|
||||
}
|
||||
int expireTime = retryLimit.limitTime();
|
||||
Cache<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);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@ -99,6 +93,14 @@ public class RetryAspect {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 环绕通知,拦截带有RetryLimit注解的方法
|
||||
* 检查方法的重试次数是否超过限制,如果超过则抛出异常,否则继续执行方法
|
||||
*
|
||||
* @param joinPoint 切入点对象,包含被拦截方法的信息
|
||||
* @return 被拦截方法的返回值
|
||||
* @throws Throwable 被拦截方法抛出的异常
|
||||
*/
|
||||
@Around("@annotation(cn.whaifree.springdemo.controller.interceptRetry.aspect.RetryLimit)")
|
||||
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
// 获取方法签名,设置方法可以访问
|
||||
|
@ -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
|
||||
maxSize: 24
|
||||
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://localhost:3306/springTest?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai
|
||||
url: jdbc:mysql://192.168.50.248:3306/springTest?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai
|
||||
username: root
|
||||
password: 123456
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
data:
|
||||
redis:
|
||||
|
||||
host: localhost
|
||||
host: 192.168.50.248
|
||||
port: 6379
|
||||
# 选择db1
|
||||
database: 3
|
||||
|
@ -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
|
||||
# # 节点地址
|
||||
address: redis://127.0.0.1:6379
|
||||
address: redis://192.168.50.248:6379
|
||||
# 发布和订阅连接的最小空闲连接数
|
||||
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
|
@ -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