refactor(mybatis): 重构 Mybatis 相关代码

-移动 AmountBig 类到新的包结构
- 新增 AmountTypeHandler 类用于处理 AmountBig 类型- 更新 application.yaml 配置- 重构 DBRouter 相关代码,优化切面逻辑
- 新增 LeetCode 相关解题代码
- 更新 Mybatis配置和测试代码
This commit is contained in:
whai 2024-11-27 21:46:41 +08:00
parent 0341ebc835
commit 501357cbd0
93 changed files with 5270 additions and 332 deletions

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

View File

@ -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已经是小的了小的下沉到合适的位置
// 大的不断换上去直到不换就退出
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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
View 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>
<!-- &lt;!&ndash; https://mvnrepository.com/artifact/cn.bugstack.middleware/db-router-spring-boot-starter &ndash;&gt;-->
<!-- <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>
<!-- &lt;!&ndash; openAPI包替换 Swagger 的 SpringFox &ndash;&gt;-->
<!-- <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>

View File

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

View File

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

View File

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

View File

@ -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"); // 设定缓存的名称
// }
// }
//
//}

View File

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

View File

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

View File

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

View File

@ -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 {
}

View File

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

View File

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

View File

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

View File

@ -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{
//
//}

View File

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

View File

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

View File

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

View File

@ -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");
// }
// }
//}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,7 @@
package cn.whaifree.springdemo.controller.TS.common;
public interface ProcessStrategy {
void process(byte[] frame);
void process(Object o);
}

View File

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

View File

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

View File

@ -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("图像");
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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("通过");
}
}

View File

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

View File

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

View File

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

View File

@ -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>
* exprieTimekeyOfIptime
* <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或者其他限流keyvalue为次数
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);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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需要一个新的BB需要一个新的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);
// }
//}

View File

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

View File

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

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

View File

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

View File

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.whaifree.springdemo.config.MyAutoConfiguration;

View 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

View 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

View 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"
#

Binary file not shown.

View File

@ -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() {
}
}

View File

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

View File

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

View File

@ -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 是多余的
}

View File

@ -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>
<!-- &lt;!&ndash; https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter &ndash;&gt;-->
<!-- <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>

View File

@ -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");

View File

@ -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 {

View File

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

View File

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

View File

@ -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 {
// 获取方法签名设置方法可以访问

View File

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

View File

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

View File

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

View File

@ -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> {
}

View File

@ -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{
}

View File

@ -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

View File

@ -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>

View File

@ -18,7 +18,7 @@ singleServerConfig:
# 客户端名称
#clientName: axin
# # 节点地址
address: redis://127.0.0.1:6379
address: redis://192.168.50.248:6379
# 发布和订阅连接的最小空闲连接数
subscriptionConnectionMinimumIdleSize: 1
# 发布和订阅连接池大小

View File

@ -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
View 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>

View File

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

View File

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

View File

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

View File

@ -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 {
}

View File

@ -1,4 +1,4 @@
package cn.whaifree.springdemo.mybatis.domain;
package com.whai.springcloud.mybatis.mybatisplus.mybatis.domain;
/**
* @version 1.0

View File

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

View File

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

View 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

View 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>

View File

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

View File

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