This commit is contained in:
cxh 2024-12-11 17:26:24 +08:00
commit 044721af02
37 changed files with 289 additions and 78 deletions

View File

@ -15,7 +15,7 @@
[![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]()
[![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]()
> RuoYi-Vue-Plus 是重写 RuoYi-Vue 针对 `分布式集群与多租户` 场景全方位升级(不兼容原框架)
> Dromara RuoYi-Vue-Plus 是重写 RuoYi-Vue 针对 `分布式集群与多租户` 场景全方位升级(不兼容原框架)
> 项目代码、文档 均开源免费可商用 遵循开源协议在项目中保留开源协议文件即可<br>
活到老写到老 为兴趣而开源 为学习而开源 为让大家真正可以学到技术而开源

12
pom.xml
View File

@ -10,7 +10,7 @@
<name>RuoYi-Vue-Plus</name>
<url>https://gitee.com/dromara/RuoYi-Vue-Plus</url>
<description>RuoYi-Vue-Plus多租户管理系统</description>
<description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
<properties>
<revision>5.2.3</revision>
@ -19,7 +19,7 @@
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
<mybatis.version>3.5.16</mybatis.version>
<springdoc.version>2.6.0</springdoc.version>
<springdoc.version>2.7.0</springdoc.version>
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
<easyexcel.version>4.0.3</easyexcel.version>
<velocity.version>2.3</velocity.version>
@ -28,10 +28,10 @@
<p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.8.31</hutool.version>
<spring-boot-admin.version>3.3.4</spring-boot-admin.version>
<redisson.version>3.37.0</redisson.version>
<redisson.version>3.39.0</redisson.version>
<lock4j.version>2.2.7</lock4j.version>
<dynamic-ds.version>4.3.1</dynamic-ds.version>
<snailjob.version>1.1.2</snailjob.version>
<snailjob.version>1.2.0</snailjob.version>
<mapstruct-plus.version>1.4.5</mapstruct-plus.version>
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
<lombok.version>1.18.34</lombok.version>
@ -55,7 +55,7 @@
<!-- 插件版本 -->
<maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
<maven-war-plugin.version>3.2.2</maven-war-plugin.version>
<maven-compiler-plugin.verison>3.11.0</maven-compiler-plugin.verison>
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
<maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
<flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
</properties>
@ -375,7 +375,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.verison}</version>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>

View File

@ -17,6 +17,8 @@ EXPOSE ${SERVER_PORT}
ADD ./target/ruoyi-admin.jar ./app.jar
SHELL ["/bin/bash", "-c"]
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
# 应用名称 如果想区分集群节点监控 改成不同的名称即可
#-Dskywalking.agent.service_name=ruoyi-server \

View File

@ -226,7 +226,7 @@ public class AuthController {
}
// 根据域名进行筛选
List<TenantListVo> list = StreamUtils.filter(voList, vo ->
StringUtils.equals(vo.getDomain(), host));
StringUtils.equalsIgnoreCase(vo.getDomain(), host));
result.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
return R.ok(result);
}

View File

