Compare commits

2 Commits

Author SHA1 Message Date
疯狂的狮子Li
0b331796e2 !551 ♥️发布 5.2.0 正式版本
Merge pull request !551 from 疯狂的狮子Li/dev
2024-06-20 02:10:15 +00:00
疯狂的狮子Li
456620b638 !549 ♥️发布 5.2.0-BETA2 公测版本
Merge pull request !549 from 疯狂的狮子Li/dev
2024-06-06 03:13:46 +00:00
133 changed files with 966 additions and 1955 deletions

View File

@@ -0,0 +1,49 @@
### 使用版本(未按照模板填写直接删除)
- jdk版本(带上尾号): 例如 1.8.0_202
- 框架版本(项目启动时输出的版本号): 例如 4.4.0
- 其他依赖版本(你觉得有必要的):
### 问题前提
> 功能不好用 不会用 是否已经看过项目文档
> 项目运行报错 是否已经拿着报错信息去百度 常见报错百度百度足以
> 是否搜索过其他issue 一些已经解决的问题 会在issue内留下解决方法
> 无法线上解决或者与框架无关的问题的欢迎加VIP群跟作者一对一谈
### 异常模块
> 此报错都涉及到那些系统模块
例如 ruoyi-system ruoyi-auth 等等
### 问题描述
> 越详细越容易直击问题所在
已知: XXX功能不好用 或 XXX数据不正常 等等
### 希望结果
> 想知道你觉得怎么样是正常或者合理的
希望功能可以有XXX结果 或者 XXX现象
### 重现步骤
> 作者并不知道这个问题是如何出现的
- 1
- 2
- 3
### 相关代码与报错信息(请勿发混乱格式)
> 代码可按照如下形式提供或者截图均可 越详细越好
> 大多数问题都是 代码编写错误问题 逻辑问题 或者用法错误等问题
```java
public class XXX {
}
```

View File

@@ -9,9 +9,8 @@ body:
label: 版本
description: 你当前正在使用我们软件的哪个版本(pom文件内的版本号)
value: |
注意: 未填写版本号不予处理直接关闭或删除
jdk版本(带上尾号):
框架版本(项目启动时输出的版本号):
jdk版本(带上尾号): 例如 17.0.8
框架版本(项目启动时输出的版本号): 例如 5.1.1
其他依赖版本(你觉得有必要的):
validations:
required: true

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.2.1" />
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.2.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
</settings>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-server:5.2.1" />
<option name="imageTag" value="ruoyi/ruoyi-server:5.2.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
</settings>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.2.1" />
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.2.0" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
</settings>

View File

@@ -9,7 +9,7 @@
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE)
[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
<br>
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.2.1-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.2.0-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.2-blue.svg)]()
[![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]()
[![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]()
@@ -56,12 +56,11 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
| 数据加解密 | 采用 注解 + mybatis 拦截器 对存取数据期间自动加解密<br/>支持多种策略 如BASE64、AES、RSA、SM2、SM4等 | 无 |
| 接口传输加密 | 采用 动态 AES + RSA 加密请求 body 每一次请求秘钥都不同大幅度降低可破解性 | 无 |
| 数据翻译 | 采用 注解 + jackson 序列化期间动态修改数据 数据进行翻译<br/>支持多种模式: `映射翻译` `直接翻译` `其他扩展条件翻译` 接口化两步即可完成自定义扩展 内置多种翻译实现 | 无 |
| 多数据源框架 | 采用 dynamic-datasource 支持面大部分数据库<br/>通过yml配置即可动态管理异构不同种类的数据库 也可通过前端页面添加数据源<br/>支持spel表达式从请求头参数等条件切换数据源 | 基于 druid 手动编写代码配置数据源 配置繁琐 支持性差 |
| 多数据源框架 | 采用 dynamic-datasource 支持面大部分数据库<br/>通过yml配置即可动态管理异构不同种类的数据库 也可通过前端页面添加数据源<br/>支持spel表达式从请求头参数等条件切换数据源 | 基于 druid 手动编写代码配置数据源 配置繁琐 支持性差 |
| 多数据源事务 | 采用 dynamic-datasource 支持多数据源不同种类的数据库事务回滚 | 不支持 |
| 数据库连接池 | 采用 HikariCP Spring官方内置连接池 配置简单 以性能与稳定性闻名天下 | 采用 druid bug众多 社区维护差 活跃度低 配置众多繁琐性能一般 |
| 数据库主键 | 采用 雪花ID 基于时间戳的 有序增长 唯一ID 再也不用为分库分表 数据合并主键冲突重复而发愁 | 采用 数据库自增ID 支持数据量有限 不支持多数据源主键唯一 |
| WebSocket协议 | 基于 Spring 封装的 WebSocket 协议 扩展了Token鉴权与分布式会话同步 不再只是基于单机的废物 | 无 |
| SSE推送 | 采用 Spring SSE 实现 扩展了Token鉴权与分布式会话同步 | 无 |
| 序列化 | 采用 Jackson Spring官方内置序列化 靠谱!!! | 采用 fastjson bugjson 远近闻名 |
| 分布式幂等 | 参考美团GTIS防重系统简化实现(细节可看文档) | 手动编写注解基于aop实现 |
| 分布式锁 | 采用 Lock4j 底层基于 Redisson | 无 |
@@ -73,7 +72,6 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
| 接口文档 | 采用 SpringDoc、javadoc 无注解零入侵基于java注释<br/>只需把注释写好 无需再写一大堆的文档注解了 | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成 |
| 校验框架 | 采用 Validation 支持注解与工具类校验 注解支持国际化 | 仅支持注解 且注解不支持国际化 |
| Excel框架 | 采用 Alibaba EasyExcel 基于插件化<br/>框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等 | 基于 POI 手写实现 功能有限 复杂 扩展性差 |
| 工作流支持 | 支持各种复杂审批 转办 委派 加减签 会签 或签 票签 等功能 | 无 |
| 工具类框架 | 采用 Hutool、Lombok 上百种工具覆盖90%的使用需求 基于注解自动生成 get set 等简化框架大量代码 | 手写工具稳定性差易出问题 工具数量有限 代码臃肿需自己手写 get set 等 |
| 监控框架 | 采用 SpringBoot-Admin 基于SpringBoot官方 actuator 探针机制<br/>实时监控服务状态 框架还为其扩展了在线日志查看监控 | 无 |
| 链路追踪 | 采用 Apache SkyWalking 还在为请求不知道去哪了 到哪出了问题而烦恼吗<br/>用了它即可实时查看请求经过的每一处每一个节点 | 无 |

17
pom.xml
View File

@@ -13,13 +13,13 @@
<description>RuoYi-Vue-Plus多租户管理系统</description>
<properties>
<revision>5.2.1</revision>
<spring-boot.version>3.2.8</spring-boot.version>
<revision>5.2.0</revision>
<spring-boot.version>3.2.6</spring-boot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<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.5.0</springdoc.version>
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
<poi.version>5.2.3</poi.version>
<easyexcel.version>3.3.4</easyexcel.version>
@@ -27,14 +27,14 @@
<satoken.version>1.38.0</satoken.version>
<mybatis-plus.version>3.5.7</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.8.29</hutool.version>
<hutool.version>5.8.27</hutool.version>
<okhttp.version>4.10.0</okhttp.version>
<spring-boot-admin.version>3.2.3</spring-boot-admin.version>
<redisson.version>3.33.0</redisson.version>
<redisson.version>3.31.0</redisson.version>
<lock4j.version>2.2.7</lock4j.version>
<dynamic-ds.version>4.3.1</dynamic-ds.version>
<alibaba-ttl.version>2.14.4</alibaba-ttl.version>
<snailjob.version>1.1.1</snailjob.version>
<snailjob.version>1.0.1</snailjob.version>
<mapstruct-plus.version>1.3.6</mapstruct-plus.version>
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
<lombok.version>1.18.32</lombok.version>
@@ -42,7 +42,6 @@
<justauth.version>1.16.6</justauth.version>
<!-- 离线IP地址定位库 -->
<ip2region.version>2.7.0</ip2region.version>
<undertow.version>2.3.15.Final</undertow.version>
<!-- OSS 配置 -->
<aws.sdk.version>2.25.15</aws.sdk.version>
@@ -51,10 +50,8 @@
<sms4j.version>3.2.1</sms4j.version>
<!-- 限制框架中的fastjson版本 -->
<fastjson.version>1.2.83</fastjson.version>
<!-- 面向运行时的D-ORM依赖 -->
<anyline.version>8.7.2-20240728</anyline.version>
<!--工作流配置-->
<flowable.version>7.0.1</flowable.version>
<flowable.version>7.0.0</flowable.version>
<!-- 插件版本 -->
<maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>

View File

@@ -1,9 +1,7 @@
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
#FROM bellsoft/liberica-openjdk-debian:21.0.3-cds
#FROM findepi/graalvm:java17-native
FROM openjdk:17.0.2-oraclelinux8
LABEL maintainer="Lion Li"
MAINTAINER Lion Li
RUN mkdir -p /ruoyi/server/logs \
/ruoyi/server/temp \

View File

@@ -22,28 +22,21 @@
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- &lt;!&ndash; mp支持的数据库均支持 只需要增加对应的jdbc依赖即可 &ndash;&gt;-->
<!-- &lt;!&ndash; Oracle &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>com.oracle.database.jdbc</groupId>-->
<!-- <artifactId>ojdbc8</artifactId>-->
<!-- </dependency>-->
<!-- &lt;!&ndash; 兼容oracle低版本 &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>com.oracle.database.nls</groupId>-->
<!-- <artifactId>orai18n</artifactId>-->
<!-- </dependency>-->
<!-- &lt;!&ndash; PostgreSql &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.postgresql</groupId>-->
<!-- <artifactId>postgresql</artifactId>-->
<!-- </dependency>-->
<!-- &lt;!&ndash; SqlServer &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>com.microsoft.sqlserver</groupId>-->
<!-- <artifactId>mssql-jdbc</artifactId>-->
<!-- </dependency>-->
<!-- Oracle -->
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
</dependency>
<!-- PostgreSql -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<!-- SqlServer -->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>

View File

@@ -1,7 +1,6 @@
package org.dromara.web.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.exception.NotLoginException;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
@@ -24,9 +23,9 @@ import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
import org.dromara.common.social.config.properties.SocialProperties;
import org.dromara.common.social.utils.SocialUtils;
import org.dromara.common.sse.dto.SseMessageDto;
import org.dromara.common.sse.utils.SseMessageUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.common.websocket.dto.WebSocketMessageDto;
import org.dromara.common.websocket.utils.WebSocketUtils;
import org.dromara.system.domain.bo.SysTenantBo;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysTenantVo;
@@ -102,11 +101,11 @@ public class AuthController {
Long userId = LoginHelper.getUserId();
scheduledExecutorService.schedule(() -> {
SseMessageDto dto = new SseMessageDto();
WebSocketMessageDto dto = new WebSocketMessageDto();
dto.setMessage("欢迎登录RuoYi-Vue-Plus后台管理系统");
dto.setUserIds(List.of(userId));
SseMessageUtils.publishMessage(dto);
}, 5, TimeUnit.SECONDS);
dto.setSessionKeys(List.of(userId));
WebSocketUtils.publishMessage(dto);
}, 3, TimeUnit.SECONDS);
return R.ok(loginVo);
}
@@ -195,26 +194,8 @@ public class AuthController {
*/
@GetMapping("/tenant/list")
public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
// 返回对象
LoginTenantVo result = new LoginTenantVo();
boolean enable = TenantHelper.isEnable();
result.setTenantEnabled(enable);
// 如果未开启租户这直接返回
if (!enable) {
return R.ok(result);
}
List<SysTenantVo> tenantList = tenantService.queryList(new SysTenantBo());
List<TenantListVo> voList = MapstructUtils.convert(tenantList, TenantListVo.class);
try {
// 如果只超管返回所有租户
if (LoginHelper.isSuperAdmin()) {
result.setVoList(voList);
return R.ok(result);
}
} catch (NotLoginException ignored) {
}
// 获取域名
String host;
String referer = request.getHeader("referer");
@@ -227,8 +208,11 @@ public class AuthController {
// 根据域名进行筛选
List<TenantListVo> list = StreamUtils.filter(voList, vo ->
StringUtils.equals(vo.getDomain(), host));
result.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
return R.ok(result);
// 返回对象
LoginTenantVo vo = new LoginTenantVo();
vo.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
vo.setTenantEnabled(TenantHelper.isEnable());
return R.ok(vo);
}
}

View File

@@ -3,8 +3,6 @@ package org.dromara.web.listener;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import lombok.RequiredArgsConstructor;
@@ -83,10 +81,7 @@ public class UserActionListener implements SaTokenListener {
*/
@Override
public void doLogout(String loginType, Object loginId, String tokenValue) {
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
log.info("user doLogout, userId:{}, token:{}", loginId, tokenValue);
}
@@ -95,10 +90,7 @@ public class UserActionListener implements SaTokenListener {
*/
@Override
public void doKickout(String loginType, Object loginId, String tokenValue) {
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
log.info("user doKickout, userId:{}, token:{}", loginId, tokenValue);
}
@@ -107,10 +99,7 @@ public class UserActionListener implements SaTokenListener {
*/
@Override
public void doReplaced(String loginType, Object loginId, String tokenValue) {
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
log.info("user doReplaced, userId:{}, token:{}", loginId, tokenValue);
}

View File

@@ -4,14 +4,13 @@ import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.lock.annotation.Lock4j;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthUser;
import org.dromara.common.core.constant.CacheConstants;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.domain.dto.RoleDTO;
import org.dromara.common.core.domain.model.LoginUser;
@@ -156,11 +155,16 @@ public class SysLoginService {
loginUser.setUserType(user.getUserType());
loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId()));
loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId()));
Opt<SysDeptVo> deptOpt = Opt.of(user.getDeptId()).map(deptService::selectDeptById);
loginUser.setDeptName(deptOpt.map(SysDeptVo::getDeptName).orElse(StringUtils.EMPTY));
loginUser.setDeptCategory(deptOpt.map(SysDeptVo::getDeptCategory).orElse(StringUtils.EMPTY));
List<SysRoleVo> roles = roleService.selectRolesByUserId(user.getUserId());
loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
TenantHelper.dynamic(user.getTenantId(), () -> {
SysDeptVo dept = null;
if (ObjectUtil.isNotNull(user.getDeptId())) {
dept = deptService.selectDeptById(user.getDeptId());
}
loginUser.setDeptName(ObjectUtil.isNull(dept) ? "" : dept.getDeptName());
loginUser.setDeptCategory(ObjectUtil.isNull(dept) ? "" : dept.getDeptCategory());
List<SysRoleVo> roles = roleService.selectRolesByUserId(user.getUserId());
loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
});
return loginUser;
}
@@ -182,7 +186,7 @@ public class SysLoginService {
* 登录校验
*/
public void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) {
String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
String errorKey = GlobalConstants.PWD_ERR_CNT_KEY + username;
String loginFail = Constants.LOGIN_FAIL;
// 获取用户登录错误次数默认为0 (可自定义限制策略 例如: key + username + ip)

View File

@@ -21,6 +21,7 @@ import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.SysClient;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
@@ -50,12 +51,13 @@ public class EmailAuthStrategy implements IAuthStrategy {
String tenantId = loginBody.getTenantId();
String email = loginBody.getEmail();
String emailCode = loginBody.getEmailCode();
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = loadUserByEmail(email);
loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
return loginService.buildLoginUser(user);
});
// 通过邮箱查找用户
SysUserVo user = loadUserByEmail(tenantId, email);
loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
LoginUser loginUser = loginService.buildLoginUser(user);
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
@@ -87,16 +89,18 @@ public class EmailAuthStrategy implements IAuthStrategy {
return code.equals(emailCode);
}
private SysUserVo loadUserByEmail(String email) {
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, email));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", email);
throw new UserException("user.not.exists", email);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", email);
throw new UserException("user.blocked", email);
}
return user;
private SysUserVo loadUserByEmail(String tenantId, String email) {
return TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, email));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", email);
throw new UserException("user.not.exists", email);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", email);
throw new UserException("user.blocked", email);
}
return user;
});
}
}

