新增分表功能

This commit is contained in:
czx
2024-11-23 11:51:27 +08:00
parent 4b1305fe64
commit 40302c077d
19 changed files with 759 additions and 167 deletions

277
pom.xml
View File

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

View File

@@ -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启动成功================");
}
}
}

View File

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

View File

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

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

View File

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

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

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

View File

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

View File

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

View File

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

View 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("检查完成");
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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