mirror of
https://github.com/yzcheng90/X-SpringBoot
synced 2025-11-04 05:35:45 +08:00
新增分表功能
This commit is contained in:
277
pom.xml
277
pom.xml
@@ -1,162 +1,161 @@
|
||||
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>x-springboot</groupId>
|
||||
<artifactId>x-springboot</artifactId>
|
||||
<version>6.0</version>
|
||||
<packaging>jar</packaging>
|
||||
<description>x-springboot</description>
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>x-springboot</groupId>
|
||||
<artifactId>x-springboot</artifactId>
|
||||
<version>6.0</version>
|
||||
<packaging>jar</packaging>
|
||||
<description>x-springboot</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.3.5</version>
|
||||
</parent>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.3.5</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>17</java.version>
|
||||
<mybatis-plus.version>3.5.9</mybatis-plus.version>
|
||||
<mysql.version>8.0.33</mysql.version>
|
||||
<hutool.version>5.8.21</hutool.version>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>17</java.version>
|
||||
<mybatis-plus.version>3.5.9</mybatis-plus.version>
|
||||
<mysql.version>8.0.33</mysql.version>
|
||||
<hutool.version>5.8.21</hutool.version>
|
||||
|
||||
<velocity.version>1.7</velocity.version>
|
||||
<kaptcha.version>0.0.9</kaptcha.version>
|
||||
<qiniu.version>7.12.1</qiniu.version>
|
||||
<velocity.version>1.7</velocity.version>
|
||||
<kaptcha.version>0.0.9</kaptcha.version>
|
||||
<qiniu.version>7.12.1</qiniu.version>
|
||||
|
||||
</properties>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</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-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</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-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--apk 解析-->
|
||||
<dependency>
|
||||
<groupId>net.dongliu</groupId>
|
||||
<artifactId>apk-parser</artifactId>
|
||||
<version>2.6.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.dongliu</groupId>
|
||||
<artifactId>apk-parser</artifactId>
|
||||
<version>2.6.5</version>
|
||||
</dependency>
|
||||
|
||||
<!--mybatis-->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-jsqlparser</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<!--mysql-->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>${mysql.version}</version>
|
||||
</dependency>
|
||||
<!--mybatis-->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-jsqlparser</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<!--mysql-->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>${mysql.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--代码生成模板引擎-->
|
||||
<dependency>
|
||||
<artifactId>velocity</artifactId>
|
||||
<groupId>org.apache.velocity</groupId>
|
||||
<version>${velocity.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>velocity</artifactId>
|
||||
<groupId>org.apache.velocity</groupId>
|
||||
<version>${velocity.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--hutool-->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
<!--hutool-->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.axet</groupId>
|
||||
<artifactId>kaptcha</artifactId>
|
||||
<version>${kaptcha.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.axet</groupId>
|
||||
<artifactId>kaptcha</artifactId>
|
||||
<version>${kaptcha.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--oss 相关-->
|
||||
<dependency>
|
||||
<groupId>com.qiniu</groupId>
|
||||
<artifactId>qiniu-java-sdk</artifactId>
|
||||
<version>${qiniu.version}</version>
|
||||
</dependency>
|
||||
<!--oss 相关-->
|
||||
<dependency>
|
||||
<groupId>com.qiniu</groupId>
|
||||
<artifactId>qiniu-java-sdk</artifactId>
|
||||
<version>${qiniu.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--Lombok-->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!--Lombok-->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!--swagger 依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>2.2.0</version>
|
||||
</dependency>
|
||||
<!--swagger 依赖-->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>2.2.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<build>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>public</id>
|
||||
<name>aliyun nexus</name>
|
||||
<url>https://maven.aliyun.com/nexus/content/groups/public/</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>public</id>
|
||||
<name>aliyun nexus</name>
|
||||
<url>https://maven.aliyun.com/nexus/content/groups/public/</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>public</id>
|
||||
<name>aliyun nexus</name>
|
||||
<url>https://maven.aliyun.com/nexus/content/groups/public/</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>public</id>
|
||||
<name>aliyun nexus</name>
|
||||
<url>https://maven.aliyun.com/nexus/content/groups/public/</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
</project>
|
||||
@@ -3,8 +3,10 @@ package com.suke.czx;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
@Slf4j
|
||||
@EnableScheduling
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
@@ -12,4 +14,4 @@ public class Application {
|
||||
SpringApplication.run(Application.class, args);
|
||||
log.info("==================X-SpringBoot启动成功================");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.suke.czx.common.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 分表注解
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
|
||||
public @interface ShardingTable {
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
package com.suke.czx.common.exception;
|
||||
|
||||
import com.suke.czx.common.utils.HttpContextUtils;
|
||||
import com.suke.czx.common.utils.R;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.jdbc.BadSqlGrammarException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
@@ -51,6 +53,13 @@ public class RRExceptionHandler extends R {
|
||||
return R.error("无该资源");
|
||||
}
|
||||
|
||||
@ExceptionHandler(BadSqlGrammarException.class)
|
||||
public R handleBadSqlGrammarException(BadSqlGrammarException e) {
|
||||
String contextPath = HttpContextUtils.getHttpServletRequest().getRequestURI();
|
||||
log.error("contextPath:{},SQL语法错误:{}", contextPath, e.getMessage());
|
||||
return R.error("SQL语法错误");
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public R handleException(Exception e) {
|
||||
log.error("发生异常", e);
|
||||
|
||||
20
src/main/java/com/suke/czx/common/lock/NotDoubleSubmit.java
Normal file
20
src/main/java/com/suke/czx/common/lock/NotDoubleSubmit.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package com.suke.czx.common.lock;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 基于token
|
||||
* 防用户重复提交注解
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface NotDoubleSubmit {
|
||||
|
||||
/**
|
||||
* 延时时间 在延时多久后可以再次提交,默认10秒
|
||||
* @return 秒
|
||||
*/
|
||||
int delaySeconds() default 10;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.suke.czx.common.lock;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import com.suke.czx.common.utils.Constant;
|
||||
import com.suke.czx.common.utils.HttpContextUtils;
|
||||
import com.suke.czx.common.utils.R;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.stereotype.Component;
|
||||
import org.springframework.util.DigestUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 防重复提交注解的实现,使用AOP。
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
public class NotDoubleSubmitAOP {
|
||||
|
||||
@Resource
|
||||
private RedissonLock redissonLock;
|
||||
|
||||
@Around("execution(public * *(..)) && @annotation(NotDoubleSubmit)")
|
||||
public Object interceptor(ProceedingJoinPoint pjp) throws Throwable {
|
||||
// 获取到这个注解
|
||||
MethodSignature signature = (MethodSignature) pjp.getSignature();
|
||||
Method method = signature.getMethod();
|
||||
NotDoubleSubmit notDoubleSubmit = method.getAnnotation(NotDoubleSubmit.class);
|
||||
|
||||
/*
|
||||
* 锁 key
|
||||
*/
|
||||
String token = HttpContextUtils.getHttpServletRequest().getHeader("token");
|
||||
final String lockKey = Constant.SYSTEM_NAME + generateKey(pjp, CharSequenceUtil.isEmpty(token) ? UUID.randomUUID().toString() : token);
|
||||
|
||||
// 上锁
|
||||
final boolean success = redissonLock.lock(lockKey, notDoubleSubmit.delaySeconds(), TimeUnit.SECONDS);
|
||||
if (!success) {
|
||||
// 这里也可以改为自己项目自定义的异常抛出
|
||||
return R.error("操作太频繁");
|
||||
}
|
||||
return pjp.proceed();
|
||||
}
|
||||
|
||||
private String generateKey(ProceedingJoinPoint pjp, String token) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Signature signature = pjp.getSignature();
|
||||
MethodSignature methodSignature = (MethodSignature) signature;
|
||||
Method method = methodSignature.getMethod();
|
||||
sb.append(token);
|
||||
sb.append(pjp.getTarget().getClass().getName()).append(method.getName());//方法名
|
||||
for (Object o : pjp.getArgs()) {
|
||||
sb.append(o.toString());
|
||||
}
|
||||
return DigestUtils.md5DigestAsHex(sb.toString().getBytes(Charset.defaultCharset()));
|
||||
}
|
||||
|
||||
}
|
||||
93
src/main/java/com/suke/czx/common/lock/RedisLock.java
Normal file
93
src/main/java/com/suke/czx/common/lock/RedisLock.java
Normal file
@@ -0,0 +1,93 @@
|
||||
package com.suke.czx.common.lock;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.connection.RedisStringCommands;
|
||||
import org.springframework.data.redis.connection.ReturnType;
|
||||
import org.springframework.data.redis.core.RedisCallback;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RedisLock {
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<Object, Object> redisTemplate;
|
||||
/*** 释放锁脚本,原子操作,lua脚本*/
|
||||
private static final String UNLOCK_LUA;
|
||||
/*** 默认过期时间(30ms)*/
|
||||
private static final long DEFAULT_EXPIRE = 10L;
|
||||
|
||||
static {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
|
||||
sb.append("then ");
|
||||
sb.append(" return redis.call(\"del\",KEYS[1]) ");
|
||||
sb.append("else ");
|
||||
sb.append(" return 0 ");
|
||||
sb.append("end ");
|
||||
UNLOCK_LUA = sb.toString();
|
||||
}
|
||||
|
||||
/***
|
||||
* 获取分布式锁,原子操作
|
||||
* @param lockKey 锁
|
||||
* @param lockValue 唯一ID, 可以使用UUID.randomUUID().toString();
|
||||
* @return 是否枷锁成功
|
||||
*/
|
||||
public boolean lock(String lockKey, String lockValue) {
|
||||
return this.lock(lockKey, lockValue, DEFAULT_EXPIRE, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分布式锁,原子操作
|
||||
*
|
||||
* @param lockKey 锁
|
||||
* @param lockValue 唯一ID, 可以使用UUID.randomUUID().toString();
|
||||
* @param expire 过期时间
|
||||
* @param timeUnit 时间单位
|
||||
* @return 是否枷锁成功
|
||||
*/
|
||||
public boolean lock(String lockKey, String lockValue, long expire, TimeUnit timeUnit) {
|
||||
try {
|
||||
RedisCallback<Boolean> callback = (connection) -> connection.set(lockKey.getBytes(StandardCharsets.UTF_8), lockValue.getBytes(StandardCharsets.UTF_8), Expiration.seconds(timeUnit.toSeconds(expire)), RedisStringCommands.SetOption.SET_IF_ABSENT);
|
||||
return Boolean.TRUE.equals(redisTemplate.execute(callback));
|
||||
} catch (Exception e) {
|
||||
log.error("redis lock error ,lock key: {}, value : {}, error info : {}", lockKey, lockValue, e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放锁
|
||||
*
|
||||
* @param lockKey 锁
|
||||
* @param lockValue 唯一ID
|
||||
* @return 执行结果
|
||||
*/
|
||||
public boolean unlock(String lockKey, String lockValue) {
|
||||
RedisCallback<Boolean> callback = (connection) -> connection.eval(UNLOCK_LUA.getBytes(), ReturnType.BOOLEAN, 1, lockKey.getBytes(StandardCharsets.UTF_8), lockValue.getBytes(StandardCharsets.UTF_8));
|
||||
return Boolean.TRUE.equals(redisTemplate.execute(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Redis锁的value值
|
||||
*
|
||||
* @param lockKey 锁
|
||||
*/
|
||||
public String get(String lockKey) {
|
||||
try {
|
||||
RedisCallback<String> callback = (connection) -> new String(Objects.requireNonNull(connection.get(lockKey.getBytes())), StandardCharsets.UTF_8);
|
||||
return redisTemplate.execute(callback);
|
||||
} catch (Exception e) {
|
||||
log.error("get redis value occurred an exception,the key is {}, error is {}", lockKey, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
47
src/main/java/com/suke/czx/common/lock/RedissonLock.java
Normal file
47
src/main/java/com/suke/czx/common/lock/RedissonLock.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package com.suke.czx.common.lock;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 使用Redisson加锁
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RedissonLock {
|
||||
|
||||
@Resource
|
||||
private RedisLock redisLock;
|
||||
|
||||
/**
|
||||
* Redission获取锁
|
||||
*
|
||||
* @param lockKey
|
||||
* @param delaySeconds
|
||||
* @param unit
|
||||
* @return
|
||||
*/
|
||||
public boolean lock(String lockKey, long delaySeconds, final TimeUnit unit) {
|
||||
boolean success = false;
|
||||
try {
|
||||
success = redisLock.lock(lockKey, lockKey, delaySeconds, unit);
|
||||
} catch (Exception e) {
|
||||
log.error("[RedissonLock][lock]>>>> 加锁异常: {}", e.getMessage());
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redission释放锁
|
||||
*
|
||||
* @param lockKey 锁名
|
||||
*/
|
||||
public void unlock(String lockKey) {
|
||||
boolean status = redisLock.unlock(lockKey, lockKey);
|
||||
log.debug("[RedissonLock][unlock]>>>> status: {} ", status);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,32 +9,35 @@ package com.suke.czx.common.utils;
|
||||
*/
|
||||
public class Constant {
|
||||
|
||||
public static final String NUMBER_CODE_KEY = "x-springboot:number:code:";
|
||||
public static final String MOBILE_CODE_KEY = "x-springboot:mobile:code:";
|
||||
public static final String AUTHENTICATION_TOKEN = "x-springboot:token:";
|
||||
public static final String SYSTEM_NAME = "x-springboot:";
|
||||
public static final String NUMBER_CODE_KEY = SYSTEM_NAME + "number:code:";
|
||||
public static final String MOBILE_CODE_KEY = SYSTEM_NAME + "mobile:code:";
|
||||
public static final String AUTHENTICATION_TOKEN = SYSTEM_NAME + "token:";
|
||||
|
||||
public static final String TOKEN = "token";
|
||||
public static final String TOKEN_ENTRY_POINT_URL = "/token/login";
|
||||
public static final String TOKEN_LOGOUT_URL = "/token/logout";
|
||||
public static final int TOKEN_EXPIRE = 60 * 60 * 24 * 7;
|
||||
|
||||
/** 超级管理员ID */
|
||||
public static final String SUPER_ADMIN = "0";
|
||||
/**
|
||||
* 超级管理员ID
|
||||
*/
|
||||
public static final String SUPER_ADMIN = "0";
|
||||
|
||||
public static final int CODE_SIZE = 4;
|
||||
public static final int CODE_SIZE = 4;
|
||||
|
||||
/**
|
||||
* 菜单类型
|
||||
*
|
||||
* @author czx
|
||||
* @email object_czx@163.com
|
||||
* @date 2016年11月15日 下午1:24:29
|
||||
*/
|
||||
/**
|
||||
* 菜单类型
|
||||
*
|
||||
* @author czx
|
||||
* @email object_czx@163.com
|
||||
* @date 2016年11月15日 下午1:24:29
|
||||
*/
|
||||
public enum MenuType {
|
||||
/**
|
||||
* 目录
|
||||
*/
|
||||
CATALOG(0),
|
||||
CATALOG(0),
|
||||
/**
|
||||
* 菜单
|
||||
*/
|
||||
@@ -87,4 +90,4 @@ public class Constant {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,9 @@ import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Spring Context 工具类
|
||||
*
|
||||
@@ -29,6 +32,11 @@ public class SpringContextUtils implements ApplicationContextAware {
|
||||
applicationContext.publishEvent(event);
|
||||
}
|
||||
|
||||
public static Map<String, Object> getAnnotation(Class<? extends Annotation> annotationType) {
|
||||
return applicationContext.getBeansWithAnnotation(annotationType);
|
||||
}
|
||||
|
||||
|
||||
public static Object getBean(String name) {
|
||||
return applicationContext.getBean(name);
|
||||
}
|
||||
@@ -53,4 +61,4 @@ public class SpringContextUtils implements ApplicationContextAware {
|
||||
return applicationContext.getType(name);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2,25 +2,37 @@ package com.suke.czx.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
@MapperScan(value = "com.suke.czx.modules.*.mapper")
|
||||
public class MyBatisPlusConfig {
|
||||
|
||||
@Resource
|
||||
public ShardingTableConfig shardingTableHandler;
|
||||
|
||||
/**
|
||||
* 添加分页插件
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
// 拦截器
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
// 动态表名插件
|
||||
DynamicTableNameInnerInterceptor dynamicTableNameInterceptor = new DynamicTableNameInnerInterceptor();
|
||||
dynamicTableNameInterceptor.setTableNameHandler(((sql, tableName) -> shardingTableHandler.shardingTable(tableName)));
|
||||
interceptor.addInnerInterceptor(dynamicTableNameInterceptor);
|
||||
// 分页插件
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
|
||||
// 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
103
src/main/java/com/suke/czx/config/ShardingTableConfig.java
Normal file
103
src/main/java/com/suke/czx/config/ShardingTableConfig.java
Normal file
@@ -0,0 +1,103 @@
|
||||
package com.suke.czx.config;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.db.Db;
|
||||
import cn.hutool.db.DbUtil;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.SqlRunner;
|
||||
import com.suke.czx.common.annotation.ShardingTable;
|
||||
import com.suke.czx.common.lock.RedissonLock;
|
||||
import com.suke.czx.common.utils.Constant;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 按月分表
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class ShardingTableConfig {
|
||||
|
||||
private static final ThreadLocal<String> TABLE_NAME = new ThreadLocal<>();
|
||||
|
||||
@Resource
|
||||
private RedissonLock redissonLock;
|
||||
|
||||
public String shardingTable(String tableName) {
|
||||
// 判断是否包含分表
|
||||
TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName);
|
||||
if (this.isShardingTable(tableInfo)) {
|
||||
String currentTableName = TABLE_NAME.get();
|
||||
if (StrUtil.isEmpty(currentTableName)) {
|
||||
currentTableName = this.getDefaultMonthTableName(tableName);
|
||||
log.info("使用分表:{}", currentTableName);
|
||||
}
|
||||
TABLE_NAME.remove();
|
||||
return currentTableName;
|
||||
}
|
||||
return tableName;
|
||||
}
|
||||
|
||||
private String getDefaultMonthTableName(String tableName) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
int year = calendar.get(Calendar.YEAR);
|
||||
int month = calendar.get(Calendar.MONTH) + 1; // 月份是从0开始的
|
||||
return tableName + "_" + year + "_" + String.format("%02d", month);
|
||||
}
|
||||
|
||||
private String getNextMonthTableName(String tableName) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
int year = calendar.get(Calendar.YEAR);
|
||||
int month = calendar.get(Calendar.MONTH) + 2; // 月份是从0开始的,查下个月的
|
||||
return tableName + "_" + year + "_" + String.format("%02d", month);
|
||||
}
|
||||
|
||||
public static void setTableName(String tableName) {
|
||||
TABLE_NAME.set(tableName);
|
||||
}
|
||||
|
||||
private boolean isShardingTable(TableInfo tableInfo) {
|
||||
if (tableInfo == null) {
|
||||
return false;
|
||||
}
|
||||
Class<?> entityType = tableInfo.getEntityType();
|
||||
ShardingTable shardingTable = entityType.getAnnotation(ShardingTable.class);
|
||||
return shardingTable != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 每小时执行一次
|
||||
*/
|
||||
@Scheduled(cron = "0 0 0/1 * * ?")
|
||||
private void createShardingTable() {
|
||||
// 上锁30秒
|
||||
final boolean success = redissonLock.lock(Constant.SYSTEM_NAME + "createShardingTable", 30, TimeUnit.SECONDS);
|
||||
if (success) {
|
||||
log.info("开始检查下个月的分表情况...");
|
||||
// 创建分表
|
||||
TableInfoHelper.getTableInfos().forEach(tableInfo -> {
|
||||
if (isShardingTable(tableInfo)) {
|
||||
String tableName = tableInfo.getTableName();
|
||||
// 查询下个月的表是否创建好
|
||||
String nextMonthTableName = getNextMonthTableName(tableName);
|
||||
log.info("生成下个月的分表:{}", nextMonthTableName);
|
||||
Object isExist = SqlRunner.db().selectObj("SHOW TABLES LIKE '" + nextMonthTableName + "';");
|
||||
log.info("查询分表是否存在:{}", isExist == null);
|
||||
if (isExist == null) {
|
||||
// 创建新表
|
||||
SqlRunner.db().update("CREATE TABLE " + nextMonthTableName + " LIKE " + tableName + ";");
|
||||
log.info("开始创建分表..");
|
||||
}
|
||||
}
|
||||
});
|
||||
log.info("检查完成");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.suke.czx.modules.browse.controller;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.suke.czx.common.annotation.AuthIgnore;
|
||||
import com.suke.czx.common.annotation.ResourceAuth;
|
||||
import com.suke.czx.common.annotation.SysLog;
|
||||
import com.suke.czx.common.base.AbstractController;
|
||||
import com.suke.czx.common.utils.IPUtils;
|
||||
import com.suke.czx.common.utils.R;
|
||||
import com.suke.czx.config.ShardingTableConfig;
|
||||
import com.suke.czx.modules.browse.entity.TbBrowseRecord;
|
||||
import com.suke.czx.modules.browse.service.TbBrowseRecordService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* 浏览记录
|
||||
*
|
||||
* @author czx
|
||||
* @email object_czx@163.com
|
||||
* @date 2023-05-09 17:42:42
|
||||
*/
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@RequestMapping("/browse/record")
|
||||
@Tag(name = "TbAdvertiserRecordController", description = "浏览记录")
|
||||
public class TbBrowseRecordController extends AbstractController {
|
||||
private final TbBrowseRecordService tbAdvertiserRecordService;
|
||||
|
||||
|
||||
@AuthIgnore
|
||||
@GetMapping("/list")
|
||||
//@ResourceAuth(value = "浏览记录列表", module = "浏览记录")
|
||||
public R list(@RequestParam Map<String, Object> params) {
|
||||
|
||||
//查询列表数据
|
||||
QueryWrapper<TbBrowseRecord> queryWrapper = new QueryWrapper<>();
|
||||
final String keyword = mpPageConvert.getKeyword(params);
|
||||
if (StrUtil.isNotEmpty(keyword)) {
|
||||
queryWrapper.and(func -> {
|
||||
func.like("t.name", keyword).or().like("t.username", keyword);
|
||||
});
|
||||
}
|
||||
|
||||
String startDate = MapUtil.getStr(params, "startDate");
|
||||
String endDate = MapUtil.getStr(params, "endDate");
|
||||
if (StrUtil.isNotEmpty(startDate) && StrUtil.isNotEmpty(endDate)) {
|
||||
queryWrapper.ge("create_time", startDate + " 00:00:00")
|
||||
.le("create_time", endDate + " 23:59:59");
|
||||
}
|
||||
queryWrapper.lambda().orderByDesc(TbBrowseRecord::getCreateTime);
|
||||
// 设置当前查询哪个表,如果不设置就默认查询最新分表
|
||||
ShardingTableConfig.setTableName("tb_browse_record");
|
||||
IPage<TbBrowseRecord> listPage = tbAdvertiserRecordService.getPage(mpPageConvert.<TbBrowseRecord>pageParamConvert(params), queryWrapper);
|
||||
return R.ok().setData(listPage);
|
||||
}
|
||||
|
||||
|
||||
@SysLog("新增浏览记录数据")
|
||||
@PostMapping("/save")
|
||||
@ResourceAuth(value = "新增浏览记录数据", module = "浏览记录")
|
||||
public R save(@RequestBody TbBrowseRecord param, HttpServletRequest request) {
|
||||
if (StrUtil.isEmpty(param.getWatchStatus())) {
|
||||
return R.error("观看状态为空");
|
||||
}
|
||||
|
||||
param.setCreateTime(new Date());
|
||||
String ipAddr = IPUtils.getIpAddr(request);
|
||||
param.setRequestIp(ipAddr);
|
||||
tbAdvertiserRecordService.saveInfo(param);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
|
||||
@SysLog("删除浏览记录数据")
|
||||
@PostMapping("/delete")
|
||||
@ResourceAuth(value = "删除浏览记录数据", module = "浏览记录")
|
||||
public R delete(@RequestBody TbBrowseRecord param) {
|
||||
tbAdvertiserRecordService.removeById(param.getRecordId());
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.suke.czx.modules.browse.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.suke.czx.common.annotation.ShardingTable;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
/**
|
||||
* 浏览记录
|
||||
*
|
||||
* @author czx
|
||||
* @email object_czx@163.com
|
||||
* @date 2023-05-09 17:42:42
|
||||
*/
|
||||
@Data
|
||||
@ShardingTable
|
||||
@TableName("tb_browse_record")
|
||||
public class TbBrowseRecord implements Serializable {
|
||||
public static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
@Schema(description = "记录ID")
|
||||
@JsonProperty(value = "recordId")
|
||||
public String recordId;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
@JsonProperty(value = "userId")
|
||||
public String userId;
|
||||
|
||||
@Schema(description = "观看状态")
|
||||
@JsonProperty(value = "watchStatus")
|
||||
public String watchStatus;
|
||||
|
||||
@Schema(description = "用户请求ip")
|
||||
@JsonProperty(value = "requestIp")
|
||||
public String requestIp;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
@JsonProperty(value = "createTime")
|
||||
public Date createTime;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.suke.czx.modules.browse.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.suke.czx.modules.browse.entity.TbBrowseRecord;
|
||||
|
||||
/**
|
||||
* 浏览记录
|
||||
*
|
||||
* @author czx
|
||||
* @email object_czx@163.com
|
||||
* @date 2023-05-09 17:42:42
|
||||
*/
|
||||
public interface TbBrowseRecordMapper extends BaseMapper<TbBrowseRecord> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.suke.czx.modules.browse.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.suke.czx.modules.browse.entity.TbBrowseRecord;
|
||||
|
||||
/**
|
||||
* 浏览记录
|
||||
*
|
||||
* @author czx
|
||||
* @email object_czx@163.com
|
||||
* @date 2023-05-09 17:42:42
|
||||
*/
|
||||
public interface TbBrowseRecordService extends IService<TbBrowseRecord> {
|
||||
|
||||
void saveInfo(TbBrowseRecord param);
|
||||
|
||||
IPage<TbBrowseRecord> getPage(IPage<TbBrowseRecord> iPage, QueryWrapper<TbBrowseRecord> queryWrapper);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.suke.czx.modules.browse.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.suke.czx.modules.browse.entity.TbBrowseRecord;
|
||||
import com.suke.czx.modules.browse.mapper.TbBrowseRecordMapper;
|
||||
import com.suke.czx.modules.browse.service.TbBrowseRecordService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
/**
|
||||
* 浏览记录
|
||||
*
|
||||
* @author czx
|
||||
* @email object_czx@163.com
|
||||
* @date 2023-05-09 17:42:42
|
||||
*/
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class TbBrowseRecordServiceImpl extends ServiceImpl<TbBrowseRecordMapper, TbBrowseRecord> implements TbBrowseRecordService {
|
||||
|
||||
@Async
|
||||
@Override
|
||||
public void saveInfo(TbBrowseRecord param) {
|
||||
baseMapper.insert(param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<TbBrowseRecord> getPage(IPage<TbBrowseRecord> iPage, QueryWrapper<TbBrowseRecord> queryWrapper) {
|
||||
return baseMapper.selectPage(iPage, queryWrapper);
|
||||
}
|
||||
}
|
||||
@@ -4,4 +4,4 @@ spring:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
username: root
|
||||
password: root
|
||||
url: jdbc:mysql://localhost:3306/x_springboot?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
|
||||
url: jdbc:mysql://localhost:3306/x_springboot?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
|
||||
@@ -36,14 +36,6 @@ springdoc:
|
||||
path: /swagger-ui/index.html # 自定义路径,默认为"/swagger-ui/index.html"
|
||||
|
||||
|
||||
# autofull 配置
|
||||
autofull:
|
||||
showLog: false
|
||||
maxLevel: 1
|
||||
currLevel: 0
|
||||
encryptFlag: "@autofull@"
|
||||
encryptKeys: "abcdefg123456789"
|
||||
|
||||
#七牛 AK 和 SK可以去密钥管理中查询
|
||||
qiniu:
|
||||
accessKey: 2222
|
||||
@@ -59,10 +51,19 @@ mybatis-plus:
|
||||
global-config:
|
||||
# 关闭MP3.0自带的banner
|
||||
banner: false
|
||||
enable-sql-runner: true
|
||||
db-config:
|
||||
# 主键类型 0:数据库ID自增 1.未定义 2.用户输入 3 id_worker 4.uuid 5.id_worker字符串表示
|
||||
id-type: 0
|
||||
#字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"
|
||||
field-strategy: 1
|
||||
# 默认数据库表下划线命名
|
||||
table-underline: true
|
||||
table-underline: true
|
||||
|
||||
sharding-table:
|
||||
- table:
|
||||
name: tb_browse_record
|
||||
sharding-column: create_time
|
||||
- table:
|
||||
name: tb_sign_record
|
||||
sharding-column: create_time
|
||||
Reference in New Issue
Block a user