View File

@@ -24,6 +24,7 @@ import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.common.web.config.properties.CaptchaProperties;
import org.dromara.system.domain.SysClient;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
@@ -62,12 +63,11 @@ public class PasswordAuthStrategy implements IAuthStrategy {
if (captchaEnabled) {
validateCaptcha(tenantId, username, code, uuid);
}
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = loadUserByUsername(username);
loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
// 此处可根据登录用户的数据不同 自行创建 loginUser
return loginService.buildLoginUser(user);
});
SysUserVo user = loadUserByUsername(tenantId, username);
loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
// 此处可根据登录用户的数据不同 自行创建 loginUser
LoginUser loginUser = loginService.buildLoginUser(user);
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
@@ -95,7 +95,7 @@ public class PasswordAuthStrategy implements IAuthStrategy {
* @param uuid 唯一标识
*/
private void validateCaptcha(String tenantId, String username, String code, String uuid) {
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
@@ -108,16 +108,18 @@ public class PasswordAuthStrategy implements IAuthStrategy {
}
}
private SysUserVo loadUserByUsername(String username) {
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, username));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new UserException("user.not.exists", username);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new UserException("user.blocked", username);
}
return user;
private SysUserVo loadUserByUsername(String tenantId, String username) {
return TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, username));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new UserException("user.not.exists", username);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new UserException("user.blocked", username);
}
return user;
});
}
}

View File

@@ -21,6 +21,7 @@ import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.SysClient;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
@@ -50,12 +51,13 @@ public class SmsAuthStrategy implements IAuthStrategy {
String tenantId = loginBody.getTenantId();
String phonenumber = loginBody.getPhonenumber();
String smsCode = loginBody.getSmsCode();
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = loadUserByPhonenumber(phonenumber);
loginService.checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
return loginService.buildLoginUser(user);
});
// 通过手机号查找用户
SysUserVo user = loadUserByPhonenumber(tenantId, phonenumber);
loginService.checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
LoginUser loginUser = loginService.buildLoginUser(user);
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
@@ -87,16 +89,18 @@ public class SmsAuthStrategy implements IAuthStrategy {
return code.equals(smsCode);
}
private SysUserVo loadUserByPhonenumber(String phonenumber) {
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, phonenumber));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", phonenumber);
throw new UserException("user.not.exists", phonenumber);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", phonenumber);
throw new UserException("user.blocked", phonenumber);
}
return user;
private SysUserVo loadUserByPhonenumber(String tenantId, String phonenumber) {
return TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getPhonenumber, phonenumber));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", phonenumber);
throw new UserException("user.not.exists", phonenumber);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", phonenumber);
throw new UserException("user.blocked", phonenumber);
}
return user;
});
}
}

View File

@@ -16,7 +16,6 @@ import org.dromara.common.core.domain.model.SocialLoginBody;
import org.dromara.common.core.enums.UserStatus;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.satoken.utils.LoginHelper;
@@ -84,7 +83,7 @@ public class SocialAuthStrategy implements IAuthStrategy {
}
SysSocialVo social;
if (TenantHelper.isEnable()) {
Optional<SysSocialVo> opt = StreamUtils.findAny(list, x -> x.getTenantId().equals(loginBody.getTenantId()));
Optional<SysSocialVo> opt = list.stream().filter(x -> x.getTenantId().equals(loginBody.getTenantId())).findAny();
if (opt.isEmpty()) {
throw new ServiceException("对不起,你没有权限登录当前租户!");
}
@@ -92,11 +91,11 @@ public class SocialAuthStrategy implements IAuthStrategy {
} else {
social = list.get(0);
}
LoginUser loginUser = TenantHelper.dynamic(social.getTenantId(), () -> {
SysUserVo user = loadUser(social.getUserId());
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
return loginService.buildLoginUser(user);
});
// 查找用户
SysUserVo user = loadUser(social.getTenantId(), social.getUserId());
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
LoginUser loginUser = loginService.buildLoginUser(user);
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
@@ -116,16 +115,18 @@ public class SocialAuthStrategy implements IAuthStrategy {
return loginVo;
}
private SysUserVo loadUser(Long userId) {
SysUserVo user = userMapper.selectVoById(userId);
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", "");
throw new UserException("user.not.exists", "");
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", "");
throw new UserException("user.blocked", "");
}
return user;
private SysUserVo loadUser(String tenantId, Long userId) {
return TenantHelper.dynamic(tenantId, () -> {
SysUserVo user = userMapper.selectVoById(userId);
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", "");
throw new UserException("user.not.exists", "");
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", "");
throw new UserException("user.blocked", "");
}
return user;
});
}
}

View File

@@ -5,9 +5,6 @@ spring.boot.admin.client:
url: http://localhost:9090/admin
instance:
service-host-type: IP
metadata:
username: ${spring.boot.admin.client.username}
userpassword: ${spring.boot.admin.client.password}
username: ruoyi
password: 123456
@@ -20,7 +17,7 @@ snail-job:
token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
server:
host: 127.0.0.1
port: 17888
port: 1788
# 详见 script/sql/snail_job.sql `sj_namespace` 表
namespace: ${spring.profiles.active}
# 随主应用端口飘逸
@@ -99,8 +96,8 @@ spring.data:
port: 6379
# 数据库索引
database: 0
# redis 密码必须配置
password: ruoyi123
# 密码(如没有密码请注释掉)
# password:
# 连接超时时间
timeout: 10s
# 是否开启ssl

View File

@@ -8,22 +8,19 @@ spring.boot.admin.client:
url: http://localhost:9090/admin
instance:
service-host-type: IP
metadata:
username: ${spring.boot.admin.client.username}
userpassword: ${spring.boot.admin.client.password}
username: ruoyi
password: 123456
--- # snail-job 配置
snail-job:
enabled: true
enabled: false
# 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
group: "ruoyi_group"
# SnailJob 接入验证令牌 详见 script/sql/snail_job.sql `sj_group_config` 表
token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
server:
host: 127.0.0.1
port: 17888
port: 1788
# 详见 script/sql/snail_job.sql `sj_namespace` 表
namespace: ${spring.profiles.active}
# 随主应用端口飘逸
@@ -102,8 +99,8 @@ spring.data:
port: 6379
# 数据库索引
database: 0
# redis 密码必须配置
password: ruoyi123
# 密码(如没有密码请注释掉)
# password:
# 连接超时时间
timeout: 10s
# 是否开启ssl

View File

@@ -121,6 +121,9 @@ security:
# swagger 文档配置
- /*/api-docs
- /*/api-docs/**
# actuator 监控配置
- /actuator
- /actuator/**
# 多租户配置
tenant:
@@ -256,15 +259,10 @@ management:
logfile:
external-file: ./logs/sys-console.log
--- # 默认/推荐使用sse推送
sse:
enabled: true
path: /resource/sse
--- # websocket
websocket:
# 如果关闭 需要和前端开关一起关闭
enabled: false
enabled: true
# 路径
path: /resource/websocket
# 设置访问源地址
@@ -272,10 +270,6 @@ websocket:
--- #flowable配置
flowable:
# 开关 用于启动/停用工作流
enabled: true
process.enabled: ${flowable.enabled}
eventregistry.enabled: ${flowable.enabled}
async-executor-activate: false #关闭定时任务JOB
# 将databaseSchemaUpdate设置为true。当Flowable发现库与数据库表结构不一致时会自动将数据库表结构升级至新版本。
database-schema-update: true

View File

@@ -33,7 +33,6 @@
<module>ruoyi-common-encrypt</module>
<module>ruoyi-common-tenant</module>
<module>ruoyi-common-websocket</module>
<module>ruoyi-common-sse</module>
</modules>
<artifactId>ruoyi-common</artifactId>

View File

@@ -14,7 +14,7 @@
</description>
<properties>
<revision>5.2.1</revision>
<revision>5.2.0</revision>
</properties>
<dependencyManagement>
@@ -172,13 +172,6 @@
<version>${revision}</version>
</dependency>
<!-- SSE模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-sse</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@@ -22,9 +22,4 @@ public interface CacheConstants {
*/
String SYS_DICT_KEY = "sys_dict:";
/**
* 登录账户密码错误次数 redis key
*/
String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
}

View File

@@ -27,6 +27,11 @@ public interface GlobalConstants {
*/
String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:";
/**
* 登录账户密码错误次数 redis key
*/
String PWD_ERR_CNT_KEY = GLOBAL_REDIS_KEY + "pwd_err_cnt:";
/**
* 三方认证 redis key
*/

View File

@@ -67,16 +67,6 @@ public interface UserConstants {
*/
String DICT_NORMAL = "0";
/**
* 通用存在标志
*/
String DEL_FLAG_NORMAL = "0";
/**
* 通用删除标志
*/
String DEL_FLAG_REMOVED = "2";
/**
* 是否为系统默认(是)
*/

View File

@@ -7,7 +7,6 @@ import lombok.NoArgsConstructor;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -35,34 +34,6 @@ public class StreamUtils {
return collection.stream().filter(function).collect(Collectors.toList());
}
/**
* 找到流中满足条件的第一个元素
*
* @param collection 需要查询的集合
* @param function 过滤方法
* @return 找到符合条件的第一个元素没有则返回null
*/
public static <E> E findFirst(Collection<E> collection, Predicate<E> function) {
if (CollUtil.isEmpty(collection)) {
return null;
}
return collection.stream().filter(function).findFirst().orElse(null);
}
/**
* 找到流中任意一个满足条件的元素
*
* @param collection 需要查询的集合
* @param function 过滤方法
* @return 找到符合条件的任意一个元素没有则返回null
*/
public static <E> Optional<E> findAny(Collection<E> collection, Predicate<E> function) {
if (CollUtil.isEmpty(collection)) {
return Optional.empty();
}
return collection.stream().filter(function).findAny();
}
/**
* 将collection拼接
*

View File

@@ -11,7 +11,6 @@ import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
import org.springdoc.core.properties.SpringDocConfigProperties;
@@ -231,7 +230,7 @@ public class OpenApiHandler extends OpenAPIService {
.flatMap(x -> Stream.of(x.value())).collect(Collectors.toSet());
methodTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class));
if (!CollectionUtils.isEmpty(methodTags)) {
tagsStr.addAll(StreamUtils.toSet(methodTags, tag -> propertyResolverUtils.resolve(tag.name(), locale)));
tagsStr.addAll(methodTags.stream().map(tag -> propertyResolverUtils.resolve(tag.name(), locale)).collect(Collectors.toSet()));
List<io.swagger.v3.oas.annotations.tags.Tag> allTags = new ArrayList<>(methodTags);
addTags(allTags, tags, locale);
}

View File

@@ -107,7 +107,7 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements Workbook
}
if (!cellValue.equals(val)) {
if ((i - repeatCell.getCurrent() > 1)) {
if ((i - repeatCell.getCurrent() > 1) && isMerge(list, i, field)) {
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
}
map.put(field, new RepeatCell(val, i));
@@ -115,11 +115,6 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements Workbook
if (i > repeatCell.getCurrent() && isMerge(list, i, field)) {
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
}
} else if (!isMerge(list, i, field)) {
if ((i - repeatCell.getCurrent() > 1)) {
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
}
map.put(field, new RepeatCell(val, i));
}
}
}

View File

@@ -3,10 +3,9 @@ package org.dromara.common.mybatis.annotation;
import java.lang.annotation.*;
/**
* 数据权限注解,用于标记数据权限的占位符关键字和替换值
* <p>
* 数据权限
*
* 一个注解只能对应一个模板
* </p>
*
* @author Lion Li
* @version 3.5.0
@@ -17,24 +16,13 @@ import java.lang.annotation.*;
public @interface DataColumn {
/**
* 数据权限模板的占位符关键字,默认为 "deptName"
*
* @return 占位符关键字数组
* 占位符关键字
*/
String[] key() default "deptName";
/**
* 数据权限模板的占位符替换值,默认为 "dept_id"
*
* @return 占位符替换值数组
* 占位符替换值
*/
String[] value() default "dept_id";
/**
* 权限标识符 用于通过菜单权限标识符来获取数据权限
* 拥有此标识符的角色 将不会拼接此角色的数据过滤sql
*
* @return 权限标识符
*/
String permission() default "";
}

View File

@@ -3,7 +3,7 @@ package org.dromara.common.mybatis.annotation;
import java.lang.annotation.*;
/**
* 数据权限组注解,用于标记数据权限配置数组
* 数据权限组
*
* @author Lion Li
* @version 3.5.0
@@ -13,18 +13,6 @@ import java.lang.annotation.*;
@Documented
public @interface DataPermission {
/**
* 数据权限配置数组,用于指定数据权限的占位符关键字和替换值
*
* @return 数据权限配置数组
*/
DataColumn[] value();
/**
* 权限拼接标识符(用于指定连接语句的sql符号)
* 如不填 默认 select 用 OR 其他语句用 AND
* 内容 OR 或者 AND
*/
String joinStr() default "";
}

View File

