Compare commits

...

127 Commits

Author SHA1 Message Date
cjw 761138a6b3 同步教材添加用户名搜索 2024-07-23 16:43:27 +08:00
cjw 40c6e3b3cb 目录排序优化,同步教材学校空间返回值优化 2024-07-22 15:30:02 +08:00
cjw ba6fa6dc98 学校空间与教务空间接口分离,其他优化 2024-07-22 10:21:43 +08:00
cjw fb1870328e 很多优化 2024-07-18 16:36:03 +08:00
cjw 0d337286b1 优化目录列表数量统计,其他bug修复 2024-07-17 16:12:03 +08:00
cjw ba2eec3a0c 添加定稿状态,修复bug 2024-07-17 14:59:24 +08:00
cjw 059148d97b 优化 2024-07-17 10:27:32 +08:00
cjw f756d27590 新增教务空间、学校空间;新增定稿功能 2024-07-17 10:27:05 +08:00
cjw 4b2bc5ccc5 我的空间抬头数量增加我的上传 2024-07-16 16:05:46 +08:00
cjw 255c486740 优化 2024-07-16 15:54:00 +08:00
cjw c0e8f3a222 我的空间抬头数量显示 2024-07-16 15:37:42 +08:00
cjw 69cdc27527 点赞、收藏分页查询添加是否点赞、收藏 2024-07-16 14:57:41 +08:00
cjw 0a61a6e8cf 门户同步教材详情添加是否点赞、收藏 2024-07-16 14:17:52 +08:00
cjw d80480336b 新增收藏、点赞、记录分页查询列表 2024-07-16 10:39:30 +08:00
cjw dc9a8f52b6 Merge branch 'dev.caojiawei' into dev 2024-07-16 09:57:13 +08:00
cjw a286f91ef5 新增点赞、收藏、浏览记录功能 2024-07-16 09:57:06 +08:00
cjw ed2c9bfbf9 Merge branch 'dev.caojiawei' into dev 2024-07-15 14:06:02 +08:00
cjw e3e4e9baaf 修改oss默认授权时间为一天 2024-07-15 14:05:54 +08:00
cjw cd3191ab60 Merge branch 'dev.caojiawei' into dev 2024-07-15 14:02:17 +08:00
cjw f72c1c6495 取消所有 @SaIgnore 2024-07-15 14:02:12 +08:00
cjw 4c1ced76dc Merge branch 'dev.caojiawei' into dev 2024-07-15 13:58:19 +08:00
cjw 0ed28f01ec 优化 2024-07-15 13:58:13 +08:00
cjw 3bc53a7d4b Merge branch 'dev' of http://101.37.69.204:3000/mozhe/school-file-api into dev 2024-07-15 13:56:09 +08:00
cjw 74df0be997 为资源上传相关添加逻辑删除字段;统计取消自选租户id;新增门户统计 2024-07-15 13:55:37 +08:00
jiangzhe 9a41a5ee41 增加不加密登录方法 2024-07-15 09:07:42 +08:00
cjw 6cfb02aafb 目录拖动,包含同步教材、专题资源、我的空间 2024-07-12 15:06:25 +08:00
cjw 64287e85eb 目录拖动优化 2024-07-12 14:57:12 +08:00
cjw 9785256c80 优化 2024-07-12 14:32:34 +08:00
cjw 38320d8acb 目录拖动 2024-07-12 14:31:10 +08:00
cjw 34b479fe1a 框架新增第三方登录模块 2024-07-11 13:53:47 +08:00
cjw 96d690580a 待审核列表使用nickname 2024-07-04 10:50:19 +08:00
cjw 6267fd5b5a 租户容量 2024-07-04 10:30:24 +08:00
cjw 03c44645c5 优化 2024-07-03 10:46:20 +08:00
cjw 8361200034 忘了提交了 2024-07-03 10:22:10 +08:00
cjw 2fd7323d79 去除asopse,使用第三方实现;弱口令升级;待审核数量提示 2024-07-03 10:21:57 +08:00
cjw 1ad8dec41e 下载使用原版 2024-06-27 16:31:07 +08:00
cjw 9bff9d672e 优化预览 2024-06-27 16:03:27 +08:00
cjw abfc92405c 优化 2024-06-27 14:54:07 +08:00
cjw dc3ad5a99c 预览优化 2024-06-27 14:48:20 +08:00
cjw 61488b5969 资源格式查询bug修复 2024-06-27 10:16:47 +08:00
cjw 770220bd10 去除业务表的逻辑删除字段 2024-06-27 10:10:16 +08:00
cjw 5a1c3a7c14 首页统计去除删除数据;门户资源添加格式查询 2024-06-27 10:00:06 +08:00
cjw 516a6aa641 统计接口区分删除数据 2024-06-26 18:55:12 +08:00
cjw 64063390bd 初始化数据完善 2024-06-26 18:45:38 +08:00
cjw 1f72880806 文件上传权限优化;租户主键自增;删除冗余代码 2024-06-26 17:11:40 +08:00
cjw 546e4be756 资源容量保留小数点后两位 2024-06-26 14:54:00 +08:00
cjw c72b8f2aba 门户资源分页查询优化;资源容量优化 2024-06-26 14:36:42 +08:00
cjw a786d1b81a 资源空间容量编码 2024-06-26 11:20:41 +08:00
cjw aefc1dd9a0 创建租户默认1TB容量 2024-06-25 18:16:42 +08:00
cjw 75af3a8e8f 添加资源容量功能 2024-06-25 18:10:30 +08:00
cjw 288fc89bd6 统计优化区分租户;门户首页优化区分租户 2024-06-25 16:02:52 +08:00
cjw 9a67b94219 添加我的空间类型;门户接口添加租户id 2024-06-25 15:01:02 +08:00
cjw a83bdf69ab 优化bug;租户数据初始化 2024-06-24 17:30:26 +08:00
cjw f3316fc400 修改文件上传大小限制;删除教师逻辑优化 2024-06-24 14:38:14 +08:00
cjw 27e22efbb6 排序和门户资源统计 2024-06-24 09:06:59 +08:00
cjw 837c6cb01c 排序再次优化 2024-06-21 16:11:55 +08:00
cjw 84453dac06 排序优化 2024-06-21 16:03:00 +08:00
cjw e9285a11e0 同步资源排序 2024-06-21 11:31:14 +08:00
cjw 9cad37f9be 门户文件详情 2024-06-21 11:01:36 +08:00
cjw 430af9d852 门户首页添加预览 2024-06-21 10:54:45 +08:00
cjw d8d28a6302 预览接口分开 2024-06-21 10:51:26 +08:00
cjw 7c9343d6a6 专题资源树形结构 2024-06-21 10:29:32 +08:00
cjw c4a4fadfb4 首页专题资源列表优化;其他优化 2024-06-21 10:00:34 +08:00
cjw 177b5e8a69 资源文件添加创建部门、下载数量 2024-06-20 16:20:23 +08:00
cjw c058749afd 树形查询子节点优化 2024-06-20 16:06:52 +08:00
cjw 965ef50b86 门户首页中同步教材目录接口优化 2024-06-20 15:31:39 +08:00
cjw 88f21a5d9d 树形添加额外字段 2024-06-20 14:42:58 +08:00
cjw efd38c1c4d 优化 2024-06-20 14:04:09 +08:00
cjw 125a8be83e 优化 2024-06-20 14:00:16 +08:00
cjw 230db55a99 门户首页专题资源分页 2024-06-20 11:31:54 +08:00
cjw 0d1f98811b 专题资源添加文件url 2024-06-20 11:20:46 +08:00
cjw 24c77bf396 同步资源目录type查询 2024-06-20 11:14:31 +08:00
cjw 3d1c9bc618 树形接口优化;资源目录添加类型以便区分 2024-06-20 10:31:55 +08:00
cjw c71757101b 同步教材新增树形查询 2024-06-20 09:42:24 +08:00
cjw 546656c01f 名师展示添加url 2024-06-19 17:07:40 +08:00
cjw 213766b48c 门户新增目录信息;移动复制增加后缀名 2024-06-19 17:03:17 +08:00
cjw c2d42da958 接口放行 2024-06-19 16:19:41 +08:00
cjw 6de3aa9475 门户列表 2024-06-19 15:52:47 +08:00
cjw 07b2122871 各列表查询条件 2024-06-19 15:00:49 +08:00
cjw e359147876 Merge branch 'dev' of http://101.37.69.204:3000/mozhe/school-file-api into dev 2024-06-19 14:19:54 +08:00
cjw 4012572b52 update 返回值优化 2024-06-19 14:19:40 +08:00
jiangzhe de11772200 Merge remote-tracking branch 'origin/dev' into dev 2024-06-19 14:12:33 +08:00
jiangzhe 4ba8064b64 代码提交 2024-06-19 14:12:25 +08:00
cjw 7ccc5d4dfb 文件下载优化 2024-06-19 14:11:57 +08:00
cjw 39d2e1e27c 首页类型统计 2024-06-19 10:41:38 +08:00
cjw 2cc30e4315 文件资源也添加租户信息;首页统计-数量统计,使用排行统计; 2024-06-18 20:12:18 +08:00
cjw f007ff0ff2 我的空间列表增加字段; 2024-06-18 17:06:53 +08:00
cjw 312d307772 资源上传的校验优化 2024-06-18 16:58:57 +08:00
cjw 506a7a2dc8 资源上传同级目录校验;我的空间相关接口; 2024-06-18 16:38:35 +08:00
cjw 245350d233 专题资源中的缓存代理设置; 2024-06-18 15:50:32 +08:00
cjw cbe266392c 优化 2024-06-18 15:33:52 +08:00
cjw 50b5651e74 专题资源的复制移动,分页查询 2024-06-18 15:28:49 +08:00
cjw 00626819c6 区分待审核和通过审核的列表 2024-06-18 14:52:08 +08:00
cjw 0cff3ff8bd 优化 2024-06-18 14:18:03 +08:00
cjw 4eefdb279d 上传文件新增容量字段。同步教材分页查询添加信息; 2024-06-18 13:54:12 +08:00
cjw 3d65a43faf 添加各资源管理的分页查询 2024-06-18 10:44:07 +08:00
jiangzhe f7ae4752e2 代码提交 2024-06-14 16:21:08 +08:00
jiangzhe 688b7ef0df 代码提交 2024-06-14 15:08:13 +08:00
cjw c3a46b9577 新增textbook的审核状态 2024-06-14 15:06:18 +08:00
cjw 3272abb854 我的空间限定用户id 2024-06-14 14:51:45 +08:00
cjw a16b942937 新增我的空间;优化目录的新增; 2024-06-14 14:47:22 +08:00
cjw 239b93e421 兼容前端框架,提供文件原始名 2024-06-04 17:06:25 +08:00
cjw 94ed7562ea 优化项目结构;文件上传优化; 2024-06-04 16:54:22 +08:00
cjw 54a1c30e3d 上传文件添加md5 2024-06-04 15:56:26 +08:00
cjw e095e4e5a2 资源文件上传 2024-06-04 15:43:59 +08:00
cjw 505f440af8 添加文件md5检测 2024-06-04 15:00:44 +08:00
cjw 7f71d6b2cf 新增xls,ppt的文件预览 2024-05-31 15:14:06 +08:00
cjw 26bce15c72 测试文件预览 2024-05-31 14:58:25 +08:00
cjw 7c8281a35e 用户表添加教职工关联 2024-05-31 09:46:14 +08:00
cjw 36a7b6e8fb 修改配置文件,关闭一些不用功能 2024-05-31 09:15:36 +08:00
cjw 9c088c91d1 教职工Vo数据优化 2024-05-30 15:42:09 +08:00
cjw 4cd97ae5e3 新增修改教职工参数校验;获取教职工导入模板;教职工信息导入; 2024-05-30 15:24:58 +08:00
cjw 5e0629e74a 教职工bo参数校验优化 2024-05-30 14:41:07 +08:00
cjw af496fcb33 教职工出生日期格式化;类型相关表主键优化 2024-05-30 14:38:07 +08:00
cjw 769de3cf6a 项目地址增加school;教职工权限路径优化 2024-05-30 14:04:00 +08:00
cjw 18cfb79131 添加教职工基础信息;优化目录分页查询 2024-05-30 11:08:37 +08:00
cjw ea0e4a0261 目录更新优化-祖级列表 2024-05-29 17:04:58 +08:00
cjw 6892aecb56 目录管理添加右侧展示数据接口 2024-05-29 16:42:45 +08:00
cjw ae7c9bb06d 租户列表添加创建时间 2024-05-29 15:38:45 +08:00
cjw 68c65cde33 优化图片展示字段 2024-05-28 16:34:48 +08:00
cjw f19b1fb480 门户相关表添加上下架接口 2024-05-28 16:11:48 +08:00
cjw a337791762 学校动态创建时间优化 2024-05-28 15:46:55 +08:00
cjw 8d9d1b9f67 学校动态添加创建时间 2024-05-28 15:44:21 +08:00
cjw 1d4299cd23 优化 2024-05-28 15:36:40 +08:00
cjw 77fec3d7a2 修改一些表的主键策略 2024-05-28 15:29:03 +08:00
cjw 3ff9fef182 代码优化 2024-05-28 15:13:59 +08:00
jiangzhe afed932070 生成门户代码 2024-05-28 14:13:01 +08:00
259 changed files with 11090 additions and 1857 deletions

View File

@ -9,8 +9,8 @@
[![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.0-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.1-blue.svg)]()
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.2.1-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,7 +56,7 @@ 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 支持数据量有限 不支持多数据源主键唯一 |

50
pom.xml
View File