@ -223,12 +223,12 @@ public class SysLoginService {
if (!TenantHelper.isEnable()) {
return;
}
if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
return;
}
if (StringUtils.isBlank(tenantId)) {
throw new TenantException("tenant.number.not.blank");
}
if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
return;
}
SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
if (ObjectUtil.isNull(tenant)) {
log.info("登录租户:{} 不存在.", tenantId);

View File

@ -2,7 +2,7 @@
spring.boot.admin.client:
# 增加客户端开关
enabled: true
url: http://localhost:9090/admin
url: http://localhost:9090
instance:
service-host-type: IP
metadata:

View File

@ -5,7 +5,7 @@ spring.servlet.multipart.location: /ruoyi/server/temp
spring.boot.admin.client:
# 增加客户端开关
enabled: true
url: http://localhost:9090/admin
url: http://localhost:9090
instance:
service-host-type: IP
metadata:

View File

@ -47,6 +47,7 @@ logging:
org.dromara: @logging.level@
org.springframework: warn
org.mybatis.spring.mapper: error
org.apache.fury: warn
config: classpath:logback-plus.xml
# 用户配置
@ -214,6 +215,8 @@ springdoc:
packages-to-scan: org.dromara.system
- group: 4.代码生成模块
packages-to-scan: org.dromara.generator
- group: 5.工作流模块
packages-to-scan: org.dromara.workflow
# 防止XSS攻击
xss:

View File

@ -0,0 +1,48 @@
package org.dromara.common.core.validate.enumd;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 自定义枚举校验
*
* @author 秋辞未寒
* @date 2024-12-09
*/
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Repeatable(EnumPattern.List.class) // 允许在同一元素上多次使用该注解
@Constraint(validatedBy = {EnumPatternValidator.class})
public @interface EnumPattern {
/**
* 需要校验的枚举类型
*/
Class<? extends Enum<?>> type();
/**
* 枚举类型校验值字段名称
* 需确保该字段实现了 getter 方法
*/
String fieldName();
String message() default "输入值不在枚举范围内";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@interface List {
EnumPattern[] value();
}
}

View File

@ -0,0 +1,37 @@
package org.dromara.common.core.validate.enumd;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.reflect.ReflectUtils;
/**
* 自定义枚举校验注解实现
*
* @author 秋辞未寒
* @date 2024-12-09
*/
public class EnumPatternValidator implements ConstraintValidator<EnumPattern, String> {
private EnumPattern annotation;;
@Override
public void initialize(EnumPattern annotation) {
ConstraintValidator.super.initialize(annotation);
this.annotation = annotation;
}
@Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
if (StringUtils.isNotBlank(value)) {
String fieldName = annotation.fieldName();
for (Object e : annotation.type().getEnumConstants()) {
if (value.equals(ReflectUtils.invokeGetter(e, fieldName))) {
return true;
}
}
}
return false;
}
}

View File

@ -100,7 +100,7 @@ public class LogAspect {
if (e != null) {
operLog.setStatus(BusinessStatus.FAIL.ordinal());
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 5000));
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 3800));
}
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
@ -146,7 +146,7 @@ public class LogAspect {
}
// 是否需要保存response参数和值
if (log.isSaveResponseData() && ObjectUtil.isNotNull(jsonResult)) {
operLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0, 5000));
operLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0, 3800));
}
}
@ -161,11 +161,11 @@ public class LogAspect {
String requestMethod = operLog.getRequestMethod();
if (MapUtil.isEmpty(paramsMap) && StringUtils.equalsAny(requestMethod, HttpMethod.PUT.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name())) {
String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
operLog.setOperParam(StringUtils.substring(params, 0, 5000));
operLog.setOperParam(StringUtils.substring(params, 0, 3800));
} else {
MapUtil.removeAny(paramsMap, EXCLUDE_PROPERTIES);
MapUtil.removeAny(paramsMap, excludeParamNames);
operLog.setOperParam(StringUtils.substring(JsonUtils.toJsonString(paramsMap), 0, 5000));
operLog.setOperParam(StringUtils.substring(JsonUtils.toJsonString(paramsMap), 0, 3800));
}
}

View File

@ -52,7 +52,7 @@ public class MybatisPlusConfig {
* 数据权限拦截器
*/
public PlusDataPermissionInterceptor dataPermissionInterceptor() {
return new PlusDataPermissionInterceptor();
return new PlusDataPermissionInterceptor(SpringUtils.getProperty("mybatis-plus.mapperPackage"));
}
/**

View File

@ -48,7 +48,12 @@ public enum DataScopeType {
/**
* 仅本人数据权限
*/
SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 ");
SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 "),
/**
* 部门及以下或本人数据权限
*/
DEPT_AND_CHILD_OR_SELF("6", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} ) OR #{#userName} = #{#user.userId} ", " 1 = 0 ");
private final String code;

View File