@@ -17,6 +17,7 @@ import java.util.Map;
*
* @author Lion Li
*/
@Data
public class BaseEntity implements Serializable {

View File

@@ -12,7 +12,6 @@ import com.baomidou.mybatisplus.extension.toolkit.Db;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StreamUtils;
import java.io.Serializable;
import java.util.Collection;
@@ -35,38 +34,20 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
Log log = LogFactory.getLog(BaseMapperPlus.class);
/**
* 获取当前实例对象关联的泛型类型 V 的 Class 对象
*
* @return 返回当前实例对象关联的泛型类型 V 的 Class 对象
*/
default Class<V> currentVoClass() {
return (Class<V>) GenericTypeUtils.resolveTypeArguments(this.getClass(), BaseMapperPlus.class)[1];
}
/**
* 获取当前实例对象关联的泛型类型 T 的 Class 对象
*
* @return 返回当前实例对象关联的泛型类型 T 的 Class 对象
*/
default Class<T> currentModelClass() {
return (Class<T>) GenericTypeUtils.resolveTypeArguments(this.getClass(), BaseMapperPlus.class)[0];
}
/**
* 使用默认的查询条件查询并返回结果列表
*
* @return 返回查询结果的列表
*/
default List<T> selectList() {
return this.selectList(new QueryWrapper<>());
}
/**
* 批量插入实体对象集合
*
* @param entityList 实体对象集合
* @return 插入操作是否成功的布尔值
* 批量插入
*/
default boolean insertBatch(Collection<T> entityList) {
Db.saveBatch(entityList);
@@ -75,10 +56,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
}
/**
* 批量根据ID更新实体对象集合
*
* @param entityList 实体对象集合
* @return 更新操作是否成功的布尔值
* 批量更新
*/
default boolean updateBatchById(Collection<T> entityList) {
Db.updateBatchById(entityList);
@@ -87,10 +65,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
}
/**
* 批量插入或更新实体对象集合
*
* @param entityList 实体对象集合
* @return 插入或更新操作是否成功的布尔值
* 批量插入或更新
*/
default boolean insertOrUpdateBatch(Collection<T> entityList) {
Db.saveOrUpdateBatch(entityList);
@@ -99,11 +74,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
}
/**
* 批量插入实体对象集合并指定批处理大小
*
* @param entityList 实体对象集合
* @param batchSize 批处理大小
* @return 插入操作是否成功的布尔值
* 批量插入(包含限制条数)
*/
default boolean insertBatch(Collection<T> entityList, int batchSize) {
Db.saveBatch(entityList, batchSize);
@@ -112,11 +83,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
}
/**
* 批量根据ID更新实体对象集合并指定批处理大小
*
* @param entityList 实体对象集合
* @param batchSize 批处理大小
* @return 更新操作是否成功的布尔值
* 批量更新(包含限制条数)
*/
default boolean updateBatchById(Collection<T> entityList, int batchSize) {
Db.updateBatchById(entityList, batchSize);
@@ -125,11 +92,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
}
/**
* 批量插入或更新实体对象集合并指定批处理大小
*
* @param entityList 实体对象集合
* @param batchSize 批处理大小
* @return 插入或更新操作是否成功的布尔值
* 批量插入或更新(包含限制条数)
*/
default boolean insertOrUpdateBatch(Collection<T> entityList, int batchSize) {
Db.saveOrUpdateBatch(entityList, batchSize);
@@ -137,23 +100,12 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
return true;
}
/**
* 根据ID查询单个VO对象
*
* @param id 主键ID
* @return 查询到的单个VO对象
*/
default V selectVoById(Serializable id) {
return selectVoById(id, this.currentVoClass());
}
/**
* 根据ID查询单个VO对象并将其转换为指定的VO类
*
* @param id 主键ID
* @param voClass 要转换的VO类的Class对象
* @param <C> VO类的类型
* @return 查询到的单个VO对象经过转换为指定的VO类后返回
* 根据 ID 查询
*/
default <C> C selectVoById(Serializable id, Class<C> voClass) {
T obj = this.selectById(id);
@@ -163,23 +115,12 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
return MapstructUtils.convert(obj, voClass);
}
/**
* 根据ID集合批量查询VO对象列表
*
* @param idList 主键ID集合
* @return 查询到的VO对象列表
*/
default List<V> selectVoBatchIds(Collection<? extends Serializable> idList) {
return selectVoBatchIds(idList, this.currentVoClass());
}
/**
* 根据ID集合批量查询实体对象列表并将其转换为指定的VO对象列表
*
* @param idList 主键ID集合
* @param voClass 要转换的VO类的Class对象
* @param <C> VO类的类型
* @return 查询到的VO对象列表经过转换为指定的VO类后返回
* 查询(根据ID 批量查询
*/
default <C> List<C> selectVoBatchIds(Collection<? extends Serializable> idList, Class<C> voClass) {
List<T> list = this.selectBatchIds(idList);
@@ -189,23 +130,12 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
return MapstructUtils.convert(list, voClass);
}
/**
* 根据查询条件Map查询VO对象列表
*
* @param map 查询条件Map
* @return 查询到的VO对象列表
*/
default List<V> selectVoByMap(Map<String, Object> map) {
return selectVoByMap(map, this.currentVoClass());
}
/**
* 根据查询条件Map查询实体对象列表并将其转换为指定的VO对象列表
*
* @param map 查询条件Map
* @param voClass 要转换的VO类的Class对象
* @param <C> VO类的类型
* @return 查询到的VO对象列表经过转换为指定的VO类后返回
* 查询(根据 columnMap 条件)
*/
default <C> List<C> selectVoByMap(Map<String, Object> map, Class<C> voClass) {
List<T> list = this.selectByMap(map);
@@ -215,47 +145,23 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
return MapstructUtils.convert(list, voClass);
}
/**
* 根据条件查询单个VO对象
*
* @param wrapper 查询条件Wrapper
* @return 查询到的单个VO对象
*/
default V selectVoOne(Wrapper<T> wrapper) {
return selectVoOne(wrapper, this.currentVoClass());
}
/**
* 根据条件查询单个VO对象并根据需要决定是否抛出异常
*
* @param wrapper 查询条件Wrapper
* @param throwEx 是否抛出异常的标志
* @return 查询到的单个VO对象
*/
default V selectVoOne(Wrapper<T> wrapper, boolean throwEx) {
return selectVoOne(wrapper, this.currentVoClass(), throwEx);
}
/**
* 根据条件查询单个VO对象并指定返回的VO对象的类型
*
* @param wrapper 查询条件Wrapper
* @param voClass 返回的VO对象的Class对象
* @param <C> 返回的VO对象的类型
* @return 查询到的单个VO对象经过类型转换为指定的VO类后返回
* 根据 entity 条件查询一条记录
*/
default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
return selectVoOne(wrapper, voClass, true);
}
/**
* 根据条件查询单个实体对象并将其转换为指定的VO对象
*
* @param wrapper 查询条件Wrapper
* @param voClass 要转换的VO类的Class对象
* @param throwEx 是否抛出异常的标志
* @param <C> VO类的类型
* @return 查询到的单个VO对象经过转换为指定的VO类后返回
* 根据 entity 条件查询一条记录
*/
default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass, boolean throwEx) {
T obj = this.selectOne(wrapper, throwEx);
@@ -265,32 +171,16 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
return MapstructUtils.convert(obj, voClass);
}
/**
* 查询所有VO对象列表
*
* @return 查询到的VO对象列表
*/
default List<V> selectVoList() {
return selectVoList(new QueryWrapper<>(), this.currentVoClass());
}
/**
* 根据条件查询VO对象列表
*
* @param wrapper 查询条件Wrapper
* @return 查询到的VO对象列表
*/
default List<V> selectVoList(Wrapper<T> wrapper) {
return selectVoList(wrapper, this.currentVoClass());
}
/**
* 根据条件查询实体对象列表并将其转换为指定的VO对象列表
*
* @param wrapper 查询条件Wrapper
* @param voClass 要转换的VO类的Class对象
* @param <C> VO类的类型
* @return 查询到的VO对象列表经过转换为指定的VO类后返回
* 根据 entity 条件查询全部记录
*/
default <C> List<C> selectVoList(Wrapper<T> wrapper, Class<C> voClass) {
List<T> list = this.selectList(wrapper);
@@ -300,31 +190,15 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
return MapstructUtils.convert(list, voClass);
}
/**
* 根据条件分页查询VO对象列表
*
* @param page 分页信息
* @param wrapper 查询条件Wrapper
* @return 查询到的VO对象分页列表
*/
default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) {
return selectVoPage(page, wrapper, this.currentVoClass());
}
/**
* 根据条件分页查询实体对象列表并将其转换为指定的VO对象分页列表
*
* @param page 分页信息
* @param wrapper 查询条件Wrapper
* @param voClass 要转换的VO类的Class对象
* @param <C> VO类的类型
* @param <P> VO对象分页列表的类型
* @return 查询到的VO对象分页列表经过转换为指定的VO类后返回
* 分页查询VO
*/
default <C, P extends IPage<C>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper, Class<C> voClass) {
// 根据条件分页查询实体对象列表
List<T> list = this.selectList(page, wrapper);
// 创建一个新的VO对象分页列表并设置分页信息
IPage<C> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
if (CollUtil.isEmpty(list)) {
return (P) voPage;
@@ -333,16 +207,8 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
return (P) voPage;
}
/**
* 根据条件查询符合条件的对象,并将其转换为指定类型的对象列表
*
* @param wrapper 查询条件Wrapper
* @param mapper 转换函数,用于将查询到的对象转换为指定类型的对象
* @param <C> 要转换的对象的类型
* @return 查询到的符合条件的对象列表,经过转换为指定类型的对象后返回
*/
default <C> List<C> selectObjs(Wrapper<T> wrapper, Function<? super Object, C> mapper) {
return StreamUtils.toList(this.selectObjs(wrapper), mapper);
return this.selectObjs(wrapper).stream().filter(Objects::nonNull).map(mapper).collect(Collectors.toList());
}
}

View File