@ -9,12 +9,11 @@
<version>${revision}</version>
<name>RuoYi-Vue-Plus</name>
<url>https://gitee.com/dromara/RuoYi-Vue-Plus</url>
<description>RuoYi-Vue-Plus多租户管理系统</description>
<description>校本资源管理系统</description>
<properties>
<revision>5.2.0-BETA</revision>
<spring-boot.version>3.2.5</spring-boot.version>
<revision>5.2.1</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>
@ -25,16 +24,16 @@
<easyexcel.version>3.3.4</easyexcel.version>
<velocity.version>2.3</velocity.version>
<satoken.version>1.38.0</satoken.version>
<mybatis-plus.version>3.5.6</mybatis-plus.version>
<mybatis-plus.version>3.5.7</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.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.29.0</redisson.version>
<redisson.version>3.31.0</redisson.version>
<lock4j.version>2.2.7</lock4j.version>
<dynamic-ds.version>4.3.0</dynamic-ds.version>
<dynamic-ds.version>4.3.1</dynamic-ds.version>
<alibaba-ttl.version>2.14.4</alibaba-ttl.version>
<snailjob.version>1.0.0-beta1</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>
@ -50,6 +49,8 @@
<sms4j.version>3.2.1</sms4j.version>
<!-- 限制框架中的fastjson版本 -->
<fastjson.version>1.2.83</fastjson.version>
<!--工作流配置-->
<flowable.version>7.0.0</flowable.version>
<!-- 插件版本 -->
<maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
@ -57,18 +58,9 @@
<maven-compiler-plugin.verison>3.11.0</maven-compiler-plugin.verison>
<maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
<flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
</properties>
<profiles>
<profile>
<id>local</id>
<properties>
<!-- 环境标识,需要与配置文件的名称相对应 -->
<profiles.active>local</profiles.active>
<logging.level>info</logging.level>
</properties>
</profile>
<profile>
<id>dev</id>
<properties>
@ -81,6 +73,13 @@
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>test</id>
<properties>
<profiles.active>test</profiles.active>
<logging.level>info</logging.level>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
@ -112,6 +111,14 @@
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-bom</artifactId>
<version>${flowable.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- JustAuth 的依赖配置-->
<dependency>
<groupId>me.zhyd.oauth</groupId>
@ -347,6 +354,14 @@
<artifactId>ruoyi-generator</artifactId>
<version>${revision}</version>
</dependency>
<!-- 工作流模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-workflow</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>
@ -355,7 +370,6 @@
<module>ruoyi-common</module>
<module>ruoyi-extend</module>
<module>ruoyi-modules</module>
<module>ruoyi-modules/ruoyi-file</module>
</modules>
<packaging>pom</packaging>

View File

@ -1,7 +1,9 @@
# 贝尔实验室 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
MAINTAINER Lion Li
LABEL maintainer="Lion Li"
RUN mkdir -p /ruoyi/server/logs \
/ruoyi/server/temp \

View File

@ -28,21 +28,26 @@
<artifactId>ojdbc8</artifactId>
</dependency>
<!-- PostgreSql -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.postgresql</groupId>-->
<!-- <artifactId>postgresql</artifactId>-->
<!-- </dependency>-->
<!-- SqlServer -->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.microsoft.sqlserver</groupId>-->
<!-- <artifactId>mssql-jdbc</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-doc</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-social</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-ratelimiter</artifactId>
@ -80,21 +85,6 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
</dependency>
<!-- SnailJob client -->
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>snail-job-client-starter</artifactId>
</dependency>
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>snail-job-client-job-core</artifactId>
</dependency>
<!-- skywalking 整合 logback -->
<!-- <dependency>-->
<!-- <groupId>org.apache.skywalking</groupId>-->

View File

@ -1,22 +1,34 @@
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;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginBody;
import org.dromara.common.core.domain.model.PasswordLoginBody;
import org.dromara.common.core.domain.model.RegisterBody;
import org.dromara.common.core.domain.model.SocialLoginBody;
import org.dromara.common.core.utils.*;
import org.dromara.common.encrypt.annotation.ApiEncrypt;
import org.dromara.common.json.utils.JsonUtils;
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.tenant.helper.TenantHelper;
import org.dromara.common.websocket.dto.WebSocketMessageDto;
import org.dromara.common.websocket.utils.WebSocketUtils;
import org.dromara.system.domain.SysClient;
import org.dromara.system.domain.bo.SysTenantBo;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysTenantVo;
@ -34,7 +46,10 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@ -50,6 +65,7 @@ import java.util.concurrent.TimeUnit;
@RequestMapping("/auth")
public class AuthController {
private final SocialProperties socialProperties;
private final SysLoginService loginService;
private final SysRegisterService registerService;
private final ISysConfigService configService;
@ -89,13 +105,75 @@ public class AuthController {
Long userId = LoginHelper.getUserId();
scheduledExecutorService.schedule(() -> {
WebSocketMessageDto dto = new WebSocketMessageDto();
dto.setMessage("欢迎登录RuoYi-Vue-Plus后台管理系统");
dto.setMessage("欢迎登录校本资源平台");
dto.setSessionKeys(List.of(userId));
WebSocketUtils.publishMessage(dto);
}, 3, TimeUnit.SECONDS);
return R.ok(loginVo);
}
@SaIgnore
@PostMapping("/passwordLogin")
public R<LoginVo> loginClient(@RequestBody PasswordLoginBody loginBody) {
// 授权类型和客户端id
String clientId = loginBody.getClientId();
String grantType = loginBody.getGrantType();
SysClientVo client = clientService.queryByClientId(clientId);
// 查询不到 client client 内不包含 grantType
if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) {
log.info("客户端id: {} 认证类型:{} 异常!.", clientId, grantType);
return R.fail(MessageUtils.message("auth.grant.type.error"));
} else if (!UserConstants.NORMAL.equals(client.getStatus())) {
return R.fail(MessageUtils.message("auth.grant.type.blocked"));
}
// 登录
LoginVo loginVo = IAuthStrategy.login(JsonUtils.toJsonString(loginBody), client, grantType);
return R.ok(loginVo);
}
/**
* 第三方登录请求
*
* @param source 登录来源
* @return 结果
*/
@GetMapping("/binding/{source}")
public R<String> authBinding(@PathVariable("source") String source,
@RequestParam String tenantId, @RequestParam String domain) {
SocialLoginConfigProperties obj = socialProperties.getType().get(source);
if (ObjectUtil.isNull(obj)) {
return R.fail(source + "平台账号暂不支持");
}
AuthRequest authRequest = SocialUtils.getAuthRequest(source, socialProperties);
Map<String, String> map = new HashMap<>();
map.put("tenantId", tenantId);
map.put("domain", domain);
map.put("state", AuthStateUtils.createState());
String authorizeUrl = authRequest.authorize(Base64.encode(JsonUtils.toJsonString(map), StandardCharsets.UTF_8));
return R.ok("操作成功", authorizeUrl);
}
/**
* 第三方登录回调业务处理 绑定授权
*
* @param loginBody 请求体
* @return 结果
*/
@PostMapping("/social/callback")
public R<Void> socialCallback(@RequestBody SocialLoginBody loginBody) {
// 获取第三方登录信息
AuthResponse<AuthUser> response = SocialUtils.loginAuth(
loginBody.getSource(), loginBody.getSocialCode(),
loginBody.getSocialState(), socialProperties);
AuthUser authUserData = response.getData();
// 判断授权响应是否成功
if (!response.ok()) {
return R.fail(response.getMsg());
}
loginService.socialRegister(authUserData);
return R.ok();
}
/**
* 取消授权
@ -138,8 +216,26 @@ 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");
@ -152,11 +248,8 @@ public class AuthController {
// 根据域名进行筛选
List<TenantListVo> list = StreamUtils.filter(voList, vo ->
StringUtils.equals(vo.getDomain(), host));
// 返回对象
LoginTenantVo vo = new LoginTenantVo();
vo.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
vo.setTenantEnabled(TenantHelper.isEnable());
return R.ok(vo);
result.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
return R.ok(result);
}
}

View File

@ -3,6 +3,7 @@ package org.dromara.web.service;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.system.domain.SysClient;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.web.domain.vo.LoginVo;

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;

View File

@ -4,6 +4,7 @@ import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -59,7 +60,7 @@ public class PasswordAuthStrategy implements IAuthStrategy {
boolean captchaEnabled = captchaProperties.getEnable();
// 验证码开关
if (captchaEnabled) {
if (captchaEnabled && StrUtil.isNotBlank(code)) {
validateCaptcha(tenantId, username, code, uuid);
}
@ -94,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.defaultString(uuid, "");
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {

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;

View File

@ -0,0 +1,133 @@
package org.dromara.web.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import org.dromara.common.core.domain.model.LoginUser;
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;
import org.dromara.common.social.config.properties.SocialProperties;
import org.dromara.common.social.utils.SocialUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysSocialVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.system.service.ISysSocialService;
import org.dromara.web.domain.vo.LoginVo;
import org.dromara.web.service.IAuthStrategy;
import org.dromara.web.service.SysLoginService;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
/**
* 第三方授权策略
*
* @author thiszhc is 三三
*/
@Slf4j
@Service("social" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class SocialAuthStrategy implements IAuthStrategy {
private final SocialProperties socialProperties;
private final ISysSocialService sysSocialService;
private final SysUserMapper userMapper;
private final SysLoginService loginService;
/**
* 登录-第三方授权登录
*
* @param body 登录信息
* @param client 客户端信息
*/
@Override
public LoginVo login(String body, SysClientVo client) {
SocialLoginBody loginBody = JsonUtils.parseObject(body, SocialLoginBody.class);
ValidatorUtils.validate(loginBody);
AuthResponse<AuthUser> response = SocialUtils.loginAuth(
loginBody.getSource(), loginBody.getSocialCode(),
loginBody.getSocialState(), socialProperties);
if (!response.ok()) {
throw new ServiceException(response.getMsg());
}
AuthUser authUserData = response.getData();
if ("GITEE".equals(authUserData.getSource())) {
// 如用户使用 gitee 登录顺手 star 给作者一点支持 拒绝白嫖
HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Vue-Plus")
.formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
.executeAsync();
HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Cloud-Plus")
.formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
.executeAsync();
}
List<SysSocialVo> list = sysSocialService.selectByAuthId(authUserData.getSource() + authUserData.getUuid());
if (CollUtil.isEmpty(list)) {
throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!");
}
SysSocialVo social;
if (TenantHelper.isEnable()) {
Optional<SysSocialVo> opt = StreamUtils.findAny(list, x -> x.getTenantId().equals(loginBody.getTenantId()));
if (opt.isEmpty()) {
throw new ServiceException("对不起,你没有权限登录当前租户!");
}
social = opt.get();
} else {
social = list.get(0);
}
// 查找用户
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();
model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token
LoginHelper.login(loginUser, model);
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
return loginVo;
}
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

@ -11,6 +11,7 @@ import org.dromara.common.core.enums.UserStatus;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.SysClient;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.web.domain.vo.LoginVo;

View File

@ -6,21 +6,22 @@ spring.boot.admin.client:
instance:
service-host-type: IP
username: ruoyi
password: 123456
password: Mz123456!
--- # snail-job 配置
snail-job:
enabled: true
enabled: false
# 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
group-name: "ruoyi_group"
group: "ruoyi_group"
# SnailJob 接入验证令牌 详见 script/sql/snail_job.sql `sj_group_config` 表
token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
server:
host: 127.0.0.1
port: 1788
port: 17888
# 详见 script/sql/snail_job.sql `sj_namespace` 表
namespace: ${spring.profiles.active}
# 随主应用端口飘逸
port: 2${server.port}
--- # 数据源配置
spring:
@ -95,13 +96,14 @@ spring.data:
port: 6379
# 数据库索引
database: 1
# 密码(如没有密码请注释掉)
# redis 密码必须配置
password: Mz123456*
# 连接超时时间
timeout: 10s
# 是否开启ssl
ssl.enabled: false
# redisson 配置
redisson:
# redis key前缀
keyPrefix:
@ -177,3 +179,80 @@ sms:
access-key-secret: 您的accessKeySecret
signature: 您的短信签名
sdk-app-id: 您的sdkAppId
--- # 三方授权
justauth:
# 前端外网访问地址
address: http://localhost:80
type:
maxkey:
# maxkey 服务器地址
# 注意 如下均配置均不需要修改 maxkey 已经内置好了数据
server-url: http://sso.maxkey.top
client-id: 876892492581044224
client-secret: x1Y5MTMwNzIwMjMxNTM4NDc3Mzche8
redirect-uri: ${justauth.address}/social-callback?source=maxkey
topiam:
# topiam 服务器地址
server-url: http://127.0.0.1:1989/api/v1/authorize/y0q************spq***********8ol
client-id: 449c4*********937************759
client-secret: ac7***********1e0************28d
redirect-uri: ${justauth.address}/social-callback?source=topiam
scopes: [openid, email, phone, profile]
qq:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=qq
union-id: false
weibo:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=weibo
gitee:
client-id: 91436b7940090d09c72c7daf85b959cfd5f215d67eea73acbf61b6b590751a98
client-secret: 02c6fcfd70342980cd8dd2f2c06c1a350645d76c754d7a264c4e125f9ba915ac
redirect-uri: ${justauth.address}/social-callback?source=gitee
dingtalk:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=dingtalk
baidu:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=baidu
csdn:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=csdn
coding:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=coding
coding-group-name: xx
oschina:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=oschina
alipay_wallet:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=alipay_wallet
alipay-public-key: MIIB**************DAQAB
wechat_open:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=wechat_open
wechat_mp:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=wechat_mp
wechat_enterprise:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=wechat_enterprise
agent-id: 1000002
gitlab:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=gitlab

View File

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

View File

@ -0,0 +1,256 @@
--- # 监控中心配置
spring.boot.admin.client:
# 增加客户端开关
enabled: false
url: http://localhost:9090/admin
instance:
service-host-type: IP
username: ruoyi
password: Mz123456!
--- # snail-job 配置
snail-job:
enabled: false
# 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
group-name: "ruoyi_group"
# SnailJob 接入验证令牌 详见 script/sql/snail_job.sql `sj_group_config` 表
token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
server:
host: 127.0.0.1
port: 1788
# 详见 script/sql/snail_job.sql `sj_namespace` 表
namespace: ${spring.profiles.active}
--- # 数据源配置
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
# 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
dynamic:
# 性能分析插件(有性能损耗 不建议生产环境使用)
p6spy: true
# 设置默认的数据源或者数据源组,默认值即为 master
primary: master
# 严格模式 匹配不到数据源则报错
strict: true
datasource:
# 主库数据源
master:
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
url: jdbc:mysql://127.0.0.1:3306/school_file?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
password: Mz123456*
# 从库数据源
slave:
lazy: true
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/school_file?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username:
password:
# oracle:
# type: ${spring.datasource.type}
# driverClassName: oracle.jdbc.OracleDriver
# url: jdbc:oracle:thin:@//localhost:1521/XE
# username: ROOT
# password: root
# postgres:
# type: ${spring.datasource.type}
# driverClassName: org.postgresql.Driver
# url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
# username: root
# password: root
# sqlserver:
# type: ${spring.datasource.type}
# driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
# url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true
# username: SA
# password: root
hikari:
# 最大连接池数量
maxPoolSize: 20
# 最小空闲线程数量
minIdle: 10
# 配置获取连接等待超时的时间
connectionTimeout: 30000
# 校验超时时间
validationTimeout: 5000
# 空闲连接存活最大时间默认10分钟
idleTimeout: 600000
# 此属性控制池中连接的最长生命周期值0表示无限生命周期默认30分钟
maxLifetime: 1800000
# 多久检查一次连接的活性
keepaliveTime: 30000
--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
spring.data:
redis:
# 地址
host: 127.0.0.1
# 端口默认为6379
port: 6379
# 数据库索引
database: 1
# 密码(如没有密码请注释掉)
password: Mz123456*
# 连接超时时间
timeout: 10s
# 是否开启ssl
ssl.enabled: false
redisson:
# redis key前缀
keyPrefix:
# 线程池数量
threads: 4
# Netty线程池数量
nettyThreads: 8
# 单节点配置
singleServerConfig:
# 客户端名称
clientName: ${ruoyi.name}
# 最小空闲连接数
connectionMinimumIdleSize: 8
# 连接池大小
connectionPoolSize: 32
# 连接空闲超时,单位:毫秒
idleConnectionTimeout: 10000
# 命令等待超时,单位:毫秒
timeout: 3000
# 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50
--- # mail 邮件发送
mail:
enabled: false
host: smtp.163.com
port: 465
# 是否需要用户名密码验证
auth: true
# 发送方遵循RFC-822标准
from: xxx@163.com
# 用户名注意如果使用foxmail邮箱此处user为qq号
user: xxx@163.com
# 密码注意某些邮箱需要为SMTP服务单独设置密码详情查看相关帮助
pass: xxxxxxxxxx
# 使用 STARTTLS安全连接STARTTLS是对纯文本通信协议的扩展。
starttlsEnable: true
# 使用SSL安全连接
sslEnable: true
# SMTP超时时长单位毫秒缺省值不超时
timeout: 0
# Socket连接超时值单位毫秒缺省值不超时
connectionTimeout: 0
--- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
# https://sms4j.com/doc3/ 差异配置文档地址 支持单厂商多配置,可以配置多个同时使用
sms:
# 配置源类型用于标定配置来源(interface,yaml)
config-type: yaml
# 用于标定yml中的配置是否开启短信拦截接口配置不受此限制
restricted: true
# 短信拦截限制单手机号每分钟最大发送,只对开启了拦截的配置有效
minute-max: 1
# 短信拦截限制单手机号每日最大发送量,只对开启了拦截的配置有效
account-max: 30
# 以下配置来自于 org.dromara.sms4j.provider.config.BaseConfig类中
blends:
# 唯一ID 用于发送短信寻找具体配置 随便定义别用中文即可
# 可以同时存在两个相同厂商 例如: ali1 ali2 两个不同的阿里短信账号 也可用于区分租户
config1:
# 框架定义的厂商名称标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
supplier: alibaba
# 有些称为accessKey有些称之为apiKey也有称为sdkKey或者appId。
access-key-id: 您的accessKey
# 称为accessSecret有些称之为apiSecret
access-key-secret: 您的accessKeySecret
signature: 您的短信签名
sdk-app-id: 您的sdkAppId
config2:
# 厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
supplier: tencent
access-key-id: 您的accessKey
access-key-secret: 您的accessKeySecret
signature: 您的短信签名
sdk-app-id: 您的sdkAppId
--- # 三方授权
justauth:
# 前端外网访问地址
address: http://localhost:80
type:
maxkey:
# maxkey 服务器地址
# 注意 如下均配置均不需要修改 maxkey 已经内置好了数据
server-url: http://sso.maxkey.top
client-id: 876892492581044224
client-secret: x1Y5MTMwNzIwMjMxNTM4NDc3Mzche8
redirect-uri: ${justauth.address}/social-callback?source=maxkey
topiam:
# topiam 服务器地址
server-url: http://127.0.0.1:1989/api/v1/authorize/y0q************spq***********8ol
client-id: 449c4*********937************759
client-secret: ac7***********1e0************28d
redirect-uri: ${justauth.address}/social-callback?source=topiam
scopes: [openid, email, phone, profile]
qq:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=qq
union-id: false
weibo:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=weibo
gitee:
client-id: 91436b7940090d09c72c7daf85b959cfd5f215d67eea73acbf61b6b590751a98
client-secret: 02c6fcfd70342980cd8dd2f2c06c1a350645d76c754d7a264c4e125f9ba915ac
redirect-uri: ${justauth.address}/social-callback?source=gitee
dingtalk:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=dingtalk
baidu:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=baidu
csdn:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=csdn
coding:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=coding
coding-group-name: xx
oschina:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=oschina
alipay_wallet:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=alipay_wallet
alipay-public-key: MIIB**************DAQAB
wechat_open:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=wechat_open
wechat_mp:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=wechat_mp
wechat_enterprise:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=wechat_enterprise
agent-id: 1000002
gitlab:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=gitlab

View File

@ -22,7 +22,7 @@ captcha:
# 开发环境配置
server:
# 服务器的HTTP端口默认为8080
port: 8080
port: 8090
servlet:
# 应用的访问路径
context-path: /
@ -139,9 +139,8 @@ tenant:
- sys_user_post
- sys_user_role
- sys_client
- sys_oss
- sys_oss_config
- sys_oss_resource
- sys_oss_textbook
# MyBatisPlus配置
# https://baomidou.com/config/
@ -189,7 +188,7 @@ api-decrypt:
springdoc:
api-docs:
# 是否开启接口文档
enabled: true
enabled: false
# swagger-ui:
# # 持久化认证数据
# persistAuthorization: true
@ -270,3 +269,20 @@ websocket:
# 设置访问源地址
allowedOrigins: '*'
#--- #flowable配置
#flowable:
# async-executor-activate: false #关闭定时任务JOB
# # 将databaseSchemaUpdate设置为true。当Flowable发现库与数据库表结构不一致时会自动将数据库表结构升级至新版本。
# database-schema-update: true
# activity-font-name: 宋体
# label-font-name: 宋体
# annotation-font-name: 宋体
# # 关闭各个模块生成表,目前只使用工作流基础表
# idm:
# enabled: false
# cmmn:
# enabled: false
# dmn:
# enabled: false
# app:
# enabled: false

View File

@ -11,6 +11,7 @@
<modules>
<module>ruoyi-common-bom</module>
<module>ruoyi-common-social</module>
<module>ruoyi-common-core</module>
<module>ruoyi-common-doc</module>
<module>ruoyi-common-excel</module>

View File

@ -14,7 +14,7 @@
</description>
<properties>
<revision>5.2.0-BETA</revision>
<revision>5.2.1</revision>
</properties>
<dependencyManagement>

View File

@ -10,8 +10,7 @@ import org.springframework.scheduling.annotation.EnableAsync;
* @author Lion Li
*/
@AutoConfiguration
// 表示通过aop框架暴露该代理对象,AopContext能够访问
@EnableAspectJAutoProxy(exposeProxy = true)
@EnableAspectJAutoProxy
@EnableAsync(proxyTargetClass = true)
public class ApplicationConfig {

View File

@ -55,6 +55,11 @@ public interface CacheNames {
*/
String SYS_DEPT = "sys_dept#30d";
/**
* 租户部门
*/
String SYS_TENANT_DEPT = "sys_tenant_dept#30d";
/**
* OSS内容
*/
@ -70,4 +75,7 @@ public interface CacheNames {
*/
String ONLINE_TOKEN = "online_tokens";
String SYS_CATALOG_TEXTBOOK = "sys_catalog_textbook#30d";
String SYS_CATALOG_RESOURCE = "sys_catalog_resource#30d";
String SYS_CATALOG_PERSON = "sys_catalog_person#30d";
}

View File

@ -44,7 +44,7 @@ public interface RegexConstants extends RegexPool {
/**
* 密码包含至少8个字符包括大写字母小写字母数字和特殊字符
*/
String PASSWORD = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
String PASSWORD = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&;])[A-Za-z\\d@$!%*?&;]{8,}$";
/**
* 通用状态0表示正常1表示停用

View File

@ -0,0 +1,41 @@
package org.dromara.common.core.domain.event;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 总体流程监听
*
* @author may
*/
@Data
public class ProcessEvent implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 流程定义key
*/
private String key;
/**
* 业务id
*/
private String businessKey;
/**
* 状态
*/
private String status;
/**
* 当为true时为申请人节点办理
*/
private boolean submit;
}

View File

@ -0,0 +1,40 @@
package org.dromara.common.core.domain.event;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 流程办理监听
*
* @author may
*/
@Data
public class ProcessTaskEvent implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 流程定义key
*/
private String key;
/**
* 审批节点key
*/
private String taskDefinitionKey;
/**
* 任务id
*/
private String taskId;
/**
* 业务id
*/
private String businessKey;
}

View File

@ -0,0 +1,152 @@
package org.dromara.common.core.enums;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import java.util.Arrays;
/**
* 业务状态枚举
*
* @author may
*/
@Getter
@AllArgsConstructor
public enum BusinessStatusEnum {
/**
* 已撤销
*/
CANCEL("cancel", "已撤销"),
/**
* 草稿
*/
DRAFT("draft", "草稿"),
/**
* 待审核
*/
WAITING("waiting", "待审核"),
/**
* 已完成
*/
FINISH("finish", "已完成"),
/**
* 已作废
*/
INVALID("invalid", "已作废"),
/**
* 已退回
*/
BACK("back", "已退回"),
/**
* 已终止
*/
TERMINATION("termination", "已终止");
/**
* 状态
*/
private final String status;
/**
* 描述
*/
private final String desc;
/**
* 获取业务状态
*
* @param status 状态
*/
public static String findByStatus(String status) {
if (StringUtils.isBlank(status)) {
return StrUtil.EMPTY;
}
return Arrays.stream(BusinessStatusEnum.values())
.filter(statusEnum -> statusEnum.getStatus().equals(status))
.findFirst()
.map(BusinessStatusEnum::getDesc)
.orElse(StrUtil.EMPTY);
}
/**
* 启动流程校验
*
* @param status 状态
*/
public static void checkStartStatus(String status) {
if (WAITING.getStatus().equals(status)) {
throw new ServiceException("该单据已提交过申请,正在审批中!");
} else if (FINISH.getStatus().equals(status)) {
throw new ServiceException("该单据已完成申请!");
} else if (INVALID.getStatus().equals(status)) {
throw new ServiceException("该单据已作废!");
} else if (TERMINATION.getStatus().equals(status)) {
throw new ServiceException("该单据已终止!");
} else if (StringUtils.isBlank(status)) {
throw new ServiceException("流程状态为空!");
}
}
/**
* 撤销流程校验
*
* @param status 状态
*/
public static void checkCancelStatus(String status) {
if (CANCEL.getStatus().equals(status)) {
throw new ServiceException("该单据已撤销!");
} else if (FINISH.getStatus().equals(status)) {
throw new ServiceException("该单据已完成申请!");
} else if (INVALID.getStatus().equals(status)) {
throw new ServiceException("该单据已作废!");
} else if (TERMINATION.getStatus().equals(status)) {
throw new ServiceException("该单据已终止!");
} else if (BACK.getStatus().equals(status)) {
throw new ServiceException("该单据已退回!");
} else if (StringUtils.isBlank(status)) {
throw new ServiceException("流程状态为空!");
}
}
/**
* 驳回流程校验
*
* @param status 状态
*/
public static void checkBackStatus(String status) {
if (BACK.getStatus().equals(status)) {
throw new ServiceException("该单据已退回!");
} else if (FINISH.getStatus().equals(status)) {
throw new ServiceException("该单据已完成申请!");
} else if (INVALID.getStatus().equals(status)) {
throw new ServiceException("该单据已作废!");
} else if (TERMINATION.getStatus().equals(status)) {
throw new ServiceException("该单据已终止!");
} else if (CANCEL.getStatus().equals(status)) {
throw new ServiceException("该单据已撤销!");
} else if (StringUtils.isBlank(status)) {
throw new ServiceException("流程状态为空!");
}
}
/**
* 作废,终止流程校验
*
* @param status 状态
*/
public static void checkInvalidStatus(String status) {
if (FINISH.getStatus().equals(status)) {
throw new ServiceException("该单据已完成申请!");
} else if (INVALID.getStatus().equals(status)) {
throw new ServiceException("该单据已作废!");
} else if (TERMINATION.getStatus().equals(status)) {
throw new ServiceException("该单据已终止!");
} else if (StringUtils.isBlank(status)) {
throw new ServiceException("流程状态为空!");
}
}
}

View File

@ -15,4 +15,12 @@ public interface DeptService {
*/
String selectDeptNameByIds(String deptIds);
/**
* 通过部门名称查询租户部门Id
*
* @param deptName 部门名称
* @return 部门ID
*/
Long selectDeptIdByName(String deptName);
}

View File

@ -0,0 +1,76 @@
package org.dromara.common.core.service;
import java.util.List;
import java.util.Map;
/**
* 通用 工作流服务
*
* @author may
*/
public interface WorkflowService {
/**
* 运行中的实例 删除程实例删除历史记录删除业务与流程关联信息
*
* @param businessKeys 业务id
* @return 结果
*/
boolean deleteRunAndHisInstance(List<String> businessKeys);
/**
* 获取当前流程状态
*
* @param taskId 任务id
*/
String getBusinessStatusByTaskId(String taskId);
/**
* 获取当前流程状态
*
* @param businessKey 业务id
*/
String getBusinessStatus(String businessKey);
/**
* 设置流程变量(全局变量)
*
* @param taskId 任务id
* @param variableName 变量名称
* @param value 变量值
*/
void setVariable(String taskId, String variableName, Object value);
/**
* 设置流程变量(全局变量)
*
* @param taskId 任务id
* @param variables 流程变量
*/
void setVariables(String taskId, Map<String, Object> variables);
/**
* 设置流程变量(本地变量,非全局变量)
*
* @param taskId 任务id
* @param variableName 变量名称
* @param value 变量值
*/
void setVariableLocal(String taskId, String variableName, Object value);
/**
* 设置流程变量(本地变量,非全局变量)
*
* @param taskId 任务id
* @param variables 流程变量
*/
void setVariablesLocal(String taskId, Map<String, Object> variables);
/**
* 按照业务id查询流程实例id
*
* @param businessKey 业务id
* @return 结果
*/
String getInstanceIdByBusinessKey(String businessKey);
}

View File

@ -1,7 +1,6 @@
package org.dromara.common.core.utils;
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.thread.Threading;
import org.springframework.context.ApplicationContext;
@ -50,7 +49,7 @@ public final class SpringUtils extends SpringUtil {
*/
@SuppressWarnings("unchecked")
public static <T> T getAopProxy(T invoker) {
return (T) AopContext.currentProxy();
return (T) getBean(invoker.getClass());
}

View File

@ -7,6 +7,7 @@ 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;
@ -34,6 +35,34 @@ 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

@ -60,6 +60,16 @@ public class RegexValidator extends Validator {
return isMatchRegex(ACCOUNT, value);
}
/**
* 检查输入的密码是否匹配预定义的规则
*
* @param value 要验证的密码
* @return 如果密码符合规则返回 true否则返回 false
*/
public static boolean isPassword(CharSequence value) {
return isMatchRegex(PASSWORD, value);
}
/**
* 验证输入的账号是否符合规则如果不符合则抛出 ValidateException 异常
*

View File

@ -11,6 +11,7 @@ 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;
@ -230,7 +231,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(methodTags.stream().map(tag -> propertyResolverUtils.resolve(tag.name(), locale)).collect(Collectors.toSet()));
tagsStr.addAll(StreamUtils.toSet(methodTags, tag -> propertyResolverUtils.resolve(tag.name(), locale)));
List<io.swagger.v3.oas.annotations.tags.Tag> allTags = new ArrayList<>(methodTags);
addTags(allTags, tags, locale);
}

View File

@ -21,7 +21,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
@AutoConfiguration
@ConditionalOnProperty(prefix = "snail-job", name = "enabled", havingValue = "true")
@EnableScheduling
@EnableSnailJob(group = "${snail-job.group-name}")
@EnableSnailJob
public class SnailJobConfig {
@EventListener(SnailClientStartingEvent.class)

View File

@ -56,7 +56,7 @@ public class LogAspect {
* 处理请求前执行
*/
@Before(value = "@annotation(controllerLog)")
public void boBefore(JoinPoint joinPoint, Log controllerLog) {
public void doBefore(JoinPoint joinPoint, Log controllerLog) {
StopWatch stopWatch = new StopWatch();
KEY_CACHE.set(stopWatch);
stopWatch.start();

View File

@ -3,9 +3,10 @@ package org.dromara.common.mybatis.annotation;
import java.lang.annotation.*;
/**
* 数据权限
*
* 数据权限注解用于标记数据权限的占位符关键字和替换值
* <p>
* 一个注解只能对应一个模板
* </p>
*
* @author Lion Li
* @version 3.5.0
@ -16,12 +17,16 @@ import java.lang.annotation.*;
public @interface DataColumn {
/**
* 占位符关键字
* 数据权限模板的占位符关键字默认为 "deptName"
*
* @return 占位符关键字数组
*/
String[] key() default "deptName";
/**
* 占位符替换值
* 数据权限模板的占位符替换值默认为 "dept_id"
*
* @return 占位符替换值数组
*/
String[] value() default "dept_id";

View File

@ -3,7 +3,7 @@ package org.dromara.common.mybatis.annotation;
import java.lang.annotation.*;
/**
* 数据权限组
* 数据权限组注解用于标记数据权限配置数组
*
* @author Lion Li
* @version 3.5.0
@ -13,6 +13,11 @@ import java.lang.annotation.*;
@Documented
public @interface DataPermission {
/**
* 数据权限配置数组用于指定数据权限的占位符关键字和替换值
*
* @return 数据权限配置数组
*/
DataColumn[] value();
}

View File

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

View File

@ -12,6 +12,7 @@ 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;
@ -34,73 +35,125 @@ 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) {
return Db.saveBatch(entityList);
Db.saveBatch(entityList);
// 临时解决 新版本 mp 插入状态判断错误问题
return true;
}
/**
* 批量更新
* 批量根据ID更新实体对象集合
*
* @param entityList 实体对象集合
* @return 更新操作是否成功的布尔值
*/
default boolean updateBatchById(Collection<T> entityList) {
return Db.updateBatchById(entityList);
Db.updateBatchById(entityList);
// 临时解决 新版本 mp 插入状态判断错误问题
return true;
}
/**
* 批量插入或更新
* 批量插入或更新实体对象集合
*
* @param entityList 实体对象集合
* @return 插入或更新操作是否成功的布尔值
*/
default boolean insertOrUpdateBatch(Collection<T> entityList) {
return Db.saveOrUpdateBatch(entityList);
Db.saveOrUpdateBatch(entityList);
// 临时解决 新版本 mp 插入状态判断错误问题
return true;
}
/**
* 批量插入(包含限制条数)
* 批量插入实体对象集合并指定批处理大小
*
* @param entityList 实体对象集合
* @param batchSize 批处理大小
* @return 插入操作是否成功的布尔值
*/
default boolean insertBatch(Collection<T> entityList, int batchSize) {
return Db.saveBatch(entityList, batchSize);
Db.saveBatch(entityList, batchSize);
// 临时解决 新版本 mp 插入状态判断错误问题
return true;
}
/**
* 批量更新(包含限制条数)
* 批量根据ID更新实体对象集合并指定批处理大小
*
* @param entityList 实体对象集合
* @param batchSize 批处理大小
* @return 更新操作是否成功的布尔值
*/
default boolean updateBatchById(Collection<T> entityList, int batchSize) {
return Db.updateBatchById(entityList, batchSize);
Db.updateBatchById(entityList, batchSize);
// 临时解决 新版本 mp 插入状态判断错误问题
return true;
}
/**
* 批量插入或更新(包含限制条数)
* 批量插入或更新实体对象集合并指定批处理大小
*
* @param entityList 实体对象集合
* @param batchSize 批处理大小
* @return 插入或更新操作是否成功的布尔值
*/
default boolean insertOrUpdateBatch(Collection<T> entityList, int batchSize) {
return Db.saveOrUpdateBatch(entityList, batchSize);
Db.saveOrUpdateBatch(entityList, batchSize);
// 临时解决 新版本 mp 插入状态判断错误问题
return true;
}
/**
* 插入或更新(包含限制条数)
* 根据ID查询单个VO对象
*
* @param id 主键ID
* @return 查询到的单个VO对象
*/
default boolean insertOrUpdate(T entity) {
return Db.saveOrUpdate(entity);
}
default V selectVoById(Serializable id) {
return selectVoById(id, this.currentVoClass());
}
/**
* 根据 ID 查询
* 根据ID查询单个VO对象并将其转换为指定的VO类
*
* @param id 主键ID
* @param voClass 要转换的VO类的Class对象
* @param <C> VO类的类型
* @return 查询到的单个VO对象经过转换为指定的VO类后返回
*/
default <C> C selectVoById(Serializable id, Class<C> voClass) {
T obj = this.selectById(id);
@ -110,12 +163,23 @@ 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 批量查询
* 根据ID集合批量查询实体对象列表并将其转换为指定的VO对象列表
*
* @param idList 主键ID集合
* @param voClass 要转换的VO类的Class对象
* @param <C> VO类的类型
* @return 查询到的VO对象列表经过转换为指定的VO类后返回
*/
default <C> List<C> selectVoBatchIds(Collection<? extends Serializable> idList, Class<C> voClass) {
List<T> list = this.selectBatchIds(idList);
@ -125,12 +189,23 @@ 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());
}
/**
* 查询根据 columnMap 条件
* 根据查询条件Map查询实体对象列表并将其转换为指定的VO对象列表
*
* @param map 查询条件Map
* @param voClass 要转换的VO类的Class对象
* @param <C> VO类的类型
* @return 查询到的VO对象列表经过转换为指定的VO类后返回
*/
default <C> List<C> selectVoByMap(Map<String, Object> map, Class<C> voClass) {
List<T> list = this.selectByMap(map);
@ -140,23 +215,47 @@ 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);
}
/**
* 根据 entity 条件查询一条记录
* 根据条件查询单个VO对象并指定返回的VO对象的类型
*
* @param wrapper 查询条件Wrapper
* @param voClass 返回的VO对象的Class对象
* @param <C> 返回的VO对象的类型
* @return 查询到的单个VO对象经过类型转换为指定的VO类后返回
*/
default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
return selectVoOne(wrapper, voClass, true);
}
/**
* 根据 entity 条件查询一条记录
* 根据条件查询单个实体对象并将其转换为指定的VO对象
*
* @param wrapper 查询条件Wrapper
* @param voClass 要转换的VO类的Class对象
* @param throwEx 是否抛出异常的标志
* @param <C> VO类的类型
* @return 查询到的单个VO对象经过转换为指定的VO类后返回
*/
default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass, boolean throwEx) {
T obj = this.selectOne(wrapper, throwEx);
@ -166,16 +265,32 @@ 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());
}
/**
* 根据 entity 条件查询全部记录
* 根据条件查询实体对象列表并将其转换为指定的VO对象列表
*
* @param wrapper 查询条件Wrapper
* @param voClass 要转换的VO类的Class对象
* @param <C> VO类的类型
* @return 查询到的VO对象列表经过转换为指定的VO类后返回
*/
default <C> List<C> selectVoList(Wrapper<T> wrapper, Class<C> voClass) {
List<T> list = this.selectList(wrapper);
@ -185,15 +300,31 @@ 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
* 根据条件分页查询实体对象列表并将其转换为指定的VO对象分页列表
*
* @param page 分页信息
* @param wrapper 查询条件Wrapper
* @param voClass 要转换的VO类的Class对象
* @param <C> VO类的类型
* @param <P> VO对象分页列表的类型
* @return 查询到的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;
@ -202,8 +333,16 @@ 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 this.selectObjs(wrapper).stream().filter(Objects::nonNull).map(mapper).collect(Collectors.toList());
return StreamUtils.toList(this.selectObjs(wrapper), mapper);
}
}

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,7 +19,6 @@ import java.util.List;
*
* @author Lion Li
*/
@Data
public class PageQuery implements Serializable {
@ -56,6 +55,9 @@ 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,7 +14,6 @@ import java.util.List;
*
* @author Lion Li
*/
@Data
@NoArgsConstructor
public class TableDataInfo<T> implements Serializable {
@ -53,6 +52,9 @@ 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);
@ -62,6 +64,9 @@ 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);
@ -71,6 +76,9 @@ 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,8 +33,17 @@ 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,19 +1,22 @@
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 模板表达式
* <p>
* 内置数据 user 当前用户 内容参考 LoginUser
* 如需扩展数据 可使用 {@link DataPermissionHelper} 操作
* 内置服务 sdss 系统数据权限服务 内容参考 SysDataScopeService
* 如需扩展更多自定义服务 可以参考 sdss 自行编写
* 支持使用 SpEL 模板表达式定义 SQL 查询条件
* 内置数据
* - {@code user}: 当前登录用户信息参考 {@link LoginUser}
* 内置服务
* - {@code sdss}: 系统数据权限服务参考 {@link ISysDataScopeService}
* 如需扩展数据可以通过 {@link DataPermissionHelper} 进行操作
* 如需扩展服务可以通过 {@link ISysDataScopeService} 自行编写
* </p>
*
* @author Lion Li
* @version 3.5.0
@ -29,36 +32,50 @@ 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 模板表达式
* SpEL 模板表达式用于构建 SQL 查询条件
*/
private final String sqlTemplate;
/**
* 不满足 sqlTemplate 则填充
* 如果不满足 {@code sqlTemplate} 的条件则使用此默认 SQL 表达式
*/
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,21 +21,28 @@ 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());
@ -47,19 +54,24 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
}
}
/**
* 更新填充方法用于在更新数据时自动填充实体对象中的更新时间和更新人信息
*
* @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);
}
}
} catch (Exception e) {
throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
@ -67,7 +79,9 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
}
/**
* 获取登录用户名
* 获取当前登录用户信息
*
* @return 当前登录用户的信息如果用户未登录则返回 null
*/
private LoginUser getLoginUser() {
LoginUser loginUser;

View File

@ -1,15 +1,14 @@
package org.dromara.common.mybatis.handler;
import org.dromara.common.core.domain.R;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
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,13 +68,27 @@ 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();
@ -84,6 +98,7 @@ public class PlusDataPermissionHandler {
if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
return where;
}
// 构造数据过滤条件的 SQL 片段
String dataFilterSql = buildDataFilter(dataPermission.value(), isSelect);
if (StringUtils.isBlank(dataFilterSql)) {
return where;
@ -103,7 +118,12 @@ public class PlusDataPermissionHandler {
}
/**
* 构造数据过滤sql
* 构建数据过滤条件的 SQL 语句
*
* @param dataColumns 数据权限注解中的列信息
* @param isSelect 标志当前操作是否为查询操作查询操作和更新或删除操作在处理过滤条件时会有不同的处理方式
* @return 构建的数据过滤条件的 SQL 语句
* @throws ServiceException 如果角色的数据范围异常或者 key value 的长度不匹配则抛出 ServiceException 异常
*/
private String buildDataFilter(DataColumn[] dataColumns, boolean isSelect) {
// 更新或删除需满足所有条件
@ -159,20 +179,29 @@ public class PlusDataPermissionHandler {
}
/**
* 通过 mapperPackage 设置的扫描包 扫描缓存有注解的方法与类
* 扫描指定包下的 Mapper 并查找其中带有特定注解的方法或类
*
* @param mapperPackage Mapper 类所在的包路径
*/
private void scanMapperClasses(String mapperPackage) {
// 创建资源解析器和元数据读取工厂
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
// Mapper 包路径按分隔符拆分为数组
String[] packagePatternArray = StringUtils.splitPreserveAllTokens(mapperPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;
try {
for (String packagePattern : packagePatternArray) {
// 将包路径转换为资源路径
String path = ClassUtils.convertClassNameToResourcePath(packagePattern);
// 获取指定路径下的所有 .class 文件资源
Resource[] resources = resolver.getResources(classpath + path + "/*.class");
for (Resource resource : resources) {
// 获取资源的类元数据
ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata();
// 获取资源对应的类对象
Class<?> clazz = Resources.classForName(classMetadata.getClassName());
// 查找类中的特定注解
findAnnotation(clazz);
}
}
@ -181,9 +210,13 @@ 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;
@ -194,17 +227,24 @@ 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);
@ -213,7 +253,10 @@ 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,7 +14,6 @@ import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* 数据库助手

View File

@ -24,17 +24,35 @@ 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);
@ -64,6 +82,7 @@ public class DataPermissionHelper {
/**
* 在忽略数据权限中执行
* <p>禁止在忽略数据权限中执行忽略数据权限</p>
*
* @param handle 处理执行方法
*/
@ -78,6 +97,7 @@ public class DataPermissionHelper {
/**
* 在忽略数据权限中执行
* <p>禁止在忽略数据权限中执行忽略数据权限</p>
*
* @param handle 处理执行方法
*/

View File

@ -37,17 +37,33 @@ 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;
}
@ -56,16 +72,26 @@ 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;
}
@ -74,6 +100,14 @@ 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) {
@ -84,6 +118,14 @@ 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);
@ -92,6 +134,14 @@ 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);
@ -101,10 +151,10 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
}
/**
* 设置 where 条件
* 设置 SELECT 语句的 WHERE 条件
*
* @param plainSelect 查询对象
* @param mappedStatementId 执行方法id
* @param plainSelect SELECT 查询对象
* @param mappedStatementId 映射语句的 ID
*/
protected void setWhere(PlainSelect plainSelect, String mappedStatementId) {
Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true);
@ -113,6 +163,14 @@ 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=SELECT 1
exclude=

View File

@ -162,13 +162,14 @@ public class OssClient {
/**
* 上传文件到 Amazon S3并返回上传结果
*
* @param filePath 本地文件路径
* @param key Amazon S3 中的对象键
* @param md5Digest 本地文件的 MD5 哈希值可选
* @param filePath 本地文件路径
* @param key Amazon S3 中的对象键
* @param md5Digest 本地文件的 MD5 哈希值可选
* @param contentType 文件内容类型
* @return UploadResult 包含上传后的文件信息
* @throws OssException 如果上传失败抛出自定义异常
*/
public UploadResult upload(Path filePath, String key, String md5Digest) {
public UploadResult upload(Path filePath, String key, String md5Digest, String contentType) {
try {
// 构建上传请求对象
FileUpload fileUpload = transferManager.uploadFile(
@ -176,6 +177,8 @@ public class OssClient {
y -> y.bucket(properties.getBucketName())
.key(key)
.contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null)
.contentType(contentType)
.acl(getAccessPolicy().getObjectCannedACL())
.build())
.addTransferListener(LoggingTransferListener.create())
.source(filePath).build());
@ -201,10 +204,11 @@ public class OssClient {
* @param inputStream 要上传的输入流
* @param key Amazon S3 中的对象键
* @param length 输入流的长度
* @param contentType 文件内容类型
* @return UploadResult 包含上传后的文件信息
* @throws OssException 如果上传失败抛出自定义异常
*/
public UploadResult upload(InputStream inputStream, String key, Long length) {
public UploadResult upload(InputStream inputStream, String key, Long length, String contentType) {
// 如果输入流不是 ByteArrayInputStream则将其读取为字节数组再创建 ByteArrayInputStream
if (!(inputStream instanceof ByteArrayInputStream)) {
inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream));
@ -219,6 +223,8 @@ public class OssClient {
.putObjectRequest(
y -> y.bucket(properties.getBucketName())
.key(key)
.contentType(contentType)
.acl(getAccessPolicy().getObjectCannedACL())
.build())
.build());
@ -311,12 +317,12 @@ public class OssClient {
* 获取私有URL链接
*
* @param objectKey 对象KEY
* @param second 授权时间
* @param day 授权时间
*/
public String getPrivateUrl(String objectKey, Integer second) {
public String getPrivateUrl(String objectKey, Integer day) {
// 使用 AWS S3 预签名 URL 的生成器 获取对象的预签名 URL
URL url = presigner.presignGetObject(
x -> x.signatureDuration(Duration.ofSeconds(second))
x -> x.signatureDuration(Duration.ofDays(day))
.getObjectRequest(
y -> y.bucket(properties.getBucketName())
.key(objectKey)
@ -335,7 +341,7 @@ public class OssClient {
* @throws OssException 如果上传失败抛出自定义异常
*/
public UploadResult uploadSuffix(byte[] data, String suffix) {
return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Long.valueOf(data.length));
return upload(new ByteArrayInputStream(data), getPath(properties.getPrefix(), suffix), Long.valueOf(data.length), FileUtils.getMimeType(suffix));
}
/**
@ -348,7 +354,7 @@ public class OssClient {
* @throws OssException 如果上传失败抛出自定义异常
*/
public UploadResult uploadSuffix(InputStream inputStream, String suffix, Long length) {
return upload(inputStream, getPath(properties.getPrefix(), suffix), length);
return upload(inputStream, getPath(properties.getPrefix(), suffix), length, FileUtils.getMimeType(suffix));
}
/**
@ -360,7 +366,7 @@ public class OssClient {
* @throws OssException 如果上传失败抛出自定义异常
*/
public UploadResult uploadSuffix(File file, String suffix) {
return upload(file.toPath(), getPath(properties.getPrefix(), suffix), null);
return upload(file.toPath(), getPath(properties.getPrefix(), suffix), null, FileUtils.getMimeType(suffix));
}
/**

View File

@ -48,7 +48,10 @@ public class OssFactory {
}
OssProperties properties = JsonUtils.parseObject(json, OssProperties.class);
// 使用租户标识避免多个租户相同key实例覆盖
String key = properties.getTenantId() + ":" + configKey;
String key = configKey;
if (StringUtils.isNotBlank(properties.getTenantId())) {
key = properties.getTenantId() + ":" + configKey;
}
OssClient client = CLIENT_CACHE.get(key);
// 客户端不存在或配置不相同则重新构建
if (client == null || !client.checkPropertiesSame(properties)) {

View File

@ -23,7 +23,7 @@ public class RedisExceptionHandler {
@ExceptionHandler(LockFailureException.class)
public R<Void> handleLockFailureException(LockFailureException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("获取锁失败了'{}',发生Lock4j异常." + requestURI, e.getMessage());
log.error("获取锁失败了'{}',发生Lock4j异常.", requestURI, e);
return R.fail(HttpStatus.HTTP_UNAVAILABLE, "业务处理中,请稍后再试...");
}

View File

@ -1,12 +1,13 @@
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.Consumer;
import java.util.function.Function;
/**
* 分布式队列工具
@ -224,7 +225,7 @@ public class QueueUtils {
/**
* 订阅阻塞队列(可订阅所有实现类 例如: 延迟 优先 有界 )
*/
public static <T> void subscribeBlockingQueue(String queueName, Consumer<T> consumer, boolean isDelayed) {
public static <T> void subscribeBlockingQueue(String queueName, Function<T, CompletionStage<Void>> consumer, boolean isDelayed) {
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
if (isDelayed) {
// 订阅延迟队列

View File

@ -2,7 +2,6 @@ package org.dromara.common.satoken.core.dao;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.hutool.core.lang.Console;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.dromara.common.redis.utils.RedisUtils;
@ -54,7 +53,7 @@ public class PlusSaTokenDao implements SaTokenDao {
} else {
RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
}
CAFFEINE.put(key, value);
CAFFEINE.invalidate(key);
}
/**
@ -64,7 +63,7 @@ public class PlusSaTokenDao implements SaTokenDao {
public void update(String key, String value) {
if (RedisUtils.hasKey(key)) {
RedisUtils.setCacheObject(key, value, true);
CAFFEINE.put(key, value);
CAFFEINE.invalidate(key);
}
}
@ -117,7 +116,7 @@ public class PlusSaTokenDao implements SaTokenDao {
} else {
RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout));
}
CAFFEINE.put(key, object);
CAFFEINE.invalidate(key);
}
/**
@ -127,7 +126,7 @@ public class PlusSaTokenDao implements SaTokenDao {
public void updateObject(String key, Object object) {
if (RedisUtils.hasKey(key)) {
RedisUtils.setCacheObject(key, object, true);
CAFFEINE.put(key, object);
CAFFEINE.invalidate(key);
}
}

View File

@ -3,6 +3,7 @@ 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;
@ -87,6 +88,13 @@ public class LoginHelper {
return Convert.toLong(getExtra(USER_KEY));
}
/**
* 获取用户账户
*/
public static String getUsername() {
return Convert.toStr(getExtra(USER_NAME_KEY));
}
/**
* 获取租户ID
*/
@ -129,13 +137,6 @@ public class LoginHelper {
}
}
/**
* 获取用户账户
*/
public static String getUsername() {
return getLoginUser().getUsername();
}
/**
* 获取用户类型
*/
@ -170,6 +171,9 @@ 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);
}
@ -188,7 +192,11 @@ public class LoginHelper {
* @return 结果
*/
public static boolean isLogin() {
return getLoginUser() != null;
try {
return getLoginUser() != null;
} catch (Exception e) {
return false;
}
}
}

View File

@ -1,6 +1,7 @@
package org.dromara.common.sms.config;
import org.dromara.common.sms.core.dao.PlusSmsDao;
import org.dromara.common.sms.handler.SmsExceptionHandler;
import org.dromara.sms4j.api.dao.SmsDao;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
@ -21,4 +22,12 @@ public class SmsAutoConfiguration {
return new PlusSmsDao();
}
/**
* 异常处理器
*/
@Bean
public SmsExceptionHandler smsExceptionHandler() {
return new SmsExceptionHandler();
}
}

View File

@ -0,0 +1,30 @@
package org.dromara.common.sms.handler;
import cn.hutool.http.HttpStatus;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.dromara.sms4j.comm.exception.SmsBlendException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* SMS异常处理器
*
* @author AprilWind
*/
@Slf4j
@RestControllerAdvice
public class SmsExceptionHandler {
/**
* sms异常
*/
@ExceptionHandler(SmsBlendException.class)
public R<Void> handleSmsBlendException(SmsBlendException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生sms短信异常.", requestURI, e);
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "短信发送失败,请稍后再试...");
}
}

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
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-social</artifactId>
<description>
ruoyi-common-social 授权认证
</description>
<dependencies>
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-json</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-redis</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,23 @@
package org.dromara.common.social.config;
import me.zhyd.oauth.cache.AuthStateCache;
import org.dromara.common.social.config.properties.SocialProperties;
import org.dromara.common.social.utils.AuthRedisStateCache;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
/**
* Social 配置属性
* @author thiszhc
*/
@AutoConfiguration
@EnableConfigurationProperties(SocialProperties.class)
public class SocialAutoConfiguration {
@Bean
public AuthStateCache authStateCache() {
return new AuthRedisStateCache();
}
}

View File

@ -0,0 +1,75 @@
package org.dromara.common.social.config.properties;
import lombok.Data;
import java.util.List;
/**
* 社交登录配置
*
* @author thiszhc
*/
@Data
public class SocialLoginConfigProperties {
/**
* 应用 ID
*/
private String clientId;
/**
* 应用密钥
*/
private String clientSecret;
/**
* 回调地址
*/
private String redirectUri;
/**
* 是否获取unionId
*/
private boolean unionId;
/**
* Coding 企业名称
*/
private String codingGroupName;
/**
* 支付宝公钥
*/
private String alipayPublicKey;
/**
* 企业微信应用ID
*/
private String agentId;
/**
* stackoverflow api key
*/
private String stackOverflowKey;
/**
* 设备ID
*/
private String deviceId;
/**
* 客户端系统类型
*/
private String clientOsType;
/**
* maxkey 服务器地址
*/
private String serverUrl;
/**
* 请求范围
*/
private List<String> scopes;
}

View File

@ -0,0 +1,24 @@
package org.dromara.common.social.config.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* Social 配置属性
*
* @author thiszhc
*/
@Data
@Component
@ConfigurationProperties(prefix = "justauth")
public class SocialProperties {
/**
* 授权类型
*/
private Map<String, SocialLoginConfigProperties> type;
}

View File

@ -0,0 +1,80 @@
package org.dromara.common.social.maxkey;
import cn.hutool.core.lang.Dict;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthDefaultRequest;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.json.utils.JsonUtils;
/**
* @author 长春叭哥 2023年03月26日
*/
public class AuthMaxKeyRequest extends AuthDefaultRequest {
public static final String SERVER_URL = SpringUtils.getProperty("justauth.type.maxkey.server-url");
/**
* 设定归属域
*/
public AuthMaxKeyRequest(AuthConfig config) {
super(config, AuthMaxKeySource.MAXKEY);
}
public AuthMaxKeyRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthMaxKeySource.MAXKEY, authStateCache);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
String body = doPostAuthorizationCode(authCallback.getCode());
Dict object = JsonUtils.parseMap(body);
// oauth/token 验证异常
if (object.containsKey("error")) {
throw new AuthException(object.getStr("error_description"));
}
// user 验证异常
if (object.containsKey("message")) {
throw new AuthException(object.getStr("message"));
}
return AuthToken.builder()
.accessToken(object.getStr("access_token"))
.refreshToken(object.getStr("refresh_token"))
.idToken(object.getStr("id_token"))
.tokenType(object.getStr("token_type"))
.scope(object.getStr("scope"))
.build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String body = doGetUserInfo(authToken);
Dict object = JsonUtils.parseMap(body);
// oauth/token 验证异常
if (object.containsKey("error")) {
throw new AuthException(object.getStr("error_description"));
}
// user 验证异常
if (object.containsKey("message")) {
throw new AuthException(object.getStr("message"));
}
return AuthUser.builder()
.uuid(object.getStr("userId"))
.username(object.getStr("username"))
.nickname(object.getStr("displayName"))
.avatar(object.getStr("avatar_url"))
.blog(object.getStr("web_url"))
.company(object.getStr("organization"))
.location(object.getStr("location"))
.email(object.getStr("email"))
.remark(object.getStr("bio"))
.token(authToken)
.source(source.toString())
.build();
}
}

View File

@ -0,0 +1,52 @@
package org.dromara.common.social.maxkey;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.request.AuthDefaultRequest;
/**
* Oauth2 默认接口说明
*
* @author 长春叭哥 2023年03月26日
*
*/
public enum AuthMaxKeySource implements AuthSource {
/**
* 自己搭建的 maxkey 私服
*/
MAXKEY {
/**
* 授权的api
*/
@Override
public String authorize() {
return AuthMaxKeyRequest.SERVER_URL + "/sign/authz/oauth/v20/authorize";
}
/**
* 获取accessToken的api
*/
@Override
public String accessToken() {
return AuthMaxKeyRequest.SERVER_URL + "/sign/authz/oauth/v20/token";
}
/**
* 获取用户信息的api
*/
@Override
public String userInfo() {
return AuthMaxKeyRequest.SERVER_URL + "/sign/api/oauth/v20/me";
}
/**
* 平台对应的 AuthRequest 实现类必须继承自 {@link AuthDefaultRequest}
*/
@Override
public Class<? extends AuthDefaultRequest> getTargetClass() {
return AuthMaxKeyRequest.class;
}
}
}

View File

@ -0,0 +1,100 @@
package org.dromara.common.social.topiam;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import com.xkcoding.http.support.HttpHeader;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthDefaultRequest;
import me.zhyd.oauth.utils.HttpUtils;
import me.zhyd.oauth.utils.UrlBuilder;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.json.utils.JsonUtils;
import static org.dromara.common.social.topiam.AuthTopiamSource.TOPIAM;
/**
* TopIAM 认证请求
*
* @author xlsea
* @since 2024-01-06
*/
@Slf4j
public class AuthTopIamRequest extends AuthDefaultRequest {
public static final String SERVER_URL = SpringUtils.getProperty("justauth.type.topiam.server-url");
/**
* 设定归属域
*/
public AuthTopIamRequest(AuthConfig config) {
super(config, TOPIAM);
}
public AuthTopIamRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, TOPIAM, authStateCache);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
String body = doPostAuthorizationCode(authCallback.getCode());
Dict object = JsonUtils.parseMap(body);
checkResponse(object);
return AuthToken.builder()
.accessToken(object.getStr("access_token"))
.refreshToken(object.getStr("refresh_token"))
.idToken(object.getStr("id_token"))
.tokenType(object.getStr("token_type"))
.scope(object.getStr("scope"))
.build();
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
String body = doGetUserInfo(authToken);
Dict object = JsonUtils.parseMap(body);
checkResponse(object);
return AuthUser.builder()
.uuid(object.getStr("sub"))
.username(object.getStr("preferred_username"))
.nickname(object.getStr("nickname"))
.avatar(object.getStr("picture"))
.email(object.getStr("email"))
.token(authToken)
.source(source.toString())
.build();
}
@Override
protected String doGetUserInfo(AuthToken authToken) {
return new HttpUtils(config.getHttpConfig()).get(source.userInfo(), null, new HttpHeader()
.add("Content-Type", "application/json")
.add("Authorization", "Bearer " + authToken.getAccessToken()), false).getBody();
}
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(super.authorize(state))
.queryParam("scope", StrUtil.join("%20", config.getScopes()))
.build();
}
public static void checkResponse(Dict object) {
// oauth/token 验证异常
if (object.containsKey("error")) {
throw new AuthException(object.getStr("error_description"));
}
// user 验证异常
if (object.containsKey("message")) {
throw new AuthException(object.getStr("message"));
}
}
}

View File

@ -0,0 +1,51 @@
package org.dromara.common.social.topiam;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.request.AuthDefaultRequest;
/**
* Oauth2 默认接口说明
*
* @author xlsea
* @since 2024-01-06
*/
public enum AuthTopiamSource implements AuthSource {
/**
* 测试
*/
TOPIAM {
/**
* 授权的api
*/
@Override
public String authorize() {
return AuthTopIamRequest.SERVER_URL + "/oauth2/auth";
}
/**
* 获取accessToken的api
*/
@Override
public String accessToken() {
return AuthTopIamRequest.SERVER_URL + "/oauth2/token";
}
/**
* 获取用户信息的api
*/
@Override
public String userInfo() {
return AuthTopIamRequest.SERVER_URL + "/oauth2/userinfo";
}
/**
* 平台对应的 AuthRequest 实现类必须继承自 {@link AuthDefaultRequest}
*/
@Override
public Class<? extends AuthDefaultRequest> getTargetClass() {
return AuthTopIamRequest.class;
}
}
}

View File

@ -0,0 +1,61 @@
package org.dromara.common.social.utils;
import lombok.AllArgsConstructor;
import me.zhyd.oauth.cache.AuthStateCache;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.redis.utils.RedisUtils;
import java.time.Duration;
/**
* 授权状态缓存
*/
@AllArgsConstructor
public class AuthRedisStateCache implements AuthStateCache {
/**
* 存入缓存
*
* @param key 缓存key
* @param value 缓存内容
*/
@Override
public void cache(String key, String value) {
// 授权超时时间 默认三分钟
RedisUtils.setCacheObject(GlobalConstants.SOCIAL_AUTH_CODE_KEY + key, value, Duration.ofMinutes(3));
}
/**
* 存入缓存
*
* @param key 缓存key
* @param value 缓存内容
* @param timeout 指定缓存过期时间(毫秒)
*/
@Override
public void cache(String key, String value, long timeout) {
RedisUtils.setCacheObject(GlobalConstants.SOCIAL_AUTH_CODE_KEY + key, value, Duration.ofMillis(timeout));
}
/**
* 获取缓存内容
*
* @param key 缓存key
* @return 缓存内容
*/
@Override
public String get(String key) {
return RedisUtils.getCacheObject(GlobalConstants.SOCIAL_AUTH_CODE_KEY + key);
}
/**
* 是否存在key如果对应key的value值已过期也返回false
*
* @param key 缓存key
* @return true存在key并且value没过期falsekey不存在或者已过期
*/
@Override
public boolean containsKey(String key) {
return RedisUtils.hasKey(GlobalConstants.SOCIAL_AUTH_CODE_KEY + key);
}
}

View File

@ -0,0 +1,73 @@
package org.dromara.common.social.utils;
import cn.hutool.core.util.ObjectUtil;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.*;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
import org.dromara.common.social.config.properties.SocialProperties;
import org.dromara.common.social.maxkey.AuthMaxKeyRequest;
import org.dromara.common.social.topiam.AuthTopIamRequest;
/**
* 认证授权工具类
*
* @author thiszhc
*/
public class SocialUtils {
private static final AuthRedisStateCache STATE_CACHE = SpringUtils.getBean(AuthRedisStateCache.class);
@SuppressWarnings("unchecked")
public static AuthResponse<AuthUser> loginAuth(String source, String code, String state, SocialProperties socialProperties) throws AuthException {
AuthRequest authRequest = getAuthRequest(source, socialProperties);
AuthCallback callback = new AuthCallback();
callback.setCode(code);
callback.setState(state);
return authRequest.login(callback);
}
public static AuthRequest getAuthRequest(String source, SocialProperties socialProperties) throws AuthException {
SocialLoginConfigProperties obj = socialProperties.getType().get(source);
if (ObjectUtil.isNull(obj)) {
throw new AuthException("不支持的第三方登录类型");
}
AuthConfig.AuthConfigBuilder builder = AuthConfig.builder()
.clientId(obj.getClientId())
.clientSecret(obj.getClientSecret())
.redirectUri(obj.getRedirectUri())
.scopes(obj.getScopes());
return switch (source.toLowerCase()) {
case "dingtalk" -> new AuthDingTalkRequest(builder.build(), STATE_CACHE);
case "baidu" -> new AuthBaiduRequest(builder.build(), STATE_CACHE);
case "github" -> new AuthGithubRequest(builder.build(), STATE_CACHE);
case "gitee" -> new AuthGiteeRequest(builder.build(), STATE_CACHE);
case "weibo" -> new AuthWeiboRequest(builder.build(), STATE_CACHE);
case "coding" -> new AuthCodingRequest(builder.build(), STATE_CACHE);
case "oschina" -> new AuthOschinaRequest(builder.build(), STATE_CACHE);
// 支付宝在创建回调地址时不允许使用localhost或者127.0.0.1所以这儿的回调地址使用的局域网内的ip
case "alipay_wallet" -> new AuthAlipayRequest(builder.build(), socialProperties.getType().get("alipay_wallet").getAlipayPublicKey(), STATE_CACHE);
case "qq" -> new AuthQqRequest(builder.build(), STATE_CACHE);
case "wechat_open" -> new AuthWeChatOpenRequest(builder.build(), STATE_CACHE);
case "taobao" -> new AuthTaobaoRequest(builder.build(), STATE_CACHE);
case "douyin" -> new AuthDouyinRequest(builder.build(), STATE_CACHE);
case "linkedin" -> new AuthLinkedinRequest(builder.build(), STATE_CACHE);
case "microsoft" -> new AuthMicrosoftRequest(builder.build(), STATE_CACHE);
case "renren" -> new AuthRenrenRequest(builder.build(), STATE_CACHE);
case "stack_overflow" -> new AuthStackOverflowRequest(builder.stackOverflowKey("").build(), STATE_CACHE);
case "huawei" -> new AuthHuaweiRequest(builder.build(), STATE_CACHE);
case "wechat_enterprise" -> new AuthWeChatEnterpriseQrcodeRequest(builder.agentId("").build(), STATE_CACHE);
case "gitlab" -> new AuthGitlabRequest(builder.build(), STATE_CACHE);
case "wechat_mp" -> new AuthWeChatMpRequest(builder.build(), STATE_CACHE);
case "aliyun" -> new AuthAliyunRequest(builder.build(), STATE_CACHE);
case "maxkey" -> new AuthMaxKeyRequest(builder.build(), STATE_CACHE);
case "topiam" -> new AuthTopIamRequest(builder.build(), STATE_CACHE);
default -> throw new AuthException("未获取到有效的Auth配置");
};
}
}

View File

@ -0,0 +1 @@
org.dromara.common.social.config.SocialAutoConfiguration

View File

@ -35,7 +35,7 @@ public class TenantConfig {
@ConditionalOnBean(MybatisPlusConfig.class)
@AutoConfiguration(after = {MybatisPlusConfig.class})
static class MybatisPlusConfigation {
static class MybatisPlusConfiguration {
/**
* 多租户插件

View File

@ -1,6 +1,5 @@
package org.dromara.common.tenant.helper;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.convert.Convert;
import com.alibaba.ttl.TransmittableThreadLocal;
@ -79,22 +78,28 @@ public class TenantHelper {
}
}
public static void setDynamic(String tenantId) {
setDynamic(tenantId, false);
}
/**
* 设置动态租户(一直有效 需要手动清理)
* <p>
* 如果为未登录状态下 那么只在当前线程内生效
*
* @param tenantId 租户id
* @param global 是否全局生效
*/
public static void setDynamic(String tenantId) {
public static void setDynamic(String tenantId, boolean global) {
if (!isEnable()) {
return;
}
if (!isLogin()) {
if (!isLogin() || !global) {
TEMP_DYNAMIC_TENANT.set(tenantId);
return;
}
String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
RedisUtils.setCacheObject(cacheKey, tenantId);
SaHolder.getStorage().set(cacheKey, tenantId);
}
/**
@ -109,13 +114,13 @@ public class TenantHelper {
if (!isLogin()) {
return TEMP_DYNAMIC_TENANT.get();
}
String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
String tenantId = (String) SaHolder.getStorage().get(cacheKey);
// 如果线程内有值 优先返回
String tenantId = TEMP_DYNAMIC_TENANT.get();
if (StringUtils.isNotBlank(tenantId)) {
return tenantId;
}
String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
tenantId = RedisUtils.getCacheObject(cacheKey);
SaHolder.getStorage().set(cacheKey, tenantId);
return tenantId;
}
@ -130,9 +135,9 @@ public class TenantHelper {
TEMP_DYNAMIC_TENANT.remove();
return;
}
TEMP_DYNAMIC_TENANT.remove();
String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
RedisUtils.deleteObject(cacheKey);
SaHolder.getStorage().delete(cacheKey);
}
/**

View File

@ -16,15 +16,11 @@ 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(false, 512));
webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(true, 1024));
deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
// 使用虚拟线程
if (SpringUtils.isVirtual()) {

View File

@ -27,17 +27,20 @@ public class WebSocketConfig {
@Bean
public WebSocketConfigurer webSocketConfigurer(HandshakeInterceptor handshakeInterceptor,
WebSocketHandler webSocketHandler,
WebSocketProperties webSocketProperties) {
WebSocketHandler webSocketHandler, WebSocketProperties webSocketProperties) {
// 如果WebSocket的路径为空则设置默认路径为 "/websocket"
if (StrUtil.isBlank(webSocketProperties.getPath())) {
webSocketProperties.setPath("/websocket");
}
// 如果允许跨域访问的地址为空则设置为 "*"表示允许所有来源的跨域请求
if (StrUtil.isBlank(webSocketProperties.getAllowedOrigins())) {
webSocketProperties.setAllowedOrigins("*");
}
// 返回一个WebSocketConfigurer对象用于配置WebSocket
return registry -> registry
// 添加WebSocket处理程序和拦截器到指定路径设置允许的跨域来源
.addHandler(webSocketHandler, webSocketProperties.getPath())
.addInterceptors(handshakeInterceptor)
.setAllowedOrigins(webSocketProperties.getAllowedOrigins());

View File

@ -6,6 +6,7 @@ package org.dromara.common.websocket.constant;
* @author zendwang
*/
public interface WebSocketConstants {
/**
* websocketSession中的参数的key
*/

View File

@ -1,5 +1,6 @@
package org.dromara.common.websocket.handler;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.websocket.dto.WebSocketMessageDto;
@ -8,6 +9,7 @@ import org.dromara.common.websocket.utils.WebSocketUtils;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
import java.io.IOException;
import java.util.List;
import static org.dromara.common.websocket.constant.WebSocketConstants.LOGIN_USER_KEY;
@ -24,40 +26,54 @@ public class PlusWebSocketHandler extends AbstractWebSocketHandler {
* 连接成功后
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) {
public void afterConnectionEstablished(WebSocketSession session) throws IOException {
LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
if (ObjectUtil.isNull(loginUser)) {
session.close(CloseStatus.BAD_DATA);
log.info("[connect] invalid token received. sessionId: {}", session.getId());
return;
}
WebSocketSessionHolder.addSession(loginUser.getUserId(), session);
log.info("[connect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserId(), loginUser.getUserType());
}
/**
* 处理发送来的文本消息
* 处理接收到的文本消息
*
* @param session
* @param message
* @throws Exception
* @param session WebSocket会话
* @param message 接收到的文本消息
* @throws Exception 处理消息过程中可能抛出的异常
*/
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// 从WebSocket会话中获取登录用户信息
LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
List<Long> userIds = List.of(loginUser.getUserId());
// 创建WebSocket消息DTO对象
WebSocketMessageDto webSocketMessageDto = new WebSocketMessageDto();
webSocketMessageDto.setSessionKeys(userIds);
webSocketMessageDto.setSessionKeys(List.of(loginUser.getUserId()));
webSocketMessageDto.setMessage(message.getPayload());
WebSocketUtils.publishMessage(webSocketMessageDto);
}
/**
* 处理接收到的二进制消息
*
* @param session WebSocket会话
* @param message 接收到的二进制消息
* @throws Exception 处理消息过程中可能抛出的异常
*/
@Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
super.handleBinaryMessage(session, message);
}
/**
* 心跳监测的回复
* 处理接收到的Pong消息心跳监测
*
* @param session
* @param message
* @throws Exception
* @param session WebSocket会话
* @param message 接收到的Pong消息
* @throws Exception 处理消息过程中可能抛出的异常
*/
@Override
protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
@ -65,11 +81,11 @@ public class PlusWebSocketHandler extends AbstractWebSocketHandler {
}
/**
* 连接出错时
* 处理WebSocket传输错误
*
* @param session
* @param exception
* @throws Exception
* @param session WebSocket会话
* @param exception 发生的异常
* @throws Exception 处理过程中可能抛出的异常
*/
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
@ -77,22 +93,26 @@ public class PlusWebSocketHandler extends AbstractWebSocketHandler {
}
/**
* 连接关闭后
* 在WebSocket连接关闭后执行清理操作
*
* @param session
* @param status
* @param session WebSocket会话
* @param status 关闭状态信息
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
if (ObjectUtil.isNull(loginUser)) {
log.info("[disconnect] invalid token received. sessionId: {}", session.getId());
return;
}
WebSocketSessionHolder.removeSession(loginUser.getUserId());
log.info("[disconnect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserId(), loginUser.getUserType());
}
/**
* 是否支持分消息
* 指示处理程序是否支持接收部分消息
*
* @return
* @return 如果支持接收部分消息则返回true否则返回false
*/
@Override
public boolean supportsPartialMessages() {

View File

@ -18,24 +18,52 @@ public class WebSocketSessionHolder {
private static final Map<Long, WebSocketSession> USER_SESSION_MAP = new ConcurrentHashMap<>();
/**
* 将WebSocket会话添加到用户会话Map中
*
* @param sessionKey 会话键用于检索会话
* @param session 要添加的WebSocket会话
*/
public static void addSession(Long sessionKey, WebSocketSession session) {
USER_SESSION_MAP.put(sessionKey, session);
}
/**
* 从用户会话Map中移除指定会话键对应的WebSocket会话
*
* @param sessionKey 要移除的会话键
*/
public static void removeSession(Long sessionKey) {
if (USER_SESSION_MAP.containsKey(sessionKey)) {
USER_SESSION_MAP.remove(sessionKey);
}
}
/**
* 根据会话键从用户会话Map中获取WebSocket会话
*
* @param sessionKey 要获取的会话键
* @return 与给定会话键对应的WebSocket会话如果不存在则返回null
*/
public static WebSocketSession getSessions(Long sessionKey) {
return USER_SESSION_MAP.get(sessionKey);
}
/**
* 获取存储在用户会话Map中所有WebSocket会话的会话键集合
*
* @return 所有WebSocket会话的会话键集合
*/
public static Set<Long> getSessionsAll() {
return USER_SESSION_MAP.keySet();
}
/**
* 检查给定的会话键是否存在于用户会话Map中
*
* @param sessionKey 要检查的会话键
* @return 如果存在对应的会话键则返回true否则返回false
*/
public static Boolean existSession(Long sessionKey) {
return USER_SESSION_MAP.containsKey(sessionKey);
}

View File

@ -1,8 +1,12 @@
package org.dromara.common.websocket.interceptor;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.satoken.utils.LoginHelper;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
@ -21,31 +25,51 @@ import static org.dromara.common.websocket.constant.WebSocketConstants.LOGIN_USE
public class PlusWebSocketInterceptor implements HandshakeInterceptor {
/**
* 握手前
* WebSocket握手执行的前置处理方法
*
* @param request request
* @param response response
* @param wsHandler wsHandler
* @param attributes attributes
* @return 是否握手成功
* @param request WebSocket握手请求
* @param response WebSocket握手响应
* @param wsHandler WebSocket处理程序
* @param attributes 与WebSocket会话关联的属性
* @return 如果允许握手继续进行则返回true否则返回false
*/
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) {
LoginUser loginUser = LoginHelper.getLoginUser();
attributes.put(LOGIN_USER_KEY, loginUser);
return true;
try {
// 检查是否登录 是否有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;
}
}
/**
* 握手后
* WebSocket握手成功执行的后置处理方法
*
* @param request request
* @param response response
* @param wsHandler wsHandler
* @param exception 异常
* @param request WebSocket握手请求
* @param response WebSocket握手响应
* @param wsHandler WebSocket处理程序
* @param exception 握手过程中可能出现的异常
*/
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
// 在这个方法中可以执行一些握手成功后的后续处理逻辑比如记录日志或者其他操作
}
}

View File

@ -1,9 +1,9 @@
package org.dromara.common.websocket.listener;
import cn.hutool.core.collection.CollUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.websocket.holder.WebSocketSessionHolder;
import org.dromara.common.websocket.utils.WebSocketUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.Ordered;
@ -16,8 +16,15 @@ import org.springframework.core.Ordered;
@Slf4j
public class WebSocketTopicListener implements ApplicationRunner, Ordered {
/**
* 在Spring Boot应用程序启动时初始化WebSocket主题订阅监听器
*
* @param args 应用程序参数
* @throws Exception 初始化过程中可能抛出的异常
*/
@Override
public void run(ApplicationArguments args) throws Exception {
// 订阅WebSocket消息
WebSocketUtils.subscribeMessage((message) -> {
log.info("WebSocket主题订阅收到消息session keys={} message={}", message.getSessionKeys(), message.getMessage());
// 如果key不为空就按照key发消息 如果为空就群发

View File

@ -29,10 +29,10 @@ import static org.dromara.common.websocket.constant.WebSocketConstants.WEB_SOCKE
public class WebSocketUtils {
/**
* 发送消息
* 向指定的WebSocket会话发送消息
*
* @param sessionKey session主键 一般为用户id
* @param message 消息文本
* @param sessionKey 要发送消息的用户id
* @param message 要发送的消息内容
*/
public static void sendMessage(Long sessionKey, String message) {
WebSocketSession session = WebSocketSessionHolder.getSessions(sessionKey);
@ -40,18 +40,18 @@ public class WebSocketUtils {
}
/**
* 订阅消息
* 订阅WebSocket消息主题并提供一个消费者函数来处理接收到的消息
*
* @param consumer 自定义处理
* @param consumer 处理WebSocket消息的消费者函数
*/
public static void subscribeMessage(Consumer<WebSocketMessageDto> consumer) {
RedisUtils.subscribe(WEB_SOCKET_TOPIC, WebSocketMessageDto.class, consumer);
}
/**
* 发布订阅消息
* 发布WebSocket订阅消息
*
* @param webSocketMessage 消息对象
* @param webSocketMessage 要发布的WebSocket消息对象
*/
public static void publishMessage(WebSocketMessageDto webSocketMessage) {
List<Long> unsentSessionKeys = new ArrayList<>();
@ -76,9 +76,9 @@ public class WebSocketUtils {
}
/**
* 发布订阅的消息(群发)
* 向所有的WebSocket会话发布订阅的消息(群发)
*
* @param message 消息内容
* @param message 要发布的消息内容
*/
public static void publishAll(String message) {
WebSocketMessageDto broadcastMessage = new WebSocketMessageDto();
@ -88,14 +88,31 @@ public class WebSocketUtils {
});
}
/**
* 向指定的WebSocket会话发送Pong消息
*
* @param session 要发送Pong消息的WebSocket会话
*/
public static void sendPongMessage(WebSocketSession session) {
sendMessage(session, new PongMessage());
}
/**
* 向指定的WebSocket会话发送文本消息
*
* @param session WebSocket会话
* @param message 要发送的文本消息内容
*/
public static void sendMessage(WebSocketSession session, String message) {
sendMessage(session, new TextMessage(message));
}
/**
* 向指定的WebSocket会话发送WebSocket消息对象
*
* @param session WebSocket会话
* @param message 要发送的WebSocket消息对象
*/
private static void sendMessage(WebSocketSession session, WebSocketMessage<?> message) {
if (session == null || !session.isOpen()) {
log.warn("[send] session会话已经关闭");

View File

@ -1,7 +1,9 @@
# 贝尔实验室 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
MAINTAINER Lion Li
LABEL maintainer="Lion Li"
RUN mkdir -p /ruoyi/monitor/logs

View File

@ -1,7 +1,9 @@
# 贝尔实验室 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
MAINTAINER Lion Li
LABEL maintainer="Lion Li"
RUN mkdir -p /ruoyi/snailjob/logs
@ -10,7 +12,7 @@ WORKDIR /ruoyi/snailjob
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS="-Xms512m -Xmx1024m"
EXPOSE 8800
EXPOSE 1788
EXPOSE 17888
ADD ./target/ruoyi-snailjob-server.jar ./app.jar

View File

@ -21,7 +21,7 @@ snail-job:
# 拉取重试数据的每批次的大小
job-pull-page-size: 1000
# 服务端netty端口
netty-port: 1788
netty-port: 17888
# 一个客户端每秒最多接收的重试数量指令
limiter: 1000
# 号段模式下步长配置

View File

@ -21,7 +21,7 @@ snail-job:
# 拉取重试数据的每批次的大小
job-pull-page-size: 1000
# 服务端 netty 端口
netty-port: 1788
netty-port: 17888
# 一个客户端每秒最多接收的重试数量指令
limiter: 1000
# 号段模式下步长配置

View File

@ -13,7 +13,6 @@
<module>ruoyi-generator</module>
<module>ruoyi-job</module>
<module>ruoyi-system</module>
<module>ruoyi-file</module>
</modules>
<artifactId>ruoyi-modules</artifactId>

View File

@ -1,38 +0,0 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

View File

@ -1,65 +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-modules</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-file</artifactId>
<dependencies>
<!-- 通用工具-->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-mybatis</artifactId>
</dependency>
<!-- OSS功能模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-oss</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-log</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-tenant</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-security</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-web</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-idempotent</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-sensitive</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-encrypt</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,104 +0,0 @@
package org.dromara.file.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.web.core.BaseController;
import org.dromara.file.domain.bo.SysCatalogResourceBo;
import org.dromara.file.domain.vo.SysCatalogResourceVo;
import org.dromara.file.service.ISysCatalogResourceService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 目录-专题资源
*
* @author Lion Li
* @date 2024-05-28
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/catalog/resource")
public class SysCatalogResourceController extends BaseController {
private final ISysCatalogResourceService sysCatalogResourceService;
/**
* 查询目录-专题资源列表
*/
@SaCheckPermission("resource:catalogResource:list")
@GetMapping("/list")
public R<List<SysCatalogResourceVo>> list(SysCatalogResourceBo bo) {
List<SysCatalogResourceVo> list = sysCatalogResourceService.queryList(bo);
return R.ok(list);
}
/**
* 导出目录-专题资源列表
*/
// @SaCheckPermission("resource:catalogResource:export")
// @Log(title = "目录-专题资源", businessType = BusinessType.EXPORT)
// @PostMapping("/export")
// public void export(SysCatalogResourceBo bo, HttpServletResponse response) {
// List<SysCatalogResourceVo> list = sysCatalogResourceService.queryList(bo);
// ExcelUtil.exportExcel(list, "目录-专题资源", SysCatalogResourceVo.class, response);
// }
/**
* 获取目录-专题资源详细信息
*
* @param catalogId 主键
*/
@SaCheckPermission("resource:catalogResource:query")
@GetMapping("/{catalogId}")
public R<SysCatalogResourceVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long catalogId) {
return R.ok(sysCatalogResourceService.queryById(catalogId));
}
/**
* 新增目录-专题资源
*/
@SaCheckPermission("resource:catalogResource:add")
@Log(title = "目录-专题资源", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysCatalogResourceBo bo) {
return toAjax(sysCatalogResourceService.insertByBo(bo));
}
/**
* 修改目录-专题资源
*/
@SaCheckPermission("resource:catalogResource:edit")
@Log(title = "目录-专题资源", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysCatalogResourceBo bo) {
return toAjax(sysCatalogResourceService.updateByBo(bo));
}
/**
* 删除目录-专题资源
*
* @param catalogIds 主键串
*/
@SaCheckPermission("resource:catalogResource:remove")
@Log(title = "目录-专题资源", businessType = BusinessType.DELETE)
@DeleteMapping("/{catalogIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] catalogIds) {
return toAjax(sysCatalogResourceService.deleteWithValidByIds(List.of(catalogIds), true));
}
}

View File

@ -1,104 +0,0 @@
package org.dromara.file.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.web.core.BaseController;
import org.dromara.file.domain.bo.SysCatalogTextbookBo;
import org.dromara.file.domain.vo.SysCatalogTextbookVo;
import org.dromara.file.service.ISysCatalogTextbookService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 目录-同步教材
*
* @author Lion Li
* @date 2024-05-28
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/catalog/textbook")
public class SysCatalogTextbookController extends BaseController {
private final ISysCatalogTextbookService sysCatalogTextbookService;
/**
* 查询目录-同步教材列表
*/
@SaCheckPermission("resource:catalogTextbook:list")
@GetMapping("/list")
public R<List<SysCatalogTextbookVo>> list(SysCatalogTextbookBo bo) {
List<SysCatalogTextbookVo> list = sysCatalogTextbookService.queryList(bo);
return R.ok(list);
}
/**
* 导出目录-同步教材列表
*/
// @SaCheckPermission("resource:catalogTextbook:export")
// @Log(title = "目录-同步教材", businessType = BusinessType.EXPORT)
// @PostMapping("/export")
// public void export(SysCatalogTextbookBo bo, HttpServletResponse response) {
// List<SysCatalogTextbookVo> list = sysCatalogTextbookService.queryList(bo);
// ExcelUtil.exportExcel(list, "目录-同步教材", SysCatalogTextbookVo.class, response);
// }
/**
* 获取目录-同步教材详细信息
*
* @param catalogId 主键
*/
@SaCheckPermission("resource:catalogTextbook:query")
@GetMapping("/{catalogId}")
public R<SysCatalogTextbookVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long catalogId) {
return R.ok(sysCatalogTextbookService.queryById(catalogId));
}
/**
* 新增目录-同步教材
*/
@SaCheckPermission("resource:catalogTextbook:add")
@Log(title = "目录-同步教材", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysCatalogTextbookBo bo) {
return toAjax(sysCatalogTextbookService.insertByBo(bo));
}
/**
* 修改目录-同步教材
*/
@SaCheckPermission("resource:catalogTextbook:edit")
@Log(title = "目录-同步教材", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysCatalogTextbookBo bo) {
return toAjax(sysCatalogTextbookService.updateByBo(bo));
}
/**
* 删除目录-同步教材
*
* @param catalogIds 主键串
*/
@SaCheckPermission("resource:catalogTextbook:remove")
@Log(title = "目录-同步教材", businessType = BusinessType.DELETE)
@DeleteMapping("/{catalogIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] catalogIds) {
return toAjax(sysCatalogTextbookService.deleteWithValidByIds(List.of(catalogIds), true));
}
}

View File

@ -1,104 +0,0 @@
package org.dromara.file.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
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.web.core.BaseController;
import org.dromara.file.domain.bo.SysOssResourceBo;
import org.dromara.file.domain.vo.SysOssResourceVo;
import org.dromara.file.service.ISysOssResourceService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 请填写功能名称
*
* @author Lion Li
* @date 2024-05-28
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/file/resource")
public class SysOssResourceController extends BaseController {
private final ISysOssResourceService sysOssResourceService;
/**
* 查询请填写功能名称列表
*/
@SaCheckPermission("system:ossResource:list")
@GetMapping("/list")
public TableDataInfo<SysOssResourceVo> list(SysOssResourceBo bo, PageQuery pageQuery) {
return sysOssResourceService.queryPageList(bo, pageQuery);
}
/**
* 导出请填写功能名称列表
*/
// @SaCheckPermission("system:ossResource:export")
// @Log(title = "【请填写功能名称】", businessType = BusinessType.EXPORT)
// @PostMapping("/export")
// public void export(SysOssResourceBo bo, HttpServletResponse response) {
// List<SysOssResourceVo> list = sysOssResourceService.queryList(bo);
// ExcelUtil.exportExcel(list, "【请填写功能名称】", SysOssResourceVo.class, response);
// }
/**
* 获取请填写功能名称详细信息
*
* @param ossId 主键
*/
@SaCheckPermission("system:ossResource:query")
@GetMapping("/{ossId}")
public R<SysOssResourceVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long ossId) {
return R.ok(sysOssResourceService.queryById(ossId));
}
/**
* 新增请填写功能名称
*/
@SaCheckPermission("system:ossResource:add")
@Log(title = "【请填写功能名称】", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysOssResourceBo bo) {
return toAjax(sysOssResourceService.insertByBo(bo));
}
/**
* 修改请填写功能名称
*/
@SaCheckPermission("system:ossResource:edit")
@Log(title = "【请填写功能名称】", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysOssResourceBo bo) {
return toAjax(sysOssResourceService.updateByBo(bo));
}
/**
* 删除请填写功能名称
*
* @param ossIds 主键串
*/
@SaCheckPermission("system:ossResource:remove")
@Log(title = "【请填写功能名称】", businessType = BusinessType.DELETE)
@DeleteMapping("/{ossIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ossIds) {
return toAjax(sysOssResourceService.deleteWithValidByIds(List.of(ossIds), true));
}
}

View File

@ -1,104 +0,0 @@
package org.dromara.file.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
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.web.core.BaseController;
import org.dromara.file.domain.bo.SysOssTextbookBo;
import org.dromara.file.domain.vo.SysOssTextbookVo;
import org.dromara.file.service.ISysOssTextbookService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 请填写功能名称
*
* @author Lion Li
* @date 2024-05-28
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/file/textbook")
public class SysOssTextbookController extends BaseController {
private final ISysOssTextbookService sysOssTextbookService;
/**
* 查询请填写功能名称列表
*/
@SaCheckPermission("system:ossTextbook:list")
@GetMapping("/list")
public TableDataInfo<SysOssTextbookVo> list(SysOssTextbookBo bo, PageQuery pageQuery) {
return sysOssTextbookService.queryPageList(bo, pageQuery);
}
// /**
// * 导出请填写功能名称列表
// */
// @SaCheckPermission("system:ossTextbook:export")
// @Log(title = "【请填写功能名称】", businessType = BusinessType.EXPORT)
// @PostMapping("/export")
// public void export(SysOssTextbookBo bo, HttpServletResponse response) {
// List<SysOssTextbookVo> list = sysOssTextbookService.queryList(bo);
// ExcelUtil.exportExcel(list, "【请填写功能名称】", SysOssTextbookVo.class, response);
// }
/**
* 获取请填写功能名称详细信息
*
* @param ossId 主键
*/
@SaCheckPermission("system:ossTextbook:query")
@GetMapping("/{ossId}")
public R<SysOssTextbookVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long ossId) {
return R.ok(sysOssTextbookService.queryById(ossId));
}
/**
* 新增请填写功能名称
*/
@SaCheckPermission("system:ossTextbook:add")
@Log(title = "【请填写功能名称】", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysOssTextbookBo bo) {
return toAjax(sysOssTextbookService.insertByBo(bo));
}
/**
* 修改请填写功能名称
*/
@SaCheckPermission("system:ossTextbook:edit")
@Log(title = "【请填写功能名称】", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysOssTextbookBo bo) {
return toAjax(sysOssTextbookService.updateByBo(bo));
}
/**
* 删除请填写功能名称
*
* @param ossIds 主键串
*/
@SaCheckPermission("system:ossTextbook:remove")
@Log(title = "【请填写功能名称】", businessType = BusinessType.DELETE)
@DeleteMapping("/{ossIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ossIds) {
return toAjax(sysOssTextbookService.deleteWithValidByIds(List.of(ossIds), true));
}
}

View File

@ -1,37 +0,0 @@
package org.dromara.file.domain;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 请填写功能名称对象 sys_oss_resource
*
* @author Lion Li
* @date 2024-05-28
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_oss_resource")
public class SysOssResource extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
*
*/
@TableId(value = "oss_id")
private Long ossId;
/**
*
*/
@TableId(value = "catalog_id")
private Long catalogId;
}

View File

@ -1,42 +0,0 @@
package org.dromara.file.domain;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 请填写功能名称对象 sys_oss_textbook
*
* @author Lion Li
* @date 2024-05-28
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_oss_textbook")
public class SysOssTextbook extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
*
*/
@TableId(value = "oss_id")
private Long ossId;
/**
*
*/
@TableId(value = "catalog_id")
private Long catalogId;
/**
* 1课件2课堂3作业4试卷
*/
private Integer type;
}

View File

@ -1,42 +0,0 @@
package org.dromara.file.domain.bo;
import org.dromara.file.domain.SysOssTextbook;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
/**
* 请填写功能名称业务对象 sys_oss_textbook
*
* @author Lion Li
* @date 2024-05-28
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = SysOssTextbook.class, reverseConvertGenerate = false)
public class SysOssTextbookBo extends BaseEntity {
/**
*
*/
@NotNull(message = "不能为空", groups = { EditGroup.class })
private Long ossId;
/**
*
*/
@NotNull(message = "不能为空", groups = { EditGroup.class })
private Long catalogId;
/**
* 1课件2课堂3作业4试卷
*/
@NotNull(message = "1课件2课堂3作业4试卷不能为空", groups = { AddGroup.class, EditGroup.class })
private Integer type;
}

View File

@ -1,37 +0,0 @@
package org.dromara.file.domain.vo;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.file.domain.SysOssResource;
import java.io.Serial;
import java.io.Serializable;
/**
* 请填写功能名称视图对象 sys_oss_resource
*
* @author Lion Li
* @date 2024-05-28
*/
@Data
@AutoMapper(target = SysOssResource.class)
public class SysOssResourceVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
*
*/
private Long ossId;
/**
*
*/
private Long catalogId;
}

View File

@ -1,42 +0,0 @@
package org.dromara.file.domain.vo;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.file.domain.SysOssTextbook;
import java.io.Serial;
import java.io.Serializable;
/**
* 请填写功能名称视图对象 sys_oss_textbook
*
* @author Lion Li
* @date 2024-05-28
*/
@Data
@AutoMapper(target = SysOssTextbook.class)
public class SysOssTextbookVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
*
*/
private Long ossId;
/**
*
*/
private Long catalogId;
/**
* 1课件2课堂3作业4试卷
*/
private Integer type;
}

View File

@ -1,16 +0,0 @@
package org.dromara.file.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.file.domain.SysCatalogResource;
import org.dromara.file.domain.vo.SysCatalogResourceVo;
/**
* 目录-专题资源Mapper接口
*
* @author Lion Li
* @date 2024-05-28
*/
public interface SysCatalogResourceMapper extends BaseMapperPlus<SysCatalogResource, SysCatalogResourceVo> {
}

View File

@ -1,16 +0,0 @@
package org.dromara.file.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.file.domain.SysCatalogTextbook;
import org.dromara.file.domain.vo.SysCatalogTextbookVo;
/**
* 目录-同步教材Mapper接口
*
* @author Lion Li
* @date 2024-05-28
*/
public interface SysCatalogTextbookMapper extends BaseMapperPlus<SysCatalogTextbook, SysCatalogTextbookVo> {
}

View File

@ -1,16 +0,0 @@
package org.dromara.file.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.file.domain.SysOssResource;
import org.dromara.file.domain.vo.SysOssResourceVo;
/**
* 请填写功能名称Mapper接口
*
* @author Lion Li
* @date 2024-05-28
*/
public interface SysOssResourceMapper extends BaseMapperPlus<SysOssResource, SysOssResourceVo> {
}

View File

@ -1,16 +0,0 @@
package org.dromara.file.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.file.domain.SysOssTextbook;
import org.dromara.file.domain.vo.SysOssTextbookVo;
/**
* 请填写功能名称Mapper接口
*
* @author Lion Li
* @date 2024-05-28
*/
public interface SysOssTextbookMapper extends BaseMapperPlus<SysOssTextbook, SysOssTextbookVo> {
}

View File

@ -1,59 +0,0 @@
package org.dromara.file.service;
import org.dromara.file.domain.bo.SysCatalogResourceBo;
import org.dromara.file.domain.vo.SysCatalogResourceVo;
import java.util.Collection;
import java.util.List;
/**
* 目录-专题资源Service接口
*
* @author Lion Li
* @date 2024-05-28
*/
public interface ISysCatalogResourceService {
/**
* 查询目录-专题资源
*
* @param catalogId 主键
* @return 目录-专题资源
*/
SysCatalogResourceVo queryById(Long catalogId);
/**
* 查询符合条件的目录-专题资源列表
*
* @param bo 查询条件
* @return 目录-专题资源列表
*/
List<SysCatalogResourceVo> queryList(SysCatalogResourceBo bo);
/**
* 新增目录-专题资源
*
* @param bo 目录-专题资源
* @return 是否新增成功
*/
Boolean insertByBo(SysCatalogResourceBo bo);
/**
* 修改目录-专题资源
*
* @param bo 目录-专题资源
* @return 是否修改成功
*/
Boolean updateByBo(SysCatalogResourceBo bo);
/**
* 校验并批量删除目录-专题资源信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}

View File

@ -1,59 +0,0 @@
package org.dromara.file.service;
import org.dromara.file.domain.bo.SysCatalogTextbookBo;
import org.dromara.file.domain.vo.SysCatalogTextbookVo;
import java.util.Collection;
import java.util.List;
/**
* 目录-同步教材Service接口
*
* @author Lion Li
* @date 2024-05-28
*/
public interface ISysCatalogTextbookService {
/**
* 查询目录-同步教材
*
* @param catalogId 主键
* @return 目录-同步教材
*/
SysCatalogTextbookVo queryById(Long catalogId);
/**
* 查询符合条件的目录-同步教材列表
*
* @param bo 查询条件
* @return 目录-同步教材列表
*/
List<SysCatalogTextbookVo> queryList(SysCatalogTextbookBo bo);
/**
* 新增目录-同步教材
*
* @param bo 目录-同步教材
* @return 是否新增成功
*/
Boolean insertByBo(SysCatalogTextbookBo bo);
/**
* 修改目录-同步教材
*
* @param bo 目录-同步教材
* @return 是否修改成功
*/
Boolean updateByBo(SysCatalogTextbookBo bo);
/**
* 校验并批量删除目录-同步教材信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}

View File

@ -1,117 +0,0 @@
package org.dromara.file.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.file.domain.SysCatalogResource;
import org.dromara.file.domain.bo.SysCatalogResourceBo;
import org.dromara.file.domain.vo.SysCatalogResourceVo;
import org.dromara.file.mapper.SysCatalogResourceMapper;
import org.dromara.file.service.ISysCatalogResourceService;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 目录-专题资源Service业务层处理
*
* @author Lion Li
* @date 2024-05-28
*/
@RequiredArgsConstructor
@Service
public class SysCatalogResourceServiceImpl implements ISysCatalogResourceService {
private final SysCatalogResourceMapper baseMapper;
/**
* 查询目录-专题资源
*
* @param catalogId 主键
* @return 目录-专题资源
*/
@Override
public SysCatalogResourceVo queryById(Long catalogId){
return baseMapper.selectVoById(catalogId);
}
/**
* 查询符合条件的目录-专题资源列表
*
* @param bo 查询条件
* @return 目录-专题资源列表
*/
@Override
public List<SysCatalogResourceVo> queryList(SysCatalogResourceBo bo) {
LambdaQueryWrapper<SysCatalogResource> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<SysCatalogResource> buildQueryWrapper(SysCatalogResourceBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<SysCatalogResource> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getParentId() != null, SysCatalogResource::getParentId, bo.getParentId());
lqw.eq(StringUtils.isNotBlank(bo.getAncestors()), SysCatalogResource::getAncestors, bo.getAncestors());
lqw.like(StringUtils.isNotBlank(bo.getCatalogName()), SysCatalogResource::getCatalogName, bo.getCatalogName());
lqw.eq(bo.getOrderNum() != null, SysCatalogResource::getOrderNum, bo.getOrderNum());
lqw.eq(bo.getCover() != null, SysCatalogResource::getCover, bo.getCover());
return lqw;
}
/**
* 新增目录-专题资源
*
* @param bo 目录-专题资源
* @return 是否新增成功
*/
@Override
public Boolean insertByBo(SysCatalogResourceBo bo) {
SysCatalogResource add = MapstructUtils.convert(bo, SysCatalogResource.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setCatalogId(add.getCatalogId());
}
return flag;
}
/**
* 修改目录-专题资源
*
* @param bo 目录-专题资源
* @return 是否修改成功
*/
@Override
public Boolean updateByBo(SysCatalogResourceBo bo) {
SysCatalogResource update = MapstructUtils.convert(bo, SysCatalogResource.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(SysCatalogResource entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 校验并批量删除目录-专题资源信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
}

View File

@ -1,117 +0,0 @@
package org.dromara.file.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.file.domain.SysCatalogTextbook;
import org.dromara.file.domain.bo.SysCatalogTextbookBo;
import org.dromara.file.domain.vo.SysCatalogTextbookVo;
import org.dromara.file.mapper.SysCatalogTextbookMapper;
import org.dromara.file.service.ISysCatalogTextbookService;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 目录-同步教材Service业务层处理
*
* @author Lion Li
* @date 2024-05-28
*/
@RequiredArgsConstructor
@Service
public class SysCatalogTextbookServiceImpl implements ISysCatalogTextbookService {
private final SysCatalogTextbookMapper baseMapper;
/**
* 查询目录-同步教材
*
* @param catalogId 主键
* @return 目录-同步教材
*/
@Override
public SysCatalogTextbookVo queryById(Long catalogId){
return baseMapper.selectVoById(catalogId);
}
/**
* 查询符合条件的目录-同步教材列表
*
* @param bo 查询条件
* @return 目录-同步教材列表
*/
@Override
public List<SysCatalogTextbookVo> queryList(SysCatalogTextbookBo bo) {
LambdaQueryWrapper<SysCatalogTextbook> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<SysCatalogTextbook> buildQueryWrapper(SysCatalogTextbookBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<SysCatalogTextbook> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getParentId() != null, SysCatalogTextbook::getParentId, bo.getParentId());
lqw.eq(StringUtils.isNotBlank(bo.getAncestors()), SysCatalogTextbook::getAncestors, bo.getAncestors());
lqw.like(StringUtils.isNotBlank(bo.getCatalogName()), SysCatalogTextbook::getCatalogName, bo.getCatalogName());
lqw.eq(bo.getOrderNum() != null, SysCatalogTextbook::getOrderNum, bo.getOrderNum());
lqw.eq(bo.getType() != null, SysCatalogTextbook::getType, bo.getType());
return lqw;
}
/**
* 新增目录-同步教材
*
* @param bo 目录-同步教材
* @return 是否新增成功
*/
@Override
public Boolean insertByBo(SysCatalogTextbookBo bo) {
SysCatalogTextbook add = MapstructUtils.convert(bo, SysCatalogTextbook.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setCatalogId(add.getCatalogId());
}
return flag;
}
/**
* 修改目录-同步教材
*
* @param bo 目录-同步教材
* @return 是否修改成功
*/
@Override
public Boolean updateByBo(SysCatalogTextbookBo bo) {
SysCatalogTextbook update = MapstructUtils.convert(bo, SysCatalogTextbook.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(SysCatalogTextbook entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 校验并批量删除目录-同步教材信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
}

View File

@ -1,127 +0,0 @@
package org.dromara.file.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.utils.MapstructUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.file.domain.SysOssResource;
import org.dromara.file.domain.bo.SysOssResourceBo;
import org.dromara.file.domain.vo.SysOssResourceVo;
import org.dromara.file.mapper.SysOssResourceMapper;
import org.dromara.file.service.ISysOssResourceService;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 请填写功能名称Service业务层处理
*
* @author Lion Li
* @date 2024-05-28
*/
@RequiredArgsConstructor
@Service
public class SysOssResourceServiceImpl implements ISysOssResourceService {
private final SysOssResourceMapper baseMapper;
/**
* 查询请填写功能名称
*
* @param ossId 主键
* @return 请填写功能名称
*/
@Override
public SysOssResourceVo queryById(Long ossId){
return baseMapper.selectVoById(ossId);
}
/**
* 分页查询请填写功能名称列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 请填写功能名称分页列表
*/
@Override
public TableDataInfo<SysOssResourceVo> queryPageList(SysOssResourceBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<SysOssResource> lqw = buildQueryWrapper(bo);
Page<SysOssResourceVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询符合条件的请填写功能名称列表
*
* @param bo 查询条件
* @return 请填写功能名称列表
*/
@Override
public List<SysOssResourceVo> queryList(SysOssResourceBo bo) {
LambdaQueryWrapper<SysOssResource> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<SysOssResource> buildQueryWrapper(SysOssResourceBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<SysOssResource> lqw = Wrappers.lambdaQuery();
return lqw;
}
/**
* 新增请填写功能名称
*
* @param bo 请填写功能名称
* @return 是否新增成功
*/
@Override
public Boolean insertByBo(SysOssResourceBo bo) {
SysOssResource add = MapstructUtils.convert(bo, SysOssResource.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setOssId(add.getOssId());
}
return flag;
}
/**
* 修改请填写功能名称
*
* @param bo 请填写功能名称
* @return 是否修改成功
*/
@Override
public Boolean updateByBo(SysOssResourceBo bo) {
SysOssResource update = MapstructUtils.convert(bo, SysOssResource.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(SysOssResource entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 校验并批量删除请填写功能名称信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
}

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