feat: initial commit

This commit is contained in:
gin
2026-05-07 18:39:00 +08:00
commit cdee21ee8e
653 changed files with 63946 additions and 0 deletions
+137
View File
@@ -0,0 +1,137 @@
<?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">
<parent>
<artifactId>agileboot</artifactId>
<groupId>com.agileboot</groupId>
<version>1.0.0</version>
</parent>
<packaging>jar</packaging>
<modelVersion>4.0.0</modelVersion>
<artifactId>agileboot-infrastructure</artifactId>
<description>
infrastructure框架核心
</description>
<dependencies>
<!-- SpringBoot Web容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot 拦截器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!-- 验证码 -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<exclusions>
<exclusion>
<artifactId>javax.servlet-api</artifactId>
<groupId>javax.servlet</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 获取系统信息 -->
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
</dependency>
<!-- 通用模块-->
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>agileboot-common</artifactId>
</dependency>
<!-- mybatis plus 代码生成器依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
</dependency>
<!--velocity代码生成使用模板 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
</dependency>
<!-- Mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- springboot测试模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 引入guava包 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
</configuration>
<executions>
<!-- <execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>-->
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<warName>${project.artifactId}</warName>
</configuration>
</plugin>
</plugins>
<finalName>${project.artifactId}</finalName>
</build>
</project>
@@ -0,0 +1,16 @@
package com.agileboot.infrastructure;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
/**
* 对于Spring Boot应用,我们一般会打成jar包使用内置容器运行,但是有时候我们想要像使用传统spring web项目一样,
* 将Spring Boot应用打成WAR包,然后部署到外部容器运行,那么我们传统的使用Main类启动的方式稍显蹩脚,
* 因为外部容器无法识别到应用启动类,需要在应用中继承SpringBootServletInitializer类,然后重写config方法,
* 将其指向应用启动类。
* @author valarchie
*/
public class WarDeploymentInitializer extends SpringBootServletInitializer {
// protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
// return application.sources(AgileBootApplication.class);
// }
}
@@ -0,0 +1,122 @@
package com.agileboot.infrastructure.annotations.ratelimit;
import cn.hutool.extra.servlet.ServletUtil;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.common.utils.ServletHolderUtil;
import com.agileboot.infrastructure.user.AuthenticationUtils;
import com.agileboot.infrastructure.user.app.AppLoginUser;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 限流注解
*
* @author valarchie
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
/**
* 限流key
*/
String key() default "None";
/**
* 限流时间,单位秒
*/
int time() default 60;
/**
* 限流次数
*/
int maxCount() default 100;
/**
* 限流条件类型
*/
LimitType limitType() default LimitType.GLOBAL;
/**
* 限流使用的缓存类型
*/
CacheType cacheType() default CacheType.REDIS;
enum LimitType {
/**
* 默认策略全局限流 不区分IP和用户
*/
GLOBAL{
@Override
public String generateCombinedKey(RateLimit rateLimiter) {
return rateLimiter.key() + this.name();
}
},
/**
* 根据请求者IP进行限流
*/
IP {
@Override
public String generateCombinedKey(RateLimit rateLimiter) {
String clientIP = ServletUtil.getClientIP(ServletHolderUtil.getRequest());
return rateLimiter.key() + clientIP;
}
},
/**
* 按Web用户限流
*/
SYSTEM_USER {
@Override
public String generateCombinedKey(RateLimit rateLimiter) {
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
if (loginUser == null) {
throw new ApiException(ErrorCode.Client.COMMON_NO_AUTHORIZATION);
}
return rateLimiter.key() + loginUser.getUsername();
}
},
/**
* 按App用户限流
*/
APP_USER {
@Override
public String generateCombinedKey(RateLimit rateLimiter) {
AppLoginUser loginUser = AuthenticationUtils.getAppLoginUser();
if (loginUser == null) {
throw new ApiException(ErrorCode.Client.COMMON_NO_AUTHORIZATION);
}
return rateLimiter.key() + loginUser.getUsername();
}
};
public abstract String generateCombinedKey(RateLimit rateLimiter);
}
enum CacheType {
/**
* 使用redis做缓存
*/
REDIS,
/**
* 使用map做缓存
*/
Map
}
}
@@ -0,0 +1,17 @@
package com.agileboot.infrastructure.annotations.ratelimit;
/**
* 限流key
* @author valarchie
*/
public class RateLimitKey {
public static final String PREFIX = "Rate-Limit:";
public static final String LOGIN_CAPTCHA_KEY = PREFIX + "Login-Captcha:";
public static final String TEST_KEY = PREFIX + "Test:";
private RateLimitKey() {
}
}
@@ -0,0 +1,51 @@
package com.agileboot.infrastructure.annotations.ratelimit;
import com.agileboot.infrastructure.annotations.ratelimit.implementation.MapRateLimitChecker;
import com.agileboot.infrastructure.annotations.ratelimit.implementation.RedisRateLimitChecker;
import java.lang.reflect.Method;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
/**
* 限流切面处理
*
* @author valarchie
*/
@Aspect
@Component
@Slf4j
@ConditionalOnExpression("'${agileboot.embedded.redis}' != 'true'")
@RequiredArgsConstructor
public class RateLimiterAspect {
private final RedisRateLimitChecker redisRateLimitChecker;
private final MapRateLimitChecker mapRateLimitChecker;
@Before("@annotation(rateLimiter)")
public void doBefore(JoinPoint point, RateLimit rateLimiter) {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
log.info("当前限流方法:" + method.toGenericString());
switch (rateLimiter.cacheType()) {
case REDIS:
redisRateLimitChecker.check(rateLimiter);
break;
case Map:
mapRateLimitChecker.check(rateLimiter);
return;
default:
redisRateLimitChecker.check(rateLimiter);
}
}
}
@@ -0,0 +1,17 @@
package com.agileboot.infrastructure.annotations.ratelimit.implementation;
import com.agileboot.infrastructure.annotations.ratelimit.RateLimit;
/**
* @author valarchie
*/
public abstract class AbstractRateLimitChecker {
/**
* 检查是否超出限流
*
* @param rateLimiter RateLimit
*/
public abstract void check(RateLimit rateLimiter);
}
@@ -0,0 +1,42 @@
package com.agileboot.infrastructure.annotations.ratelimit.implementation;
import cn.hutool.cache.impl.LRUCache;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.infrastructure.annotations.ratelimit.RateLimit;
import com.google.common.util.concurrent.RateLimiter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @author valarchie
*/
@SuppressWarnings("UnstableApiUsage")
@Component
@RequiredArgsConstructor
@Slf4j
public class MapRateLimitChecker extends AbstractRateLimitChecker{
/**
* 最大仅支持4096个key 超出这个key 限流将可能失效
*/
private final LRUCache<String, RateLimiter> cache = new LRUCache<>(4096);
@Override
public void check(RateLimit rateLimit) {
String combinedKey = rateLimit.limitType().generateCombinedKey(rateLimit);
RateLimiter rateLimiter = cache.get(combinedKey,
() -> RateLimiter.create((double) rateLimit.maxCount() / rateLimit.time())
);
if (!rateLimiter.tryAcquire()) {
throw new ApiException(ErrorCode.Client.COMMON_REQUEST_TOO_OFTEN);
}
log.info("限制请求key:{}, combined key:{}", rateLimit.key(), combinedKey);
}
}
@@ -0,0 +1,67 @@
package com.agileboot.infrastructure.annotations.ratelimit.implementation;
import cn.hutool.core.collection.ListUtil;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.infrastructure.annotations.ratelimit.RateLimit;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
/**
* @author valarchie
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class RedisRateLimitChecker extends AbstractRateLimitChecker{
private final RedisTemplate<Object, Object> redisTemplate;
private final RedisScript<Long> limitScript = new DefaultRedisScript<>(limitScriptText(), Long.class);
@Override
public void check(RateLimit rateLimiter) {
int maxCount = rateLimiter.maxCount();
String combineKey = rateLimiter.limitType().generateCombinedKey(rateLimiter);
Long currentCount;
try {
currentCount = redisTemplate.execute(limitScript, ListUtil.of(combineKey), maxCount, rateLimiter.time());
log.info("限制请求:{}, 当前请求次数:{}, 缓存key:{}", combineKey, currentCount, rateLimiter.key());
} catch (Exception e) {
throw new RuntimeException("redis限流器异常,请确保redis启动正常");
}
if (currentCount == null) {
throw new RuntimeException("redis限流器异常,请稍后再试");
}
if (currentCount.intValue() > maxCount) {
throw new ApiException(ErrorCode.Client.COMMON_REQUEST_TOO_OFTEN);
}
}
/**
* 限流脚本
*/
private static String limitScriptText() {
return "local key = KEYS[1]\n" +
"local count = tonumber(ARGV[1])\n" +
"local time = tonumber(ARGV[2])\n" +
"local current = redis.call('get', key);\n" +
"if current and tonumber(current) > count then\n" +
" return tonumber(current);\n" +
"end\n" +
"current = redis.call('incr', key)\n" +
"if tonumber(current) == 1 then\n" +
" redis.call('expire', key, time)\n" +
"end\n" +
"return tonumber(current);";
}
}
@@ -0,0 +1,95 @@
package com.agileboot.infrastructure.annotations.unrepeatable;
import cn.hutool.core.util.StrUtil;
import com.agileboot.infrastructure.user.AuthenticationUtils;
import com.agileboot.infrastructure.user.app.AppLoginUser;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import lombok.extern.slf4j.Slf4j;
/**
* 自定义注解防止表单重复提交
* 仅生效于有RequestBody注解的参数 因为使用RequestBodyAdvice来实现
* @author valarchie
*/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Unrepeatable {
/**
* 间隔时间(s),小于此时间视为重复提交
*/
int interval() default 5;
// TODO改成和rate limit一样 可以选择类型
/**
* 检测条件类型
*/
CheckType checkType() default CheckType.SYSTEM_USER;
@Slf4j
enum CheckType {
/**
* 按App用户
*/
APP_USER {
@Override
public String generateResubmitRedisKey(Method method) {
String username;
try {
AppLoginUser loginUser = AuthenticationUtils.getAppLoginUser();
username = loginUser.getUsername();
} catch (Exception e) {
username = NO_LOGIN;
log.error("could not find the related user to check repeatable submit.");
}
return StrUtil.format(RESUBMIT_REDIS_KEY,
this.name(),
method.getDeclaringClass().getName(),
method.getName(),
username);
}
},
/**
* 按Web用户
*/
SYSTEM_USER {
@Override
public String generateResubmitRedisKey(Method method) {
String username;
try {
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
username = loginUser.getUsername();
} catch (Exception e) {
username = NO_LOGIN;
log.error("could not find the related user to check repeatable submit.");
}
return StrUtil.format(RESUBMIT_REDIS_KEY,
this.name(),
method.getDeclaringClass().getName(),
method.getName(),
username);
}
};
public static final String NO_LOGIN = "Anonymous";
public static final String RESUBMIT_REDIS_KEY = "resubmit:{}:{}:{}:{}";
public abstract String generateResubmitRedisKey(Method method);
}
}
@@ -0,0 +1,67 @@
package com.agileboot.infrastructure.annotations.unrepeatable;
import cn.hutool.json.JSONUtil;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.infrastructure.cache.RedisUtil;
import java.lang.reflect.Type;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
/**
* 重复提交拦截器 如果涉及前后端加解密的话 也可以通过继承RequestBodyAdvice来实现
*
* @author valarchie
*/
@ControllerAdvice(basePackages = "com.agileboot")
@Slf4j
@RequiredArgsConstructor
public class UnrepeatableInterceptor extends RequestBodyAdviceAdapter {
private final RedisUtil redisUtil;
@Override
public boolean supports(MethodParameter methodParameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
return methodParameter.hasMethodAnnotation(Unrepeatable.class);
}
/**
* @param body 仅获取有RequestBody注解的参数
*/
@NotNull
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
// 仅获取有RequestBody注解的参数
String currentRequest = JSONUtil.toJsonStr(body);
Unrepeatable resubmitAnno = parameter.getMethodAnnotation(Unrepeatable.class);
if (resubmitAnno != null) {
String redisKey = resubmitAnno.checkType().generateResubmitRedisKey(parameter.getMethod());
log.info("请求重复提交拦截,当前key:{}, 当前参数:{}", redisKey, currentRequest);
String preRequest = redisUtil.getCacheObject(redisKey);
if (preRequest != null) {
boolean isSameRequest = Objects.equals(currentRequest, preRequest);
if (isSameRequest) {
throw new ApiException(ErrorCode.Client.COMMON_REQUEST_RESUBMIT);
}
}
redisUtil.setCacheObject(redisKey, currentRequest, resubmitAnno.interval(), TimeUnit.SECONDS);
}
return body;
}
}
@@ -0,0 +1,210 @@
package com.agileboot.infrastructure.cache;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
/**
* spring redis 工具类
*
* @author valarchie
**/
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
@RequiredArgsConstructor
public class RedisUtil {
public final RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public Boolean expire(final String key, final long timeout, final TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*/
public void deleteObject(final String key) {
redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
*/
public Long deleteObject(final Collection collection) {
return redisTemplate.delete(collection);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList) {
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
for (T t : dataSet) {
setOperation.add(t);
}
return setOperation;
}
/**
* 获得缓存的set
*/
public <T> Set<T> getCacheSet(final String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*/
public <T> Map<String, T> getCacheMap(final String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey) {
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 删除Hash中的数据
*/
public void delCacheMapValue(final String key, final String hKey) {
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern) {
return redisTemplate.keys(pattern);
}
}
@@ -0,0 +1,14 @@
package com.agileboot.infrastructure.cache.aop;
/**
* @author valarchie
*/
public class CacheNameConstants {
public static final String GUAVA = "guava";
public static final String REDIS = "redis";
private CacheNameConstants() {
}
}
@@ -0,0 +1,91 @@
package com.agileboot.infrastructure.cache.aop;
import cn.hutool.core.util.StrUtil;
import com.google.common.cache.CacheBuilder;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
/**
* @author valarchie
*/
//@Component
public class GuavaCacheBean implements Cache {
/**
* 缓存仓库
*/
private com.google.common.cache.Cache<Object, Object> storage;
@PostConstruct
private void init() {
storage = CacheBuilder.newBuilder()
// 设置缓存的容量为100
.maximumSize(100)
// 设置初始容量为16
.initialCapacity(16)
// 设置过期时间为写入缓存后10分钟过期
.refreshAfterWrite(10, TimeUnit.MINUTES)
.build();
}
@Override
public String getName() {
return CacheNameConstants.GUAVA;
}
@Override
public ValueWrapper get(Object key) {
if (Objects.isNull(key)) {
return null;
}
Object ifPresent = storage.getIfPresent(key.toString());
return Objects.isNull(ifPresent) ? null : new SimpleValueWrapper(ifPresent);
}
@Override
public void put(Object key, Object value) {
if (StrUtil.isEmpty((CharSequence) key)) {
return;
}
storage.put(key, value);
}
@Override
public void evict(Object key) {
if (key == null) {
return;
}
storage.invalidate(key);
}
/*-----------------------暂时不用实现的方法-----------------*/
@Override
public <T> T get(Object key, Class<T> type) {
return null;
}
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
return null;
}
@Override
public void clear() {
}
@Override
public Object getNativeCache() {
return null;
}
}
@@ -0,0 +1,70 @@
package com.agileboot.infrastructure.cache.aop;
import cn.hutool.core.util.StrUtil;
import com.agileboot.infrastructure.cache.RedisUtil;
import java.util.concurrent.Callable;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
/**
* @author valarchie
*/
//@Component
@RequiredArgsConstructor
public class RedisCacheBean implements Cache {
/**
* 缓存仓库
*/
private final RedisUtil redisUtil;
@Override
public String getName() {
return CacheNameConstants.REDIS;
}
@Override
public void put(Object key, Object value) {
if (StrUtil.isNotEmpty((CharSequence) key)) {
redisUtil.setCacheObject((String) key, value);
}
}
@Override
public void evict(Object key) {
if (StrUtil.isNotEmpty((CharSequence) key)) {
redisUtil.deleteObject((String) key);
}
}
@Override
public ValueWrapper get(Object key) {
return key == null ? null : new SimpleValueWrapper(redisUtil.getCacheObject((String) key));
}
/*-----------------------暂时不用实现的方法-----------------*/
@Override
public <T> T get(Object key, Class<T> type) {
return null;
}
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
return null;
}
@Override
public Object getNativeCache() {
return null;
}
@Override
public void clear() {
}
}
@@ -0,0 +1,11 @@
package com.agileboot.infrastructure.cache.aop;
/**
* 本来想通过注解的形式 @cacheable来实现缓存
* 但是MybatisPlus 生成的Service类 里面是空的 不好打注解
* 并且本项目借鉴CQRS的想法,所有写相关的操作,都放置在domainService当中,而查询可以通过
* mybatis plus普通的service进行查询。
* 查询和操作 分别在两个地方 如果用@cacheable注解的话, 在两个地方进行分别注解 很容易疏漏出问题
* 所以最终还是想采用手动操作缓存的方式
* 利用三级缓存 map-> guava -> redis 的形式
*/
@@ -0,0 +1,107 @@
package com.agileboot.infrastructure.cache.guava;
import cn.hutool.core.util.StrUtil;
import com.google.common.base.Ticker;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
/**
* 缓存接口实现类 二级缓存
* @author valarchie
*/
@Slf4j
public abstract class AbstractGuavaCacheTemplate<T> {
private final LoadingCache<String, Optional<T>> guavaCache = CacheBuilder.newBuilder()
// 基于容量回收。缓存的最大数量。超过就取MAXIMUM_CAPACITY = 1 << 30。依靠LRU队列recencyQueue来进行容量淘汰
.maximumSize(1024)
// 基于容量回收。但这是统计占用内存大小,maximumWeight与maximumSize不能同时使用。设置最大总权重
// .maximumWeight(1000)
// 设置权重(可当成每个缓存占用的大小)
// .weigher((o, o2) -> 5)
// 软弱引用(引用强度顺序:强软弱虚)
// -- 弱引用key
// .weakKeys()
// -- 弱引用value
// .weakValues()
// -- 软引用value
// .softValues()
// 过期失效回收
// -- 没读写访问下,超过5秒会失效(非自动失效,需有任意get put方法才会扫描过期失效数据)
// .expireAfterAccess(5L, TimeUnit.MINUTES)
// -- 没写访问下,超过5秒会失效(非自动失效,需有任意put get方法才会扫描过期失效数据)
// .expireAfterWrite(5L, TimeUnit.MINUTES)
// 没写访问下,超过5秒会失效(非自动失效,需有任意put get方法才会扫描过期失效数据。但区别是会开一个异步线程进行刷新,刷新过程中访问返回旧数据)
.refreshAfterWrite(5L, TimeUnit.MINUTES)
// 移除监听事件
.removalListener(removal -> {
// 可做一些删除后动作,比如上报删除数据用于统计
log.info("触发删除动作,删除的key={}, value={}", removal.getKey(), removal.getValue());
})
// 并行等级。决定segment数量的参数,concurrencyLevel与maxWeight共同决定
.concurrencyLevel(16)
// 开启缓存统计。比如命中次数、未命中次数等
.recordStats()
// 所有segment的初始总容量大小
.initialCapacity(128)
// 用于测试,可任意改变当前时间。参考:https://www.geek-share.com/detail/2689756248.html
// 大坑 设置ticket为0 会导致缓存不会失效 定时刷新也不会触发,因为距离上次刷新时刻(永远是0)不会超过refreshAfterWrite定义的间隔。
// 所以可以总结,这个Ticker直接返回0,会导致Cache的所有基于时间的过期和刷新策略都无法正常工作。Cache内容也不会按预期失效或刷新。
// .ticker(new Ticker() {
// @Override
// public long read() {
// return 0;
// }
// })
.build(new CacheLoader<String, Optional<T>>() {
@Override
public Optional<T> load(String key) {
T cacheObject = getObjectFromDb(key);
log.debug("find the local guava cache of key: {} is {}", key, cacheObject);
return Optional.ofNullable(cacheObject);
}
});
public T get(String key) {
try {
if (StrUtil.isEmpty(key)) {
return null;
}
Optional<T> optional = guavaCache.get(key);
return optional.orElse(null);
} catch (ExecutionException e) {
log.error("get cache object from guava cache failed.", e);
return null;
}
}
public void invalidate(String key) {
if (StrUtil.isEmpty(key)) {
return;
}
guavaCache.invalidate(key);
}
public void invalidateAll() {
guavaCache.invalidateAll();
}
/**
* 从数据库加载数据
*
* @param id id
* @return T
*/
public abstract T getObjectFromDb(Object id);
}
@@ -0,0 +1,46 @@
package com.agileboot.infrastructure.cache.redis;
import java.util.concurrent.TimeUnit;
/**
* @author valarchie
*/
public enum CacheKeyEnum {
/**
* Redis各类缓存集合
*/
CAPTCHAT("captcha_codes:", 2, TimeUnit.MINUTES),
LOGIN_USER_KEY("login_tokens:", 30, TimeUnit.MINUTES),
RATE_LIMIT_KEY("rate_limit:", 60, TimeUnit.SECONDS),
USER_ENTITY_KEY("user_entity:", 60, TimeUnit.MINUTES),
ROLE_ENTITY_KEY("role_entity:", 60, TimeUnit.MINUTES),
POST_ENTITY_KEY("post_entity:", 60, TimeUnit.MINUTES),
ROLE_MODEL_INFO_KEY("role_model_info:", 60, TimeUnit.MINUTES),
;
CacheKeyEnum(String key, int expiration, TimeUnit timeUnit) {
this.key = key;
this.expiration = expiration;
this.timeUnit = timeUnit;
}
private final String key;
private final int expiration;
private final TimeUnit timeUnit;
public String key() {
return key;
}
public int expiration() {
return expiration;
}
public TimeUnit timeUnit() {
return timeUnit;
}
}
@@ -0,0 +1,127 @@
package com.agileboot.infrastructure.cache.redis;
import com.agileboot.infrastructure.cache.RedisUtil;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
/**
* 缓存接口实现类 三级缓存
* @author valarchie
*/
@Slf4j
public class RedisCacheTemplate<T> {
private final RedisUtil redisUtil;
private final CacheKeyEnum redisRedisEnum;
private final LoadingCache<String, Optional<T>> guavaCache;
public RedisCacheTemplate(RedisUtil redisUtil, CacheKeyEnum redisRedisEnum) {
this.redisUtil = redisUtil;
this.redisRedisEnum = redisRedisEnum;
this.guavaCache = CacheBuilder.newBuilder()
// 基于容量回收。缓存的最大数量。超过就取MAXIMUM_CAPACITY = 1 << 30。依靠LRU队列recencyQueue来进行容量淘汰
.maximumSize(1024)
.softValues()
// 没写访问下,超过5秒会失效(非自动失效,需有任意put get方法才会扫描过期失效数据。
// 但区别是会开一个异步线程进行刷新,刷新过程中访问返回旧数据)
.expireAfterWrite(redisRedisEnum.expiration(), TimeUnit.MINUTES)
// 并行等级。决定segment数量的参数,concurrencyLevel与maxWeight共同决定
.concurrencyLevel(64)
// 所有segment的初始总容量大小
.initialCapacity(128)
.build(new CacheLoader<String, Optional<T>>() {
@Override
public Optional<T> load(String cachedKey) {
T cacheObject = redisUtil.getCacheObject(cachedKey);
log.debug("find the redis cache of key: {} is {}", cachedKey, cacheObject);
return Optional.ofNullable(cacheObject);
}
});
}
/**
* 从缓存中获取对象 如果获取不到的话 从DB层面获取
*
* @param id id
*/
public T getObjectById(Object id) {
String cachedKey = generateKey(id);
try {
Optional<T> optional = guavaCache.get(cachedKey);
// log.debug("find the guava cache of key: {}", cachedKey);
if (!optional.isPresent()) {
T objectFromDb = getObjectFromDb(id);
set(id, objectFromDb);
return objectFromDb;
}
return optional.get();
} catch (ExecutionException e) {
log.error("从缓存中获取对象失败", e);
return null;
}
}
/**
* 从缓存中获取 对象, 即使找不到的话 也不从DB中找
* @param id id
*/
public T getObjectOnlyInCacheById(Object id) {
String cachedKey = generateKey(id);
try {
Optional<T> optional = guavaCache.get(cachedKey);
log.debug("find the guava cache of key: {}", cachedKey);
return optional.orElse(null);
} catch (ExecutionException e) {
log.error("从缓存中获取对象失败", e);
return null;
}
}
/**
* 从缓存中获取 对象, 即使找不到的话 也不从DB中找
* @param cachedKey 直接通过redis的key来搜索
*/
public T getObjectOnlyInCacheByKey(String cachedKey) {
try {
Optional<T> optional = guavaCache.get(cachedKey);
log.debug("find the guava cache of key: {}", cachedKey);
return optional.orElse(null);
} catch (ExecutionException e) {
log.error("从缓存中获取对象失败", e);
return null;
}
}
public void set(Object id, T obj) {
redisUtil.setCacheObject(generateKey(id), obj, redisRedisEnum.expiration(), redisRedisEnum.timeUnit());
guavaCache.refresh(generateKey(id));
}
public void delete(Object id) {
redisUtil.deleteObject(generateKey(id));
guavaCache.refresh(generateKey(id));
}
public void refresh(Object id) {
redisUtil.expire(generateKey(id), redisRedisEnum.expiration(), redisRedisEnum.timeUnit());
guavaCache.refresh(generateKey(id));
}
public String generateKey(Object id) {
return redisRedisEnum.key() + id;
}
public T getObjectFromDb(Object id) {
return null;
}
}
@@ -0,0 +1,21 @@
package com.agileboot.infrastructure.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 程序注解配置
* 注解EnableAspectJAutoProxy 表示通过aop框架暴露该代理对象,AopContext能够访问
* 注解MapperScan 指定要扫描的Mapper类的包的路径
* @author valarchie
*/
@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)
@EnableScheduling
// 因为如果直接指定db包 service也会被扫描到 所以通过markerInterface 进行限定
@MapperScan(value = "com.agileboot.**.db", markerInterface = com.baomidou.mybatisplus.core.mapper.BaseMapper.class)
public class ApplicationConfig {
}
@@ -0,0 +1,80 @@
package com.agileboot.infrastructure.config;
import com.agileboot.infrastructure.exception.GlobalExceptionFilter;
import com.agileboot.infrastructure.filter.TestFilter;
import com.agileboot.infrastructure.filter.TraceIdFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* Filter配置
* @author valarchie
*/
@Configuration
public class FilterConfig {
// TODO 后续统一到一个properties 类中比较好
@Value("${agileboot.traceRequestIdKey}")
private String requestIdKey;
@Bean
public FilterRegistrationBean<TestFilter> testFilterRegistrationBean() {
FilterRegistrationBean<TestFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new TestFilter());
registration.addUrlPatterns("/*");
registration.setName("testFilter");
registration.setOrder(2);
return registration;
}
@Bean
public FilterRegistrationBean<TraceIdFilter> traceIdFilterRegistrationBean() {
FilterRegistrationBean<TraceIdFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new TraceIdFilter(requestIdKey));
registration.addUrlPatterns("/*");
registration.setName("traceIdFilter");
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registration;
}
@Bean
public FilterRegistrationBean<GlobalExceptionFilter> exceptionFilterRegistrationBean() {
FilterRegistrationBean<GlobalExceptionFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new GlobalExceptionFilter());
registration.addUrlPatterns("/*");
registration.setName("exceptionFilter");
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registration;
}
/**
* 跨域配置
*/
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// 设置访问源地址
config.addAllowedOriginPattern("*");
// 设置访问源请求头
config.addAllowedHeader("*");
// 设置访问源请求方法
config.addAllowedMethod("*");
// 有效期 1800秒
config.setMaxAge(1800L);
// 添加映射路径,拦截一切请求
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
// 返回新的CorsFilter
return new CorsFilter(source);
}
}
@@ -0,0 +1,108 @@
package com.agileboot.infrastructure.config;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionInterceptor;
/**
* 全局事务管理器
* @author valarchie
*/
@Configuration
@RequiredArgsConstructor
public class GlobalTransactionConfig {
/**
* 配置全局事务的切点为service层的所有方法 AOP切面表达式 可参考(<a
* href="https://blog.csdn.net/ycf921244819/article/details/106599489">https://blog.csdn.net/ycf921244819/article/details/106599489</a>
* 本项目设置在 applicationService层
*/
private static final String POINTCUT_EXPRESSION = "execution(public * com.agileboot.domain..*.*ApplicationService.*(..))";
/**
* 注入事务管理器
*/
private final TransactionManager transactionManager;
/**
* 配置事务拦截器
*
* @return TransactionInterceptor
*/
@Bean
public TransactionInterceptor txAdvice() {
RuleBasedTransactionAttribute txAttrRequired = new RuleBasedTransactionAttribute();
txAttrRequired.setName("REQUIRED事务");
//设置事务传播机制,默认是PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
txAttrRequired.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
//设置异常回滚为Exception 默认是RuntimeException
List<RollbackRuleAttribute> rollbackRuleAttributes = new ArrayList<>();
rollbackRuleAttributes.add(new RollbackRuleAttribute(Exception.class));
txAttrRequired.setRollbackRules(rollbackRuleAttributes);
RuleBasedTransactionAttribute txAttrRequiredReadOnly = new RuleBasedTransactionAttribute();
txAttrRequiredReadOnly.setName("SUPPORTS事务");
//设置事务传播机制,PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
txAttrRequiredReadOnly.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
//设置异常回滚为Exception 默认是RuntimeException
txAttrRequiredReadOnly.setRollbackRules(rollbackRuleAttributes);
txAttrRequiredReadOnly.setReadOnly(true);
// 事务管理规则,声明具备事务管理的方法名
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
// 方法名规则限制,必须以下列开头才会加入事务管理当中
// 新增
source.addTransactionalMethod("add*", txAttrRequired);
source.addTransactionalMethod("save*", txAttrRequired);
source.addTransactionalMethod("create*", txAttrRequired);
source.addTransactionalMethod("insert*", txAttrRequired);
// 修改
source.addTransactionalMethod("submit*", txAttrRequired);
source.addTransactionalMethod("edit*", txAttrRequired);
source.addTransactionalMethod("update*", txAttrRequired);
source.addTransactionalMethod("modify*", txAttrRequired);
source.addTransactionalMethod("exec*", txAttrRequired);
source.addTransactionalMethod("set*", txAttrRequired);
// 删除
source.addTransactionalMethod("del*", txAttrRequired);
source.addTransactionalMethod("remove*", txAttrRequired);
//对于查询方法,根据实际情况添加事务管理 可能存在查询多个数据时,已查询出来的数据刚好被改变的情况
source.addTransactionalMethod("get*", txAttrRequiredReadOnly);
source.addTransactionalMethod("select*", txAttrRequiredReadOnly);
source.addTransactionalMethod("query*", txAttrRequiredReadOnly);
source.addTransactionalMethod("find*", txAttrRequiredReadOnly);
source.addTransactionalMethod("list*", txAttrRequiredReadOnly);
source.addTransactionalMethod("count*", txAttrRequiredReadOnly);
source.addTransactionalMethod("is*", txAttrRequiredReadOnly);
return new TransactionInterceptor(transactionManager, source);
}
/**
* 设置切面
* @return Advisor
*/
@Bean
public Advisor txAdviceAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(POINTCUT_EXPRESSION);
return new DefaultPointcutAdvisor(pointcut, txAdvice());
}
}
@@ -0,0 +1,31 @@
package com.agileboot.infrastructure.config;
import com.agileboot.infrastructure.security.xss.JsonHtmlXssTrimSerializer;
import java.util.TimeZone;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
/**
* @author valarchie
*/
@Configuration
public class JacksonConfig implements Jackson2ObjectMapperBuilderCustomizer{
// 这种配置方式会覆盖 yml中的jackson配置, 使用下面的customize配置则不会
// @Bean
// Jackson2ObjectMapperBuilder objectMapperBuilder() {
// Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
// builder.deserializers(new JsonHtmlXssTrimSerializer());
// return builder;
// }
@Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
// 防XSS脚本注入
jacksonObjectMapperBuilder.deserializers(new JsonHtmlXssTrimSerializer());
// 默认时区配置
jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault());
}
}
@@ -0,0 +1,37 @@
package com.agileboot.infrastructure.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* Mybatis支持*匹配扫描包
*
* @author valarchie
*/
@Configuration
@EnableTransactionManagement
public class MyBatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
@@ -0,0 +1,27 @@
package com.agileboot.infrastructure.config;
import com.agileboot.common.config.AgileBootConfig;
import com.agileboot.common.constant.Constants;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 通用配置
*
* @author valarchie
*/
@Configuration
@RequiredArgsConstructor
public class ResourcesConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
/* 本地文件上传路径 */
registry.addResourceHandler("/" + Constants.RESOURCE_PREFIX + "/**")
.addResourceLocations("file:" + AgileBootConfig.getFileBaseDir() + "/");
}
}
@@ -0,0 +1,29 @@
package com.agileboot.infrastructure.config;
import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author valarchie
* SpringDoc API文档相关配置
*/
@Configuration
public class SpringDocConfig {
@Bean
public OpenAPI agileBootApi() {
return new OpenAPI()
.info(new Info().title("Agileboot后台管理系统")
.description("Agileboot API 演示")
.version("v1.8.0")
.license(new License().name("MIT 3.0").url("https://github.com/valarchie/AgileBoot-Back-End")))
.externalDocs(new ExternalDocumentation()
.description("Agileboot后台管理系统接口文档")
.url("https://juejin.cn/column/7159946528827080734"));
}
}
@@ -0,0 +1,97 @@
package com.agileboot.infrastructure.config.captcha;
import static com.google.code.kaptcha.Constants.KAPTCHA_BORDER;
import static com.google.code.kaptcha.Constants.KAPTCHA_BORDER_COLOR;
import static com.google.code.kaptcha.Constants.KAPTCHA_IMAGE_HEIGHT;
import static com.google.code.kaptcha.Constants.KAPTCHA_IMAGE_WIDTH;
import static com.google.code.kaptcha.Constants.KAPTCHA_NOISE_COLOR;
import static com.google.code.kaptcha.Constants.KAPTCHA_NOISE_IMPL;
import static com.google.code.kaptcha.Constants.KAPTCHA_OBSCURIFICATOR_IMPL;
import static com.google.code.kaptcha.Constants.KAPTCHA_SESSION_CONFIG_KEY;
import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH;
import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_CHAR_SPACE;
import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR;
import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES;
import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE;
import static com.google.code.kaptcha.Constants.KAPTCHA_TEXTPRODUCER_IMPL;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import java.util.Properties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 验证码配置
*
* @author ruoyi
*/
@Configuration
public class CaptchaConfig {
@Bean(name = "captchaProducer")
public DefaultKaptcha getCaptchaBean() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 是否有边框 默认为true 我们可以自己设置yes,no
properties.setProperty(KAPTCHA_BORDER, "yes");
// 验证码文本字符颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");
// 验证码图片宽度 默认为200
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
// 验证码图片高度 默认为50
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
// 验证码文本字符大小 默认为40
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38");
// KAPTCHA_SESSION_KEY
properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode");
// 验证码文本字符长度 默认为5
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
// 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy
// 阴影com.google.code.kaptcha.impl.ShadowGimpy
properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
@Bean(name = "captchaProducerMath")
public DefaultKaptcha getKaptchaBeanMath() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 是否有边框 默认为true 我们可以自己设置yes,no
properties.setProperty(KAPTCHA_BORDER, "yes");
// 边框颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
// 验证码文本字符颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
// 验证码图片宽度 默认为200
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
// 验证码图片高度 默认为50
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
// 验证码文本字符大小 默认为40
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
// KAPTCHA_SESSION_KEY
properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
// 验证码文本生成器 需要填 文本生成器类的全限定包名
properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.agileboot.infrastructure.config.captcha.CaptchaMathTextCreator");
// 验证码文本字符间距 默认为2
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
// 验证码文本字符长度 默认为5
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
// 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
// 验证码噪点颜色 默认为Color.BLACK
properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
// 干扰实现类
properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
// 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy
// 阴影com.google.code.kaptcha.impl.ShadowGimpy
properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
@@ -0,0 +1,75 @@
package com.agileboot.infrastructure.config.captcha;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.RandomUtil;
import com.google.code.kaptcha.text.impl.DefaultTextCreator;
/**
* verification code of math Generator
*
* @author valarchie
*/
public class CaptchaMathTextCreator extends DefaultTextCreator {
@Override
public String getText() {
int x = RandomUtil.randomInt(13);
int y = RandomUtil.randomInt(13);
Operand randomOperand = EnumUtil.getEnumAt(Operand.class, RandomUtil.randomInt(4));
StringBuilder mathExpression = new StringBuilder();
int result = randomOperand.generateMathExpression(x, y, mathExpression);
mathExpression.append("=?@").append(result);
return mathExpression.toString();
}
enum Operand {
/**
* 加减乘除操作 用来生成验证码的图片表达式
*/
ADD {
@Override
public int generateMathExpression(int x, int y, StringBuilder expression) {
expression.append(x);
expression.append("+");
expression.append(y);
return x + y;
}
},
MINUS {
@Override
public int generateMathExpression(int x, int y, StringBuilder expression) {
expression.append(Math.max(x, y));
expression.append("-");
expression.append(Math.min(x, y));
return Math.abs(x - y);
}
},
MULTIPLE {
@Override
public int generateMathExpression(int x, int y, StringBuilder expression) {
expression.append(x);
expression.append("*");
expression.append(y);
return x * y;
}
},
DIVIDE {
@Override
public int generateMathExpression(int x, int y, StringBuilder expression) {
// Judge whether an integer can be divided
if (x != 0 && y % x == 0) {
expression.append(y);
expression.append("/");
expression.append(x);
return y / x;
} else {
// use add addition instead
return Operand.ADD.generateMathExpression(x, y, expression);
}
}
};
public abstract int generateMathExpression(int x, int y, StringBuilder expression);
}
}
@@ -0,0 +1,38 @@
package com.agileboot.infrastructure.config.redis;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Configuration;
import redis.embedded.RedisServer;
/**
* @author valarchie
*/
@Configuration
@ConditionalOnExpression("'${agileboot.embedded.redis}' == 'true'")
public class EmbeddedRedisConfig {
@Value("${spring.redis.port}")
private Integer port;
private RedisServer redisServer;
@PostConstruct
public void postConstruct() {
RedisServer redisServer = RedisServer.builder().port(port)
.setting("maxheap 32M")
.setting("daemonize no")
.setting("appendonly no").build();
this.redisServer = redisServer;
redisServer.start();
}
@PreDestroy
public void preDestroy() {
redisServer.stop();
}
}
@@ -0,0 +1,61 @@
package com.agileboot.infrastructure.config.redis;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
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.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* redis配置
*
* @author valarchie
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModule(new JavaTimeModule());
objectMapper.registerModule((new SimpleModule()));
//有属性不能映射的时候不报错
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
//对象为空时不抛异常
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY);
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer(objectMapper);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
// Hash的key也采用StringRedisSerializer的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
@@ -0,0 +1,73 @@
package com.agileboot.infrastructure.exception;
import cn.hutool.core.map.MapUtil;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode.Internal;
import java.sql.SQLException;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.exceptions.PersistenceException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @author valarchie
*/
@Aspect
@Component
@Slf4j
public class DbExceptionAspect {
@Pointcut("execution(* com.agileboot..db..*(..))")
public void dbException() {
}
/**
* 包装成ApiException 再交给globalExceptionHandler处理
*
* @param joinPoint joinPoint
* @return object
*/
@Around("dbException()")
public Object aroundDbException(ProceedingJoinPoint joinPoint) throws Throwable {
Object proceed;
try {
// 将应用层的数据库错误 捕获并进行转换 主要捕获 sql形式的异常
proceed = joinPoint.proceed();
} catch (ApiException apiException) {
throw apiException;
} catch (Exception sqlException) {
ApiException wrapException = new ApiException(sqlException, Internal.DB_INTERNAL_ERROR);
wrapException.setPayload(MapUtil.of("detail", sqlException.getMessage()));
throw wrapException;
}
return proceed;
}
@Pointcut("bean(*ApplicationService)")
public void applicationDbException() {
}
@Around("applicationDbException()")
public Object aroundApplicationDbException(ProceedingJoinPoint joinPoint) throws Throwable {
Object proceed;
try {
// 将应用层的数据库错误 捕获并进行转换 主要捕获 jpa形式的 insert save 等模型抛出的错误
proceed = joinPoint.proceed();
} catch (ApiException ae) {
throw ae;
} catch (SQLException | PersistenceException sqlException) {
ApiException wrapException = new ApiException(sqlException, Internal.DB_INTERNAL_ERROR);
wrapException.setPayload(MapUtil.of("detail", sqlException.getMessage()));
throw wrapException;
}
return proceed;
}
}
@@ -0,0 +1,58 @@
package com.agileboot.infrastructure.exception;
import cn.hutool.json.JSONUtil;
import com.agileboot.common.core.dto.ResponseDTO;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode.Internal;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import lombok.extern.slf4j.Slf4j;
/**
* 异常过滤器,因为配置的全局异常捕获器只能捕获MVC框架的异常
* 不能捕获filter的异常
* @author valarchie
*/
@Slf4j
@WebFilter(filterName = "ExceptionFilter", urlPatterns = "/*")
public class GlobalExceptionFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException {
try {
chain.doFilter(request, response);
} catch (ApiException ex) {
log.error("global filter exceptions", ex);
String resultJson = JSONUtil.toJsonStr(ResponseDTO.fail(ex));
writeToResponse(response, resultJson);
} catch (Exception e) {
log.error("global filter exceptions, unknown error:", e);
ResponseDTO<Object> responseDTO = ResponseDTO.fail(new ApiException(Internal.INTERNAL_ERROR, e.getMessage()));
String resultJson = JSONUtil.toJsonStr(responseDTO);
writeToResponse(response, resultJson);
}
}
private void writeToResponse(ServletResponse response, String resultJson) throws IOException {
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(resultJson);
}
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void destroy() {
}
}
@@ -0,0 +1,106 @@
package com.agileboot.infrastructure.exception;
import com.agileboot.common.core.dto.ResponseDTO;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.common.exception.error.ErrorCode.Business;
import com.agileboot.common.exception.error.ErrorCode.Client;
import com.agileboot.common.exception.error.ErrorCode.Internal;
import com.google.common.util.concurrent.UncheckedExecutionException;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理器
*
* @author valarchie
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionInterceptor {
/**
* 权限校验异常
*/
@ExceptionHandler(AccessDeniedException.class)
public ResponseDTO<?> handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) {
log.error("请求地址'{}',权限校验失败'{}'", request.getRequestURI(), e.getMessage());
return ResponseDTO.fail(new ApiException(Business.PERMISSION_NOT_ALLOWED_TO_OPERATE));
}
/**
* 请求方式不支持
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseDTO<?> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
HttpServletRequest request) {
log.error("请求地址'{}',不支持'{}'请求", request.getRequestURI(), e.getMethod());
return ResponseDTO.fail(new ApiException(Client.COMMON_REQUEST_METHOD_INVALID, e.getMethod()));
}
/**
* 业务异常
*/
@ExceptionHandler(ApiException.class)
public ResponseDTO<?> handleServiceException(ApiException e) {
log.error(e.getMessage(), e);
return ResponseDTO.fail(e, e.getPayload());
}
/**
* 捕获缓存类当中的错误
*/
@ExceptionHandler(UncheckedExecutionException.class)
public ResponseDTO<?> handleServiceException(UncheckedExecutionException e) {
log.error(e.getMessage(), e);
return ResponseDTO.fail(new ApiException(Internal.GET_CACHE_FAILED, e.getMessage()));
}
/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public ResponseDTO<?> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
String errorMsg = String.format("请求地址'%s',发生未知异常.", request.getRequestURI());
log.error(errorMsg, e);
return ResponseDTO.fail(new ApiException(Internal.INTERNAL_ERROR, e.getMessage()));
}
/**
* 系统异常
*/
@ExceptionHandler(Exception.class)
public ResponseDTO<?> handleException(Exception e, HttpServletRequest request) {
String errorMsg = String.format("请求地址'%s',发生未知异常.", request.getRequestURI());
log.error(errorMsg, e);
return ResponseDTO.fail(new ApiException(Internal.INTERNAL_ERROR, e.getMessage()));
}
/**
* 自定义验证异常
*/
@ExceptionHandler(BindException.class)
public ResponseDTO<?> handleBindException(BindException e) {
log.error(e.getMessage(), e);
String message = e.getAllErrors().get(0).getDefaultMessage();
return ResponseDTO.fail(new ApiException(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, message));
}
/**
* 自定义验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseDTO<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error(e.getMessage(), e);
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return ResponseDTO.fail(new ApiException(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, message));
}
}
@@ -0,0 +1,33 @@
package com.agileboot.infrastructure.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* 过滤器模板代码
* @author valarchie
*/
public class TestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 实现你的逻辑
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
@@ -0,0 +1,61 @@
package com.agileboot.infrastructure.filter;
import cn.hutool.core.util.StrUtil;
import java.io.IOException;
import java.util.UUID;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
/**
* 给每一个线程分配一个uuid 以便日志可以可以精准查询一条请求的所有日志
* 过滤器
* @author valarchie
*/
@AllArgsConstructor
@Slf4j
public class TraceIdFilter implements Filter {
private String requestIdKey;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
// 生成UUID并存储在MDC中 然后在日志中打印出来
String uuid = UUID.randomUUID().toString();
if (StrUtil.isNotEmpty(requestIdKey)) {
MDC.put(requestIdKey, uuid);
if (request instanceof HttpServletRequest) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 把RequestId放到response的header中去方便追踪
httpResponse.setHeader(requestIdKey, uuid);
}
} else {
// 如果requestIdKey为空的话 说明配置有问题
log.error("traceRequestIdKey 为null, 请检查项目yml中traceRequestIdKey的配置是否正确。");
}
// 继续处理请求
chain.doFilter(request, response);
} finally {
// 清除MDC中的UUID
removeRequestIdSafely(requestIdKey);
}
}
public void removeRequestIdSafely(String requestIdKey) {
try {
MDC.remove(requestIdKey);
} catch (Exception e) {
log.error("traceRequestIdKey 为null, 请检查项目yml中traceRequestIdKey的配置是否正确。", e);
}
}
}
@@ -0,0 +1,52 @@
package com.agileboot.infrastructure.i18n;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.common.exception.error.ErrorCodeInterface;
import com.agileboot.common.utils.i18n.MessageUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
/**
* 检测 未添加到i18n文件(messages.properties)中的message
* @author valarchie
*/
@Component
@Slf4j
public class MessageI18nCheckerRunner implements ApplicationRunner {
@Value("agileboot.checkI18nKey")
private String checkI18nKey;
public static Object[] allErrorCodes = ArrayUtil.addAll(
ErrorCode.Internal.values(),
ErrorCode.External.values(),
ErrorCode.Client.values(),
ErrorCode.Business.values());
@Override
public void run(ApplicationArguments args) {
if (Convert.toBool(checkI18nKey)) {
checkEveryMessage();
}
}
/**
* 如果想支持i18n, 请把对应的错误码描述填到 /resources/i18n/messages.properties 文件中
*/
public void checkEveryMessage() {
for (Object errorCode : allErrorCodes) {
ErrorCodeInterface errorInterface = (ErrorCodeInterface) errorCode;
try {
MessageUtils.message(errorInterface.i18nKey());
} catch (Exception e) {
log.warn("could not find i18n message for:{} in the file /resources/i18n/messages.properties.",
errorInterface.i18nKey());
}
}
}
}
@@ -0,0 +1,85 @@
package com.agileboot.infrastructure.log;
import cn.hutool.json.JSONUtil;
import com.agileboot.common.utils.jackson.JacksonUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @author valarchie
*/
@Aspect
@Component
@Slf4j
public class MethodLogAspect {
@Pointcut("execution(public * com.agileboot..db.*Service.*(..))")
public void dbService() {
}
@Around("dbService()")
public Object aroundDbService(ProceedingJoinPoint joinPoint) throws Throwable {
Object proceed = joinPoint.proceed();
log.info("DB SERVICE : {} ; REQUEST{} ; RESPONSE : {}", joinPoint.getSignature().toShortString(),
safeToJson(joinPoint.getArgs()), safeToJson(proceed));
return proceed;
}
@AfterThrowing(value = "dbService()", throwing = "e")
public void afterDbServiceThrow(JoinPoint joinPoint, Exception e) {
log.error("DB SERVICE : {} ; REQUEST{} ; EXCEPTION : {}", joinPoint.getSignature().toShortString(),
safeToJson(joinPoint.getArgs()), e.getMessage());
}
@Pointcut("bean(*ApplicationService)")
public void applicationServiceLog() {
}
@Around("applicationServiceLog()")
public Object aroundApplicationService(ProceedingJoinPoint joinPoint) throws Throwable {
Object proceed = joinPoint.proceed();
log.info("APPLICATION SERVICE : {} ; REQUEST{} ; RESPONSE : {}", joinPoint.getSignature().toShortString(),
safeToJson(joinPoint.getArgs()), safeToJson(proceed));
return proceed;
}
@AfterThrowing(value = "applicationServiceLog()", throwing = "e")
public void afterApplicationServiceThrow(JoinPoint joinPoint, Exception e) {
log.error("APPLICATION SERVICE : {} ; REQUEST{} ; EXCEPTION : {}", joinPoint.getSignature().toShortString(),
safeToJson(joinPoint.getArgs()), e.getMessage());
}
/**
* 安全的打印出Json字符串 因为Jackson的Json格式化要求比较高,可能会报错
* 如果报错的话 使用Hutool的JSON工具 如果还是报错,直接使用对象的toString方法即可
* 目的只是为了打印参数和返回值
* 逻辑上不用太严格
*/
private String safeToJson(Object o) {
if (o == null) {
return "null";
}
String json = null;
try {
json = JacksonUtil.to(o);
} catch (Exception e) {
json = JSONUtil.toJsonStr(o);
} finally {
if (json == null) {
json = o.toString();
}
}
return json;
}
}
@@ -0,0 +1,258 @@
package com.agileboot.infrastructure.mybatisplus;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONUtil;
import com.agileboot.common.core.base.BaseController;
import com.agileboot.common.core.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig.Builder;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.TemplateType;
import com.baomidou.mybatisplus.generator.config.builder.Entity;
import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert;
import com.baomidou.mybatisplus.generator.config.querys.MySqlQuery;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;
import com.baomidou.mybatisplus.generator.fill.Column;
import com.baomidou.mybatisplus.generator.fill.Property;
import com.baomidou.mybatisplus.generator.keywords.MySqlKeyWordsHandler;
import java.util.Collections;
import lombok.Data;
import org.yaml.snakeyaml.Yaml;
/**
* @author valarchie
*/
@Data
@lombok.Builder
public class CodeGenerator {
private String author;
private String module;
private String tableName;
private String databaseUrl;
private String username;
private String password;
private String parentPackage;
private Boolean isExtendsFromBaseEntity;
/**
* 避免覆盖掉原有生成的类 生成的类 放在orm子模块下的/target/generated-code目录底下
* 有需要更新的实体自己在手动覆盖 或者 挪动过去
*/
public static void main(String[] args) {
// 默认读取application-dev yml中的master数据库配置
// JSON ymlJson = JSONUtil.parse(new Yaml().load(ResourceUtil.getStream("application-dev.yml")));
String databaseUrl = "jdbc:mysql://localhost:33067/agileboot-pure";
String username = "root";
String password = "12345";
CodeGenerator generator = CodeGenerator.builder()
.databaseUrl(databaseUrl)
.username(username)
.password(password)
.author("valarchie")
//生成的类 放在orm子模块下的/target/generated-code目录底下
.module("/agileboot-orm/target/generated-code")
.parentPackage("com.agileboot")
.tableName("sys_menu")
// 决定是否继承基类
.isExtendsFromBaseEntity(true)
.build();
generator.generateCode();
}
public void generateCode() {
FastAutoGenerator generator = FastAutoGenerator.create(
new Builder(databaseUrl, username, password)
// .schema("mybatis-plus")
// all these three options
.dbQuery(new MySqlQuery())
.typeConvert(new MySqlTypeConvert())
.keyWordsHandler(new MySqlKeyWordsHandler()));
globalConfig(generator);
packageConfig(generator);
// templateConfig(generator);
injectionConfig(generator);
strategyConfig(generator);
// 默认的是Velocity引擎模板
generator.templateEngine(new VelocityTemplateEngine());
generator.execute();
}
/**
* 为了避免 覆盖掉service中的方法
* @param generator 生成器
*/
private void globalConfig(FastAutoGenerator generator) {
generator.globalConfig(
builder -> builder
// override old code of file
.fileOverride()
.outputDir(System.getProperty("user.dir") + module + "/src/main/java")
// use date type under package of java utils
.dateType(DateType.ONLY_DATE)
// 配置生成文件中的author
.author(author)
// .enableKotlin()
// generate swagger annotations.
.enableSwagger()
// 注释日期的格式
.commentDate("yyyy-MM-dd")
.build());
}
private void packageConfig(FastAutoGenerator generator) {
generator.packageConfig(builder -> builder
// parent package name
.parent(parentPackage)
.moduleName("orm")
.entity("entity")
.service("service")
.serviceImpl("service.impl")
.mapper("mapper")
.xml("mapper.xml")
.controller("controller")
.other("other")
// define dir related to OutputFileType(entity,mapper,service,controller,mapper.xml)
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, System.getProperty("user.dir") + module
+ "/src/main/resources/mapper/system/test"))
.build());
}
private void templateConfig(FastAutoGenerator generator) {
// customization code template. disable if you don't have specific requirement.
generator.templateConfig(builder -> builder
.disable(TemplateType.ENTITY)
.entity("/templates/entity.java")
.service("/templates/service.java")
.serviceImpl("/templates/serviceImpl.java")
.mapper("/templates/mapper.java")
.mapperXml("/templates/mapper.xml")
.controller("/templates/controller.java")
.build());
}
private void injectionConfig(FastAutoGenerator generator) {
// customization code template. disable if you don't have specific requirement.
generator.injectionConfig(builder -> {
// Customization
builder.beforeOutputFile((tableInfo, objectMap) -> System.out.println("tableInfo: " +
tableInfo.getEntityName() + " objectMap: " + objectMap.size()))
// .customMap(Collections.singletonMap("test", "baomidou"))
// .customFile(Collections.singletonMap("test.txt", "/templates/test.vm"))
.build();
});
}
private void strategyConfig(FastAutoGenerator generator) {
// customization code template. disable if you don't have specific requirement.
generator.strategyConfig(builder -> {
builder
// Global Configuration
.enableCapitalMode()
// does not generate view
.enableSkipView()
.disableSqlFilter()
// filter which tables need to be generated
// .likeTable(new LikeTable("USER"))
// .addInclude("t_simple")
// .addTablePrefix("t_", "c_")
// .addFieldSuffix("_flag")
.addInclude(tableName);
entityConfig(builder);
controllerConfig(builder);
serviceConfig(builder);
mapperConfig(builder);
});
}
private void entityConfig(StrategyConfig.Builder builder) {
Entity.Builder entityBuilder = builder.entityBuilder();
entityBuilder
// .superClass(BaseEntity.class)
// .disableSerialVersionUID()
// .enableChainModel()
.enableLombok()
// boolean field
// .enableRemoveIsPrefix()
.enableTableFieldAnnotation()
// operate entity like JPA.
.enableActiveRecord()
// .versionColumnName("version")
// .versionPropertyName("version")
// deleted的字段设置成tinyint 长度为1
.logicDeleteColumnName("deleted")
// .logicDeletePropertyName("deleteFlag")
.naming(NamingStrategy.underline_to_camel)
.columnNaming(NamingStrategy.underline_to_camel)
// 如果不需要BaseEntity 请注释掉以下两行
// .superClass(BaseEntity.class)
// .addSuperEntityColumns("creator_id", "create_time", "creator_name", "updater_id", "update_time", "updater_name", "deleted")
// .addIgnoreColumns("age")
// 两种配置方式 都可以
.addTableFills(new Column("create_time", FieldFill.INSERT))
.addTableFills(new Column("creator_id", FieldFill.INSERT))
.addTableFills(new Property("updateTime", FieldFill.INSERT_UPDATE))
.addTableFills(new Property("updaterId", FieldFill.INSERT_UPDATE))
// ID strategy AUTO, NONE, INPUT, ASSIGN_ID, ASSIGN_UUID;
.idType(IdType.AUTO)
.formatFileName("%sEntity");
if (isExtendsFromBaseEntity) {
entityBuilder
.superClass(BaseEntity.class)
.addSuperEntityColumns("creator_id", "create_time", "creator_name", "updater_id", "update_time",
"updater_name", "deleted");
}
entityBuilder.build();
}
private void controllerConfig(StrategyConfig.Builder builder) {
builder.controllerBuilder()
.superClass(BaseController.class)
.enableHyphenStyle()
.enableRestStyle()
.formatFileName("%sController")
.build();
}
private void serviceConfig(StrategyConfig.Builder builder) {
builder.serviceBuilder()
// .superServiceClass(BaseService.class)
// .superServiceImplClass(BaseServiceImpl.class)
.formatServiceFileName("%sService")
.formatServiceImplFileName("%sServiceImpl")
.build();
}
private void mapperConfig(StrategyConfig.Builder builder) {
builder.mapperBuilder()
// .superClass(BaseMapper.class)
// .enableMapperAnnotation()
// .enableBaseResultMap()
// .enableBaseColumnList()
// .cache(MyMapperCache.class)
.formatMapperFileName("%sMapper")
.formatXmlFileName("%sMapper")
.build();
}
}
@@ -0,0 +1,62 @@
package com.agileboot.infrastructure.mybatisplus;
import com.agileboot.infrastructure.user.AuthenticationUtils;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import java.util.Date;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
/**
* Mybatis Plus允许在插入或者更新的时候
* 自定义设定值
* @author valarchie
*/
@Component
@Slf4j
public class CustomMetaObjectHandler implements MetaObjectHandler {
public static final String CREATE_TIME_FIELD = "createTime";
public static final String CREATOR_ID_FIELD = "creatorId";
public static final String UPDATE_TIME_FIELD = "updateTime";
public static final String UPDATER_ID_FIELD = "updaterId";
@Override
public void insertFill(MetaObject metaObject) {
if (metaObject.hasSetter(CREATE_TIME_FIELD)) {
this.setFieldValByName(CREATE_TIME_FIELD, new Date(), metaObject);
}
Long userId = getUserIdSafely();
if (metaObject.hasSetter(CREATOR_ID_FIELD) && userId != null) {
this.strictInsertFill(metaObject, CREATOR_ID_FIELD, Long.class, getUserIdSafely());
}
}
@Override
public void updateFill(MetaObject metaObject) {
if (metaObject.hasSetter(UPDATE_TIME_FIELD)) {
this.setFieldValByName(UPDATE_TIME_FIELD, new Date(), metaObject);
}
Long userId = getUserIdSafely();
if (metaObject.hasSetter(UPDATER_ID_FIELD) && userId != null) {
this.strictUpdateFill(metaObject, UPDATER_ID_FIELD, Long.class, getUserIdSafely());
}
}
public Long getUserIdSafely() {
Long userId = null;
try {
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
userId = loginUser.getUserId();
} catch (Exception e) {
log.warn("can not find user in current thread.");
}
return userId;
}
}
@@ -0,0 +1,30 @@
package com.agileboot.infrastructure.mybatisplus;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import java.util.List;
/**
* 由于H2不支持大部分Mysql的函数 所以要自己实现
* 在H2的初始化 h2sql/agileboot_schema.sql加上这句
* CREATE ALIAS FIND_IN_SET FOR "com.agileboot.infrastructure.mybatisplus.MySqlFunction.find_in_set";
*
* @author valarchie
*/
public class MySqlFunction {
private MySqlFunction() {
}
public static boolean findInSet(String target, String setString) {
if (setString == null) {
return false;
}
List<String> split = StrUtil.split(setString, ",");
return CollUtil.contains(split, target);
}
}
@@ -0,0 +1,61 @@
package com.agileboot.infrastructure.schedule;
import cn.hutool.core.date.DateUtil;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
/**
* 如果想开启定时任务 请不要注释@Component注解
* @author valarchie
*/
//@Component
@Slf4j
public class ScheduleJobManager {
/**
* fixedRate:固定速率执行。每60秒执行一次。
*/
@Scheduled(fixedRate = 60000)
public void reportCurrentTimeWithFixedRate() {
log.info("Current Thread : {}, Fixed Rate Task : The time is now {}",
Thread.currentThread().getName(), DateUtil.formatTime(new Date()));
}
/**
* fixedDelay:固定延迟执行。距离上一次调用成功后30秒才执。
*/
@Scheduled(fixedDelay = 30000)
public void reportCurrentTimeWithFixedDelay() {
try {
TimeUnit.SECONDS.sleep(60);
log.info("Current Thread : {}, Fixed Delay Task : The time is now {}",
Thread.currentThread().getName(), DateUtil.formatTime(new Date()));
} catch (InterruptedException e) {
log.error("计划任务执行失败", e);
}
}
/**
* initialDelay:初始延迟。任务的第一次执行将延迟30秒,然后将以60秒的固定间隔执行。
*/
@Scheduled(initialDelay = 30000, fixedRate = 60000)
public void reportCurrentTimeWithInitialDelay() {
log.info("Current Thread : {}, Fixed Rate Task with Initial Delay : The time is now {}",
Thread.currentThread().getName(), DateUtil.formatTime(new Date()));
}
/**
* cron:使用Cron表达式。 每分钟的1,2秒运行
* <a href="https://cron.qqe2.com/">https://cron.qqe2.com/</a>
* cron表达式 在线解析
*/
@Scheduled(cron = "1-2 * * * * ? ")
public void reportCurrentTimeWithCronExpression() {
log.info("Current Thread : {}, Cron Expression: The time is now {}",
Thread.currentThread().getName(), DateUtil.formatTime(new Date()));
}
}
@@ -0,0 +1,23 @@
package com.agileboot.infrastructure.security;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.RSA;
/**
* Rsa key生成
* @author valarchie
*/
public class RsaKeyPairGenerator {
public static void main(String[] args) {
RSA rsa = SecureUtil.rsa();
String privateKeyBase64 = rsa.getPrivateKeyBase64();
String publicKeyBase64 = rsa.getPublicKeyBase64();
System.out.println(privateKeyBase64);
System.out.println(publicKeyBase64);
}
}
@@ -0,0 +1,34 @@
package com.agileboot.infrastructure.security.xss;
import cn.hutool.http.HtmlUtil;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
/**
* 直接将html标签去掉
* @author valarchie
*/
public class JsonHtmlXssTrimSerializer extends JsonDeserializer<String> {
public JsonHtmlXssTrimSerializer() {
super();
}
@Override
public String deserialize(JsonParser p, DeserializationContext context) throws IOException {
String value = p.getValueAsString();
if( value != null) {
// 去除掉html标签 如果想要转义的话 可使用 HtmlUtil.escape()
return HtmlUtil.cleanHtmlTag(value);
}
return null;
}
@Override
public Class<String> handledType() {
return String.class;
}
}
@@ -0,0 +1,32 @@
package com.agileboot.infrastructure.thread;
import javax.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 确保应用退出时能关闭后台线程
*
* @author ruoyi
*/
@Component
@Slf4j
public class ShutdownHook {
@PreDestroy
public void destroy() {
shutdownAllThreadPool();
}
/**
* 停止异步执行任务
*/
private void shutdownAllThreadPool() {
try {
log.info("close thread pool");
ThreadPoolManager.shutdown();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
@@ -0,0 +1,23 @@
package com.agileboot.infrastructure.thread;
/**
* @author valarchie
*/
public class ThreadConfig {
public static final int CORE_POOL_SIZE = 50;
public static final int MAX_POOL_SIZE = 200;
public static final int QUEUE_CAPACITY = 1000;
public static final int KEEP_ALIVE_SECONDS = 300;
/**
* 操作延迟10毫秒
*/
public static final int OPERATE_DELAY_TIME = 10;
private ThreadConfig() {
}
}
@@ -0,0 +1,72 @@
package com.agileboot.infrastructure.thread;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import java.util.TimerTask;
import java.util.concurrent.*;
/**
* 异步任务管理器
*
* @author valarchie
*/
@Slf4j
public class ThreadPoolManager {
private static final ThreadPoolExecutor THREAD_EXECUTOR = new ThreadPoolExecutor(
ThreadConfig.CORE_POOL_SIZE, ThreadConfig.MAX_POOL_SIZE,
ThreadConfig.KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
new SynchronousQueue<>(), new ThreadPoolExecutor.CallerRunsPolicy());
private static final ScheduledExecutorService SCHEDULED_EXECUTOR = new ScheduledThreadPoolExecutor(
ThreadConfig.CORE_POOL_SIZE,
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
new ThreadPoolExecutor.CallerRunsPolicy()) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
if (t == null && r instanceof Future<?>) {
try {
Future<?> future = (Future<?>) r;
if (future.isDone()) {
future.get();
}
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
if (t != null) {
log.error(t.getMessage(), t);
}
}
};
private ThreadPoolManager() {
}
/**
* 执行schedule任务
*/
public static void schedule(TimerTask task) {
SCHEDULED_EXECUTOR.schedule(task, ThreadConfig.OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
}
/**
* 执行异步任务任务
*/
public static void execute(Runnable task) {
THREAD_EXECUTOR.execute(task);
}
/**
* 停止任务线程池
*/
public static void shutdown() {
THREAD_EXECUTOR.shutdown();
SCHEDULED_EXECUTOR.shutdown();
}
}
@@ -0,0 +1,85 @@
package com.agileboot.infrastructure.user;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.infrastructure.user.app.AppLoginUser;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* 安全服务工具类
*
* @author valarchie
*/
public class AuthenticationUtils {
private AuthenticationUtils() {}
/**
* 用户ID
**/
public static Long getUserId() {
try {
return getSystemLoginUser().getUserId();
} catch (Exception e) {
throw new ApiException(ErrorCode.Business.USER_FAIL_TO_GET_USER_ID);
}
}
/**
* 获取系统用户
**/
public static SystemLoginUser getSystemLoginUser() {
try {
return (SystemLoginUser) getAuthentication().getPrincipal();
} catch (Exception e) {
throw new ApiException(ErrorCode.Business.USER_FAIL_TO_GET_USER_INFO);
}
}
/**
* 获取App用户
**/
public static AppLoginUser getAppLoginUser() {
try {
return (AppLoginUser) getAuthentication().getPrincipal();
} catch (Exception e) {
throw new ApiException(ErrorCode.Business.USER_FAIL_TO_GET_USER_INFO);
}
}
/**
* 获取Authentication
*/
public static Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
/**
* 生成BCryptPasswordEncoder密码
*
* @param password 密码
* @return 加密字符串
*/
public static String encryptPassword(String password) {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder.encode(password);
}
/**
* 判断密码是否相同
*
* @param rawPassword 真实密码
* @param encodedPassword 加密后字符
* @return 结果
*/
public static boolean matchesPassword(String rawPassword, String encodedPassword) {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder.matches(rawPassword, encodedPassword);
}
}
@@ -0,0 +1,27 @@
package com.agileboot.infrastructure.user.app;
import com.agileboot.infrastructure.user.base.BaseLoginUser;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 登录用户身份权限
* @author valarchie
*/
@Data
@NoArgsConstructor
public class AppLoginUser extends BaseLoginUser {
private static final long serialVersionUID = 1L;
private boolean isVip;
public AppLoginUser(Long userId, Boolean isVip, String cachedKey) {
this.cachedKey = cachedKey;
this.userId = userId;
this.isVip = isVip;
}
}
@@ -0,0 +1,129 @@
package com.agileboot.infrastructure.user.base;
import cn.hutool.extra.servlet.ServletUtil;
import com.agileboot.common.utils.ServletHolderUtil;
import com.agileboot.common.utils.ip.IpRegionUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import eu.bitwalker.useragentutils.UserAgent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
/**
* 登录用户身份权限
* @author valarchie
*/
@Data
@NoArgsConstructor
public class BaseLoginUser implements UserDetails {
private static final long serialVersionUID = 1L;
protected Long userId;
/**
* 用户唯一标识,缓存的key
*/
protected String cachedKey;
protected String username;
protected String password;
protected List<GrantedAuthority> authorities = new ArrayList<>();
/**
* 登录信息
*/
protected final LoginInfo loginInfo = new LoginInfo();
public BaseLoginUser(Long userId, String username, String password) {
this.userId = userId;
this.username = username;
this.password = password;
}
/**
* 设置用户代理信息
*
*/
public void fillLoginInfo() {
UserAgent userAgent = UserAgent.parseUserAgentString(ServletHolderUtil.getRequest().getHeader("User-Agent"));
String ip = ServletUtil.getClientIP(ServletHolderUtil.getRequest());
this.getLoginInfo().setIpAddress(ip);
this.getLoginInfo().setLocation(IpRegionUtil.getBriefLocationByIp(ip));
this.getLoginInfo().setBrowser(userAgent.getBrowser().getName());
this.getLoginInfo().setOperationSystem(userAgent.getOperatingSystem().getName());
this.getLoginInfo().setLoginTime(System.currentTimeMillis());
}
public void grantAppPermission(String appName) {
authorities.add(new SimpleGrantedAuthority(appName));
}
@Override
public String getUsername() {
return this.username;
}
@JsonIgnore
@Override
public String getPassword() {
return this.password;
}
/**
* 账户是否未过期,过期无法验证
* 未实现此功能
*/
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 指定用户是否解锁,锁定的用户无法进行身份验证
* 未实现此功能
*/
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
* 未实现此功能
*/
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 是否可用 ,禁用的用户不能身份验证
* 未实现此功能
*/
@Override
public boolean isEnabled() {
return true;
}
@JsonIgnore
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
}
@@ -0,0 +1,36 @@
package com.agileboot.infrastructure.user.base;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class LoginInfo {
/**
* 登录IP地址
*/
private String ipAddress;
/**
* 登录地点
*/
private String location;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String operationSystem;
/**
* 登录时间
*/
private Long loginTime;
}
@@ -0,0 +1,42 @@
package com.agileboot.infrastructure.user.web;
import com.agileboot.common.enums.BasicEnum;
/**
* 对应sys_role表的data_scope字段
* @author valarchie
*/
public enum DataScopeEnum implements BasicEnum<Integer> {
/**
* 数据权限范围
*/
ALL(1, "所有数据权限"),
CUSTOM_DEFINE(2, "自定义数据权限"),
SINGLE_DEPT(3, "本部门数据权限"),
DEPT_TREE(4, "本部门以及子孙部门数据权限"),
ONLY_SELF(5, "仅本人数据权限");
private final int value;
private final String description;
DataScopeEnum(int value, String description) {
this.value = value;
this.description = description;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String description() {
return description;
}
public String getDescription() {
return description;
}
}
@@ -0,0 +1,44 @@
package com.agileboot.infrastructure.user.web;
import java.util.Set;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.collections4.SetUtils;
/**
* @author valarchie
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RoleInfo {
public static final RoleInfo EMPTY_ROLE = new RoleInfo();
public static final long ADMIN_ROLE_ID = -1;
public static final String ADMIN_ROLE_KEY = "admin";
public static final String ALL_PERMISSIONS = "*:*:*";
public static final Set<String> ADMIN_PERMISSIONS = SetUtils.hashSet(ALL_PERMISSIONS);
public RoleInfo(Long roleId, String roleKey, DataScopeEnum dataScope, Set<Long> deptIdSet,
Set<String> menuPermissions, Set<Long> menuIds) {
this.roleId = roleId;
this.roleKey = roleKey;
this.dataScope = dataScope;
this.deptIdSet = deptIdSet;
this.menuPermissions = menuPermissions != null ? menuPermissions : SetUtils.emptySet();
this.menuIds = menuIds != null ? menuIds : SetUtils.emptySet();
}
private Long roleId;
private String roleName;
private DataScopeEnum dataScope;
private Set<Long> deptIdSet;
private String roleKey;
private Set<String> menuPermissions;
private Set<Long> menuIds;
}
@@ -0,0 +1,52 @@
package com.agileboot.infrastructure.user.web;
import com.agileboot.infrastructure.user.base.BaseLoginUser;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 登录用户身份权限
* @author valarchie
*/
@Data
@NoArgsConstructor
public class SystemLoginUser extends BaseLoginUser {
private static final long serialVersionUID = 1L;
private boolean isAdmin;
private Long deptId;
private RoleInfo roleInfo;
/**
* 当超过这个时间 则触发刷新缓存时间
*/
private Long autoRefreshCacheTime;
public SystemLoginUser(Long userId, Boolean isAdmin, String username, String password, RoleInfo roleInfo,
Long deptId) {
this.userId = userId;
this.isAdmin = isAdmin;
this.username = username;
this.password = password;
this.roleInfo = roleInfo;
this.deptId = deptId;
}
public RoleInfo getRoleInfo() {
return roleInfo;
}
public Long getRoleId() {
return getRoleInfo().getRoleId();
}
public Long getDeptId() {
return deptId;
}
}
@@ -0,0 +1,129 @@
# 基础公用的配置参数放置在这份yml中
# 项目相关配置
agileboot:
# 名称
name: AgileBoot
# 版本
version: 1.8.0
# 版权年份
copyright-year: 2022
# 实例演示开关
demo-enabled: true
# 获取ip地址开关
addressEnabled: false
# 验证码类型 math 数组计算 char 字符验证
captchaType: math
# 对应的公钥放在前端项目中的utils/rsaUtils类中
rsaPrivateKey: MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAKHoeQr6sIzfsUDMIdXK2FNzqnG+vnnUrTsJGF1f82MJ10cC5Aw53/ntHl+IjCBU0R8KwPVwjJLoDwhaPgO0ktHa3b0l6E+mqIVIniE20Nws3E0mJhFwJC/IxB7JAg8TkuNzflzvZjAO1ACwgkKGxq7Iutz4TocLeCfUkObXwc1fAgMBAAECgYAWwCzqDwnp8bDdkxGaEhPNvi4QJ6ZqRilFZ2TGEiqIGyTl9JEI6sT/QIOJFw3hqSltfDxbAMKwDe221b9rE9+hZhE2rrpwcTKuehob9Z8CObYeUHR9HG7Qb2tYRElvSCWo74iz2zajXAvJLjIE4MPuPYqXC5zOabH+EJ/eaOzVwQJBANmRkMlb+qzp1GWuqFMHP+5MeYhFwUHVX7fxKNA24oHldX8zjPIZ6d3vaRfliTvxOaz1T80acvJkkb7zHBmaW38CQQC+gfF8Lg+nvBY/S3wfOPL8FcntP16jdFhNNZmbOxq72ZmCfl5Zk5cYNBc4rDSrd9Sj4TkLLug+wrK6Wr117P4hAkBOVxnZR2NVy8SM8HzvmJauiZ7hMKzLtbcHlrBpeLnKqALM0JUZv7b0EPa4ghAOI2fvHU2kvrdRDGFmbkdZ+LilAkBnX8eT5MKl+A/yZJmDr7laRNB/poVKGNXZf55Md3P4Pwlnn/6+iLHSdmGrZPZnnOyLyKjVgqyPccLeEGMCXIlBAkAt2OMwss16OH2x79OcfBrabU5iCVbDHg56JYGbWP8KcPfvspxtL/4TdACRsa+yCMcI6L29Q4wn791SEEnE834a
# 是否检查 i18n 资源文件(messages.properties)是否都有对应的键值对
checkI18nKey: false
traceRequestIdKey: AG-RequestId
## 开发环境配置
#server:
# # 服务器的HTTP端口,默认为8080
# port: 8080
# servlet:
# # 应用的访问路径
# context-path: /
# tomcat:
# # tomcat的URI编码
# uri-encoding: UTF-8
# # 连接数满后的排队数,默认为100
# accept-count: 1000
# threads:
# # tomcat最大线程数,默认为200
# max: 800
# # Tomcat启动初始化的线程数,默认值10
# min-spare: 100
# 日志配置
logging:
level:
# 记得配置到包名
com.agileboot: debug
org.springframework: info
pattern:
console: "%date requestId:%X{${agileboot.traceRequestIdKey}} %thread %green(%level) [%cyan(%logger{10}):%magenta(%line)] %red(%method) | %msg%n"
# Spring配置
spring:
# 资源信息
messages:
# 国际化资源文件路径
basename: i18n/messages
# 有些版本的 IDEA 会自动设置properties的编码为IOS-8859-1 请在IDEA配置里设置成UTF8
encoding: UTF-8
# 文件上传
servlet:
multipart:
# 单个文件大小
max-file-size: 10MB
# 设置总上传的文件大小
max-request-size: 20MB
# 服务模块
devtools:
restart:
# 热部署开关 线上记得关闭
enabled: false
# compatible with swagger
mvc:
pathmatch:
matching-strategy: ANT_PATH_MATCHER
jackson:
deserialization:
fail-on-unknown-properties: false
serialization:
write-dates-as-timestamps: false
date-format: yyyy-MM-dd HH:mm:ss
# token配置
token:
# 令牌自定义标识
header: Authorization
# 令牌密钥 TODO 记得更换
secret: sdhfkjshBN6rr32df38
# 自动刷新token的时间
autoRefreshTime: 20
# MyBatis配置
mybatis-plus:
# 搜索指定包别名 使用简短的类型名称来代替完整的类型名称
# typeAliasesPackage: com.agileboot.orm.*
# 配置mapper的扫描,找到所有的mapper.xml映射文件
mapperLocations: classpath*:mapper/**/*Mapper.xml
# 加载全局的配置文件
configuration:
cacheEnabled: true
useGeneratedKeys: true
defaultExecutorType: SIMPLE
logImpl: org.apache.ibatis.logging.slf4j.Slf4jImpl
global-config:
refresh: true
# PageHelper分页插件
pagehelper:
helperDialect: mysql
supportMethodsArguments: true
params: count=countSql
springdoc:
api-docs:
enabled: true
path: /v3/api-docs
groups:
enabled: true
# 配置需要生成接口文档的扫描包
packages-to-scan: com.agileboot
@@ -0,0 +1,8 @@
Application Version: ${revision}
Spring Boot Version: ${spring-boot.version}
_ _ _ ____ _
/ \ __ _ (_)| | ___ | __ ) ___ ___ | |_
/ _ \ / _` || || | / _ \| _ \ / _ \ / _ \ | __|
/ ___ \| (_| || || || __/| |_) || (_) || (_) || |_
/_/ \_\\__, ||_||_| \___||____/ \___/ \___/ \__|
|___/
@@ -0,0 +1,225 @@
/*
Navicat MySQL Data Transfer
Source Server : Local-Mysql
Source Server Version : 80029
Source Host : localhost:33066
Source Database : agileboot
Target Server Type : MYSQL
Target Server Version : 80029
File Encoding : 65001
Date: 2022-10-07 16:05:40
*/
-- SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Records of sys_config
-- ----------------------------
INSERT INTO `sys_config` VALUES ('1', '主框架页-默认皮肤样式名称', 'sys.index.skinName', '["skin-blue","skin-green","skin-purple","skin-red","skin-yellow"]', 'skin-blue', '1', null, null, '2022-08-28 22:12:19', '2022-05-21 08:30:55', '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow', '0');
INSERT INTO `sys_config` VALUES ('2', '用户管理-账号初始密码', 'sys.user.initPassword', '', '1234567', '1', null, null, '2022-08-28 21:54:19', '2022-05-21 08:30:55', '初始化密码 123456', '0');
INSERT INTO `sys_config` VALUES ('3', '主框架页-侧边栏主题', 'sys.index.sideTheme', '["theme-dark","theme-light"]', 'theme-dark', '1', null, null, '2022-08-28 22:12:15', '2022-08-20 08:30:55', '深色主题theme-dark,浅色主题theme-light', '0');
INSERT INTO `sys_config` VALUES ('4', '账号自助-验证码开关', 'sys.account.captchaOnOff', '["true","false"]', 'false', '0', null, null, '2022-08-28 22:03:37', '2022-05-21 08:30:55', '是否开启验证码功能(true开启,false关闭)', '0');
INSERT INTO `sys_config` VALUES ('5', '账号自助-是否开启用户注册功能', 'sys.account.registerUser', '["true","false"]', 'true', '0', null, '1', '2022-10-05 22:18:57', '2022-05-21 08:30:55', '是否开启注册用户功能(true开启,false关闭)', '0');
-- ----------------------------
-- Records of sys_dept
-- ----------------------------
INSERT INTO `sys_dept` VALUES ('1', '0', '0', 'AgileBoot科技', '0', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_dept` VALUES ('2', '1', '0,1', '深圳总公司', '1', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_dept` VALUES ('3', '1', '0,1', '长沙分公司', '2', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_dept` VALUES ('4', '2', '0,1,2', '研发部门', '1', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_dept` VALUES ('5', '2', '0,1,2', '市场部门', '2', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_dept` VALUES ('6', '2', '0,1,2', '测试部门', '3', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_dept` VALUES ('7', '2', '0,1,2', '财务部门', '4', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_dept` VALUES ('8', '2', '0,1,2', '运维部门', '5', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_dept` VALUES ('9', '3', '0,1,3', '市场部门', '1', null, 'valarchie', '15888888888', 'valarchie@163.com', '1', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_dept` VALUES ('10', '3', '0,1,3', '财务部门', '2', null, 'valarchie', '15888888888', 'valarchie@163.com', '0', null, '2022-05-21 08:30:54', null, null, '0');
-- ----------------------------
-- Records of sys_login_info
-- ----------------------------
-- ----------------------------
-- Records of sys_menu
-- ----------------------------
INSERT INTO sys_menu VALUES (1, '系统管理', 2, '', 0, '/system', 0, '', '{"title":"系统管理","icon":"ep:management","showParent":1,"rank":1}', 1, '系统管理目录', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:08:50', 0);
INSERT INTO sys_menu VALUES (2, '系统监控', 2, '', 0, '/monitor', 0, '', '{"title":"系统监控","icon":"ep:monitor","showParent":1,"rank":3}', 1, '系统监控目录', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:09:15', 0);
INSERT INTO sys_menu VALUES (3, '系统工具', 2, '', 0, '/tool', 0, '', '{"title":"系统工具","icon":"ep:tools","showParent":1,"rank":2}', 1, '系统工具目录', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:09:03', 0);
INSERT INTO sys_menu VALUES (4, 'AgileBoot官网', 3, 'AgileBootguanwangIframeRouter', 0, '/AgileBootguanwangIframeLink', 0, '', '{"title":"AgileBoot官网","icon":"ep:link","showParent":1,"frameSrc":"https://element-plus.org/zh-CN/","rank":8}', 1, 'Agileboot官网地址', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:09:40', 0);
INSERT INTO sys_menu VALUES (5, '用户管理', 1, 'SystemUser', 1, '/system/user/index', 0, 'system:user:list', '{"title":"用户管理","icon":"ep:user-filled","showParent":1}', 1, '用户管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:16:13', 0);
INSERT INTO sys_menu VALUES (6, '角色管理', 1, 'SystemRole', 1, '/system/role/index', 0, 'system:role:list', '{"title":"角色管理","icon":"ep:user","showParent":1}', 1, '角色管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:16:23', 0);
INSERT INTO sys_menu VALUES (7, '菜单管理', 1, 'MenuManagement', 1, '/system/menu/index', 0, 'system:menu:list', '{"title":"菜单管理","icon":"ep:menu","showParent":1}', 1, '菜单管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:15:41', 0);
INSERT INTO sys_menu VALUES (8, '部门管理', 1, 'Department', 1, '/system/dept/index', 0, 'system:dept:list', '{"title":"部门管理","icon":"fa-solid:code-branch","showParent":1}', 1, '部门管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:15:35', 0);
INSERT INTO sys_menu VALUES (9, '岗位管理', 1, 'Post', 1, '/system/post/index', 0, 'system:post:list', '{"title":"岗位管理","icon":"ep:postcard","showParent":1}', 1, '岗位管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:15:11', 0);
INSERT INTO sys_menu VALUES (10, '参数设置', 1, 'Config', 1, '/system/config/index', 0, 'system:config:list', '{"title":"参数设置","icon":"ep:setting","showParent":1}', 1, '参数设置菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:15:03', 0);
INSERT INTO sys_menu VALUES (11, '通知公告', 1, 'SystemNotice', 1, '/system/notice/index', 0, 'system:notice:list', '{"title":"通知公告","icon":"ep:notification","showParent":1}', 1, '通知公告菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:14:56', 0);
INSERT INTO sys_menu VALUES (12, '日志管理', 1, 'LogManagement', 1, '/system/logd', 0, '', '{"title":"日志管理","icon":"ep:document","showParent":1}', 1, '日志管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:14:47', 0);
INSERT INTO sys_menu VALUES (13, '在线用户', 1, 'OnlineUser', 2, '/system/monitor/onlineUser/index', 0, 'monitor:online:list', '{"title":"在线用户","icon":"fa-solid:users","showParent":1}', 1, '在线用户菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:13:13', 0);
INSERT INTO sys_menu VALUES (14, '数据监控', 1, 'DataMonitor', 2, '/system/monitor/druid/index', 0, 'monitor:druid:list', '{"title":"数据监控","icon":"fa:database","showParent":1,"frameSrc":"/druid/login.html","isFrameSrcInternal":1}', 1, '数据监控菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:13:25', 0);
INSERT INTO sys_menu VALUES (15, '服务监控', 1, 'ServerInfo', 2, '/system/monitor/server/index', 0, 'monitor:server:list', '{"title":"服务监控","icon":"fa:server","showParent":1}', 1, '服务监控菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:13:34', 0);
INSERT INTO sys_menu VALUES (16, '缓存监控', 1, 'CacheInfo', 2, '/system/monitor/cache/index', 0, 'monitor:cache:list', '{"title":"缓存监控","icon":"ep:reading","showParent":1}', 1, '缓存监控菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:12:59', 0);
INSERT INTO sys_menu VALUES (17, '系统接口', 1, 'SystemAPI', 3, '/tool/swagger/index', 0, 'tool:swagger:list', '{"title":"系统接口","icon":"ep:document-remove","showParent":1,"frameSrc":"/swagger-ui/index.html","isFrameSrcInternal":1}', 1, '系统接口菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:14:01', 0);
INSERT INTO sys_menu VALUES (18, '操作日志', 1, 'OperationLog', 12, '/system/log/operationLog/index', 0, 'monitor:operlog:list', '{"title":"操作日志"}', 1, '操作日志菜单', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (19, '登录日志', 1, 'LoginLog', 12, '/system/log/loginLog/index', 0, 'monitor:logininfor:list', '{"title":"登录日志"}', 1, '登录日志菜单', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (20, '用户查询', 0, ' ', 5, '', 1, 'system:user:query', '{"title":"用户查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (21, '用户新增', 0, ' ', 5, '', 1, 'system:user:add', '{"title":"用户新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (22, '用户修改', 0, ' ', 5, '', 1, 'system:user:edit', '{"title":"用户修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (23, '用户删除', 0, ' ', 5, '', 1, 'system:user:remove', '{"title":"用户删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (24, '用户导出', 0, ' ', 5, '', 1, 'system:user:export', '{"title":"用户导出"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (25, '用户导入', 0, ' ', 5, '', 1, 'system:user:import', '{"title":"用户导入"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (26, '重置密码', 0, ' ', 5, '', 1, 'system:user:resetPwd', '{"title":"重置密码"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (27, '角色查询', 0, ' ', 6, '', 1, 'system:role:query', '{"title":"角色查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (28, '角色新增', 0, ' ', 6, '', 1, 'system:role:add', '{"title":"角色新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (29, '角色修改', 0, ' ', 6, '', 1, 'system:role:edit', '{"title":"角色修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (30, '角色删除', 0, ' ', 6, '', 1, 'system:role:remove', '{"title":"角色删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (31, '角色导出', 0, ' ', 6, '', 1, 'system:role:export', '{"title":"角色导出"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (32, '菜单查询', 0, ' ', 7, '', 1, 'system:menu:query', '{"title":"菜单查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (33, '菜单新增', 0, ' ', 7, '', 1, 'system:menu:add', '{"title":"菜单新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (34, '菜单修改', 0, ' ', 7, '', 1, 'system:menu:edit', '{"title":"菜单修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (35, '菜单删除', 0, ' ', 7, '', 1, 'system:menu:remove', '{"title":"菜单删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (36, '部门查询', 0, ' ', 8, '', 1, 'system:dept:query', '{"title":"部门查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (37, '部门新增', 0, ' ', 8, '', 1, 'system:dept:add', '{"title":"部门新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (38, '部门修改', 0, ' ', 8, '', 1, 'system:dept:edit', '{"title":"部门修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (39, '部门删除', 0, ' ', 8, '', 1, 'system:dept:remove', '{"title":"部门删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (40, '岗位查询', 0, ' ', 9, '', 1, 'system:post:query', '{"title":"岗位查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (41, '岗位新增', 0, ' ', 9, '', 1, 'system:post:add', '{"title":"岗位新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (42, '岗位修改', 0, ' ', 9, '', 1, 'system:post:edit', '{"title":"岗位修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (43, '岗位删除', 0, ' ', 9, '', 1, 'system:post:remove', '{"title":"岗位删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (44, '岗位导出', 0, ' ', 9, '', 1, 'system:post:export', '{"title":"岗位导出"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (45, '参数查询', 0, ' ', 10, '', 1, 'system:config:query', '{"title":"参数查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (46, '参数新增', 0, ' ', 10, '', 1, 'system:config:add', '{"title":"参数新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (47, '参数修改', 0, ' ', 10, '', 1, 'system:config:edit', '{"title":"参数修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (48, '参数删除', 0, ' ', 10, '', 1, 'system:config:remove', '{"title":"参数删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (49, '参数导出', 0, ' ', 10, '', 1, 'system:config:export', '{"title":"参数导出"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (50, '公告查询', 0, ' ', 11, '', 1, 'system:notice:query', '{"title":"公告查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (51, '公告新增', 0, ' ', 11, '', 1, 'system:notice:add', '{"title":"公告新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (52, '公告修改', 0, ' ', 11, '', 1, 'system:notice:edit', '{"title":"公告修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (53, '公告删除', 0, ' ', 11, '', 1, 'system:notice:remove', '{"title":"公告删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (54, '操作查询', 0, ' ', 18, '', 1, 'monitor:operlog:query', '{"title":"操作查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (55, '操作删除', 0, ' ', 18, '', 1, 'monitor:operlog:remove', '{"title":"操作删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (56, '日志导出', 0, ' ', 18, '', 1, 'monitor:operlog:export', '{"title":"日志导出"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (57, '登录查询', 0, ' ', 19, '', 1, 'monitor:logininfor:query', '{"title":"登录查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (58, '登录删除', 0, ' ', 19, '', 1, 'monitor:logininfor:remove', '{"title":"登录删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (59, '日志导出', 0, ' ', 19, '', 1, 'monitor:logininfor:export', '{"title":"日志导出","rank":22}', 1, '', 0, '2022-05-21 08:30:54', 1, '2023-07-22 17:02:28', 0);
INSERT INTO sys_menu VALUES (60, '在线查询', 0, ' ', 13, '', 1, 'monitor:online:query', '{"title":"在线查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (61, '批量强退', 0, ' ', 13, '', 1, 'monitor:online:batchLogout', '{"title":"批量强退"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (62, '单条强退', 0, ' ', 13, '', 1, 'monitor:online:forceLogout', '{"title":"单条强退"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO sys_menu VALUES (63, 'AgileBoot Github地址', 4, 'https://github.com/valarchie/AgileBoot-Back-End', 0, '/external', 0, '', '{"title":"AgileBoot Github地址","icon":"fa-solid:external-link-alt","showParent":1,"rank":9}', 1, 'Agileboot github地址', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:12:13', 0);
INSERT INTO sys_menu VALUES (64, '首页', 2, '', 0, '/global', 0, '121212', '{"title":"首页","showParent":1,"rank":3}', 1, '', 1, '2023-07-24 22:36:03', 1, '2023-07-24 22:38:37', 1);
INSERT INTO sys_menu VALUES (65, '个人中心', 1, 'PersonalCenter', 2053, '/system/user/profile', 0, '434sdf', '{"title":"个人中心","showParent":1,"rank":3}', 1, '', 1, '2023-07-24 22:36:55', null, null, 1);
-- ----------------------------
-- Records of sys_notice
-- ----------------------------
INSERT INTO `sys_notice` VALUES ('1', '温馨提醒:2018-07-01 AgileBoot新版本发布啦', '2', '新版本内容~~~~~~~~~~', '1', '1', '2022-05-21 08:30:55', '1', '2022-08-29 20:12:37', '管理员', '0');
INSERT INTO `sys_notice` VALUES ('2', '维护通知:2018-07-01 AgileBoot系统凌晨维护', '1', '维护内容', '1', '1', '2022-05-21 08:30:55', null, null, '管理员', '0');
-- ----------------------------
-- Records of sys_operation_log
-- ----------------------------
-- ----------------------------
-- Records of sys_post
-- ----------------------------
INSERT INTO `sys_post` VALUES ('1', 'ceo', '董事长', '1', '1', '', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_post` VALUES ('2', 'se', '项目经理', '2', '1', '', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_post` VALUES ('3', 'hr', '人力资源', '3', '1', '', null, '2022-05-21 08:30:54', null, null, '0');
INSERT INTO `sys_post` VALUES ('4', 'user', '普通员工', '5', '0', '', null, '2022-05-21 08:30:54', null, null, '0');
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES ('1', '超级管理员', 'admin', '1', '1', '', '1', null, '2022-05-21 08:30:54', null, null, '超级管理员', '0');
INSERT INTO `sys_role` VALUES ('2', '普通角色', 'common', '3', '2', '', '1', null, '2022-05-21 08:30:54', null, null, '普通角色', '0');
INSERT INTO `sys_role` VALUES ('3', '闲置角色', 'unused', '4', '2', '', '0', null, '2022-05-21 08:30:54', null, null, '未使用的角色', '0');
-- ----------------------------
-- Records of sys_role_menu
-- ----------------------------
INSERT INTO `sys_role_menu` VALUES ('2', '1');
INSERT INTO `sys_role_menu` VALUES ('2', '2');
INSERT INTO `sys_role_menu` VALUES ('2', '3');
INSERT INTO `sys_role_menu` VALUES ('2', '4');
INSERT INTO `sys_role_menu` VALUES ('2', '5');
INSERT INTO `sys_role_menu` VALUES ('2', '6');
INSERT INTO `sys_role_menu` VALUES ('2', '7');
INSERT INTO `sys_role_menu` VALUES ('2', '8');
INSERT INTO `sys_role_menu` VALUES ('2', '9');
INSERT INTO `sys_role_menu` VALUES ('2', '10');
INSERT INTO `sys_role_menu` VALUES ('2', '11');
INSERT INTO `sys_role_menu` VALUES ('2', '12');
INSERT INTO `sys_role_menu` VALUES ('2', '13');
INSERT INTO `sys_role_menu` VALUES ('2', '14');
INSERT INTO `sys_role_menu` VALUES ('2', '15');
INSERT INTO `sys_role_menu` VALUES ('2', '16');
INSERT INTO `sys_role_menu` VALUES ('2', '17');
INSERT INTO `sys_role_menu` VALUES ('2', '18');
INSERT INTO `sys_role_menu` VALUES ('2', '19');
INSERT INTO `sys_role_menu` VALUES ('2', '20');
INSERT INTO `sys_role_menu` VALUES ('2', '21');
INSERT INTO `sys_role_menu` VALUES ('2', '22');
INSERT INTO `sys_role_menu` VALUES ('2', '23');
INSERT INTO `sys_role_menu` VALUES ('2', '24');
INSERT INTO `sys_role_menu` VALUES ('2', '25');
INSERT INTO `sys_role_menu` VALUES ('2', '26');
INSERT INTO `sys_role_menu` VALUES ('2', '27');
INSERT INTO `sys_role_menu` VALUES ('2', '28');
INSERT INTO `sys_role_menu` VALUES ('2', '29');
INSERT INTO `sys_role_menu` VALUES ('2', '30');
INSERT INTO `sys_role_menu` VALUES ('2', '31');
INSERT INTO `sys_role_menu` VALUES ('2', '32');
INSERT INTO `sys_role_menu` VALUES ('2', '33');
INSERT INTO `sys_role_menu` VALUES ('2', '34');
INSERT INTO `sys_role_menu` VALUES ('2', '35');
INSERT INTO `sys_role_menu` VALUES ('2', '36');
INSERT INTO `sys_role_menu` VALUES ('2', '37');
INSERT INTO `sys_role_menu` VALUES ('2', '38');
INSERT INTO `sys_role_menu` VALUES ('2', '39');
INSERT INTO `sys_role_menu` VALUES ('2', '40');
INSERT INTO `sys_role_menu` VALUES ('2', '41');
INSERT INTO `sys_role_menu` VALUES ('2', '42');
INSERT INTO `sys_role_menu` VALUES ('2', '43');
INSERT INTO `sys_role_menu` VALUES ('2', '44');
INSERT INTO `sys_role_menu` VALUES ('2', '45');
INSERT INTO `sys_role_menu` VALUES ('2', '46');
INSERT INTO `sys_role_menu` VALUES ('2', '47');
INSERT INTO `sys_role_menu` VALUES ('2', '48');
INSERT INTO `sys_role_menu` VALUES ('2', '49');
INSERT INTO `sys_role_menu` VALUES ('2', '50');
INSERT INTO `sys_role_menu` VALUES ('2', '51');
INSERT INTO `sys_role_menu` VALUES ('2', '52');
INSERT INTO `sys_role_menu` VALUES ('2', '53');
INSERT INTO `sys_role_menu` VALUES ('2', '54');
INSERT INTO `sys_role_menu` VALUES ('2', '55');
INSERT INTO `sys_role_menu` VALUES ('2', '56');
INSERT INTO `sys_role_menu` VALUES ('2', '57');
INSERT INTO `sys_role_menu` VALUES ('2', '58');
INSERT INTO `sys_role_menu` VALUES ('2', '59');
INSERT INTO `sys_role_menu` VALUES ('2', '60');
INSERT INTO `sys_role_menu` VALUES ('2', '61');
-- roleId = 2的权限 特地少一个 方便测试
INSERT INTO `sys_role_menu` VALUES ('3', '1');
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES ('1', '1', '1', '4', 'admin', 'valarchie1', '0', 'agileboot@163.com', '15888888889', '0', '', '$2a$10$rb1wRoEIkLbIknREEN1LH.FGs4g0oOS5t6l5LQ793nRaFO.SPHDHy', '1', '127.0.0.1', '2022-10-06 17:00:06', 1, null, '2022-05-21 08:30:54', '1', '2022-10-06 17:00:06', '管理员', '0');
INSERT INTO `sys_user` VALUES ('2', '2', '2', '5', 'ag1', 'valarchie2', '0', 'agileboot1@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '1', '127.0.0.1', '2022-05-21 08:30:54', 0, null, '2022-05-21 08:30:54', null, null, '测试员1', '0');
INSERT INTO `sys_user` VALUES ('3', '2', '0', '5', 'ag2', 'valarchie3', '0', 'agileboot2@qq.com', '15666666667', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '1', '127.0.0.1', '2022-05-21 08:30:54', 0, null, '2022-05-21 08:30:54', null, null, '测试员2', '0');
@@ -0,0 +1,184 @@
--- int后面不能带数字, 索引相关的语句也不允许, 保留最简单原始的语句即可
create sequence if not exists sys_config_seq start with 6 increment by 1;
create table sys_config
(
config_id int default next value for sys_config_seq,
config_name varchar(128) default '' not null comment '配置名称',
config_key varchar(128) default '' not null comment '配置键名',
config_options varchar(1024) default '' not null comment '可选的选项',
config_value varchar(256) default '' not null comment '配置值',
is_allow_change int not null comment '是否允许修改',
creator_id int null comment '创建者ID',
updater_id int null comment '更新者ID',
update_time datetime null comment '更新时间',
create_time datetime null comment '创建时间',
remark varchar(128) null comment '备注',
deleted int default 0 not null comment '逻辑删除'
);
create sequence if not exists sys_dept_seq start with 11 increment by 1;
create table sys_dept
(
dept_id int default next value for sys_dept_seq,
parent_id bigint default 0 not null comment '父部门id',
ancestors text not null comment '祖级列表',
dept_name varchar(64) default '' not null comment '部门名称',
order_num int default 0 not null comment '显示顺序',
leader_id bigint null,
leader_name varchar(64) null comment '负责人',
phone varchar(16) null comment '联系电话',
email varchar(128) null comment '邮箱',
status smallint default 0 not null comment '部门状态(0正常 1停用)',
creator_id bigint null comment '创建者ID',
create_time datetime null comment '创建时间',
updater_id bigint null comment '更新者ID',
update_time datetime null comment '更新时间',
deleted tinyint default 0 not null comment '逻辑删除'
);
create sequence if not exists sys_login_info_seq start with 1 increment by 1;
create table sys_login_info
(
info_id bigint default next value for sys_login_info_seq,
username varchar(50) default '' not null comment '用户账号',
ip_address varchar(128) default '' not null comment '登录IP地址',
login_location varchar(255) default '' not null comment '登录地点',
browser varchar(50) default '' not null comment '浏览器类型',
operation_system varchar(50) default '' not null comment '操作系统',
status smallint default 0 not null comment '登录状态(1成功 0失败)',
msg varchar(255) default '' not null comment '提示消息',
login_time datetime null comment '访问时间',
deleted tinyint default 0 not null comment '逻辑删除'
);
create sequence if not exists sys_menu_seq start with 63 increment by 1;
create table sys_menu
(
menu_id bigint auto_increment comment '菜单ID'
primary key,
menu_name varchar(64) not null comment '菜单名称',
menu_type smallint default 0 not null comment '菜单的类型(1为普通菜单2为目录3为内嵌iFrame4为外链跳转)',
router_name varchar(255) default '' not null comment '路由名称(需保持和前端对应的vue文件中的name保持一致defineOptions方法中设置的name',
parent_id bigint default 0 not null comment '父菜单ID',
path varchar(255) null comment '组件路径(对应前端项目view文件夹中的路径)',
is_button tinyint(1) default 0 not null comment '是否按钮',
permission varchar(128) null comment '权限标识',
meta_info varchar(1024) default '{}' not null comment '路由元信息(前端根据这个信息进行逻辑处理)',
status smallint default 0 not null comment '菜单状态(1启用 0停用)',
remark varchar(256) default '' null comment '备注',
creator_id bigint null comment '创建者ID',
create_time datetime null comment '创建时间',
updater_id bigint null comment '更新者ID',
update_time datetime null comment '更新时间',
deleted tinyint(1) default 0 not null comment '逻辑删除'
);
create sequence if not exists sys_notice_seq start with 3 increment by 1;
create table sys_notice
(
notice_id int default next value for sys_notice_seq,
notice_title varchar(64) not null comment '公告标题',
notice_type smallint not null comment '公告类型(1通知 2公告)',
notice_content text null comment '公告内容',
status smallint default 0 not null comment '公告状态(1正常 0关闭)',
creator_id bigint not null comment '创建者ID',
create_time datetime null comment '创建时间',
updater_id bigint null comment '更新者ID',
update_time datetime null comment '更新时间',
remark varchar(255) default '' not null comment '备注',
deleted tinyint default 0 not null comment '逻辑删除'
);
create sequence if not exists sys_operation_log_seq start with 1 increment by 1;
create table sys_operation_log
(
operation_id bigint default next value for sys_operation_log_seq,
business_type smallint default 0 not null comment '业务类型(0其它 1新增 2修改 3删除)',
request_method smallint default 0 not null comment '请求方式',
request_module varchar(64) default '' not null comment '请求模块',
request_url varchar(256) default '' not null comment '请求URL',
called_method varchar(128) default '' not null comment '调用方法',
operator_type smallint default 0 not null comment '操作类别(0其它 1后台用户 2手机端用户)',
user_id bigint default 0 null comment '用户ID',
username varchar(32) default '' null comment '操作人员',
operator_ip varchar(128) default '' null comment '操作人员ip',
operator_location varchar(256) default '' null comment '操作地点',
dept_id bigint default 0 null comment '部门ID',
dept_name varchar(64) null comment '部门名称',
operation_param varchar(2048) default '' null comment '请求参数',
operation_result varchar(2048) default '' null comment '返回参数',
status smallint default 1 not null comment '操作状态(1正常 0异常)',
error_stack varchar(2048) default '' null comment '错误消息',
operation_time datetime not null comment '操作时间',
deleted tinyint default 0 not null comment '逻辑删除'
);
create sequence if not exists sys_post_seq start with 5 increment by 1;
create table sys_post
(
post_id bigint default next value for sys_post_seq,
post_code varchar(64) not null comment '岗位编码',
post_name varchar(64) not null comment '岗位名称',
post_sort int not null comment '显示顺序',
status smallint not null comment '状态(1正常 0停用)',
remark varchar(512) null comment '备注',
creator_id bigint null,
create_time datetime null comment '创建时间',
updater_id bigint null,
update_time datetime null comment '更新时间',
deleted tinyint default 0 not null comment '逻辑删除'
);
create sequence if not exists sys_role_seq start with 4 increment by 1;
create table sys_role
(
role_id bigint default next value for sys_role_seq,
role_name varchar(32) not null comment '角色名称',
role_key varchar(128) not null comment '角色权限字符串',
role_sort int not null comment '显示顺序',
data_scope smallint default 1 null comment '数据范围(1:全部数据权限 2:自定数据权限 3: 本部门数据权限 4: 本部门及以下数据权限 5: 本人权限)',
dept_id_set varchar(1024) default '' null comment '角色所拥有的部门数据权限',
status smallint not null comment '角色状态(1正常 0停用)',
creator_id bigint null comment '创建者ID',
create_time datetime null comment '创建时间',
updater_id bigint null comment '更新者ID',
update_time datetime null comment '更新时间',
remark varchar(512) null comment '备注',
deleted tinyint default 0 not null comment '删除标志(0代表存在 1代表删除)'
);
create table sys_role_menu
(
role_id bigint auto_increment not null comment '角色ID',
menu_id bigint auto_increment not null comment '菜单ID'
);
create sequence if not exists sys_user_seq start with 4 increment by 1;
create table sys_user
(
user_id bigint default next value for sys_user_seq,
post_id bigint null comment '职位id',
role_id bigint null comment '角色id',
dept_id bigint null comment '部门ID',
username varchar(64) not null comment '用户账号',
nickname varchar(32) not null comment '用户昵称',
user_type smallint default 0 null comment '用户类型(00系统用户)',
email varchar(128) default '' null comment '用户邮箱',
phone_number varchar(18) default '' null comment '手机号码',
sex smallint default 0 null comment '用户性别(0男 1女 2未知)',
avatar varchar(512) default '' null comment '头像地址',
password varchar(128) default '' not null comment '密码',
status smallint default 0 not null comment '帐号状态(1正常 2停用 3冻结)',
login_ip varchar(128) default '' null comment '最后登录IP',
login_date datetime null comment '最后登录时间',
is_admin tinyint default 0 not null comment '超级管理员标志(1是,0否)',
creator_id bigint null comment '更新者ID',
create_time datetime null comment '创建时间',
updater_id bigint null comment '更新者ID',
update_time datetime null comment '更新时间',
remark varchar(512) null comment '备注',
deleted tinyint default 0 not null comment '删除标志(0代表存在 1代表删除)'
);
CREATE ALIAS FIND_IN_SET FOR "com.agileboot.infrastructure.mybatisplus.MySqlFunction.findInSet";
@@ -0,0 +1,10 @@
#错误消息 有些版本的 IDEA 会自动设置properties的编码为IOS-8859-1 请设置成UTF8
SUCCESS=操作成功
FAIL=操作失败
Business.LOGIN_WRONG_USER_PASSWORD=用户不存在/密码错误
Business.LOGIN_CAPTCHA_CODE_WRONG=验证码错误
Business.LOGIN_CAPTCHA_CODE_EXPIRE=验证码过期
Client.COMMON_REQUEST_TOO_OFTEN=调用太过频繁,请稍后再试
@@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志存放路径 在xml文件夹中 -->
<springProperty name="log.path" source="logging.file.path"/>
<springProperty name="console.pattern" source="logging.pattern.console"/>
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n"/>
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${console.pattern}</pattern>
</encoder>
</appender>
<!-- 系统日志输出 -->
<appender name="info_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-info.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="error_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>ERROR</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- debug日志输出 -->
<appender name="debug_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-debug.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天回滚 daily -->
<fileNamePattern>${log.path}/sys-debug.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 异步输出 -->
<appender name="async_info_log" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>256</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="info_log"/>
<!-- %class%line中没有显示正确的值,而是两个问号? 解决办法:一般出现在AsyncAppender中,需要添加属性 -->
<includeCallerData>true</includeCallerData>
</appender>
<!-- 异步输出 -->
<appender name="async_error_log" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>256</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="error_log"/>
<includeCallerData>true</includeCallerData>
</appender>
<!-- 异步输出 -->
<appender name="async_debug_log" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>256</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>1024</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="debug_log"/>
<includeCallerData>true</includeCallerData>
</appender>
<root level="info">
<appender-ref ref="console"/>
<appender-ref ref="async_info_log"/>
<appender-ref ref="async_error_log"/>
<appender-ref ref="async_debug_log"/>
</root>
</configuration>
@@ -0,0 +1,195 @@
INSERT INTO app.sys_config (config_id, config_name, config_key, config_options, config_value, is_allow_change, creator_id, updater_id, update_time, create_time, remark, deleted) VALUES (1, '主框架页-默认皮肤样式名称', 'sys.index.skinName', '["skin-blue","skin-green","skin-purple","skin-red","skin-yellow"]', 'skin-blue', true, null, null, '2022-08-28 22:12:19', '2022-05-21 08:30:55', '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow', 0);
INSERT INTO app.sys_config (config_id, config_name, config_key, config_options, config_value, is_allow_change, creator_id, updater_id, update_time, create_time, remark, deleted) VALUES (2, '用户管理-账号初始密码', 'sys.user.initPassword', '', '123456', true, null, 1, '2023-07-20 14:42:08', '2022-05-21 08:30:55', '初始化密码 123456', 0);
INSERT INTO app.sys_config (config_id, config_name, config_key, config_options, config_value, is_allow_change, creator_id, updater_id, update_time, create_time, remark, deleted) VALUES (3, '主框架页-侧边栏主题', 'sys.index.sideTheme', '["theme-dark","theme-light"]', 'theme-dark', true, null, null, '2022-08-28 22:12:15', '2022-08-20 08:30:55', '深色主题theme-dark,浅色主题theme-light', 0);
INSERT INTO app.sys_config (config_id, config_name, config_key, config_options, config_value, is_allow_change, creator_id, updater_id, update_time, create_time, remark, deleted) VALUES (4, '账号自助-验证码开关', 'sys.account.captchaOnOff', '["true","false"]', 'false', false, null, 1, '2023-07-20 14:39:36', '2022-05-21 08:30:55', '是否开启验证码功能(true开启,false关闭)', 0);
INSERT INTO app.sys_config (config_id, config_name, config_key, config_options, config_value, is_allow_change, creator_id, updater_id, update_time, create_time, remark, deleted) VALUES (5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', '["true","false"]', 'true', false, null, 1, '2022-10-05 22:18:57', '2022-05-21 08:30:55', '是否开启注册用户功能(true开启,false关闭)', 0);
INSERT INTO app.sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader_id, leader_name, phone, email, status, creator_id, create_time, updater_id, update_time, deleted) VALUES (1, 0, '0', 'AgileBoot科技', 0, null, 'valarchie', '15888888888', 'valarchie@163.com', 1, null, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader_id, leader_name, phone, email, status, creator_id, create_time, updater_id, update_time, deleted) VALUES (2, 1, '0,1', '深圳总公司', 1, null, 'valarchie', '15888888888', 'valarchie@163.com', 1, null, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader_id, leader_name, phone, email, status, creator_id, create_time, updater_id, update_time, deleted) VALUES (3, 1, '0,1', '长沙分公司', 2, null, 'valarchie', '15888888888', 'valarchie@163.com', 1, null, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader_id, leader_name, phone, email, status, creator_id, create_time, updater_id, update_time, deleted) VALUES (4, 2, '0,1,2', '研发部门', 1, null, 'valarchie', '15888888888', 'valarchie@163.com', 1, null, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader_id, leader_name, phone, email, status, creator_id, create_time, updater_id, update_time, deleted) VALUES (5, 2, '0,1,2', '市场部门', 2, null, 'valarchie', '15888888888', 'valarchie@163.com', 0, null, '2022-05-21 08:30:54', 1, '2023-07-20 22:46:41', 0);
INSERT INTO app.sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader_id, leader_name, phone, email, status, creator_id, create_time, updater_id, update_time, deleted) VALUES (6, 2, '0,1,2', '测试部门', 3, null, 'valarchie', '15888888888', 'valarchie@163.com', 1, null, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader_id, leader_name, phone, email, status, creator_id, create_time, updater_id, update_time, deleted) VALUES (7, 2, '0,1,2', '财务部门', 4, null, 'valarchie', '15888888888', 'valarchie@163.com', 1, null, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader_id, leader_name, phone, email, status, creator_id, create_time, updater_id, update_time, deleted) VALUES (8, 2, '0,1,2', '运维部门', 5, null, 'valarchie', '15888888888', 'valarchie@163.com', 1, null, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader_id, leader_name, phone, email, status, creator_id, create_time, updater_id, update_time, deleted) VALUES (9, 3, '0,1,3', '市场部!', 1, null, 'valarchie!!', '15888188888', 'valarc1hie@163.com', 0, null, '2022-05-21 08:30:54', 1, '2023-07-20 22:33:31', 0);
INSERT INTO app.sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader_id, leader_name, phone, email, status, creator_id, create_time, updater_id, update_time, deleted) VALUES (10, 3, '0,1,3', '财务部门', 2, null, 'valarchie', '15888888888', 'valarchie@163.com', 0, null, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_login_info (info_id, username, ip_address, login_location, browser, operation_system, status, msg, login_time, deleted) VALUES (415, 'admin', '127.0.0.1', '内网IP', 'Chrome 11', 'Mac OS X', 1, '登录成功', '2023-06-29 22:49:37', 0);
INSERT INTO app.sys_login_info (info_id, username, ip_address, login_location, browser, operation_system, status, msg, login_time, deleted) VALUES (416, 'admin', '127.0.0.1', '内网IP', 'Chrome 11', 'Mac OS X', 1, '登录成功', '2023-07-02 22:12:30', 0);
INSERT INTO app.sys_login_info (info_id, username, ip_address, login_location, browser, operation_system, status, msg, login_time, deleted) VALUES (417, 'admin', '127.0.0.1', '内网IP', 'Chrome 11', 'Mac OS X', 0, '验证码过期', '2023-07-02 22:16:06', 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (1, '系统管理', 2, '', 0, '/system', false, '', '{"title":"系统管理","icon":"ep:management","showParent":true,"rank":1}', 1, '系统管理目录', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:08:50', 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (2, '系统监控', 2, '', 0, '/monitor', false, '', '{"title":"系统监控","icon":"ep:monitor","showParent":true,"rank":3}', 1, '系统监控目录', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:09:15', 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (3, '系统工具', 2, '', 0, '/tool', false, '', '{"title":"系统工具","icon":"ep:tools","showParent":true,"rank":2}', 1, '系统工具目录', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:09:03', 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (4, 'AgileBoot官网', 3, 'AgileBootguanwangIframeRouter', 0, '/AgileBootguanwangIframeLink', false, '', '{"title":"AgileBoot官网","icon":"ep:link","showParent":true,"frameSrc":"https://element-plus.org/zh-CN/","rank":8}', 1, 'Agileboot官网地址', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:09:40', 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (5, '用户管理', 1, 'SystemUser', 1, '/system/user/index', false, 'system:user:list', '{"title":"用户管理","icon":"ep:user-filled","showParent":true}', 1, '用户管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:16:13', 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (6, '角色管理', 1, 'SystemRole', 1, '/system/role/index', false, 'system:role:list', '{"title":"角色管理","icon":"ep:user","showParent":true}', 1, '角色管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:16:23', 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (7, '菜单管理', 1, 'MenuManagement', 1, '/system/menu/index', false, 'system:menu:list', '{"title":"菜单管理","icon":"ep:menu","showParent":true}', 1, '菜单管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:15:41', 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (8, '部门管理', 1, 'Department', 1, '/system/dept/index', false, 'system:dept:list', '{"title":"部门管理","icon":"fa-solid:code-branch","showParent":true}', 1, '部门管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:15:35', 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (9, '岗位管理', 1, 'Post', 1, '/system/post/index', false, 'system:post:list', '{"title":"岗位管理","icon":"ep:postcard","showParent":true}', 1, '岗位管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:15:11', 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (10, '参数设置', 1, 'Config', 1, '/system/config/index', false, 'system:config:list', '{"title":"参数设置","icon":"ep:setting","showParent":true}', 1, '参数设置菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:15:03', 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (11, '通知公告', 1, 'SystemNotice', 1, '/system/notice/index', false, 'system:notice:list', '{"title":"通知公告","icon":"ep:notification","showParent":true}', 1, '通知公告菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:14:56', 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (12, '日志管理', 1, 'LogManagement', 1, '/system/logd', false, '', '{"title":"日志管理","icon":"ep:document","showParent":true}', 1, '日志管理菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:14:47', 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (13, '在线用户', 1, 'OnlineUser', 2, '/system/monitor/onlineUser/index', false, 'monitor:online:list', '{"title":"在线用户","icon":"fa-solid:users","showParent":true}', 1, '在线用户菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:13:13', 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (14, '数据监控', 1, 'DataMonitor', 2, '/system/monitor/druid/index', false, 'monitor:druid:list', '{"title":"数据监控","icon":"fa:database","showParent":true,"frameSrc":"/druid/login.html","isFrameSrcInternal":true}', 1, '数据监控菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:13:25', 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (15, '服务监控', 1, 'ServerInfo', 2, '/system/monitor/server/index', false, 'monitor:server:list', '{"title":"服务监控","icon":"fa:server","showParent":true}', 1, '服务监控菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:13:34', 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (16, '缓存监控', 1, 'CacheInfo', 2, '/system/monitor/cache/index', false, 'monitor:cache:list', '{"title":"缓存监控","icon":"ep:reading","showParent":true}', 1, '缓存监控菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:12:59', 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (17, '系统接口', 1, 'SystemAPI', 3, '/tool/swagger/index', false, 'tool:swagger:list', '{"title":"系统接口","icon":"ep:document-remove","showParent":true,"frameSrc":"/swagger-ui/index.html","isFrameSrcInternal":true}', 1, '系统接口菜单', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:14:01', 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (18, '操作日志', 1, 'OperationLog', 12, '/system/log/operationLog/index', false, 'monitor:operlog:list', '{"title":"操作日志"}', 1, '操作日志菜单', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (19, '登录日志', 1, 'LoginLog', 12, '/system/log/loginLog/index', false, 'monitor:logininfor:list', '{"title":"登录日志"}', 1, '登录日志菜单', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (20, '用户查询', 0, ' ', 5, '', true, 'system:user:query', '{"title":"用户查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (21, '用户新增', 0, ' ', 5, '', true, 'system:user:add', '{"title":"用户新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (22, '用户修改', 0, ' ', 5, '', true, 'system:user:edit', '{"title":"用户修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (23, '用户删除', 0, ' ', 5, '', true, 'system:user:remove', '{"title":"用户删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (24, '用户导出', 0, ' ', 5, '', true, 'system:user:export', '{"title":"用户导出"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (25, '用户导入', 0, ' ', 5, '', true, 'system:user:import', '{"title":"用户导入"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (26, '重置密码', 0, ' ', 5, '', true, 'system:user:resetPwd', '{"title":"重置密码"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (27, '角色查询', 0, ' ', 6, '', true, 'system:role:query', '{"title":"角色查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (28, '角色新增', 0, ' ', 6, '', true, 'system:role:add', '{"title":"角色新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (29, '角色修改', 0, ' ', 6, '', true, 'system:role:edit', '{"title":"角色修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (30, '角色删除', 0, ' ', 6, '', true, 'system:role:remove', '{"title":"角色删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (31, '角色导出', 0, ' ', 6, '', true, 'system:role:export', '{"title":"角色导出"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (32, '菜单查询', 0, ' ', 7, '', true, 'system:menu:query', '{"title":"菜单查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (33, '菜单新增', 0, ' ', 7, '', true, 'system:menu:add', '{"title":"菜单新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (34, '菜单修改', 0, ' ', 7, '', true, 'system:menu:edit', '{"title":"菜单修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (35, '菜单删除', 0, ' ', 7, '', true, 'system:menu:remove', '{"title":"菜单删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (36, '部门查询', 0, ' ', 8, '', true, 'system:dept:query', '{"title":"部门查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (37, '部门新增', 0, ' ', 8, '', true, 'system:dept:add', '{"title":"部门新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (38, '部门修改', 0, ' ', 8, '', true, 'system:dept:edit', '{"title":"部门修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (39, '部门删除', 0, ' ', 8, '', true, 'system:dept:remove', '{"title":"部门删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (40, '岗位查询', 0, ' ', 9, '', true, 'system:post:query', '{"title":"岗位查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (41, '岗位新增', 0, ' ', 9, '', true, 'system:post:add', '{"title":"岗位新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (42, '岗位修改', 0, ' ', 9, '', true, 'system:post:edit', '{"title":"岗位修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (43, '岗位删除', 0, ' ', 9, '', true, 'system:post:remove', '{"title":"岗位删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (44, '岗位导出', 0, ' ', 9, '', true, 'system:post:export', '{"title":"岗位导出"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (45, '参数查询', 0, ' ', 10, '', true, 'system:config:query', '{"title":"参数查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (46, '参数新增', 0, ' ', 10, '', true, 'system:config:add', '{"title":"参数新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (47, '参数修改', 0, ' ', 10, '', true, 'system:config:edit', '{"title":"参数修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (48, '参数删除', 0, ' ', 10, '', true, 'system:config:remove', '{"title":"参数删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (49, '参数导出', 0, ' ', 10, '', true, 'system:config:export', '{"title":"参数导出"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (50, '公告查询', 0, ' ', 11, '', true, 'system:notice:query', '{"title":"公告查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (51, '公告新增', 0, ' ', 11, '', true, 'system:notice:add', '{"title":"公告新增"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (52, '公告修改', 0, ' ', 11, '', true, 'system:notice:edit', '{"title":"公告修改"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (53, '公告删除', 0, ' ', 11, '', true, 'system:notice:remove', '{"title":"公告删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (54, '操作查询', 0, ' ', 18, '', true, 'monitor:operlog:query', '{"title":"操作查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (55, '操作删除', 0, ' ', 18, '', true, 'monitor:operlog:remove', '{"title":"操作删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (56, '日志导出', 0, ' ', 18, '', true, 'monitor:operlog:export', '{"title":"日志导出"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (57, '登录查询', 0, ' ', 19, '', true, 'monitor:logininfor:query', '{"title":"登录查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (58, '登录删除', 0, ' ', 19, '', true, 'monitor:logininfor:remove', '{"title":"登录删除"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (59, '日志导出', 0, ' ', 19, '', true, 'monitor:logininfor:export', '{"title":"日志导出","rank":22}', 1, '', 0, '2022-05-21 08:30:54', 1, '2023-07-22 17:02:28', 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (60, '在线查询', 0, ' ', 13, '', true, 'monitor:online:query', '{"title":"在线查询"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (61, '批量强退', 0, ' ', 13, '', true, 'monitor:online:batchLogout', '{"title":"批量强退"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (62, '单条强退', 0, ' ', 13, '', true, 'monitor:online:forceLogout', '{"title":"单条强退"}', 1, '', 0, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (63, 'AgileBoot Github地址', 4, 'https://github.com/valarchie/AgileBoot-Back-End', 0, '/external', false, '', '{"title":"AgileBoot Github地址","icon":"fa-solid:external-link-alt","showParent":true,"rank":9}', 1, 'Agileboot github地址', 0, '2022-05-21 08:30:54', 1, '2023-08-14 23:12:13', 0);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (64, '首页', 2, '', 0, '/global', false, '121212', '{"title":"首页","showParent":true,"rank":3}', 1, '', 1, '2023-07-24 22:36:03', 1, '2023-07-24 22:38:37', 1);
INSERT INTO app.sys_menu (menu_id, menu_name, menu_type, router_name, parent_id, path, is_button, permission, meta_info, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (65, '个人中心', 1, 'PersonalCenter', 2053, '/system/user/profile', false, '434sdf', '{"title":"个人中心","showParent":true,"rank":3}', 1, '', 1, '2023-07-24 22:36:55', null, null, 1);
INSERT INTO app.sys_notice (notice_id, notice_title, notice_type, notice_content, status, creator_id, create_time, updater_id, update_time, remark, deleted) VALUES (1, '温馨提醒:2018-07-01 AgileBoot新版本发布啦', 2, '新版本内容~~~~~~~~~~', 1, 1, '2022-05-21 08:30:55', 1, '2022-08-29 20:12:37', '管理员', 0);
INSERT INTO app.sys_notice (notice_id, notice_title, notice_type, notice_content, status, creator_id, create_time, updater_id, update_time, remark, deleted) VALUES (2, '维护通知:2018-07-01 AgileBoot系统凌晨维护', 1, '维护内容', 1, 1, '2022-05-21 08:30:55', null, null, '管理员', 0);
INSERT INTO app.sys_operation_log (operation_id, business_type, request_method, request_module, request_url, called_method, operator_type, user_id, username, operator_ip, operator_location, dept_id, dept_name, operation_param, operation_result, status, error_stack, operation_time, deleted) VALUES (561, 1, 2, '菜单管理', '/system/menus', 'it.upos.builder.admin.controller.system.SysMenuController.add()', 1, 0, 'admin', '127.0.0.1', '内网IP', 0, null, '{"menuName":"","permission":"","parentId":2035,"path":"","isButton":false,"routerName":"","meta":{"showParent":true,"rank":0},"status":1},', '', 1, '', '2023-07-22 17:06:57', 0);
INSERT INTO app.sys_post (post_id, post_code, post_name, post_sort, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (1, 'ceo', '董事长', 1, 1, '', null, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_post (post_id, post_code, post_name, post_sort, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (2, 'se', '项目经理', 2, 1, '', null, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_post (post_id, post_code, post_name, post_sort, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (3, 'hr', '人力资源', 3, 1, '', null, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_post (post_id, post_code, post_name, post_sort, status, remark, creator_id, create_time, updater_id, update_time, deleted) VALUES (4, 'user', '普通员工', 5, 0, '', null, '2022-05-21 08:30:54', null, null, 0);
INSERT INTO app.sys_role (role_id, role_name, role_key, role_sort, data_scope, dept_id_set, status, creator_id, create_time, updater_id, update_time, remark, deleted) VALUES (1, '超级管理员', 'admin', 1, 1, '', 1, null, '2022-05-21 08:30:54', null, null, '超级管理员', 0);
INSERT INTO app.sys_role (role_id, role_name, role_key, role_sort, data_scope, dept_id_set, status, creator_id, create_time, updater_id, update_time, remark, deleted) VALUES (2, '普通角色', 'common', 3, 2, '', 1, null, '2022-05-21 08:30:54', null, null, '普通角色', 0);
INSERT INTO app.sys_role (role_id, role_name, role_key, role_sort, data_scope, dept_id_set, status, creator_id, create_time, updater_id, update_time, remark, deleted) VALUES (3, '闲置角色', 'unused', 4, 2, '', 0, null, '2022-05-21 08:30:54', null, null, '未使用的角色', 0);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 1);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 2);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 3);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 4);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 5);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 6);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 7);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 8);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 9);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 10);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 11);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 12);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 13);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 14);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 15);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 16);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 17);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 18);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 19);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 20);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 21);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 22);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 23);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 24);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 25);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 26);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 27);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 28);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 29);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 30);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 31);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 32);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 33);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 34);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 35);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 36);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 37);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 38);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 39);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 40);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 41);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 42);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 43);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 44);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 45);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 46);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 47);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 48);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 49);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 50);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 51);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 52);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 53);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 54);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 55);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 56);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 57);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 58);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 59);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 60);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (2, 61);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (3, 1);
INSERT INTO app.sys_role_menu (role_id, menu_id) VALUES (111, 1);
INSERT INTO app.sys_user (user_id, post_id, role_id, dept_id, username, nickname, user_type, email, phone_number, sex, avatar, password, status, login_ip, login_date, is_admin, creator_id, create_time, updater_id, update_time, remark, deleted) VALUES (1, 1, 1, 4, 'admin', 'valarchie1', 0, 'agileboot@163.com', '15888888883', 0, '/profile/avatar/20230725164110_blob_6b7a989b1cdd4dd396665d2cfd2addc5.png', '$2a$10$o55UFZAtyWnDpRV6dvQe8.c/MjlFacC49ASj2usNXm9BY74SYI/uG', 1, '127.0.0.1', '2023-08-14 23:07:03', true, null, '2022-05-21 08:30:54', 1, '2023-08-14 23:07:03', '管理员', 0);
INSERT INTO app.sys_user (user_id, post_id, role_id, dept_id, username, nickname, user_type, email, phone_number, sex, avatar, password, status, login_ip, login_date, is_admin, creator_id, create_time, updater_id, update_time, remark, deleted) VALUES (2, 2, 2, 5, 'ag1', 'valarchie2', 0, 'agileboot1@qq.com', '15666666666', 1, '/profile/avatar/20230725114818_avatar_b5bf400732bb43369b4df58802049b22.png', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', 1, '127.0.0.1', '2022-05-21 08:30:54', false, null, '2022-05-21 08:30:54', null, null, '测试员1', 0);
INSERT INTO app.sys_user (user_id, post_id, role_id, dept_id, username, nickname, user_type, email, phone_number, sex, avatar, password, status, login_ip, login_date, is_admin, creator_id, create_time, updater_id, update_time, remark, deleted) VALUES (3, 2, 0, 5, 'ag2', 'valarchie3', 0, 'agileboot2@qq.com', '15666666667', 1, '/profile/avatar/20230725114818_avatar_b5bf400732bb43369b4df58802049b22.png', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', 1, '127.0.0.1', '2022-05-21 08:30:54', false, null, '2022-05-21 08:30:54', null, null, '测试员2', 0);
-- 序列更新
select setval('app.sys_config_config_id_seq',COALESCE(max(config_id),1)) from app.sys_config;
select setval('app.sys_dept_dept_id_seq',COALESCE(max(dept_id),1)) from app.sys_dept;
select setval('app.sys_login_info_info_id_seq',COALESCE(max(info_id),1)) from app.sys_login_info;
select setval('app.sys_menu_menu_id_seq',COALESCE(max(menu_id),1)) from app.sys_menu;
select setval('app.sys_notice_notice_id_seq',COALESCE(max(notice_id),1)) from app.sys_notice;
select setval('app.sys_operation_log_operation_id_seq',COALESCE(max(operation_id),1)) from app.sys_operation_log;
select setval('app.sys_post_post_id_seq',COALESCE(max(post_id),1)) from app.sys_post;
select setval('app.sys_role_role_id_seq',COALESCE(max(role_id),1)) from app.sys_role;
select setval('app.sys_user_user_id_seq',COALESCE(max(user_id),1)) from app.sys_user;
@@ -0,0 +1,174 @@
-- 创建表 sys_config 参数配置表
CREATE TABLE app.sys_config (
config_id serial8 PRIMARY KEY NOT NULL, -- 参数主键
config_name VARCHAR(128) NOT NULL DEFAULT '', -- 配置名称
config_key VARCHAR(128) NOT NULL, -- 配置键名
config_options VARCHAR(1024) NOT NULL DEFAULT '', -- 可选的选项
config_value VARCHAR(256) NOT NULL DEFAULT '', -- 配置值
is_allow_change bool NOT NULL, -- 是否允许修改
creator_id int8, -- 创建者ID
updater_id int8, -- 更新者ID
update_time TIMESTAMPTZ, -- 更新时间
create_time TIMESTAMPTZ, -- 创建时间
remark VARCHAR(128), -- 备注
deleted int2 DEFAULT 0 NOT NULL -- 逻辑删除
);
-- 创建表 sys_dept 部门表
CREATE TABLE app.sys_dept (
dept_id serial8 PRIMARY KEY NOT NULL, -- 部门id
parent_id int8 NOT NULL DEFAULT 0, -- 父部门id
ancestors TEXT NOT NULL, -- 祖级列表
dept_name VARCHAR(64) NOT NULL DEFAULT '', -- 部门名称
order_num INT NOT NULL DEFAULT 0, -- 显示顺序
leader_id int8, -- 负责人
leader_name VARCHAR(64), -- 负责人姓名
phone VARCHAR(16), -- 联系电话
email VARCHAR(128), -- 邮箱
status SMALLINT NOT NULL DEFAULT 0, -- 部门状态(0停用 1启用)
creator_id int8, -- 创建者ID
create_time TIMESTAMPTZ, -- 创建时间
updater_id int8, -- 更新者ID
update_time TIMESTAMPTZ, -- 更新时间
deleted int2 DEFAULT 0 NOT NULL -- 逻辑删除
);
-- 创建表 sys_login_info 系统访问记录
CREATE TABLE app.sys_login_info (
info_id serial8 PRIMARY KEY NOT NULL, -- 访问ID
username VARCHAR(50) NOT NULL DEFAULT '', -- 用户账号
ip_address VARCHAR(128) NOT NULL DEFAULT '', -- 登录IP地址
login_location VARCHAR(255) NOT NULL DEFAULT '', -- 登录地点
browser VARCHAR(50) NOT NULL DEFAULT '', -- 浏览器类型
operation_system VARCHAR(50) NOT NULL DEFAULT '', -- 操作系统
status int2 NOT NULL DEFAULT 0, -- 登录状态(1成功 0失败)
msg VARCHAR(255) NOT NULL DEFAULT '', -- 提示消息
login_time TIMESTAMPTZ, -- 访问时间
deleted int2 DEFAULT 0 NOT NULL -- 逻辑删除
);
-- 创建表 sys_notice 通知公告表
CREATE TABLE app.sys_notice (
notice_id serial8 PRIMARY KEY NOT NULL, -- 公告ID
notice_title VARCHAR(64) NOT NULL, -- 公告标题
notice_type int2 NOT NULL, -- 公告类型(1通知 2公告)
notice_content TEXT, -- 公告内容
status int2 NOT NULL DEFAULT 0, -- 公告状态(1正常 0关闭)
creator_id int8 NOT NULL, -- 创建者ID
create_time TIMESTAMPTZ, -- 创建时间
updater_id int8, -- 更新者ID
update_time TIMESTAMPTZ, -- 更新时间
remark VARCHAR(255) NOT NULL DEFAULT '', -- 备注
deleted int2 DEFAULT 0 NOT NULL -- 逻辑删除
);
-- 创建表 sys_operation_log 操作日志记录
CREATE TABLE app.sys_operation_log (
operation_id serial8 PRIMARY KEY NOT NULL, -- 日志主键
business_type int2 NOT NULL DEFAULT 0, -- 业务类型(0其它 1新增 2修改 3删除)
request_method int2 NOT NULL DEFAULT 0, -- 请求方式
request_module VARCHAR(64) NOT NULL DEFAULT '', -- 请求模块
request_url VARCHAR(256) NOT NULL DEFAULT '', -- 请求URL
called_method VARCHAR(128) NOT NULL DEFAULT '', -- 调用方法
operator_type int2 NOT NULL DEFAULT 0, -- 操作类别(0其它 1后台用户 2手机端用户)
user_id int8, -- 用户ID
username VARCHAR(32), -- 操作人员
operator_ip VARCHAR(128), -- 操作人员ip
operator_location VARCHAR(256), -- 操作地点
dept_id int8, -- 部门ID
dept_name VARCHAR(64), -- 部门名称
operation_param VARCHAR(2048), -- 请求参数
operation_result VARCHAR(2048), -- 返回参数
status int2 NOT NULL DEFAULT 1, -- 操作状态(1正常 0异常)
error_stack VARCHAR(2048), -- 错误消息
operation_time TIMESTAMPTZ NOT NULL, -- 操作时间
deleted int2 DEFAULT 0 NOT NULL -- 逻辑删除
);
-- 创建表 sys_post 岗位信息表
CREATE TABLE app.sys_post (
post_id serial8 PRIMARY KEY NOT NULL, -- 岗位ID
post_code VARCHAR(64) NOT NULL, -- 岗位编码
post_name VARCHAR(64) NOT NULL, -- 岗位名称
post_sort INT NOT NULL, -- 显示顺序
status int2 NOT NULL, -- 状态(1正常 0停用)
remark VARCHAR(512), -- 备注
creator_id int8, -- 创建者ID
create_time TIMESTAMPTZ, -- 创建时间
updater_id int8, -- 更新者ID
update_time TIMESTAMPTZ, -- 更新时间
deleted int2 DEFAULT 0 NOT NULL -- 逻辑删除
);
-- 创建表 sys_menu 菜单权限表
CREATE TABLE app.sys_menu (
menu_id serial8 PRIMARY KEY NOT NULL, -- 菜单ID
menu_name VARCHAR(64) NOT NULL, -- 菜单名称
menu_type int2 NOT NULL DEFAULT 0, -- 菜单的类型(1为普通菜单2为目录3为内嵌iFrame4为外链跳转)
router_name VARCHAR(255) NOT NULL DEFAULT '', -- 路由名称
parent_id int8 NOT NULL DEFAULT 0, -- 父菜单ID
path VARCHAR(255), -- 组件路径
is_button bool NOT NULL DEFAULT false, -- 是否按钮
permission VARCHAR(128), -- 权限标识
meta_info VARCHAR(1024) NOT NULL DEFAULT '{}', -- 路由元信息
status int2 NOT NULL DEFAULT 0, -- 菜单状态(1启用 0停用)
remark VARCHAR(256), -- 备注
creator_id int8, -- 创建者ID
create_time TIMESTAMPTZ, -- 创建时间
updater_id int8, -- 更新者ID
update_time TIMESTAMPTZ, -- 更新时间
deleted int2 DEFAULT 0 NOT NULL -- 逻辑删除
);
-- 创建表 sys_role 角色信息表
CREATE TABLE app.sys_role (
role_id serial8 PRIMARY KEY NOT NULL, -- 角色ID
role_name VARCHAR(32) NOT NULL, -- 角色名称
role_key VARCHAR(128) NOT NULL, -- 角色权限字符串
role_sort INT NOT NULL, -- 显示顺序
data_scope int2, -- 数据范围
dept_id_set VARCHAR(1024) DEFAULT '', -- 角色所拥有的部门数据权限
status int2 NOT NULL, -- 角色状态(1正常 0停用)
creator_id int8, -- 创建者ID
create_time TIMESTAMPTZ, -- 创建时间
updater_id int8, -- 更新者ID
update_time TIMESTAMPTZ, -- 更新时间
remark VARCHAR(512), -- 备注
deleted int2 DEFAULT 0 NOT NULL -- 删除标志(0代表存在 1代表删除)
);
-- 创建表 sys_role_menu 角色和菜单关联表
CREATE TABLE app.sys_role_menu (
role_id int8 NOT NULL, -- 角色ID
menu_id int8 NOT NULL, -- 菜单ID
PRIMARY KEY (role_id, menu_id) -- 设置复合主键
);
-- 创建表 sys_user 用户信息表
CREATE TABLE app.sys_user (
user_id serial8 PRIMARY KEY NOT NULL, -- 用户ID
post_id int8, -- 职位id
role_id int8, -- 角色id
dept_id int8, -- 部门ID
username VARCHAR(64) NOT NULL, -- 用户账号
nickname VARCHAR(32) NOT NULL, -- 用户昵称
user_type int2 DEFAULT 0, -- 用户类型(00系统用户)
email VARCHAR(128), -- 用户邮箱
phone_number VARCHAR(18), -- 手机号码
sex int2, -- 用户性别(0男 1女 2未知)
avatar VARCHAR(512), -- 头像地址
password VARCHAR(128) NOT NULL, -- 密码
status int2 NOT NULL, -- 帐号状态(1正常 2停用 3冻结)
login_ip VARCHAR(128), -- 最后登录IP
login_date TIMESTAMPTZ, -- 最后登录时间
is_admin bool DEFAULT false NOT NULL, -- 超级管理员标志(1是,0否)
creator_id int8, -- 更新者ID
create_time TIMESTAMPTZ, -- 创建时间
updater_id int8, -- 更新者ID
update_time TIMESTAMPTZ, -- 更新时间
remark VARCHAR(512), -- 备注
deleted int2 DEFAULT 0 NOT NULL -- 删除标志(0代表存在 1代表删除)
);
@@ -0,0 +1,27 @@
package com.agileboot.framework.config;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.math.Calculator;
import cn.hutool.core.util.StrUtil;
import com.agileboot.infrastructure.config.captcha.CaptchaMathTextCreator;
import org.junit.Assert;
import org.junit.Test;
public class CaptchaMathTextCreatorTest {
@Test
public void test() {
CaptchaMathTextCreator captchaMathTextCreator = new CaptchaMathTextCreator();
for (int i = 0; i < 50; i++) {
validateExpressionAndResult(captchaMathTextCreator.getText());
}
}
private void validateExpressionAndResult(String expression) {
String[] expressionAndResult = expression.split("@");
Assert.assertEquals(expressionAndResult.length, 2);
System.out.println(expressionAndResult[0] + " answer is " + expressionAndResult[1]);
String safeExpression = StrUtil.removeSuffix(expressionAndResult[0], "=?");
Assert.assertEquals(Convert.toInt(Calculator.conversion(safeExpression)) + "", expressionAndResult[1]);
}
}
@@ -0,0 +1,23 @@
package com.agileboot.infrastructure.annotations;
import com.agileboot.infrastructure.annotations.ratelimit.RateLimit;
import com.agileboot.infrastructure.annotations.ratelimit.RateLimit.LimitType;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class RateLimitTypeTest {
@Test
void testCombinedKey() {
RateLimit mockLimit = mock(RateLimit.class);
when(mockLimit.key()).thenReturn("Test");
String combinedKey = LimitType.GLOBAL.generateCombinedKey(mockLimit);
Assertions.assertEquals("Test-GLOBAL", combinedKey);
}
}
@@ -0,0 +1,17 @@
package com.agileboot.infrastructure.mybatisplus;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class MySqlFunctionTest {
@Test
void find_in_set() {
String searchStr = ",2,4,5,9";
Assertions.assertTrue(MySqlFunction.findInSet("2", searchStr));
Assertions.assertTrue(MySqlFunction.findInSet("5", searchStr));
Assertions.assertTrue(MySqlFunction.findInSet("9", searchStr));
}
}
@@ -0,0 +1,27 @@
package com.agileboot.infrastructure.web.util;
import com.agileboot.infrastructure.user.AuthenticationUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class AuthenticationUtilsTest {
@Test
void testMatchesPassword() {
// 每次加密散列后的密码是不同的 但是代表的是同一份密码
String password = "admin123";
String encryptPassword1 = "$2a$10$vh0eep5P2Rj2x.mjjOSq/O1LT645Qwmohp.ciOA0.6E261rlOVSSO";
String encryptPassword2 = "$2a$10$rb1wRoEIkLbIknREEN1LH.FGs4g0oOS5t6l5LQ793nRaFO.SPHDHy";
Assertions.assertTrue(AuthenticationUtils.matchesPassword(password,encryptPassword1));
Assertions.assertTrue(AuthenticationUtils.matchesPassword(password,encryptPassword2));
}
@Test
void testEncryptPassword() {
String encrypt1 = AuthenticationUtils.encryptPassword("admin123");
String encrypt2 = AuthenticationUtils.encryptPassword("admin123");
Assertions.assertNotEquals(encrypt1, encrypt2);
}
}