@@ -4,10 +4,10 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.Data;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.sql.SqlUtil;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@@ -19,6 +19,7 @@ import java.util.List;
*
* @author Lion Li
*/
@Data
public class PageQuery implements Serializable {
@@ -55,9 +56,6 @@ public class PageQuery implements Serializable {
*/
public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE;
/**
* 构建分页对象
*/
public <T> Page<T> build() {
Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM);
Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);

View File

@@ -14,6 +14,7 @@ import java.util.List;
*
* @author Lion Li
*/
@Data
@NoArgsConstructor
public class TableDataInfo<T> implements Serializable {
@@ -52,9 +53,6 @@ public class TableDataInfo<T> implements Serializable {
this.total = total;
}
/**
* 根据分页对象构建表格分页数据对象
*/
public static <T> TableDataInfo<T> build(IPage<T> page) {
TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK);
@@ -64,9 +62,6 @@ public class TableDataInfo<T> implements Serializable {
return rspData;
}
/**
* 根据数据列表构建表格分页数据对象
*/
public static <T> TableDataInfo<T> build(List<T> list) {
TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK);
@@ -76,9 +71,6 @@ public class TableDataInfo<T> implements Serializable {
return rspData;
}
/**
* 构建表格分页数据对象
*/
public static <T> TableDataInfo<T> build() {
TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK);

View File

@@ -1,8 +1,8 @@
package org.dromara.common.mybatis.enums;
import org.dromara.common.core.utils.StringUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.common.core.utils.StringUtils;
/**
* 数据库类型
@@ -33,17 +33,8 @@ public enum DataBaseType {
*/
SQL_SERVER("Microsoft SQL Server");
/**
* 数据库类型
*/
private final String type;
/**
* 根据数据库产品名称查找对应的数据库类型
*
* @param databaseProductName 数据库产品名称
* @return 对应的数据库类型枚举值,如果未找到则返回 null
*/
public static DataBaseType find(String databaseProductName) {
if (StringUtils.isBlank(databaseProductName)) {
return null;

View File

@@ -1,22 +1,19 @@
package org.dromara.common.mybatis.enums;
import org.dromara.common.core.utils.StringUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.helper.DataPermissionHelper;
/**
* 数据权限类型枚举
* 数据权限类型
* <p>
* 支持使用 SpEL 模板表达式定义 SQL 查询条件
* 内置数据:
* - {@code user}: 当前登录用户信息,参考 {@link LoginUser}
* 内置服务:
* - {@code sdss}: 系统数据权限服务,参考 {@link ISysDataScopeService}
* 如需扩展数据,可以通过 {@link DataPermissionHelper} 进行操作
* 如需扩展服务,可以通过 {@link ISysDataScopeService} 自行编写
* </p>
* 语法支持 spel 模板表达式
* <p>
* 内置数据 user 当前用户 内容参考 LoginUser
* 如需扩展数据 可使用 {@link DataPermissionHelper} 操作
* 内置服务 sdss 系统数据权限服务 内容参考 SysDataScopeService
* 如需扩展更多自定义服务 可以参考 sdss 自行编写
*
* @author Lion Li
* @version 3.5.0
@@ -32,50 +29,36 @@ public enum DataScopeType {
/**
* 自定数据权限
* 使用 SpEL 表达式:`#{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} )`
* 如果不满足条件,则使用默认 SQL 表达式:`1 = 0`
*/
CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", " 1 = 0 "),
/**
* 部门数据权限
* 使用 SpEL 表达式:`#{#deptName} = #{#user.deptId}`
* 如果不满足条件,则使用默认 SQL 表达式:`1 = 0`
*/
DEPT("3", " #{#deptName} = #{#user.deptId} ", " 1 = 0 "),
/**
* 部门及以下数据权限
* 使用 SpEL 表达式:`#{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )}`
* 如果不满足条件,则使用默认 SQL 表达式:`1 = 0`
*/
DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", " 1 = 0 "),
/**
* 仅本人数据权限
* 使用 SpEL 表达式:`#{#userName} = #{#user.userId}`
* 如果不满足条件,则使用默认 SQL 表达式:`1 = 0`
*/
SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 ");
private final String code;
/**
* SpEL 模板表达式,用于构建 SQL 查询条件
* 语法 采用 spel 模板表达式
*/
private final String sqlTemplate;
/**
* 如果不满足 {@code sqlTemplate} 的条件,则使用此默认 SQL 表达式
* 不满足 sqlTemplate 则填充
*/
private final String elseSql;
/**
* 根据枚举代码查找对应的枚举值
*
* @param code 枚举代码
* @return 对应的枚举值,如果未找到则返回 null
*/
public static DataScopeType findCode(String code) {
if (StringUtils.isBlank(code)) {
return null;

View File

@@ -3,12 +3,12 @@ package org.dromara.common.mybatis.handler;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpStatus;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.satoken.utils.LoginHelper;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import java.util.Date;
@@ -21,63 +21,45 @@ import java.util.Date;
@Slf4j
public class InjectionMetaObjectHandler implements MetaObjectHandler {
/**
* 插入填充方法,用于在插入数据时自动填充实体对象中的创建时间、更新时间、创建人、更新人等信息
*
* @param metaObject 元对象,用于获取原始对象并进行填充
*/
@Override
public void insertFill(MetaObject metaObject) {
try {
if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
// 获取当前时间作为创建时间和更新时间,如果创建时间不为空,则使用创建时间,否则使用当前时间
Date current = ObjectUtil.isNotNull(baseEntity.getCreateTime())
? baseEntity.getCreateTime() : new Date();
baseEntity.setCreateTime(current);
baseEntity.setUpdateTime(current);
// 如果创建人为空,则填充当前登录用户的信息
if (ObjectUtil.isNull(baseEntity.getCreateBy())) {
LoginUser loginUser = getLoginUser();
if (ObjectUtil.isNotNull(loginUser)) {
Long userId = loginUser.getUserId();
// 填充创建人、更新人和创建部门信息
// 当前已登录 且 创建人为空 则填充
baseEntity.setCreateBy(userId);
// 当前已登录 且 更新人为空 则填充
baseEntity.setUpdateBy(userId);
baseEntity.setCreateDept(ObjectUtil.isNotNull(baseEntity.getCreateDept())
? baseEntity.getCreateDept() : loginUser.getDeptId());
}
}
} else {
Date date = new Date();
this.strictInsertFill(metaObject, "createTime", Date.class, date);
this.strictInsertFill(metaObject, "updateTime", Date.class, date);
}
} catch (Exception e) {
throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
}
}
/**
* 更新填充方法,用于在更新数据时自动填充实体对象中的更新时间和更新人信息
*
* @param metaObject 元对象,用于获取原始对象并进行填充
*/
@Override
public void updateFill(MetaObject metaObject) {
try {
if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
// 获取当前时间作为更新时间,无论原始对象中的更新时间是否为空都填充
Date current = new Date();
// 更新时间填充(不管为不为空)
baseEntity.setUpdateTime(current);
// 获取当前登录用户的ID并填充更新人信息
// 当前已登录 更新人填充(不管为不为空)
Long userId = LoginHelper.getUserId();
if (ObjectUtil.isNotNull(userId)) {
baseEntity.setUpdateBy(userId);
}
} else {
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
}
} catch (Exception e) {
throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
@@ -85,9 +67,7 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
}
/**
* 获取当前登录用户信息
*
* @return 当前登录用户的信息,如果用户未登录则返回 null
* 获取登录用户
*/
private LoginUser getLoginUser() {
LoginUser loginUser;

View File

@@ -1,14 +1,15 @@
package org.dromara.common.mybatis.handler;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.StringUtils;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import jakarta.servlet.http.HttpServletRequest;
/**
* Mybatis异常处理器
*

View File

@@ -68,27 +68,13 @@ public class PlusDataPermissionHandler {
*/
private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory());
/**
* 构造方法,扫描指定包下的 Mapper 类并初始化缓存
*
* @param mapperPackage Mapper 类所在的包路径
*/
public PlusDataPermissionHandler(String mapperPackage) {
scanMapperClasses(mapperPackage);
}
/**
* 获取数据过滤条件的 SQL 片段
*
* @param where 原始的查询条件表达式
* @param mappedStatementId Mapper 方法的 ID
* @param isSelect 是否为查询语句
* @return 数据过滤条件的 SQL 片段
*/
public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) {
// 获取数据权限配置
DataPermission dataPermission = getDataPermission(mappedStatementId);
// 获取当前登录用户信息
LoginUser currentUser = DataPermissionHelper.getVariable("user");
if (ObjectUtil.isNull(currentUser)) {
currentUser = LoginHelper.getLoginUser();
@@ -98,8 +84,7 @@ public class PlusDataPermissionHandler {
if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
return where;
}
// 构造数据过滤条件的 SQL 片段
String dataFilterSql = buildDataFilter(dataPermission, isSelect);
String dataFilterSql = buildDataFilter(dataPermission.value(), isSelect);
if (StringUtils.isBlank(dataFilterSql)) {
return where;
}
@@ -118,19 +103,11 @@ public class PlusDataPermissionHandler {
}
/**
* 构数据过滤条件的 SQL 语句
*
* @param dataPermission 数据权限注解
* @param isSelect 标志当前操作是否为查询操作,查询操作和更新或删除操作在处理过滤条件时会有不同的处理方式
* @return 构建的数据过滤条件的 SQL 语句
* @throws ServiceException 如果角色的数据范围异常或者 key 与 value 的长度不匹配,则抛出 ServiceException 异常
* 构数据过滤sql
*/
private String buildDataFilter(DataPermission dataPermission, boolean isSelect) {
private String buildDataFilter(DataColumn[] dataColumns, boolean isSelect) {
// 更新或删除需满足所有条件
String joinStr = isSelect ? " OR " : " AND ";
if (StringUtils.isNotBlank(dataPermission.joinStr())) {
joinStr = " " + dataPermission.joinStr() + " ";
}
LoginUser user = DataPermissionHelper.getVariable("user");
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(beanResolver);
@@ -148,7 +125,7 @@ public class PlusDataPermissionHandler {
return "";
}
boolean isSuccess = false;
for (DataColumn dataColumn : dataPermission.value()) {
for (DataColumn dataColumn : dataColumns) {
if (dataColumn.key().length != dataColumn.value().length) {
throw new ServiceException("角色数据范围异常 => key与value长度不匹配");
}
@@ -158,13 +135,6 @@ public class PlusDataPermissionHandler {
)) {
continue;
}
// 包含权限标识符 这直接跳过
if (StringUtils.isNotBlank(dataColumn.permission()) &&
CollUtil.contains(user.getMenuPermission(), dataColumn.permission())
) {
isSuccess = true;
continue;
}
// 设置注解变量 key 为表达式变量 value 为变量值
for (int i = 0; i < dataColumn.key().length; i++) {
context.setVariable(dataColumn.key()[i], dataColumn.value()[i]);
@@ -189,29 +159,20 @@ public class PlusDataPermissionHandler {
}
/**
* 扫描指定包下的 Mapper 类,并查找其中带有特定注解的方法
*
* @param mapperPackage Mapper 类所在的包路径
* 通过 mapperPackage 设置的扫描包 扫描缓存有注解的方法
*/
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());
// 查找类中的特定注解
findAnnotation(clazz);
}
}
@@ -220,13 +181,9 @@ public class PlusDataPermissionHandler {
}
}
/**
* 在指定的类中查找特定的注解 DataPermission并将带有这个注解的方法或类存储到 dataPermissionCacheMap 中
*
* @param clazz 要查找的类
*/
private void findAnnotation(Class<?> clazz) {
DataPermission dataPermission;
// 获取方法注解
for (Method method : clazz.getMethods()) {
if (method.isDefault() || method.isVarArgs()) {
continue;
@@ -237,24 +194,17 @@ public class PlusDataPermissionHandler {
dataPermissionCacheMap.put(mappedStatementId, dataPermission);
}
}
// 获取类注解
if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) {
dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class);
dataPermissionCacheMap.put(clazz.getName(), dataPermission);
}
}
/**
* 根据映射语句 ID 或类名获取对应的 DataPermission 注解对象
*
* @param mapperId 映射语句 ID
* @return DataPermission 注解对象,如果不存在则返回 null
*/
public DataPermission getDataPermission(String mapperId) {
// 检查缓存中是否包含映射语句 ID 对应的 DataPermission 注解对象
if (dataPermissionCacheMap.containsKey(mapperId)) {
return dataPermissionCacheMap.get(mapperId);
}
// 如果缓存中不包含映射语句 ID 对应的 DataPermission 注解对象,则尝试使用类名作为键查找
String clazzName = mapperId.substring(0, mapperId.lastIndexOf("."));
if (dataPermissionCacheMap.containsKey(clazzName)) {
return dataPermissionCacheMap.get(clazzName);
@@ -263,10 +213,7 @@ public class PlusDataPermissionHandler {
}
/**
* 检查给定的映射语句 ID 是否有效,即是否能够找到对应的 DataPermission 注解对象
*
* @param mapperId 映射语句 ID
* @return 如果找到对应的 DataPermission 注解对象,则返回 false否则返回 true
* 是否无效
*/
public boolean invalid(String mapperId) {
return getDataPermission(mapperId) == null;

View File

@@ -2,11 +2,11 @@ package org.dromara.common.mybatis.helper;
import cn.hutool.core.convert.Convert;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.mybatis.enums.DataBaseType;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import javax.sql.DataSource;
import java.sql.Connection;
@@ -14,6 +14,7 @@ import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* 数据库助手

View File

@@ -24,35 +24,17 @@ public class DataPermissionHelper {
private static final String DATA_PERMISSION_KEY = "data:permission";
/**
* 从上下文中获取指定键的变量值,并将其转换为指定的类型
*
* @param key 变量的键
* @param <T> 变量值的类型
* @return 指定键的变量值,如果不存在则返回 null
*/
public static <T> T getVariable(String key) {
Map<String, Object> context = getContext();
return (T) context.get(key);
}
/**
* 向上下文中设置指定键的变量值
*
* @param key 要设置的变量的键
* @param value 要设置的变量值
*/
public static void setVariable(String key, Object value) {
Map<String, Object> context = getContext();
context.put(key, value);
}
/**
* 获取数据权限上下文
*
* @return 存储在SaStorage中的Map对象用于存储数据权限相关的上下文信息
* @throws NullPointerException 如果数据权限上下文类型异常则抛出NullPointerException
*/
public static Map<String, Object> getContext() {
SaStorage saStorage = SaHolder.getStorage();
Object attribute = saStorage.get(DATA_PERMISSION_KEY);
@@ -82,7 +64,6 @@ public class DataPermissionHelper {
/**
* 在忽略数据权限中执行
* <p>禁止在忽略数据权限中执行忽略数据权限</p>
*
* @param handle 处理执行方法
*/
@@ -97,7 +78,6 @@ public class DataPermissionHelper {
/**
* 在忽略数据权限中执行
* <p>禁止在忽略数据权限中执行忽略数据权限</p>
*
* @param handle 处理执行方法
*/

View File

@@ -37,33 +37,17 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
private final PlusDataPermissionHandler dataPermissionHandler;
/**
* 构造函数,初始化 PlusDataPermissionHandler 实例
*
* @param mapperPackage 扫描的映射器包
*/
public PlusDataPermissionInterceptor(String mapperPackage) {
this.dataPermissionHandler = new PlusDataPermissionHandler(mapperPackage);
}
/**
* 在执行查询之前,检查并处理数据权限相关逻辑
*
* @param executor MyBatis 执行器对象
* @param ms 映射语句对象
* @param parameter 方法参数
* @param rowBounds 分页对象
* @param resultHandler 结果处理器
* @param boundSql 绑定的 SQL 对象
* @throws SQLException 如果发生 SQL 异常
*/
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
// 检查是否需要忽略数据权限处理
// 检查忽略注解
if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
return;
}
// 检查是否缺少有效的数据权限注解
// 检查是否无效 无数据权限注解
if (dataPermissionHandler.invalid(ms.getId())) {
return;
}
@@ -72,26 +56,16 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
mpBs.sql(parserSingle(mpBs.sql(), ms.getId()));
}
/**
* 在准备 SQL 语句之前,检查并处理更新和删除操作的数据权限相关逻辑
*
* @param sh MyBatis StatementHandler 对象
* @param connection 数据库连接对象
* @param transactionTimeout 事务超时时间
*/
@Override
public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
MappedStatement ms = mpSh.mappedStatement();
// 获取 SQL 命令类型(增、删、改、查)
SqlCommandType sct = ms.getSqlCommandType();
// 只处理更新和删除操作的 SQL 语句
if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
return;
}
// 检查是否缺少有效的数据权限注解
// 检查是否无效 无数据权限注解
if (dataPermissionHandler.invalid(ms.getId())) {
return;
}
@@ -100,14 +74,6 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
}
}
/**
* 处理 SELECT 查询语句中的 WHERE 条件
*
* @param select SELECT 查询对象
* @param index 查询语句的索引
* @param sql 查询语句
* @param obj WHERE 条件参数
*/
@Override
protected void processSelect(Select select, int index, String sql, Object obj) {
if (select instanceof PlainSelect) {
@@ -118,14 +84,6 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
}
}
/**
* 处理 UPDATE 语句中的 WHERE 条件
*
* @param update UPDATE 查询对象
* @param index 查询语句的索引
* @param sql 查询语句
* @param obj WHERE 条件参数
*/
@Override
protected void processUpdate(Update update, int index, String sql, Object obj) {
Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), (String) obj, false);
@@ -134,14 +92,6 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
}
}
/**
* 处理 DELETE 语句中的 WHERE 条件
*
* @param delete DELETE 查询对象
* @param index 查询语句的索引
* @param sql 查询语句
* @param obj WHERE 条件参数
*/
@Override
protected void processDelete(Delete delete, int index, String sql, Object obj) {
Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), (String) obj, false);
@@ -151,10 +101,10 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
}
/**
* 设置 SELECT 语句的 WHERE 条件
* 设置 where 条件
*
* @param plainSelect SELECT 查询对象
* @param mappedStatementId 映射语句的 ID
* @param plainSelect 查询对象
* @param mappedStatementId 执行方法id
*/
protected void setWhere(PlainSelect plainSelect, String mappedStatementId) {
Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true);
@@ -163,14 +113,6 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
}
}
/**
* 构建表达式,用于处理表的数据权限
*
* @param table 表对象
* @param where WHERE 条件表达式
* @param whereSegment WHERE 条件片段
* @return 构建的表达式
*/
@Override
public Expression buildTableExpression(Table table, Expression where, String whereSegment) {
// 只有新版数据权限处理器才会执行到这里

View File

@@ -17,4 +17,4 @@ databaseDialectTimestampFormat=yyyy-MM-dd HH:mm:ss
# 是否过滤 Log
filter=true
# 过滤 Log 时所排除的 sql 关键字,以逗号分隔
exclude=
exclude=SELECT 1

View File

@@ -178,7 +178,6 @@ public class OssClient {
.key(key)
.contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null)
.contentType(contentType)
.acl(getAccessPolicy().getObjectCannedACL())
.build())
.addTransferListener(LoggingTransferListener.create())
.source(filePath).build());
@@ -224,7 +223,6 @@ public class OssClient {
y -> y.bucket(properties.getBucketName())
.key(key)
.contentType(contentType)
.acl(getAccessPolicy().getObjectCannedACL())
.build())
.build());

View File