@ -1,5 +1,6 @@
package org.dromara.common.mybatis.handler;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
@ -8,6 +9,7 @@ import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import org.apache.ibatis.io.Resources;
import org.dromara.common.core.domain.dto.RoleDTO;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
@ -19,17 +21,23 @@ import org.dromara.common.mybatis.annotation.DataPermission;
import org.dromara.common.mybatis.enums.DataScopeType;
import org.dromara.common.mybatis.helper.DataPermissionHelper;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.ClassUtils;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
/**
@ -41,6 +49,11 @@ import java.util.function.Function;
@Slf4j
public class PlusDataPermissionHandler {
/**
* 类名称与注解的映射关系缓存(由于aop无法拦截mybatis接口类上的注解 只能通过启动预扫描的方式进行)
*/
private final Map<String, DataPermission> dataPermissionCacheMap = new ConcurrentHashMap<>();
/**
* spel 解析器
*/
@ -51,6 +64,15 @@ public class PlusDataPermissionHandler {
*/
private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory());
/**
* 构造方法扫描指定包下的 Mapper 类并初始化缓存
*
* @param mapperPackage Mapper 类所在的包路径
*/
public PlusDataPermissionHandler(String mapperPackage) {
scanMapperClasses(mapperPackage);
}
/**
* 获取数据过滤条件的 SQL 片段
*
@ -62,7 +84,7 @@ public class PlusDataPermissionHandler {
public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) {
try {
// 获取数据权限配置
DataPermission dataPermission = DataPermissionHelper.getPermission();
DataPermission dataPermission = getDataPermission(mappedStatementId);
// 获取当前登录用户信息
LoginUser currentUser = DataPermissionHelper.getVariable("user");
if (ObjectUtil.isNull(currentUser)) {
@ -124,14 +146,20 @@ public class PlusDataPermissionHandler {
return "";
}
boolean isSuccess = false;
List<String> keys = new ArrayList<>();
for (DataColumn dataColumn : dataPermission.value()) {
if (dataColumn.key().length != dataColumn.value().length) {
throw new ServiceException("角色数据范围异常 => key与value长度不匹配");
}
// 设置注解变量 key 为表达式变量 value 为变量值
for (int i = 0; i < dataColumn.key().length; i++) {
context.setVariable(dataColumn.key()[i], dataColumn.value()[i]);
}
keys.addAll(Arrays.stream(dataColumn.key()).map(key -> "#" + key).toList());
}
for (DataColumn dataColumn : dataPermission.value()) {
// 不包含 key 变量 则不处理
if (!StringUtils.containsAny(type.getSqlTemplate(),
Arrays.stream(dataColumn.key()).map(key -> "#" + key).toArray(String[]::new)
)) {
if (!StringUtils.containsAny(type.getSqlTemplate(), keys.toArray(String[]::new))) {
continue;
}
// 包含权限标识符 这直接跳过
@ -143,10 +171,6 @@ public class PlusDataPermissionHandler {
isSuccess = true;
continue;
}
// 设置注解变量 key 为表达式变量 value 为变量值
for (int i = 0; i < dataColumn.key().length; i++) {
context.setVariable(dataColumn.key()[i], dataColumn.value()[i]);
}
// 忽略数据权限 防止spel表达式内有其他sql查询导致死循环调用
String sql = DataPermissionHelper.ignore(() ->
@ -169,12 +193,68 @@ public class PlusDataPermissionHandler {
return "";
}
/**
* 扫描指定包下的 Mapper 并查找其中带有特定注解的方法或类
*
* @param mapperPackage Mapper 类所在的包路径
*/
private void scanMapperClasses(String mapperPackage) {
// 创建资源解析器和元数据读取工厂
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
// Mapper 包路径按分隔符拆分为数组
String[] packagePatternArray = StringUtils.splitPreserveAllTokens(mapperPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;
try {
for (String packagePattern : packagePatternArray) {
// 将包路径转换为资源路径
String path = ClassUtils.convertClassNameToResourcePath(packagePattern);
// 获取指定路径下的所有 .class 文件资源
Resource[] resources = resolver.getResources(classpath + path + "/*.class");
for (Resource resource : resources) {
// 获取资源的类元数据
ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata();
// 获取资源对应的类对象
Class<?> clazz = Resources.classForName(classMetadata.getClassName());
// 查找类中的特定注解
if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) {
DataPermission dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class);
dataPermissionCacheMap.put(clazz.getName(), dataPermission);
}
}
}
} catch (Exception e) {
log.error("初始化数据安全缓存时出错:{}", e.getMessage());
}
}
/**
* 根据映射语句 ID 或类名获取对应的 DataPermission 注解对象
*
* @param mapperId 映射语句 ID
* @return DataPermission 注解对象如果不存在则返回 null
*/
public DataPermission getDataPermission(String mapperId) {
// 检查上下文中是否包含映射语句 ID 对应的 DataPermission 注解对象
if (DataPermissionHelper.getPermission() != null) {
return DataPermissionHelper.getPermission();
}
// 如果缓存中不包含映射语句 ID 对应的 DataPermission 注解对象则尝试使用类名作为键查找
String clazzName = mapperId.substring(0, mapperId.lastIndexOf("."));
if (dataPermissionCacheMap.containsKey(clazzName)) {
return dataPermissionCacheMap.get(clazzName);
}
return null;
}
/**
* 检查给定的映射语句 ID 是否有效即是否能够找到对应的 DataPermission 注解对象
*
* @param mapperId 映射语句 ID
* @return 如果找到对应的 DataPermission 注解对象则返回 false否则返回 true
*/
public boolean invalid() {
return DataPermissionHelper.getPermission() == null;
public boolean invalid(String mapperId) {
return getDataPermission(mapperId) == null;
}
}

View File

@ -39,9 +39,11 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
/**
* 构造函数初始化 PlusDataPermissionHandler 实例
*
* @param mapperPackage 扫描的映射器包
*/
public PlusDataPermissionInterceptor() {
this.dataPermissionHandler = new PlusDataPermissionHandler();
public PlusDataPermissionInterceptor(String mapperPackage) {
this.dataPermissionHandler = new PlusDataPermissionHandler(mapperPackage);
}
/**
@ -62,7 +64,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
return;
}
// 检查是否缺少有效的数据权限注解
if (dataPermissionHandler.invalid()) {
if (dataPermissionHandler.invalid(ms.getId())) {
return;
}
// 解析 sql 分配对应方法
@ -90,7 +92,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
return;
}
// 检查是否缺少有效的数据权限注解
if (dataPermissionHandler.invalid()) {
if (dataPermissionHandler.invalid(ms.getId())) {
return;
}
PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();

View File

@ -42,6 +42,18 @@
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<!-- &lt;!&ndash; redis序列化替代方案 比json快无数的跨语言二进制序列化 &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.apache.fury</groupId>-->
<!-- <artifactId>fury-core</artifactId>-->
<!-- <version>0.9.0</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-api</artifactId>-->
<!-- </dependency>-->
</dependencies>
</project>

View File

@ -53,6 +53,9 @@ public class RedisConfig {
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型类必须是非final修饰的序列化时将对象全类名一起保存下来
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
// LoggerFactory.useSlf4jLogging(true);
// FuryCodec furyCodec = new FuryCodec();
// CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, furyCodec, furyCodec);
TypedJsonJacksonCodec jsonCodec = new TypedJsonJacksonCodec(Object.class, om);
// 组合序列化 key 使用 String 内容使用通用 json 格式
CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, jsonCodec, jsonCodec);

View File

@ -174,12 +174,12 @@ public class QueueUtils {
*
* @param queueName 队列名
* @param capacity 容量
* @param destroy 已存在是否销毁
* @param destroy 是否销毁
*/
public static <T> boolean trySetBoundedQueueCapacity(String queueName, int capacity, boolean destroy) {
RBoundedBlockingQueue<T> boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName);
if (boundedBlockingQueue.isExists() && destroy) {
destroyQueue(queueName);
if (destroy) {
boundedBlockingQueue.delete();
}
return boundedBlockingQueue.trySetCapacity(capacity);
}

View File

@ -58,9 +58,9 @@ public class SocialUtils {
case "linkedin" -> new AuthLinkedinRequest(builder.build(), STATE_CACHE);
case "microsoft" -> new AuthMicrosoftRequest(builder.build(), STATE_CACHE);
case "renren" -> new AuthRenrenRequest(builder.build(), STATE_CACHE);
case "stack_overflow" -> new AuthStackOverflowRequest(builder.build(), STATE_CACHE);
case "stack_overflow" -> new AuthStackOverflowRequest(builder.stackOverflowKey(obj.getStackOverflowKey()).build(), STATE_CACHE);
case "huawei" -> new AuthHuaweiRequest(builder.build(), STATE_CACHE);
case "wechat_enterprise" -> new AuthWeChatEnterpriseQrcodeRequest(builder.build(), STATE_CACHE);
case "wechat_enterprise" -> new AuthWeChatEnterpriseQrcodeRequest(builder.agentId(obj.getAgentId()).build(), STATE_CACHE);
case "gitlab" -> new AuthGitlabRequest(builder.build(), STATE_CACHE);
case "wechat_mp" -> new AuthWeChatMpRequest(builder.build(), STATE_CACHE);
case "aliyun" -> new AuthAliyunRequest(builder.build(), STATE_CACHE);

View File

@ -63,7 +63,7 @@ public class GlobalExceptionHandler {
@ExceptionHandler(SseException.class)
public String handleNotLoginException(SseException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage());
log.debug("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage());
return JsonUtils.toJsonString(R.fail(HttpStatus.HTTP_UNAUTHORIZED, "认证失败,无法访问系统资源"));
}

View File

@ -15,6 +15,8 @@ EXPOSE 9090
ADD ./target/ruoyi-monitor-admin.jar ./app.jar
SHELL ["/bin/bash", "-c"]
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom \
-XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \
-jar app.jar

View File

@ -19,7 +19,6 @@ spring:
admin:
ui:
title: RuoYi-Vue-Plus服务监控中心
context-path: /admin
--- # Actuator 监控端点的配置项
management:
@ -38,7 +37,7 @@ spring.boot.admin.client:
# 增加客户端开关
enabled: true
# 设置 Spring Boot Admin Server 地址
url: http://localhost:9090/admin
url: http://localhost:9090
instance:
service-host-type: IP
metadata:

View File

@ -16,6 +16,8 @@ EXPOSE 17888
ADD ./target/ruoyi-snailjob-server.jar ./app.jar
SHELL ["/bin/bash", "-c"]
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom \
-XX:+HeapDumpOnOutOfMemoryError -XX:+UseZGC ${JAVA_OPTS} \
-jar app.jar

View File

@ -22,7 +22,7 @@ public class SecurityConfig {
public FilterRegistrationBean<ActuatorAuthFilter> actuatorFilterRegistrationBean() {
FilterRegistrationBean<ActuatorAuthFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new ActuatorAuthFilter(username, password));
registrationBean.addUrlPatterns("/actuator", "/actuator/**");
registrationBean.addUrlPatterns("/actuator", "/actuator/*");
return registrationBean;
}

View File

@ -40,7 +40,7 @@ snail-job:
spring.boot.admin.client:
# 增加客户端开关
enabled: true
url: http://localhost:9090/admin
url: http://localhost:9090
instance:
service-host-type: IP
metadata:

View File

@ -40,7 +40,7 @@ snail-job:
spring.boot.admin.client:
# 增加客户端开关
enabled: true
url: http://localhost:9090/admin
url: http://localhost:9090
instance:
service-host-type: IP
metadata:

View File

@ -106,7 +106,7 @@ public class GenTableServiceImpl implements IGenTableService {
.like(StringUtils.isNotBlank(genTable.getTableComment()), "lower(table_comment)", StringUtils.lowerCase(genTable.getTableComment()))
.between(params.get("beginTime") != null && params.get("endTime") != null,
"create_time", params.get("beginTime"), params.get("endTime"))
.orderByAsc("table_id");
.orderByDesc("update_time");
return wrapper;
}
@ -165,7 +165,8 @@ public class GenTableServiceImpl implements IGenTableService {
gen.setCreateTime(x.getCreateTime());
gen.setUpdateTime(x.getUpdateTime());
return gen;
}).toList();
}).sorted(Comparator.comparing(GenTable::getCreateTime).reversed())
.toList();
IPage<GenTable> page = pageQuery.build();
page.setTotal(tables.size());
@ -202,7 +203,7 @@ public class GenTableServiceImpl implements IGenTableService {
}
List<Table<?>> tableList = tablesMap.values().stream()
.filter(x -> !StringUtils.containsAnyIgnoreCase(x.getName(), TABLE_IGNORE))
.filter(x -> !startWithAnyIgnoreCase(x.getName(), TABLE_IGNORE))
.filter(x -> tableNameSet.contains(x.getName())).toList();
if (CollUtil.isEmpty(tableList)) {
@ -303,14 +304,18 @@ public class GenTableServiceImpl implements IGenTableService {
@DS("#dataName")
@Override
public List<GenTableColumn> selectDbTableColumnsByName(String tableName, String dataName) {
LinkedHashMap<String, Column> columns = ServiceProxy.metadata().columns(tableName);
Table<?> table = ServiceProxy.metadata().table(tableName);
if (ObjectUtil.isNull(table)) {
return new ArrayList<>();
}
LinkedHashMap<String, Column> columns = table.getColumns();
List<GenTableColumn> tableColumns = new ArrayList<>();
columns.forEach((columnName, column) -> {
GenTableColumn tableColumn = new GenTableColumn();
tableColumn.setIsPk(String.valueOf(column.isPrimaryKey()));
tableColumn.setColumnName(column.getName());
tableColumn.setColumnComment(column.getComment());
tableColumn.setColumnType(column.getTypeName().toLowerCase());
tableColumn.setColumnType(column.getOriginType().toLowerCase());
tableColumn.setSort(column.getPosition());
tableColumn.setIsRequired(column.isNullable() == 0 ? "1" : "0");
tableColumn.setIsIncrement(column.isAutoIncrement() == -1 ? "0" : "1");

View File

@ -29,6 +29,8 @@ public class GenUtils {
genTable.setBusinessName(getBusinessName(genTable.getTableName()));
genTable.setFunctionName(replaceText(genTable.getTableComment()));
genTable.setFunctionAuthor(GenConfig.getAuthor());
genTable.setCreateTime(null);
genTable.setUpdateTime(null);
}
/**
@ -36,8 +38,11 @@ public class GenUtils {
*/
public static void initColumnField(GenTableColumn column, GenTable table) {
String dataType = getDbType(column.getColumnType());
String columnName = column.getColumnName();
// 统一转小写 避免有些数据库默认大写问题 如果需要特别书写方式 请在实体类增加注解标注别名
String columnName = column.getColumnName().toLowerCase();
column.setTableId(table.getTableId());
column.setCreateTime(null);
column.setUpdateTime(null);
// 设置java字段名
column.setJavaField(StringUtils.toCamelCase(columnName));
// 设置默认类型

View File

@ -95,6 +95,10 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service {
${ClassName}::get$AttrName ,params.get("begin$AttrName"), params.get("end$AttrName"));
#end
#end
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#if($column.isPk==1)
lqw.orderByAsc(${ClassName}::get$AttrName);
#end
#end
return lqw;
}

View File

@ -19,6 +19,7 @@ import org.dromara.system.domain.SysUserOnline;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@ -43,7 +44,7 @@ public class SysUserOnlineController extends BaseController {
@GetMapping("/list")
public TableDataInfo<SysUserOnline> list(String ipaddr, String userName) {
// 获取所有未过期的 token
List<String> keys = StpUtil.searchTokenValue("", 0, -1, false);
Collection<String> keys = RedisUtils.keys(CacheConstants.ONLINE_TOKEN_KEY + "*");
List<UserOnlineDTO> userOnlineDTOList = new ArrayList<>();
for (String key : keys) {
String token = StringUtils.substringAfterLast(key, ":");
@ -113,7 +114,7 @@ public class SysUserOnlineController extends BaseController {
* @param tokenId token值
*/
@Log(title = "在线设备", businessType = BusinessType.FORCE)
@PostMapping("/{tokenId}")
@DeleteMapping("/myself/{tokenId}")
public R<Void> remove(@PathVariable("tokenId") String tokenId) {
try {
// 获取指定账号 id token 集合

View File

@ -59,7 +59,7 @@ public class SysTenantController extends BaseController {
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenant:export")
@Log(title = "租户", businessType = BusinessType.EXPORT)
@Log(title = "租户管理", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(SysTenantBo bo, HttpServletResponse response) {
List<SysTenantVo> list = tenantService.queryList(bo);
@ -85,7 +85,7 @@ public class SysTenantController extends BaseController {
@ApiEncrypt
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenant:add")
@Log(title = "租户", businessType = BusinessType.INSERT)
@Log(title = "租户管理", businessType = BusinessType.INSERT)
@Lock4j
@RepeatSubmit()
@PostMapping()
@ -101,7 +101,7 @@ public class SysTenantController extends BaseController {
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenant:edit")
@Log(title = "租户", businessType = BusinessType.UPDATE)
@Log(title = "租户管理", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysTenantBo bo) {
@ -117,7 +117,7 @@ public class SysTenantController extends BaseController {
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenant:edit")
@Log(title = "租户", businessType = BusinessType.UPDATE)
@Log(title = "租户管理", businessType = BusinessType.UPDATE)
@PutMapping("/changeStatus")
public R<Void> changeStatus(@RequestBody SysTenantBo bo) {
tenantService.checkTenantAllowed(bo.getTenantId());
@ -131,7 +131,7 @@ public class SysTenantController extends BaseController {
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenant:remove")
@Log(title = "租户", businessType = BusinessType.DELETE)
@Log(title = "租户管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
@ -169,7 +169,7 @@ public class SysTenantController extends BaseController {
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@SaCheckPermission("system:tenant:edit")
@Log(title = "租户", businessType = BusinessType.UPDATE)
@Log(title = "租户管理", businessType = BusinessType.UPDATE)
@GetMapping("/syncTenantPackage")
public R<Void> syncTenantPackage(@NotBlank(message = "租户ID不能为空") String tenantId,
@NotNull(message = "套餐ID不能为空") Long packageId) {
@ -180,7 +180,7 @@ public class SysTenantController extends BaseController {
* 同步租户字典
*/
@SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
@Log(title = "同步租户字典", businessType = BusinessType.INSERT)
@Log(title = "租户管理", businessType = BusinessType.INSERT)
@GetMapping("/syncTenantDict")
public R<Void> syncTenantDict() {
if (!TenantHelper.isEnable()) {

View File

@ -110,7 +110,8 @@ public class SysDeptServiceImpl implements ISysDeptService, DeptService {
tree.setId(dept.getDeptId())
.setParentId(dept.getParentId())
.setName(dept.getDeptName())
.setWeight(dept.getOrderNum()));
.setWeight(dept.getOrderNum())
.putExtra("disabled", SystemConstants.DISABLE.equals(dept.getStatus())));
Tree<Long> tree = trees.stream().filter(it -> it.getId().longValue() == d.getDeptId()).findFirst().get();
treeList.add(tree);
}

View File

@ -96,7 +96,7 @@ http {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://monitor-admin/admin/;
proxy_pass http://monitor-admin/;
}
location /snail-job/ {

View File

@ -738,10 +738,10 @@ create table sys_oper_log (
oper_url varchar2(255) default '',
oper_ip varchar2(128) default '',
oper_location varchar2(255) default '',
oper_param varchar2(5500) default '',
json_result varchar2(5500) default '',
oper_param varchar2(4000) default '',
json_result varchar2(4000) default '',
status number(1) default 0,
error_msg varchar2(5500) default '',
error_msg varchar2(4000) default '',
oper_time date,
cost_time number(20) default 0
);

View File

@ -741,10 +741,10 @@ create table if not exists sys_oper_log
oper_url varchar(255) default ''::varchar,
oper_ip varchar(128) default ''::varchar,
oper_location varchar(255) default ''::varchar,
oper_param varchar(5000) default ''::varchar,
json_result varchar(5000) default ''::varchar,
oper_param varchar(4000) default ''::varchar,
json_result varchar(4000) default ''::varchar,
status int4 default 0,
error_msg varchar(5000) default ''::varchar,
error_msg varchar(4000) default ''::varchar,
oper_time timestamp,
cost_time int8 default 0,
constraint sys_oper_log_pk primary key (oper_id)
@ -1209,7 +1209,7 @@ comment on column sys_oss_config.remark is '备注';
insert into sys_oss_config values (1, '000000', 'minio', 'ruoyi', 'ruoyi123', 'ruoyi', '', '127.0.0.1:9000', '','N', '', '1', '0', '', 103, 1, now(), 1, now(), null);
insert into sys_oss_config values (2, '000000', 'qiniu', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 's3-cn-north-1.qiniucs.com', '','N', '', '1', '1', '', 103, 1, now(), 1, now(), null);
insert into sys_oss_config values (3, '000000', 'aliyun', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 'oss-cn-beijing.aliyuncs.com', '','N', '', '1', '1', '', 103, 1, now(), 1, now(), null);
insert into sys_oss_config values (4, '000000', 'qcloud', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi-1250000000', '', 'cos.ap-beijing.myqcloud.com', '','N', 'ap-beijing', '1', '1', '', 103, 1, now(), 1, now(), null);
insert into sys_oss_config values (4, '000000', 'qcloud', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi-1240000000', '', 'cos.ap-beijing.myqcloud.com', '','N', 'ap-beijing', '1', '1', '', 103, 1, now(), 1, now(), null);
insert into sys_oss_config values (5, '000000', 'image', 'ruoyi', 'ruoyi123', 'ruoyi', 'image', '127.0.0.1:9000', '','N', '', '1', '1', '', 103, 1, now(), 1, now(), NULL);
-- ----------------------------

View File

@ -554,10 +554,10 @@ create table sys_oper_log (
oper_url varchar(255) default '' comment '请求URL',
oper_ip varchar(128) default '' comment '主机地址',
oper_location varchar(255) default '' comment '操作地点',
oper_param varchar(5000) default '' comment '请求参数',
json_result varchar(5000) default '' comment '返回参数',
oper_param varchar(4000) default '' comment '请求参数',
json_result varchar(4000) default '' comment '返回参数',
status int(1) default 0 comment '操作状态0正常 1异常',
error_msg varchar(5000) default '' comment '错误消息',
error_msg varchar(4000) default '' comment '错误消息',
oper_time datetime comment '操作时间',
cost_time bigint(20) default 0 comment '消耗时间',
primary key (oper_id),
@ -840,7 +840,7 @@ create table sys_oss_config (
insert into sys_oss_config values (1, '000000', 'minio', 'ruoyi', 'ruoyi123', 'ruoyi', '', '127.0.0.1:9000', '','N', '', '1' ,'0', '', 103, 1, sysdate(), 1, sysdate(), null);
insert into sys_oss_config values (2, '000000', 'qiniu', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 's3-cn-north-1.qiniucs.com', '','N', '', '1' ,'1', '', 103, 1, sysdate(), 1, sysdate(), null);
insert into sys_oss_config values (3, '000000', 'aliyun', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 'oss-cn-beijing.aliyuncs.com', '','N', '', '1' ,'1', '', 103, 1, sysdate(), 1, sysdate(), null);
insert into sys_oss_config values (4, '000000', 'qcloud', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi-1250000000', '', 'cos.ap-beijing.myqcloud.com', '','N', 'ap-beijing', '1' ,'1', '', 103, 1, sysdate(), 1, sysdate(), null);
insert into sys_oss_config values (4, '000000', 'qcloud', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi-1240000000', '', 'cos.ap-beijing.myqcloud.com', '','N', 'ap-beijing', '1' ,'1', '', 103, 1, sysdate(), 1, sysdate(), null);
insert into sys_oss_config values (5, '000000', 'image', 'ruoyi', 'ruoyi123', 'ruoyi', 'image', '127.0.0.1:9000', '','N', '', '1' ,'1', '', 103, 1, sysdate(), 1, sysdate(), null);
-- ----------------------------

View File

@ -2002,10 +2002,10 @@ CREATE TABLE sys_oper_log
oper_url nvarchar(255) DEFAULT '' NULL,
oper_ip nvarchar(128) DEFAULT '' NULL,
oper_location nvarchar(255) DEFAULT '' NULL,
oper_param nvarchar(5000) DEFAULT '' NULL,
json_result nvarchar(5000) DEFAULT '' NULL,
oper_param nvarchar(4000) DEFAULT '' NULL,
json_result nvarchar(4000) DEFAULT '' NULL,
status int DEFAULT ((0)) NULL,
error_msg nvarchar(5000) DEFAULT '' NULL,
error_msg nvarchar(4000) DEFAULT '' NULL,
oper_time datetime2(7) NULL,
cost_time bigint DEFAULT ((0)) NULL,
CONSTRAINT PK__sys_oper__34723BF9BD954573 PRIMARY KEY CLUSTERED (oper_id)