@@ -80,11 +80,11 @@ public class RateLimiterAspect {
private String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
String key = rateLimiter.key();
// 判断 key 不为空 和 不是表达式
if (StringUtils.isNotBlank(key) && StringUtils.containsAny(key, "#")) {
if (StringUtils.isNotBlank(key)) {
MethodSignature signature = (MethodSignature) point.getSignature();
Method targetMethod = signature.getMethod();
Object[] args = point.getArgs();
//noinspection DataFlowIssue
MethodBasedEvaluationContext context =
new MethodBasedEvaluationContext(null, targetMethod, args, pnd);
context.setBeanResolver(new BeanFactoryResolver(SpringUtils.getBeanFactory()));

View File

@@ -15,17 +15,15 @@ public class CaffeineCacheDecorator implements Cache {
private static final com.github.benmanes.caffeine.cache.Cache<Object, Object>
CAFFEINE = SpringUtils.getBean("caffeine");
private final String name;
private final Cache cache;
public CaffeineCacheDecorator(String name, Cache cache) {
this.name = name;
public CaffeineCacheDecorator(Cache cache) {
this.cache = cache;
}
@Override
public String getName() {
return name;
return cache.getName();
}
@Override
@@ -34,7 +32,7 @@ public class CaffeineCacheDecorator implements Cache {
}
public String getUniqueKey(Object key) {
return name + ":" + key;
return cache.getName() + ":" + key;
}
@Override

View File

@@ -156,7 +156,7 @@ public class PlusSpringCacheManager implements CacheManager {
private Cache createMap(String name, CacheConfig config) {
RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
Cache cache = new CaffeineCacheDecorator(name, new RedissonCache(map, allowNullValues));
Cache cache = new CaffeineCacheDecorator(new RedissonCache(map, allowNullValues));
if (transactionAware) {
cache = new TransactionAwareCacheDecorator(cache);
}
@@ -170,7 +170,7 @@ public class PlusSpringCacheManager implements CacheManager {
private Cache createMapCache(String name, CacheConfig config) {
RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
Cache cache = new CaffeineCacheDecorator(name, new RedissonCache(map, config, allowNullValues));
Cache cache = new CaffeineCacheDecorator(new RedissonCache(map, config, allowNullValues));
if (transactionAware) {
cache = new TransactionAwareCacheDecorator(cache);
}

View File

@@ -1,13 +1,12 @@
package org.dromara.common.redis.utils;
import org.dromara.common.core.utils.SpringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.SpringUtils;
import org.redisson.api.*;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Consumer;
/**
* 分布式队列工具
@@ -225,7 +224,7 @@ public class QueueUtils {
/**
* 订阅阻塞队列(可订阅所有实现类 例如: 延迟 优先 有界 等)
*/
public static <T> void subscribeBlockingQueue(String queueName, Function<T, CompletionStage<Void>> consumer, boolean isDelayed) {
public static <T> void subscribeBlockingQueue(String queueName, Consumer<T> consumer, boolean isDelayed) {
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
if (isDelayed) {
// 订阅延迟队列

View File

@@ -3,7 +3,6 @@ package org.dromara.common.satoken.utils;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import lombok.AccessLevel;
@@ -88,13 +87,6 @@ public class LoginHelper {
return Convert.toLong(getExtra(USER_KEY));
}
/**
* 获取用户账户
*/
public static String getUsername() {
return Convert.toStr(getExtra(USER_NAME_KEY));
}
/**
* 获取租户ID
*/
@@ -137,6 +129,13 @@ public class LoginHelper {
}
}
/**
* 获取用户账户
*/
public static String getUsername() {
return getLoginUser().getUsername();
}
/**
* 获取用户类型
*/
@@ -171,9 +170,6 @@ public class LoginHelper {
* @return 结果
*/
public static boolean isTenantAdmin(Set<String> rolePermission) {
if (CollUtil.isEmpty(rolePermission)) {
return false;
}
return rolePermission.contains(TenantConstants.TENANT_ADMIN_ROLE_KEY);
}
@@ -192,11 +188,7 @@ public class LoginHelper {
* @return 结果
*/
public static boolean isLogin() {
try {
return getLoginUser() != null;
} catch (Exception e) {
return false;
}
return getLoginUser() != null;
}
}

View File

@@ -1,15 +1,11 @@
package org.dromara.common.security.config;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
@@ -18,7 +14,6 @@ import org.dromara.common.security.config.properties.SecurityProperties;
import org.dromara.common.security.handler.AllUrlHandler;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@@ -76,19 +71,4 @@ public class SecurityConfig implements WebMvcConfigurer {
.excludePathPatterns(securityProperties.getExcludes());
}
/**
* 对 actuator 健康检查接口 做账号密码鉴权
*/
@Bean
public SaServletFilter getSaServletFilter() {
String username = SpringUtils.getProperty("spring.boot.admin.client.username");
String password = SpringUtils.getProperty("spring.boot.admin.client.password");
return new SaServletFilter()
.addInclude("/actuator", "/actuator/**")
.setAuth(obj -> {
SaHttpBasicUtil.check(username + ":" + password);
})
.setError(e -> SaResult.error(e.getMessage()).setCode(HttpStatus.UNAUTHORIZED));
}
}

View File

@@ -37,57 +37,7 @@ public enum SensitiveStrategy {
/**
* 银行卡
*/
BANK_CARD(DesensitizedUtil::bankCard),
/**
* 中文名
*/
CHINESE_NAME(DesensitizedUtil::chineseName),
/**
* 固定电话
*/
FIXED_PHONE(DesensitizedUtil::fixedPhone),
/**
* 用户ID
*/
USER_ID(s -> String.valueOf(DesensitizedUtil.userId())),
/**
* 密码
*/
PASSWORD(DesensitizedUtil::password),
/**
* ipv4
*/
IPV4(DesensitizedUtil::ipv4),
/**
* ipv6
*/
IPV6(DesensitizedUtil::ipv6),
/**
* 中国大陆车牌,包含普通车辆、新能源车辆
*/
CAR_LICENSE(DesensitizedUtil::carLicense),
/**
* 只显示第一个字符
*/
FIRST_MASK(DesensitizedUtil::firstMask),
/**
* 清空为null
*/
CLEAR(s -> DesensitizedUtil.clear()),
/**
* 清空为""
*/
CLEAR_TO_NULL(s -> DesensitizedUtil.clearToNull());
BANK_CARD(DesensitizedUtil::bankCard);
//可自行添加其他脱敏策略

View File

@@ -1,36 +0,0 @@
<?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>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-common-sse</artifactId>
<description>
ruoyi-common-sse 模块
</description>
<dependencies>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-redis</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-satoken</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-json</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,36 +0,0 @@
package org.dromara.common.sse.config;
import org.dromara.common.sse.controller.SseController;
import org.dromara.common.sse.core.SseEmitterManager;
import org.dromara.common.sse.listener.SseTopicListener;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
/**
* SSE 自动装配
*
* @author Lion Li
*/
@AutoConfiguration
@ConditionalOnProperty(value = "sse.enabled", havingValue = "true")
@EnableConfigurationProperties(SseProperties.class)
public class SseAutoConfiguration {
@Bean
public SseEmitterManager sseEmitterManager() {
return new SseEmitterManager();
}
@Bean
public SseTopicListener sseTopicListener() {
return new SseTopicListener();
}
@Bean
public SseController sseController(SseEmitterManager sseEmitterManager) {
return new SseController(sseEmitterManager);
}
}

View File

@@ -1,21 +0,0 @@
package org.dromara.common.sse.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* SSE 配置项
*
* @author Lion Li
*/
@Data
@ConfigurationProperties("sse")
public class SseProperties {
private Boolean enabled;
/**
* 路径
*/
private String path;
}

View File

@@ -1,62 +0,0 @@
package org.dromara.common.sse.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.stp.StpUtil;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.sse.core.SseEmitterManager;
import org.dromara.common.sse.dto.SseMessageDto;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.List;
@RestController
@ConditionalOnProperty(value = "sse.enabled", havingValue = "true")
@RequiredArgsConstructor
public class SseController implements DisposableBean {
private final SseEmitterManager sseEmitterManager;
@GetMapping(value = "${sse.path}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter connect() {
String tokenValue = StpUtil.getTokenValue();
Long userId = LoginHelper.getUserId();
return sseEmitterManager.connect(userId, tokenValue);
}
@SaIgnore
@GetMapping(value = "${sse.path}/close")
public R<Void> close() {
String tokenValue = StpUtil.getTokenValue();
Long userId = LoginHelper.getUserId();
sseEmitterManager.disconnect(userId, tokenValue);
return R.ok();
}
@GetMapping(value = "${sse.path}/send")
public R<Void> send(Long userId, String msg) {
SseMessageDto dto = new SseMessageDto();
dto.setUserIds(List.of(userId));
dto.setMessage(msg);
sseEmitterManager.publishMessage(dto);
return R.ok();
}
@GetMapping(value = "${sse.path}/sendAll")
public R<Void> send(String msg) {
sseEmitterManager.publishAll(msg);
return R.ok();
}
@Override
public void destroy() throws Exception {
// 销毁时不需要做什么 此方法避免无用操作报错
}
}

View File

@@ -1,134 +0,0 @@
package org.dromara.common.sse.core;
import cn.hutool.core.collection.CollUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.sse.dto.SseMessageDto;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
@Slf4j
public class SseEmitterManager {
/**
* 订阅的频道
*/
private final static String SSE_TOPIC = "global:sse";
private final static Map<Long, Map<String, SseEmitter>> USER_TOKEN_EMITTERS = new ConcurrentHashMap<>();
public SseEmitter connect(Long userId, String token) {
Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.computeIfAbsent(userId, k -> new ConcurrentHashMap<>());
SseEmitter emitter = new SseEmitter(0L);
emitters.put(token, emitter);
emitter.onCompletion(() -> emitters.remove(token));
emitter.onTimeout(() -> emitters.remove(token));
emitter.onError((e) -> emitters.remove(token));
try {
emitter.send(SseEmitter.event().comment("connected"));
} catch (IOException e) {
emitters.remove(token);
}
return emitter;
}
public void disconnect(Long userId, String token) {
Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.get(userId);
if (emitters != null) {
try {
emitters.get(token).send(SseEmitter.event().comment("disconnected"));
} catch (Exception ignore) {
}
emitters.remove(token);
}
}
/**
* 订阅SSE消息主题并提供一个消费者函数来处理接收到的消息
*
* @param consumer 处理SSE消息的消费者函数
*/
public void subscribeMessage(Consumer<SseMessageDto> consumer) {
RedisUtils.subscribe(SSE_TOPIC, SseMessageDto.class, consumer);
}
/**
* 向指定的用户会话发送消息
*
* @param userId 要发送消息的用户id
* @param message 要发送的消息内容
*/
public void sendMessage(Long userId, String message) {
Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.get(userId);
if (emitters != null) {
for (Map.Entry<String, SseEmitter> entry : emitters.entrySet()) {
try {
entry.getValue().send(SseEmitter.event()
.name("message")
.data(message));
} catch (Exception e) {
emitters.remove(entry.getKey());
}
}
}
}
/**
* 本机全用户会话发送消息
*
* @param message 要发送的消息内容
*/
public void sendMessage(String message) {
for (Long userId : USER_TOKEN_EMITTERS.keySet()) {
sendMessage(userId, message);
}
}
/**
* 发布SSE订阅消息
*
* @param sseMessageDto 要发布的SSE消息对象
*/
public void publishMessage(SseMessageDto sseMessageDto) {
List<Long> unsentUserIds = new ArrayList<>();
// 当前服务内用户,直接发送消息
for (Long userId : sseMessageDto.getUserIds()) {
if (USER_TOKEN_EMITTERS.containsKey(userId)) {
sendMessage(userId, sseMessageDto.getMessage());
continue;
}
unsentUserIds.add(userId);
}
// 不在当前服务内用户,发布订阅消息
if (CollUtil.isNotEmpty(unsentUserIds)) {
SseMessageDto broadcastMessage = new SseMessageDto();
broadcastMessage.setMessage(sseMessageDto.getMessage());
broadcastMessage.setUserIds(unsentUserIds);
RedisUtils.publish(SSE_TOPIC, broadcastMessage, consumer -> {
log.info("SSE发送主题订阅消息topic:{} session keys:{} message:{}",
SSE_TOPIC, unsentUserIds, sseMessageDto.getMessage());
});
}
}
/**
* 向所有的用户发布订阅的消息(群发)
*
* @param message 要发布的消息内容
*/
public void publishAll(String message) {
SseMessageDto broadcastMessage = new SseMessageDto();
broadcastMessage.setMessage(message);
RedisUtils.publish(SSE_TOPIC, broadcastMessage, consumer -> {
log.info("SSE发送主题订阅消息topic:{} message:{}", SSE_TOPIC, message);
});
}
}

View File

@@ -1,29 +0,0 @@
package org.dromara.common.sse.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 消息的dto
*
* @author zendwang
*/
@Data
public class SseMessageDto implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 需要推送到的session key 列表
*/
private List<Long> userIds;
/**
* 需要发送的消息
*/
private String message;
}

View File

@@ -1,48 +0,0 @@
package org.dromara.common.sse.listener;
import cn.hutool.core.collection.CollUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.sse.core.SseEmitterManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.Ordered;
/**
* SSE 主题订阅监听器
*
* @author Lion Li
*/
@Slf4j
public class SseTopicListener implements ApplicationRunner, Ordered {
@Autowired
private SseEmitterManager sseEmitterManager;
/**
* 在Spring Boot应用程序启动时初始化SSE主题订阅监听器
*
* @param args 应用程序参数
* @throws Exception 初始化过程中可能抛出的异常
*/
@Override
public void run(ApplicationArguments args) throws Exception {
sseEmitterManager.subscribeMessage((message) -> {
log.info("SSE主题订阅收到消息session keys={} message={}", message.getUserIds(), message.getMessage());
// 如果key不为空就按照key发消息 如果为空就群发
if (CollUtil.isNotEmpty(message.getUserIds())) {
message.getUserIds().forEach(key -> {
sseEmitterManager.sendMessage(key, message.getMessage());
});
} else {
sseEmitterManager.sendMessage(message.getMessage());
}
});
log.info("初始化SSE主题订阅监听器成功");
}
@Override
public int getOrder() {
return -1;
}
}

View File

@@ -1,58 +0,0 @@
package org.dromara.common.sse.utils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.sse.core.SseEmitterManager;
import org.dromara.common.sse.dto.SseMessageDto;
/**
* 工具类
*
* @author Lion Li
*/
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SseMessageUtils {
private final static SseEmitterManager MANAGER = SpringUtils.getBean(SseEmitterManager.class);
/**
* 向指定的WebSocket会话发送消息
*
* @param userId 要发送消息的用户id
* @param message 要发送的消息内容
*/
public static void sendMessage(Long userId, String message) {
MANAGER.sendMessage(userId, message);
}
/**
* 本机全用户会话发送消息
*
* @param message 要发送的消息内容
*/
public static void sendMessage(String message) {
MANAGER.sendMessage(message);
}
/**
* 发布SSE订阅消息
*
* @param sseMessageDto 要发布的SSE消息对象
*/
public static void publishMessage(SseMessageDto sseMessageDto) {
MANAGER.publishMessage(sseMessageDto);
}
/**
* 向所有的用户发布订阅的消息(群发)
*
* @param message 要发布的消息内容
*/
public static void publishAll(String message) {
MANAGER.publishAll(message);
}
}

View File

@@ -1,7 +1,5 @@
package org.dromara.common.tenant.manager;
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.redis.manager.PlusSpringCacheManager;
@@ -13,7 +11,6 @@ import org.springframework.cache.Cache;
*
* @author Lion Li
*/
@Slf4j
public class TenantSpringCacheManager extends PlusSpringCacheManager {
public TenantSpringCacheManager() {
@@ -21,16 +18,10 @@ public class TenantSpringCacheManager extends PlusSpringCacheManager {
@Override
public Cache getCache(String name) {
if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) {
return super.getCache(name);
}
if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
return super.getCache(name);
}
String tenantId = TenantHelper.getTenantId();
if (StringUtils.isBlank(tenantId)) {
log.error("无法获取有效的租户id -> Null");
}
if (StringUtils.startsWith(name, tenantId)) {
// 如果存在则直接返回
return super.getCache(name);

View File

@@ -43,22 +43,6 @@
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-core</artifactId>
<version>${undertow.version}</version>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
<version>${undertow.version}</version>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-websockets-jsr</artifactId>
<version>${undertow.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>

View File

@@ -16,11 +16,15 @@ import org.springframework.core.task.VirtualThreadTaskExecutor;
@AutoConfiguration
public class UndertowConfig implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
/**
* 设置 Undertow 的 websocket 缓冲池
*/
@Override
public void customize(UndertowServletWebServerFactory factory) {
// 默认不直接分配内存 如果项目中使用了 websocket 建议直接分配
factory.addDeploymentInfoCustomizers(deploymentInfo -> {
WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(true, 1024));
webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(false, 512));
deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
// 使用虚拟线程
if (SpringUtils.isVirtual()) {

View File

@@ -16,13 +16,10 @@ import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;
import java.io.IOException;
/**
* 全局异常处理器
*
@@ -92,20 +89,6 @@ public class GlobalExceptionHandler {
return R.fail(HttpStatus.HTTP_NOT_FOUND, e.getMessage());
}
/**
* 拦截未知的运行时异常
*/
@ResponseStatus(org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(IOException.class)
public void handleRuntimeException(IOException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
if (requestURI.contains("sse")) {
// sse 经常性连接中断 例如关闭浏览器 直接屏蔽
return;
}
log.error("请求地址'{}',连接中断", requestURI, e);
}
/**
* 拦截未知的运行时异常
*/

View File

@@ -35,28 +35,23 @@ public class PlusWebSocketInterceptor implements HandshakeInterceptor {
*/
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) {
try {
// 检查是否登录 是否有token
LoginUser loginUser = LoginHelper.getLoginUser();
// 检查是否登录 是否有token
LoginUser loginUser = LoginHelper.getLoginUser();
// 解决 ws 不走 mvc 拦截器问题(cloud 版本不受影响)
// 检查 header 与 param 里的 clientid 与 token 里的是否一致
String headerCid = ServletUtils.getRequest().getHeader(LoginHelper.CLIENT_KEY);
String paramCid = ServletUtils.getParameter(LoginHelper.CLIENT_KEY);
String clientId = StpUtil.getExtra(LoginHelper.CLIENT_KEY).toString();
if (!StringUtils.equalsAny(clientId, headerCid, paramCid)) {
// token 无效
throw NotLoginException.newInstance(StpUtil.getLoginType(),
"-100", "客户端ID与Token不匹配",
StpUtil.getTokenValue());
}
attributes.put(LOGIN_USER_KEY, loginUser);
return true;
} catch (NotLoginException e) {
log.error("WebSocket 认证失败'{}',无法访问系统资源", e.getMessage());
return false;
// 解决 ws 不走 mvc 拦截器问题(cloud 版本不受影响)
// 检查 header 与 param 里的 clientid 与 token 里的是否一致
String headerCid = ServletUtils.getRequest().getHeader(LoginHelper.CLIENT_KEY);
String paramCid = ServletUtils.getParameter(LoginHelper.CLIENT_KEY);
String clientId = StpUtil.getExtra(LoginHelper.CLIENT_KEY).toString();
if (!StringUtils.equalsAny(clientId, headerCid, paramCid)) {
// token 无效
throw NotLoginException.newInstance(StpUtil.getLoginType(),
"-100", "客户端ID与Token不匹配",
StpUtil.getTokenValue());
}
attributes.put(LOGIN_USER_KEY, loginUser);
return true;
}
/**

View File

@@ -1,9 +1,7 @@
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
#FROM bellsoft/liberica-openjdk-debian:21.0.3-cds
#FROM findepi/graalvm:java17-native
FROM openjdk:17.0.2-oraclelinux8
LABEL maintainer="Lion Li"
MAINTAINER Lion Li
RUN mkdir -p /ruoyi/monitor/logs

View File

@@ -12,21 +12,10 @@
<artifactId>ruoyi-monitor-admin</artifactId>
<dependencies>
<!-- SpringBoot Web容器 -->
<!-- SpringWeb模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- web 容器使用 undertow 性能更强 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<!-- spring security 安全认证 -->

View File

@@ -39,7 +39,9 @@ public class SecurityConfig {
.authorizeHttpRequests((authorize) ->
authorize.requestMatchers(
new AntPathRequestMatcher(adminContextPath + "/assets/**"),
new AntPathRequestMatcher(adminContextPath + "/login")
new AntPathRequestMatcher(adminContextPath + "/login"),
new AntPathRequestMatcher("/actuator"),
new AntPathRequestMatcher("/actuator/**")
).permitAll()
.anyRequest().authenticated())
.formLogin((formLogin) ->

View File

@@ -9,8 +9,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import static de.codecentric.boot.admin.server.domain.values.StatusInfo.*;
/**
* 自定义事件通知处理
*
@@ -30,26 +28,13 @@ public class CustomNotifier extends AbstractEventNotifier {
return Mono.fromRunnable(() -> {
// 实例状态改变事件
if (event instanceof InstanceStatusChangedEvent) {
// 获取实例注册名称
String registName = instance.getRegistration().getName();
// 获取实例ID
String instanceId = event.getInstance().getValue();
// 获取实例状态
String status = ((InstanceStatusChangedEvent) event).getStatusInfo().getStatus();
// 获取服务URL
String serviceUrl = instance.getRegistration().getServiceUrl();
String statusName = switch (status) {
case STATUS_UP -> "服务上线"; // 实例成功启动并可以正常处理请求
case STATUS_OFFLINE -> "服务离线"; //实例被手动或自动地从服务中移除
case STATUS_RESTRICTED -> "服务受限"; //表示实例在某些方面受限,可能无法完全提供所有服务
case STATUS_OUT_OF_SERVICE -> "停止服务状态"; //表示实例已被标记为停止提供服务,可能是计划内维护或测试
case STATUS_DOWN -> "服务下线"; //实例因崩溃、错误或其他原因停止运行
case STATUS_UNKNOWN -> "服务未知异常"; //监控系统无法确定实例的当前状态
default -> "未知状态"; //没有匹配的状态
};
log.info("Instance Status Change: 状态名称【{}】, 注册名称【{}】, 实例ID【{}】, 状态【{}】, 服务URL【{}】",
statusName, registName, instanceId, status, serviceUrl);
log.info("Instance Status Change: [{}],[{}],[{}]", registName, instanceId, status);
}
});
}
}

View File

@@ -41,8 +41,5 @@ spring.boot.admin.client:
url: http://localhost:9090/admin
instance:
service-host-type: IP
metadata:
username: ${spring.boot.admin.client.username}
userpassword: ${spring.boot.admin.client.password}
username: ruoyi
password: 123456

View File

@@ -1,9 +1,7 @@
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
#FROM bellsoft/liberica-openjdk-debian:21.0.3-cds
#FROM findepi/graalvm:java17-native
FROM openjdk:17.0.2-oraclelinux8
LABEL maintainer="Lion Li"
MAINTAINER Lion Li
RUN mkdir -p /ruoyi/snailjob/logs
@@ -12,7 +10,7 @@ WORKDIR /ruoyi/snailjob
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS="-Xms512m -Xmx1024m"
EXPOSE 8800
EXPOSE 17888
EXPOSE 1788
ADD ./target/ruoyi-snailjob-server.jar ./app.jar

View File

@@ -1,64 +0,0 @@
package com.aizuda.snailjob.server.starter.filter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class ActuatorAuthFilter implements Filter {
private final String username;
private final String password;
public ActuatorAuthFilter(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 获取 Authorization 头
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Basic ")) {
// 如果没有提供 Authorization 或者格式不对,则返回 401
response.setHeader("WWW-Authenticate", "Basic realm=\"realm\"");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
return;
}
// 解码 Base64 编码的用户名和密码
String base64Credentials = authHeader.substring("Basic ".length());
byte[] credDecoded = Base64.getDecoder().decode(base64Credentials);
String credentials = new String(credDecoded, StandardCharsets.UTF_8);
String[] split = credentials.split(":");
if (split.length != 2) {
response.setHeader("WWW-Authenticate", "Basic realm=\"realm\"");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
return;
}
// 验证用户名和密码
if (!username.equals(split[0]) && password.equals(split[1])) {
response.setHeader("WWW-Authenticate", "Basic realm=\"realm\"");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
return;
}
// 如果认证成功,继续处理请求
filterChain.doFilter(request, response);
}
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void destroy() {
}
}

View File

@@ -1,29 +0,0 @@
package com.aizuda.snailjob.server.starter.filter;
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;
/**
* 权限安全配置
*
* @author Lion Li
*/
@Configuration
public class SecurityConfig {
@Value("${spring.boot.admin.client.username}")
private String username;
@Value("${spring.boot.admin.client.password}")
private String password;
@Bean
public FilterRegistrationBean<ActuatorAuthFilter> actuatorFilterRegistrationBean() {
FilterRegistrationBean<ActuatorAuthFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new ActuatorAuthFilter(username, password));
registrationBean.addUrlPatterns("/actuator", "/actuator/**");
return registrationBean;
}
}

View File

@@ -21,7 +21,7 @@ snail-job:
# 拉取重试数据的每批次的大小
job-pull-page-size: 1000
# 服务端netty端口
netty-port: 17888
netty-port: 1788
# 一个客户端每秒最多接收的重试数量指令
limiter: 1000
# 号段模式下步长配置
@@ -43,8 +43,5 @@ spring.boot.admin.client:
url: http://localhost:9090/admin
instance:
service-host-type: IP
metadata:
username: ${spring.boot.admin.client.username}
userpassword: ${spring.boot.admin.client.password}
username: ruoyi
password: 123456

View File

@@ -21,7 +21,7 @@ snail-job:
# 拉取重试数据的每批次的大小
job-pull-page-size: 1000
# 服务端 netty 端口
netty-port: 17888
netty-port: 1788
# 一个客户端每秒最多接收的重试数量指令
limiter: 1000
# 号段模式下步长配置
@@ -43,8 +43,5 @@ spring.boot.admin.client:
url: http://localhost:9090/admin
instance:
service-host-type: IP
metadata:
username: ${spring.boot.admin.client.username}
userpassword: ${spring.boot.admin.client.password}
username: ruoyi
password: 123456

View File

@@ -1,15 +1,14 @@
package org.dromara.demo.controller.queue;
import cn.dev33.satoken.annotation.SaIgnore;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.dromara.common.redis.utils.QueueUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
@@ -43,10 +42,6 @@ public class DelayedQueueController {
QueueUtils.subscribeBlockingQueue(queueName, (String orderNum) -> {
// 观察接收时间
log.info("通道: {}, 收到数据: {}", queueName, orderNum);
return CompletableFuture.runAsync(() -> {
// 异步处理数据逻辑 不要在上方处理业务逻辑
log.info("数据处理: {}", orderNum);
});
}, true);
return R.ok("操作成功");
}

View File

@@ -4,14 +4,14 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import org.apache.poi.ss.formula.functions.T;
import org.dromara.common.mybatis.annotation.DataColumn;
import org.dromara.common.mybatis.annotation.DataPermission;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.demo.domain.TestDemo;
import org.dromara.demo.domain.vo.TestDemoVo;
import org.apache.ibatis.annotations.Param;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
@@ -43,13 +43,6 @@ public interface TestDemoMapper extends BaseMapperPlus<TestDemo, TestDemoVo> {
})
List<TestDemo> selectList(@Param(Constants.WRAPPER) Wrapper<TestDemo> queryWrapper);
@Override
@DataPermission(value = {
@DataColumn(key = "deptName", value = "dept_id"),
@DataColumn(key = "userName", value = "user_id")
}, joinStr = "AND")
List<TestDemo> selectBatchIds(@Param(Constants.COLL) Collection<? extends Serializable> idList);
@Override
@DataPermission({
@DataColumn(key = "deptName", value = "dept_id"),
@@ -57,4 +50,10 @@ public interface TestDemoMapper extends BaseMapperPlus<TestDemo, TestDemoVo> {
})
int updateById(@Param(Constants.ENTITY) TestDemo entity);
@Override
@DataPermission({
@DataColumn(key = "deptName", value = "dept_id"),
@DataColumn(key = "userName", value = "user_id")
})
int deleteBatchIds(@Param(Constants.COLL) Collection<?> idList);
}

View File

@@ -3,8 +3,6 @@ package org.dromara.demo.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
@@ -14,6 +12,7 @@ import org.dromara.demo.domain.bo.TestDemoBo;
import org.dromara.demo.domain.vo.TestDemoVo;
import org.dromara.demo.mapper.TestDemoMapper;
import org.dromara.demo.service.ITestDemoService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Collection;
@@ -100,13 +99,9 @@ public class TestDemoServiceImpl implements ITestDemoService {
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// 做一些业务上的校验,判断是否需要校验
List<TestDemo> list = baseMapper.selectBatchIds(ids);
if (list.size() != ids.size()) {
throw new ServiceException("您没有删除权限!");
}
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
return baseMapper.deleteBatchIds(ids) > 0;
}
@Override

View File

@@ -83,6 +83,6 @@ public class TestTreeServiceImpl implements ITestTreeService {
if (isValid) {
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
return baseMapper.deleteBatchIds(ids) > 0;
}
}

View File

@@ -47,38 +47,6 @@
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
</dependency>
<dependency>
<groupId>org.anyline</groupId>
<artifactId>anyline-environment-spring-data-jdbc</artifactId>
<version>${anyline.version}</version>
</dependency>
<dependency>
<groupId>org.anyline</groupId>
<artifactId>anyline-data-jdbc-mysql</artifactId>
<version>${anyline.version}</version>
</dependency>
<!-- anyline支持100+种类型数据库 添加对应的jdbc依赖与anyline对应数据库依赖包即可 -->
<!-- <dependency>-->
<!-- <groupId>org.anyline</groupId>-->
<!-- <artifactId>anyline-data-jdbc-oracle</artifactId>-->
<!-- <version>${anyline.version}</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.anyline</groupId>-->
<!-- <artifactId>anyline-data-jdbc-postgresql</artifactId>-->
<!-- <version>${anyline.version}</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.anyline</groupId>-->
<!-- <artifactId>anyline-data-jdbc-mssql</artifactId>-->
<!-- <version>${anyline.version}</version>-->
<!-- </dependency>-->
</dependencies>
</project>

View File

@@ -1,105 +0,0 @@
package org.dromara.generator.config;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.anyline.data.datasource.DataSourceMonitor;
import org.anyline.data.runtime.DataRuntime;
import org.anyline.util.ConfigTable;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.util.HashMap;
import java.util.Map;
/**
* anyline 适配 动态数据源改造
*
* @author Lion Li
*/
@Slf4j
@Component
public class MyBatisDataSourceMonitor implements DataSourceMonitor {
public MyBatisDataSourceMonitor() {
// 调整执行模式为自定义
ConfigTable.KEEP_ADAPTER = 2;
// 禁用缓存
ConfigTable.METADATA_CACHE_SCOPE = 0;
}
private final Map<String, String> features = new HashMap<>();
/**
* 数据源特征 用来定准 adapter 包含数据库或JDBC协议关键字<br/>
* 一般会通过 产品名_url 合成 如果返回null 上层方法会通过driver_产品名_url合成
*
* @param datasource 数据源
* @return String 返回null由上层自动提取
*/
@Override
public String feature(DataRuntime runtime, Object datasource) {
String feature = null;
if (datasource instanceof JdbcTemplate jdbc) {
DataSource ds = jdbc.getDataSource();
if (ds instanceof DynamicRoutingDataSource) {
String key = DynamicDataSourceContextHolder.peek();
feature = features.get(key);
if (null == feature) {
Connection con = null;
try {
con = DataSourceUtils.getConnection(ds);
DatabaseMetaData meta = con.getMetaData();
String url = meta.getURL();
feature = meta.getDatabaseProductName().toLowerCase().replace(" ", "") + "_" + url;
features.put(key, feature);
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
if (null != con && !DataSourceUtils.isConnectionTransactional(con, ds)) {
DataSourceUtils.releaseConnection(con, ds);
}
}
}
}
}
return feature;
}
/**
* 数据源唯一标识 如果不实现则默认feature
* @param datasource 数据源
* @return String 返回null由上层自动提取
*/
@Override
public String key(DataRuntime runtime, Object datasource) {
if(datasource instanceof JdbcTemplate jdbc){
DataSource ds = jdbc.getDataSource();
if(ds instanceof DynamicRoutingDataSource){
return DynamicDataSourceContextHolder.peek();
}
}
return runtime.getKey();
}
/**
* ConfigTable.KEEP_ADAPTER=2 : 根据当前接口判断是否保持同一个数据源绑定同一个adapter<br/>
* DynamicRoutingDataSource类型的返回false,因为同一个DynamicRoutingDataSource可能对应多类数据库, 如果项目中只有一种数据库 应该直接返回true
*
* @param datasource 数据源
* @return boolean
*/
@Override
public boolean keepAdapter(DataRuntime runtime, Object datasource) {
if (datasource instanceof JdbcTemplate jdbc) {
DataSource ds = jdbc.getDataSource();
return !(ds instanceof DynamicRoutingDataSource);
}
return true;
}
}

View File

@@ -17,6 +17,7 @@ import jakarta.validation.constraints.NotBlank;
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("gen_table_column")

View File

@@ -1,9 +1,13 @@
package org.dromara.generator.mapper;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import org.apache.ibatis.annotations.Param;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.generator.domain.GenTableColumn;
import java.util.List;
/**
* 业务字段 数据层
*
@@ -11,5 +15,14 @@ import org.dromara.generator.domain.GenTableColumn;
*/
@InterceptorIgnore(dataPermission = "true", tenantLine = "true")
public interface GenTableColumnMapper extends BaseMapperPlus<GenTableColumn, GenTableColumn> {
/**
* 根据表名称查询列信息
*
* @param tableName 表名称
* @param dataName 数据源名称
* @return 列信息
*/
@DS("#dataName")
List<GenTableColumn> selectDbTableColumnsByName(@Param("tableName") String tableName, String dataName);
}

View File

@@ -17,6 +17,22 @@ import java.util.List;
@InterceptorIgnore(dataPermission = "true", tenantLine = "true")
public interface GenTableMapper extends BaseMapperPlus<GenTable, GenTable> {
/**
* 查询据库列表
*
* @param genTable 查询条件
* @return 数据库表集合
*/
Page<GenTable> selectPageDbTableList(@Param("page") Page<GenTable> page, @Param("genTable") GenTable genTable);
/**
* 查询据库列表
*
* @param tableNames 表名称组
* @return 数据库表集合
*/
List<GenTable> selectDbTableListByNames(String[] tableNames);
/**
* 查询所有表信息
*

View File

@@ -3,27 +3,21 @@ package org.dromara.generator.service;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.anyline.metadata.Column;
import org.anyline.metadata.Table;
import org.anyline.proxy.ServiceProxy;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.FileUtils;
@@ -47,7 +41,11 @@ import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -65,8 +63,6 @@ public class GenTableServiceImpl implements IGenTableService {
private final GenTableColumnMapper genTableColumnMapper;
private final IdentifierGenerator identifierGenerator;
private static final String[] TABLE_IGNORE = new String[]{"sj_", "act_", "flw_", "gen_"};
/**
* 查询业务字段列表
*
@@ -103,7 +99,7 @@ public class GenTableServiceImpl implements IGenTableService {
Map<String, Object> params = genTable.getParams();
QueryWrapper<GenTable> wrapper = Wrappers.query();
wrapper
.eq(StringUtils.isNotEmpty(genTable.getDataName()), "data_name", genTable.getDataName())
.eq(StringUtils.isNotEmpty(genTable.getDataName()),"data_name", genTable.getDataName())
.like(StringUtils.isNotBlank(genTable.getTableName()), "lower(table_name)", StringUtils.lowerCase(genTable.getTableName()))
.like(StringUtils.isNotBlank(genTable.getTableComment()), "lower(table_comment)", StringUtils.lowerCase(genTable.getTableComment()))
.between(params.get("beginTime") != null && params.get("endTime") != null,
@@ -111,67 +107,11 @@ public class GenTableServiceImpl implements IGenTableService {
return wrapper;
}
/**
* 查询数据库列表
*
* @param genTable 包含查询条件的GenTable对象
* @param pageQuery 包含分页信息的PageQuery对象
* @return 包含分页结果的TableDataInfo对象
*/
@DS("#genTable.dataName")
@Override
public TableDataInfo<GenTable> selectPageDbTableList(GenTable genTable, PageQuery pageQuery) {
// 获取查询条件
String tableName = genTable.getTableName();
String tableComment = genTable.getTableComment();
LinkedHashMap<String, Table<?>> tablesMap = ServiceProxy.metadata().tables();
if (CollUtil.isEmpty(tablesMap)) {
return TableDataInfo.build();
}
List<String> tableNames = baseMapper.selectTableNameList(genTable.getDataName());
String[] tableArrays;
if (CollUtil.isNotEmpty(tableNames)) {
tableArrays = tableNames.toArray(new String[0]);
} else {
tableArrays = new String[0];
}
// 过滤并转换表格数据
List<GenTable> tables = tablesMap.values().stream()
.filter(x -> !StringUtils.containsAnyIgnoreCase(x.getName(), TABLE_IGNORE))
.filter(x -> {
if (CollUtil.isEmpty(tableNames)) {
return true;
}
return !StringUtils.equalsAnyIgnoreCase(x.getName(), tableArrays);
})
.filter(x -> {
boolean nameMatches = true;
boolean commentMatches = true;
// 进行表名称的模糊查询
if (StringUtils.isNotBlank(tableName)) {
nameMatches = StringUtils.containsIgnoreCase(x.getName(), tableName);
}
// 进行表描述的模糊查询
if (StringUtils.isNotBlank(tableComment)) {
commentMatches = StringUtils.containsIgnoreCase(x.getComment(), tableComment);
}
// 同时匹配名称和描述
return nameMatches && commentMatches;
})
.map(x -> {
GenTable gen = new GenTable();
gen.setTableName(x.getName());
gen.setTableComment(x.getComment());
gen.setCreateTime(x.getCreateTime());
gen.setUpdateTime(x.getUpdateTime());
return gen;
}).toList();
IPage<GenTable> page = pageQuery.build();
page.setTotal(tables.size());
// 手动分页 set数据
page.setRecords(CollUtil.page((int) page.getCurrent() - 1, (int) page.getSize(), tables));
genTable.getParams().put("genTableNames",baseMapper.selectTableNameList(genTable.getDataName()));
Page<GenTable> page = baseMapper.selectPageDbTableList(pageQuery.build(), genTable);
return TableDataInfo.build(page);
}
@@ -185,29 +125,7 @@ public class GenTableServiceImpl implements IGenTableService {
@DS("#dataName")
@Override
public List<GenTable> selectDbTableListByNames(String[] tableNames, String dataName) {
Set<String> tableNameSet = new HashSet<>(List.of(tableNames));
LinkedHashMap<String, Table<?>> tablesMap = ServiceProxy.metadata().tables();
if (CollUtil.isEmpty(tablesMap)) {
return new ArrayList<>();
}
List<Table<?>> tableList = tablesMap.values().stream()
.filter(x -> !StringUtils.containsAnyIgnoreCase(x.getName(), TABLE_IGNORE))
.filter(x -> tableNameSet.contains(x.getName())).toList();
if (ArrayUtil.isEmpty(tableList)) {
return new ArrayList<>();
}
return tableList.stream().map(x -> {
GenTable gen = new GenTable();
gen.setDataName(dataName);
gen.setTableName(x.getName());
gen.setTableComment(x.getComment());
gen.setCreateTime(x.getCreateTime());
gen.setUpdateTime(x.getUpdateTime());
return gen;
}).toList();
return baseMapper.selectDbTableListByNames(tableNames);
}
/**
@@ -247,7 +165,7 @@ public class GenTableServiceImpl implements IGenTableService {
@Override
public void deleteGenTableByIds(Long[] tableIds) {
List<Long> ids = Arrays.asList(tableIds);
baseMapper.deleteByIds(ids);
baseMapper.deleteBatchIds(ids);
genTableColumnMapper.delete(new LambdaQueryWrapper<GenTableColumn>().in(GenTableColumn::getTableId, ids));
}
@@ -269,7 +187,7 @@ public class GenTableServiceImpl implements IGenTableService {
int row = baseMapper.insert(table);
if (row > 0) {
// 保存列信息
List<GenTableColumn> genTableColumns = SpringUtils.getAopProxy(this).selectDbTableColumnsByName(tableName, dataName);
List<GenTableColumn> genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName, dataName);
List<GenTableColumn> saveColumns = new ArrayList<>();
for (GenTableColumn column : genTableColumns) {
GenUtils.initColumnField(column, table);
@@ -285,32 +203,6 @@ public class GenTableServiceImpl implements IGenTableService {
}
}
/**
* 根据表名称查询列信息
*
* @param tableName 表名称
* @param dataName 数据源名称
* @return 列信息
*/
@DS("#dataName")
@Override
public List<GenTableColumn> selectDbTableColumnsByName(String tableName, String dataName) {
LinkedHashMap<String, Column> columns = ServiceProxy.metadata().columns(tableName);
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.setSort(column.getPosition());
tableColumn.setIsRequired(column.isNullable() == 0 ? "1" : "0");
tableColumn.setIsIncrement(column.isAutoIncrement() == -1 ? "0" : "1");
tableColumns.add(tableColumn);
});
return tableColumns;
}
/**
* 预览代码
*
@@ -406,7 +298,7 @@ public class GenTableServiceImpl implements IGenTableService {
List<GenTableColumn> tableColumns = table.getColumns();
Map<String, GenTableColumn> tableColumnMap = StreamUtils.toIdentityMap(tableColumns, GenTableColumn::getColumnName);
List<GenTableColumn> dbTableColumns = SpringUtils.getAopProxy(this).selectDbTableColumnsByName(table.getTableName(), table.getDataName());
List<GenTableColumn> dbTableColumns = genTableColumnMapper.selectDbTableColumnsByName(table.getTableName(), table.getDataName());
if (CollUtil.isEmpty(dbTableColumns)) {
throw new ServiceException("同步数据失败,原表结构不存在");
}
@@ -440,7 +332,7 @@ public class GenTableServiceImpl implements IGenTableService {
if (CollUtil.isNotEmpty(delColumns)) {
List<Long> ids = StreamUtils.toList(delColumns, GenTableColumn::getColumnId);
if (CollUtil.isNotEmpty(ids)) {
genTableColumnMapper.deleteByIds(ids);
genTableColumnMapper.deleteBatchIds(ids);
}
}
}

View File

@@ -85,15 +85,6 @@ public interface IGenTableService {
*/
void importGenTable(List<GenTable> tableList, String dataName);
/**
* 根据表名称查询列信息
*
* @param tableName 表名称
* @param dataName 数据源名称
* @return 列信息
*/
List<GenTableColumn> selectDbTableColumnsByName(String tableName, String dataName);
/**
* 预览代码
*

View File

@@ -7,4 +7,87 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<resultMap type="org.dromara.generator.domain.GenTableColumn" id="GenTableColumnResult">
</resultMap>
<select id="selectDbTableColumnsByName" parameterType="String" resultMap="GenTableColumnResult">
<if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isMySql()">
select column_name,
(case when (is_nullable = 'no' <![CDATA[ && ]]> column_key != 'PRI') then '1' else '0' end) as is_required,
(case when column_key = 'PRI' then '1' else '0' end) as is_pk,
ordinal_position as sort,
column_comment,
(case when extra = 'auto_increment' then '1' else '0' end) as is_increment,
column_type
from information_schema.columns where table_schema = (select database()) and table_name = (#{tableName})
order by ordinal_position
</if>
<if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isOracle()">
select lower(temp.column_name) as column_name,
(case when (temp.nullable = 'N' and temp.constraint_type != 'P') then '1' else '0' end) as is_required,
(case when temp.constraint_type = 'P' then '1' else '0' end) as is_pk,
temp.column_id as sort,
temp.comments as column_comment,
(case when temp.constraint_type = 'P' then '1' else '0' end) as is_increment,
lower(temp.data_type) as column_type
from (
select col.column_id, col.column_name,col.nullable, col.data_type, colc.comments, uc.constraint_type, row_number()
over (partition by col.column_name order by uc.constraint_type desc) as row_flg
from user_tab_columns col
left join user_col_comments colc on colc.table_name = col.table_name and colc.column_name = col.column_name
left join user_cons_columns ucc on ucc.table_name = col.table_name and ucc.column_name = col.column_name
left join user_constraints uc on uc.constraint_name = ucc.constraint_name
where col.table_name = upper(#{tableName})
) temp
WHERE temp.row_flg = 1
ORDER BY temp.column_id
</if>
<if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isPostgerSql()">
SELECT column_name, is_required, is_pk, sort, column_comment, is_increment, column_type
FROM (
SELECT c.relname AS table_name,
a.attname AS column_name,
d.description AS column_comment,
CASE WHEN a.attnotnull AND con.conname IS NULL THEN 1 ELSE 0
END AS is_required,
CASE WHEN con.conname IS NOT NULL THEN 1 ELSE 0
END AS is_pk,
a.attnum AS sort,
CASE WHEN "position"(pg_get_expr(ad.adbin, ad.adrelid),
((c.relname::text || '_'::text) || a.attname::text) || '_seq'::text) > 0 THEN 1 ELSE 0
END AS is_increment,
btrim(
CASE WHEN t.typelem <![CDATA[ <> ]]> 0::oid AND t.typlen = '-1'::integer THEN 'ARRAY'::text ELSE
CASE WHEN t.typtype = 'd'::"char" THEN format_type(t.typbasetype, NULL::integer)
ELSE format_type(a.atttypid, NULL::integer) END
END, '"'::text
) AS column_type
FROM pg_attribute a
JOIN (pg_class c JOIN pg_namespace n ON c.relnamespace = n.oid) ON a.attrelid = c.oid
LEFT JOIN pg_description d ON d.objoid = c.oid AND a.attnum = d.objsubid
LEFT JOIN pg_constraint con ON con.conrelid = c.oid AND (a.attnum = ANY (con.conkey))
LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
LEFT JOIN pg_type t ON a.atttypid = t.oid
WHERE (c.relkind = ANY (ARRAY ['r'::"char", 'p'::"char"]))
AND a.attnum > 0
AND n.nspname = 'public'::name
ORDER BY c.relname, a.attnum
) temp
WHERE table_name = (#{tableName})
AND column_type <![CDATA[ <> ]]> '-'
</if>
<if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isSqlServer()">
SELECT
cast(A.NAME as nvarchar) as column_name,
cast(B.NAME as nvarchar) + (case when B.NAME = 'numeric' then '(' + cast(A.prec as nvarchar) + ',' + cast(A.scale as nvarchar) + ')' else '' end) as column_type,
cast(G.[VALUE] as nvarchar) as column_comment,
(SELECT 1 FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE Z WHERE TABLE_NAME = D.NAME and A.NAME = Z.column_name ) as is_pk,
colorder as sort
FROM SYSCOLUMNS A
LEFT JOIN SYSTYPES B ON A.XTYPE = B.XUSERTYPE
INNER JOIN SYSOBJECTS D ON A.ID = D.ID AND D.XTYPE='U' AND D.NAME != 'DTPROPERTIES'
LEFT JOIN SYS.EXTENDED_PROPERTIES G ON A.ID = G.MAJOR_ID AND A.COLID = G.MINOR_ID
LEFT JOIN SYS.EXTENDED_PROPERTIES F ON D.ID = F.MAJOR_ID AND F.MINOR_ID = 0
WHERE D.NAME = #{tableName}
ORDER BY A.COLORDER
</if>
</select>
</mapper>

View File

@@ -14,25 +14,239 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<id property="columnId" column="column_id"/>
</resultMap>
<sql id="genSelect">
<select id="selectPageDbTableList" resultMap="GenTableResult">
<if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isMySql()">
select table_name, table_comment, create_time, update_time
from information_schema.tables
where table_schema = (select database())
AND table_name NOT LIKE 'sj_%' AND table_name NOT LIKE 'gen_%'
AND table_name NOT LIKE 'act_%' AND table_name NOT LIKE 'flw_%'
<if test="genTable.params.genTableNames != null and genTable.params.genTableNames.size > 0">
AND table_name NOT IN
<foreach collection="genTable.params.genTableNames" open="(" close=")" separator="," item="item">
#{item}
</foreach>
</if>
<if test="genTable.tableName != null and genTable.tableName != ''">
AND lower(table_name) like lower(concat('%', #{genTable.tableName}, '%'))
</if>
<if test="genTable.tableComment != null and genTable.tableComment != ''">
AND lower(table_comment) like lower(concat('%', #{genTable.tableComment}, '%'))
</if>
order by create_time desc
</if>
<if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isOracle()">
select lower(dt.table_name) as table_name, dtc.comments as table_comment, uo.created as create_time, uo.last_ddl_time as update_time
from user_tables dt, user_tab_comments dtc, user_objects uo
where dt.table_name = dtc.table_name
and dt.table_name = uo.object_name
and uo.object_type = 'TABLE'
AND dt.table_name NOT LIKE 'SJ_%' AND dt.table_name NOT LIKE 'GEN_%'
AND dt.table_name NOT LIKE 'ACT_%' AND dt.table_name NOT LIKE 'FLW_%'
<if test="genTable.params.genTableNames != null and genTable.params.genTableNames.size > 0">
AND lower(dt.table_name) NOT IN
<foreach collection="genTable.params.genTableNames" open="(" close=")" separator="," item="item">
#{item}
</foreach>
</if>
<if test="genTable.tableName != null and genTable.tableName != ''">
AND lower(dt.table_name) like lower(concat(concat('%', #{genTable.tableName}), '%'))
</if>
<if test="genTable.tableComment != null and genTable.tableComment != ''">
AND lower(dtc.comments) like lower(concat(concat('%', #{genTable.tableComment}), '%'))
</if>
order by create_time desc
</if>
<if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isPostgerSql()">
select table_name, table_comment, create_time, update_time
from (
SELECT c.relname AS table_name,
obj_description(c.oid) AS table_comment,
CURRENT_TIMESTAMP AS create_time,
CURRENT_TIMESTAMP AS update_time
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE (c.relkind = ANY (ARRAY ['r'::"char", 'p'::"char"]))
AND c.relname != 'spatial_%'::text
AND n.nspname = 'public'::name
AND n.nspname <![CDATA[ <> ]]> ''::name
) list_table
where table_name NOT LIKE 'sj_%' AND table_name NOT LIKE 'gen_%'
AND table_name NOT LIKE 'act_%' AND table_name NOT LIKE 'flw_%'
<if test="genTable.params.genTableNames != null and genTable.params.genTableNames.size > 0">
AND table_name NOT IN
<foreach collection="genTable.params.genTableNames" open="(" close=")" separator="," item="item">
#{item}
</foreach>
</if>
<if test="genTable.tableName != null and genTable.tableName != ''">
AND lower(table_name) like lower(concat('%', #{genTable.tableName}, '%'))
</if>
<if test="genTable.tableComment != null and genTable.tableComment != ''">
AND lower(table_comment) like lower(concat('%', #{genTable.tableComment}, '%'))
</if>
order by create_time desc
</if>
<if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isSqlServer()">
SELECT cast(D.NAME as nvarchar) as table_name,
cast(F.VALUE as nvarchar) as table_comment,
crdate as create_time,
refdate as update_time
FROM SYSOBJECTS D
INNER JOIN SYS.EXTENDED_PROPERTIES F ON D.ID = F.MAJOR_ID
AND F.MINOR_ID = 0 AND D.XTYPE = 'U' AND D.NAME != 'DTPROPERTIES'
AND D.NAME NOT LIKE 'sj_%' AND D.NAME NOT LIKE 'gen_%'
AND D.NAME NOT LIKE 'act_%' AND D.NAME NOT LIKE 'flw_%'
<if test="genTable.params.genTableNames != null and genTable.params.genTableNames.size > 0">
AND D.NAME NOT IN
<foreach collection="genTable.params.genTableNames" open="(" close=")" separator="," item="item">
#{item}
</foreach>
</if>
<if test="genTable.tableName != null and genTable.tableName != ''">
AND lower(D.NAME) like lower(concat(N'%', N'${genTable.tableName}', N'%'))
</if>
<if test="genTable.tableComment != null and genTable.tableComment != ''">
AND lower(CAST(F.VALUE AS nvarchar)) like lower(concat(N'%', N'${genTable.tableComment}', N'%'))
</if>
order by crdate desc
</if>
</select>
<select id="selectDbTableListByNames" resultMap="GenTableResult">
<if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isMySql()">
select table_name, table_comment, create_time, update_time from information_schema.tables
where table_schema = (select database())
and table_name NOT LIKE 'sj_%' and table_name NOT LIKE 'gen_%'
and table_name NOT LIKE 'act_%' AND table_name NOT LIKE 'flw_%'
and table_name in
<foreach collection="array" item="name" open="(" separator="," close=")">
#{name}
</foreach>
</if>
<if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isOracle()">
select lower(dt.table_name) as table_name, dtc.comments as table_comment, uo.created as create_time, uo.last_ddl_time as update_time
from user_tables dt, user_tab_comments dtc, user_objects uo
where dt.table_name = dtc.table_name
and dt.table_name = uo.object_name
and uo.object_type = 'TABLE'
and dt.table_name NOT LIKE 'SJ_%' AND dt.table_name NOT LIKE 'GEN_%'
and dt.table_name NOT LIKE 'ACT_%' AND dt.table_name NOT LIKE 'FLW_%'
and lower(dt.table_name) in
<foreach collection="array" item="name" open="(" separator="," close=")">
#{name}
</foreach>
</if>
<if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isPostgerSql()">
select table_name, table_comment, create_time, update_time
from (
SELECT c.relname AS table_name,
obj_description(c.oid) AS table_comment,
CURRENT_TIMESTAMP AS create_time,
CURRENT_TIMESTAMP AS update_time
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE (c.relkind = ANY (ARRAY ['r'::"char", 'p'::"char"]))
AND c.relname != 'spatial_%'::text
AND n.nspname = 'public'::name
AND n.nspname <![CDATA[ <> ]]> ''::name
) list_table
where table_name NOT LIKE 'sj_%' and table_name NOT LIKE 'gen_%'
and table_name NOT LIKE 'act_%' and table_name NOT LIKE 'flw_%'
and table_name in
<foreach collection="array" item="name" open="(" separator="," close=")">
#{name}
</foreach>
</if>
<if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isSqlServer()">
SELECT cast(D.NAME as nvarchar) as table_name,
cast(F.VALUE as nvarchar) as table_comment,
crdate as create_time,
refdate as update_time
FROM SYSOBJECTS D
INNER JOIN SYS.EXTENDED_PROPERTIES F ON D.ID = F.MAJOR_ID
AND F.MINOR_ID = 0 AND D.XTYPE = 'U' AND D.NAME != 'DTPROPERTIES'
AND D.NAME NOT LIKE 'sj_%' AND D.NAME NOT LIKE 'gen_%'
AND D.NAME NOT LIKE 'act_%' AND D.NAME NOT LIKE 'flw_%'
AND D.NAME in
<foreach collection="array" item="name" open="(" separator="," close=")">
#{name}
</foreach>
</if>
</select>
<select id="selectTableByName" parameterType="String" resultMap="GenTableResult">
<if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isMySql()">
select table_name, table_comment, create_time, update_time from information_schema.tables
where table_schema = (select database())
and table_name NOT LIKE 'sj_%' and table_name NOT LIKE 'gen_%'
and table_name NOT LIKE 'act_%' AND table_name NOT LIKE 'flw_%'
and table_name = #{tableName}
</if>
<if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isOracle()">
select lower(dt.table_name) as table_name, dtc.comments as table_comment, uo.created as create_time, uo.last_ddl_time as update_time
from user_tables dt, user_tab_comments dtc, user_objects uo
where dt.table_name = dtc.table_name
and dt.table_name = uo.object_name
and uo.object_type = 'TABLE'
AND dt.table_name NOT LIKE 'SJ_%' AND dt.table_name NOT LIKE 'GEN_%'
AND dt.table_name NOT LIKE 'ACT_%' AND dt.table_name NOT LIKE 'FLW_%'
AND dt.table_name NOT IN (select table_name from gen_table)
and lower(dt.table_name) = #{tableName}
</if>
<if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isPostgerSql()">
select table_name, table_comment, create_time, update_time
from (
SELECT c.relname AS table_name,
obj_description(c.oid) AS table_comment,
CURRENT_TIMESTAMP AS create_time,
CURRENT_TIMESTAMP AS update_time
FROM pg_class c
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE (c.relkind = ANY (ARRAY ['r'::"char", 'p'::"char"]))
AND c.relname != 'spatial_%'::text
AND n.nspname = 'public'::name
AND n.nspname <![CDATA[ <> ]]> ''::name
) list_table
where table_name NOT LIKE 'sj_%' and table_name NOT LIKE 'gen_%'
and table_name NOT LIKE 'act_%' and table_name NOT LIKE 'flw_%'
and table_name = #{tableName}
</if>
<if test="@org.dromara.common.mybatis.helper.DataBaseHelper@isSqlServer()">
SELECT cast(D.NAME as nvarchar) as table_name,
cast(F.VALUE as nvarchar) as table_comment,
crdate as create_time,
refdate as update_time
FROM SYSOBJECTS D
INNER JOIN SYS.EXTENDED_PROPERTIES F ON D.ID = F.MAJOR_ID
AND F.MINOR_ID = 0 AND D.XTYPE = 'U' AND D.NAME != 'DTPROPERTIES'
AND D.NAME NOT LIKE 'sj_%' AND D.NAME NOT LIKE 'gen_%'
AND D.NAME NOT LIKE 'act_%' AND D.NAME NOT LIKE 'flw_%'
AND D.NAME = #{tableName}
</if>
</select>
<select id="selectGenTableById" parameterType="Long" resultMap="GenTableResult">
SELECT t.table_id, t.data_name, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark,
c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
FROM gen_table t
LEFT JOIN gen_table_column c ON t.table_id = c.table_id
</sql>
<select id="selectGenTableById" parameterType="Long" resultMap="GenTableResult">
<include refid="genSelect"/>
LEFT JOIN gen_table_column c ON t.table_id = c.table_id
where t.table_id = #{tableId} order by c.sort
</select>
<select id="selectGenTableByName" parameterType="String" resultMap="GenTableResult">
<include refid="genSelect"/>
SELECT t.table_id, t.data_name, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark,
c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
FROM gen_table t
LEFT JOIN gen_table_column c ON t.table_id = c.table_id
where t.table_name = #{tableName} order by c.sort
</select>
<select id="selectGenTableAll" parameterType="String" resultMap="GenTableResult">
<include refid="genSelect"/>
SELECT t.table_id, t.data_name, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.options, t.remark,
c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
FROM gen_table t
LEFT JOIN gen_table_column c ON t.table_id = c.table_id
order by c.sort
</select>

View File

@@ -149,6 +149,6 @@ public class ${ClassName}ServiceImpl implements I${ClassName}Service {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
return baseMapper.deleteBatchIds(ids) > 0;
}
}

View File

@@ -95,11 +95,6 @@
<artifactId>ruoyi-common-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-sse</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,9 +1,7 @@
package org.dromara.system.controller.monitor;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.CacheConstants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.log.annotation.Log;
@@ -15,6 +13,8 @@ import org.dromara.common.web.core.BaseController;
import org.dromara.system.domain.bo.SysLogininforBo;
import org.dromara.system.domain.vo.SysLogininforVo;
import org.dromara.system.service.ISysLogininforService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -79,7 +79,7 @@ public class SysLogininforController extends BaseController {
@Log(title = "账户解锁", businessType = BusinessType.OTHER)
@GetMapping("/unlock/{userName}")
public R<Void> unlock(@PathVariable("userName") String userName) {
String loginName = CacheConstants.PWD_ERR_CNT_KEY + userName;
String loginName = GlobalConstants.PWD_ERR_CNT_KEY + userName;
if (RedisUtils.hasKey(loginName)) {
RedisUtils.deleteObject(loginName);
}

View File

@@ -75,6 +75,8 @@ public class SysDeptController extends BaseController {
public R<Void> add(@Validated @RequestBody SysDeptBo dept) {
if (!deptService.checkDeptNameUnique(dept)) {
return R.fail("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在");
} else if (StringUtils.isNotBlank(dept.getDeptCategory()) && !deptService.checkDeptCategoryUnique(dept)) {
return R.fail("新增部门'" + dept.getDeptName() + "'失败,部门类别编码已存在");
}
return toAjax(deptService.insertDept(dept));
}
@@ -90,6 +92,8 @@ public class SysDeptController extends BaseController {
deptService.checkDeptDataScope(deptId);
if (!deptService.checkDeptNameUnique(dept)) {
return R.fail("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在");
} else if (StringUtils.isNotBlank(dept.getDeptCategory()) && !deptService.checkDeptCategoryUnique(dept)) {
return R.fail("修改部门'" + dept.getDeptName() + "'失败,部门类别编码已存在");
} else if (dept.getParentId().equals(deptId)) {
return R.fail("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己");
} else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus())) {

View File

@@ -8,8 +8,8 @@ import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.sse.utils.SseMessageUtils;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.websocket.utils.WebSocketUtils;
import org.dromara.system.domain.bo.SysNoticeBo;
import org.dromara.system.domain.vo.SysNoticeVo;
import org.dromara.system.service.ISysNoticeService;
@@ -62,7 +62,7 @@ public class SysNoticeController extends BaseController {
return R.fail();
}
String type = dictService.getDictLabel("sys_notice_type", notice.getNoticeType());
SseMessageUtils.publishAll("[" + type + "] " + notice.getNoticeTitle());
WebSocketUtils.publishAll("[" + type + "] " + notice.getNoticeTitle());
return R.ok();
}

View File

@@ -18,6 +18,13 @@ import java.util.List;
*/
public interface SysMenuMapper extends BaseMapperPlus<SysMenu, SysMenuVo> {
/**
* 根据用户所有权限
*
* @return 权限列表
*/
List<String> selectMenuPerms();
/**
* 根据用户查询系统菜单列表
*

View File

@@ -92,6 +92,14 @@ public interface ISysDeptService {
*/
boolean checkDeptNameUnique(SysDeptBo dept);
/**
* 校验部门类别编码是否唯一
*
* @param dept 部门信息
* @return 结果
*/
boolean checkDeptCategoryUnique(SysDeptBo dept);
/**
* 校验部门是否有数据权限
*

View File

@@ -146,6 +146,6 @@ public class SysClientServiceImpl implements ISysClientService {
if (isValid) {
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
return baseMapper.deleteBatchIds(ids) > 0;
}
}

View File

@@ -178,7 +178,7 @@ public class SysConfigServiceImpl implements ISysConfigService, ConfigService {
}
CacheUtils.evict(CacheNames.SYS_CONFIG, config.getConfigKey());
}
baseMapper.deleteByIds(Arrays.asList(configIds));
baseMapper.deleteBatchIds(Arrays.asList(configIds));
}
/**

View File

@@ -3,14 +3,14 @@ package org.dromara.system.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.mybatis.helper.DataBaseHelper;
import org.dromara.system.domain.SysDept;
import org.dromara.common.mybatis.helper.DataBaseHelper;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.system.domain.SysRoleDept;
import org.dromara.system.mapper.SysDeptMapper;
import org.dromara.system.mapper.SysRoleDeptMapper;
import org.dromara.system.service.ISysDataScopeService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -30,12 +30,6 @@ public class SysDataScopeServiceImpl implements ISysDataScopeService {
private final SysRoleDeptMapper roleDeptMapper;
private final SysDeptMapper deptMapper;
/**
* 获取角色自定义权限
*
* @param roleId 角色Id
* @return 部门Id组
*/
@Override
public String getRoleCustom(Long roleId) {
List<SysRoleDept> list = roleDeptMapper.selectList(
@@ -48,12 +42,6 @@ public class SysDataScopeServiceImpl implements ISysDataScopeService {
return null;
}
/**
* 获取部门及以下权限
*
* @param deptId 部门Id
* @return 部门Id组
*/
@Override
public String getDeptAndChild(Long deptId) {
List<SysDept> deptList = deptMapper.selectList(new LambdaQueryWrapper<SysDept>()

View File

@@ -78,7 +78,7 @@ public class SysDeptServiceImpl implements ISysDeptService, DeptService {
private LambdaQueryWrapper<SysDept> buildQueryWrapper(SysDeptBo bo) {
LambdaQueryWrapper<SysDept> lqw = Wrappers.lambdaQuery();
lqw.eq(SysDept::getDelFlag, UserConstants.DEL_FLAG_NORMAL);
lqw.eq(SysDept::getDelFlag, "0");
lqw.eq(ObjectUtil.isNotNull(bo.getDeptId()), SysDept::getDeptId, bo.getDeptId());
lqw.eq(ObjectUtil.isNotNull(bo.getParentId()), SysDept::getParentId, bo.getParentId());
lqw.like(StringUtils.isNotBlank(bo.getDeptName()), SysDept::getDeptName, bo.getDeptName());
@@ -218,6 +218,20 @@ public class SysDeptServiceImpl implements ISysDeptService, DeptService {
return !exist;
}
/**
* 校验部门类别编码是否唯一
*
* @param dept 部门信息
* @return 结果
*/
@Override
public boolean checkDeptCategoryUnique(SysDeptBo dept) {
boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysDept>()
.eq(SysDept::getDeptCategory, dept.getDeptCategory())
.ne(ObjectUtil.isNotNull(dept.getDeptId()), SysDept::getDeptId, dept.getDeptId()));
return !exist;
}
/**
* 校验部门是否有数据权限
*

View File

@@ -141,7 +141,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
}
CacheUtils.evict(CacheNames.SYS_DICT, dictType.getDictType());
}
baseMapper.deleteByIds(Arrays.asList(dictIds));
baseMapper.deleteBatchIds(Arrays.asList(dictIds));
}
/**

View File

@@ -163,7 +163,7 @@ public class SysLogininforServiceImpl implements ISysLogininforService {
*/
@Override
public int deleteLogininforByIds(Long[] infoIds) {
return baseMapper.deleteByIds(Arrays.asList(infoIds));
return baseMapper.deleteBatchIds(Arrays.asList(infoIds));
}
/**

Some files were not shown because too many files have changed in this diff Show More