Compare commits

...

304 Commits

Author SHA1 Message Date
数据小王子
5476f543f3
!10 fix: 修复swagger definition失效,展示全部接口的问题
Merge pull request !10 from objcfeng/cf/fix/springdoc-config
2025-02-24 01:25:10 +00:00
objcfeng
c820bb4e74 fix: 修复swagger definition失效,展示全部接口的问题 2025-02-22 11:07:55 +08:00
数据小王子
d4f30e7de7 mybatis-flex升级:1.9.3 => 1.9.4 2024-07-29 09:52:08 +08:00
数据小王子
384a3f9b92
!9 新增ListenerManager用以任务调度、三方接口回调等可自由控制审计字段
Merge pull request !9 from Ice/master
2024-07-01 01:31:43 +00:00
Ice
1e9abcb3f5 add:ListenerManager,用以任务调度及三方接口回调情况下自由控制审计字段(目前会因拿不到上下文用户信息将自填充的审计字段改为null值) 2024-06-27 18:24:05 +08:00
数据小王子
ee3948d8e3 mybatis-flex升级:1.9.0 => 1.9.3 2024-06-18 11:43:03 +08:00
数据小王子
4117bd6cc5
!8 修复可能导致同步套餐失败的bug
Merge pull request !8 from xiaoduan/修复可能导致同步套餐失败的bug
2024-06-18 03:38:05 +00:00
xuhaoran
0cbeb5b2df 修复可能导致同步套餐失败的bug 2024-06-07 11:09:07 +08:00
数据小王子
335f806566 mybatis-flex升级:1.8.9 => 1.9.0 2024-05-31 09:08:21 +08:00
数据小王子
8655030c7b
!7 pagehelper多数据源自动识别对应方言的分页
Merge pull request !7 from 秦白起/Pull-Request
2024-05-15 09:10:59 +00:00
GeHui
2635d74af5 ⚙️ config(common): pagehelper多数据源自动识别对应方言的分页
1.yaml里的配置是pagehelper-spring-boot-starter
  的,对pagehelper来说没用
2.使用配置类PagehelperConfig

(cherry picked from commit 2303b18a78679a3211f1caf620eb2adbdfd3cedc)
(cherry picked from commit f066ec91bc93f5a81aa7f826d227fb1b4d3fed04)
2024-05-15 15:40:49 +08:00
数据小王子
da91600948 sa-token升级:1.37.0 -> 1.38.0 2024-05-13 16:27:42 +08:00
数据小王子
08cda7b792 mybatis-flex升级:1.8.8 => 1.8.9 2024-05-11 17:52:55 +08:00
数据小王子
a0957a76c1
!6 sql脚本注释规范,避免执行出错
Merge pull request !6 from 齐家/master
2024-05-10 01:19:32 +00:00
slp
fe227e62cd sql注释规范化,否则某些软件执行时不识别会报错 2024-05-09 16:56:22 +08:00
数据小王子
502b692fee OssClient升级 2024-04-30 15:36:52 +08:00
数据小王子
d28e5fadb5 排除定时任务 2024-04-26 16:48:48 +08:00
数据小王子
935da2f093 spring-boot升级:v3.2.4 => 3.2.5 2024-04-21 08:06:16 +08:00
数据小王子
e78c09ddbe mybatis-flex升级:1.8.7 => 1.8.8 2024-04-19 09:20:53 +08:00
数据小王子
ae9c718ddd edit_columns字段添加注释 2024-04-17 09:06:13 +08:00
数据小王子
844aa24b43
!5 代码生成页面的编辑页列数允许多列
Merge pull request !5 from 晨曦/0416
2024-04-16 11:34:16 +00:00
晨曦
d86def6036 编辑页的列数由原先1列调整为多(1-4)列 2024-04-16 18:57:41 +08:00
晨曦
108bd8e7c4 编辑页的列数由原先1列调整为多(1-4)列 2024-04-16 17:39:27 +08:00
晨曦
c8738f6eb0 编辑页的列数由原先1列调整为多(1-4)列 2024-04-16 15:21:43 +08:00
晨曦
6174ca90e1 编辑页的列数由原先1列调整为多(1-4)列 2024-04-16 15:20:55 +08:00
数据小王子
3d7fd649fd 代码生成器增加导入功能 2024-04-12 21:33:42 +08:00
数据小王子
4224ffdc64 演示模块增加导入功能 2024-04-12 21:27:02 +08:00
数据小王子
0b3f82660f 更新基类TreeEntity 2024-04-12 21:17:53 +08:00
数据小王子
8230404035 树表中增加ancestors字段 2024-04-12 21:14:12 +08:00
数据小王子
4c5bb90361 字典类型添加排序字段 2024-04-11 11:55:19 +08:00
数据小王子
15b8251601 对type、status、gender:代码生成器不再自动为HtmlType赋予默认值,让用户自己选择 2024-04-10 17:00:11 +08:00
数据小王子
0e86f9a111 mybatis-flex升级:1.8.6 => 1.8.7 2024-04-10 16:43:29 +08:00
数据小王子
5de6e70ca1 更新开发文档 2024-04-09 10:48:56 +08:00
数据小王子
ee3272918b 引入另一个任务调度框架:EasyRetry 2024-04-08 21:08:10 +08:00
数据小王子
2219f02a17
!3 针对加密和docker部署
Merge pull request !3 from Duke_yzl/master
2024-04-08 12:17:08 +00:00
yuzl6
4606755e42 复原 2024-04-08 18:39:12 +08:00
yuzl6
d057d654fa 测试生产bug 2024-04-08 17:59:03 +08:00
数据小王子
8263a252f1 分离PowerJob的sql成单独文件 2024-04-08 14:12:02 +08:00
yuzl6
2c98a64d17 Merge remote-tracking branch 'origin/master' 2024-04-08 10:51:49 +08:00
yuzl6
2c721c0a09 增加请求动态加密,docker部署(适配流水线方式推送镜像) 2024-04-08 10:51:31 +08:00
yuzl6
8555e6d684 限制内存使用大小003 2024-04-08 10:35:07 +08:00
yuzl6
70d2864488 限制内存使用大小003 2024-04-08 10:15:18 +08:00
yuzl6
6517cdd084 限制内存使用大小002 2024-04-08 09:59:29 +08:00
yuzl6
226d18b627 限制内存使用大小001 2024-04-08 09:33:03 +08:00
yuzl6
0d91f15061 限制内存使用大小001 2024-04-08 09:14:55 +08:00
yuzl6
54b134e942 限制内存使用大小 2024-04-08 09:08:29 +08:00
yuzl6
a53709481c 部署文件 2024-04-07 23:33:43 +08:00
yuzl6
7a67dce923 部署文件 2024-04-07 20:57:27 +08:00
yuzl6
048fb6abcb 部署文件 2024-04-07 00:05:57 +08:00
yuzl6
3573a71af3 部署文件 2024-04-06 23:59:03 +08:00
yuzl6
41ea876a96 部署文件 2024-04-06 23:58:27 +08:00
yuzl6
d494c551d5 部署文件 2024-04-06 20:03:08 +08:00
yuzl6
445ef5763d 部署文件 2024-04-06 17:08:40 +08:00
yuzl6
92b3efb87d 部署文件 2024-04-06 15:36:38 +08:00
yuzl6
b07033ba51 部署文件 2024-04-06 15:25:47 +08:00
yuzl6
fad4f8e19e 部署文件 2024-04-06 15:18:52 +08:00
yuzl6
0b99d54edf 修改发布环境 2024-04-06 14:53:21 +08:00
yuzl6
596cf10a1c 增加请求加密 动态获取密钥 2024-04-06 14:31:25 +08:00
数据小王子
659caa1d0e
!2 修复接口文档访问错误的BUG
Merge pull request !2 from Curtion/master
2024-04-05 03:44:48 +00:00
Curtion
4358633176 修复接口文档访问错误的BUG 2024-04-04 19:00:14 +08:00
数据小王子
9660f65dad mybatis-flex升级:1.8.5 => 1.8.6 2024-04-03 08:38:17 +08:00
数据小王子
f57b1092e4 mybatis-flex升级:1.8.2 => 1.8.5 2024-04-02 08:50:17 +08:00
数据小王子
00e3ee95c3 依赖更新:
update springboot 3.2.3 => 3.2.4
update springdoc 2.3.0 => 2.4.0
update springboot-admin 3.2.2 => 3.2.3
update redisson 3.27.0 => 3.27.2
update sms4j 3.1.1 => 3.2.0
update hutool 5.8.26 => 5.8.27
2024-04-01 08:58:13 +08:00
数据小王子
e5b182a8ef 更新ip离线数据库 2024-04-01 08:57:33 +08:00
数据小王子
458f60be07 代码生成:新增导入菜单SQL脚本 2024-03-30 17:04:20 +08:00
数据小王子
32fd0311b3 更新AWS SDK 版本到2.25.15 2024-03-25 09:07:24 +08:00
数据小王子
d60ca890a2 代码生成:完善ts前端单表生成vue页面 2024-03-25 09:02:22 +08:00
数据小王子
9d6fc4f539 代码生成器ts前端:树表增加导出按钮 2024-03-22 21:18:55 +08:00
数据小王子
f3b3bec8b0 完善代码生成器ts前端类型文件 2024-03-22 21:08:15 +08:00
数据小王子
bcd06f3d2b 数据库脚本:超级管理员的role_key由admin修改为SuperAdminRole 2024-03-12 11:15:23 +08:00
数据小王子
f8c699ebbd 数据库脚本更新:超级管理员的role_key由admin修改为SuperAdminRole 2024-03-12 11:12:50 +08:00
数据小王子
c91f9686d7 update 更新 mybatis 多包扫描配置 2024-03-06 10:41:21 +08:00
数据小王子
7fb5c26cee 依赖升级:spring-boot-admin 3.2.1 => 3.2.2 2024-03-06 10:35:12 +08:00
数据小王子
c1fb7b4484 优化 AsyncConfig 虚拟线程名称支持 2024-03-06 10:31:03 +08:00
数据小王子
918d67abef redisson支持虚拟线程 2024-03-06 10:27:43 +08:00
数据小王子
a2e1acb66c 修复 空指针null问题 2024-03-06 10:23:22 +08:00
数据小王子
050a48b134 优化 mybatis依赖设置为可选依赖 避免出现不应该注入的情况 2024-03-06 10:20:24 +08:00
数据小王子
b8ab413eab 优化RateLimiter注解使用体验 2024-03-06 10:14:00 +08:00
数据小王子
7c649bb5b2 依赖升级:mybatis-flex 1.8.1 => 1.8.2 2024-03-06 10:07:21 +08:00
数据小王子
c9cb308abf 优化 GET 方法响应体支持加密 2024-03-06 09:29:10 +08:00
数据小王子
0974e65c0a 删除观测用日志记录 2024-03-06 09:01:26 +08:00
数据小王子
fa75c7155a 新增:用户、部门、角色、岗位 下拉选接口与代码实现优化 2024-03-06 08:53:11 +08:00
数据小王子
2c180c89a9 依赖升级:mybatis-flex 1.8.0 => 1.8.1 2024-03-04 11:04:42 +08:00
数据小王子
f31983c2c7 修复 excel 表达式字典 下拉框导出格式错误 2024-03-01 15:34:44 +08:00
数据小王子
62c8e1c877 优化 OssFactory,采用双重校验锁 2024-03-01 15:29:44 +08:00
数据小王子
3bedaa7ddc 优化代码格式 2024-03-01 10:31:39 +08:00
数据小王子
e7b6dac49f 优化 登录消息 支持集群发送 2024-03-01 10:00:56 +08:00
数据小王子
d151c053a7 升级 awsS3 到2.X版本 支持异步与自动分片上传下载 2024-02-29 14:15:17 +08:00
数据小王子
fc2eab2a6d 修改登录问题 2024-02-29 14:09:15 +08:00
数据小王子
e28a2a404e 新增 正则工具类 字符串提取 字符串校验 2024-02-29 11:05:33 +08:00
数据小王子
773e257fa1 增加 SpringUtils.isVirtual 方法 2024-02-29 10:30:11 +08:00
数据小王子
651da9a92a 修复 类型判断问题 2024-02-29 10:16:58 +08:00
数据小王子
e5ca734018 修改数据库id_token字段宽度 2024-02-29 10:01:07 +08:00
数据小王子
5b131ad3fd fix 修复 用户登录查询部门缓存无法获取租户id问题 2024-02-28 14:54:56 +08:00
数据小王子
fe848f418a fix: LoginHelper类 login方法 存在 重复代码 2024-02-28 14:52:31 +08:00
数据小王子
6d484c0bda 引入caffeine本地缓存 2024-02-28 14:40:14 +08:00
数据小王子
f366e430f1 依赖升级:poi 5.2.3 => 5.2.5 2024-02-28 09:10:53 +08:00
数据小王子
7e9ba78376 Merge remote-tracking branch 'origin/master' 2024-02-27 17:32:10 +08:00
数据小王子
5a814b947c 重构登录日志 2024-02-27 17:31:16 +08:00
数据小王子
0af60f5ac5
!1 update script/sql/postgresql/postgresql-ruoyiflex-V5.X.sql.
Merge pull request !1 from yuzhihaomy/N/A
2024-02-27 09:15:56 +00:00
yuzhihaomy
6b8f3e2ed1
update script/sql/postgresql/postgresql-ruoyiflex-V5.X.sql.
793行sql少了个逗号

Signed-off-by: yuzhihaomy <17150515155@163.com>
2024-02-27 09:09:20 +00:00
数据小王子
3f829e271d 解决 token与token-session 过期时间不一致问题 2024-02-27 10:48:08 +08:00
数据小王子
8027f5c2d8 调整transmittable-thread-local依赖位置 2024-02-26 17:31:53 +08:00
数据小王子
29a620eb41 修复 部门树排序问题 2024-02-26 17:24:56 +08:00
数据小王子
885918d11f 依赖HikariCP连接池:5.0.1 => 5.1.0 2024-02-26 11:27:25 +08:00
数据小王子
6d31a7107b 依赖升级:mybatis-flex 1.7.9 => 1.8.0 2024-02-26 09:19:11 +08:00
数据小王子
29ec129069 优化代码生成提交数据提示语 2024-02-23 16:17:07 +08:00
数据小王子
3f3f3d793a 依赖升级:spring-boot 3.2.2 => 3.2.3
hutool 5.8.25 => 5.8.26
   redisson 3.26.0 => 3.27.0
2024-02-23 15:39:39 +08:00
数据小王子
2b7ba78eb1 修改版本号:V5.2.0-SNAPSHOT 2024-02-23 15:33:22 +08:00
数据小王子
baf6140159 修改版本号为5.1.0 2024-02-08 19:35:35 +08:00
数据小王子
ba11dd8927 默认租户的tenant_id由0修改为1 2024-02-07 11:57:10 +08:00
数据小王子
3734a6ef23 数据库内容修改:默认租户的tenant_id由0修改为1 2024-02-07 11:54:27 +08:00
数据小王子
a601c8c54c 修复租户套餐管理无法删除的错误 2024-02-06 14:49:45 +08:00
数据小王子
9e4171f4ad 用户服务用Relations注解查询取代Join查询 2024-02-06 14:48:25 +08:00
dataprince
254dfe383b 升级mybatis-flex依赖:1.7.8 => 1.7.9 2024-02-04 14:50:03 +08:00
dataprince
a2c31048cb 添加spring-boot-starter-validation依赖 2024-02-02 21:42:26 +08:00
dataprince
fad44cc117 升级mybatis-flex依赖:1.7.7 => 1.7.8 2024-02-02 14:46:06 +08:00
dataprince
e0e190671f 修正无法修改租户的问题 2024-02-01 21:03:12 +08:00
dataprince
5e8a8f6cd9 代码生成模块导入子表类型视图对象 2024-02-01 21:01:54 +08:00
dataprince
09157fe27f 更改子表类型 2024-01-31 19:33:33 +08:00
dataprince
1183249080 代码生成支持:主子表(element-ts版本) 2024-01-31 15:43:21 +08:00
dataprince
ee7ef99c36 增加离线ip库 2024-01-29 16:49:41 +08:00
dataprince
3ae17b0657 集成JustAuth第三方登录组件 2024-01-29 15:56:48 +08:00
dataprince
84be51d691 数据库增加sys_social表 2024-01-29 14:41:27 +08:00
dataprince
e6b877366e 修正代码生成无法修改的错误 2024-01-29 14:20:43 +08:00
dataprince
e112a7b5fe 集成sms4j短信聚合框架 2024-01-27 10:57:32 +08:00
dataprince
677e5e6168 增加ruoyi-common-mail模块 2024-01-26 09:09:09 +08:00
dataprince
da582764a7 代码生成支持乐观锁、多UI前端 2024-01-25 11:48:45 +08:00
dataprince
4ee432bfee 数据库结构更新:子表mf_goods添加乐观锁字段 2024-01-25 11:41:04 +08:00
dataprince
eda8a2d33b 升级依赖:springboot 3.2.1 => 3.2.2、springboot-admin 3.2.0 => 3.2.1 2024-01-22 14:18:18 +08:00
dataprince
1d0399b9b0 增加findbugs js305依赖,消除打包警告 2024-01-22 11:35:53 +08:00
dataprince
b0204c224c 租户管理支持乐观锁 2024-01-19 21:34:11 +08:00
dataprince
ee8f101807 updateConfigByKey支持乐观锁 2024-01-19 11:40:11 +08:00
dataprince
6a6bf19db5 文档添加插件Maven Project Version描述 2024-01-18 17:26:25 +08:00
dataprince
32ef43f198 修正R.fail忘记添加return的bug 2024-01-18 17:22:06 +08:00
dataprince
b06857e8c8 通知公告模块使用websocket推送消息 2024-01-17 20:24:48 +08:00
dataprince
74853f4d9c 增加websocket模块 2024-01-17 20:24:09 +08:00
dataprince
17eb0212fd 修正修改PostgreSQL数据库结构:由integer修改为text类型。 2024-01-17 09:48:06 +08:00
dataprince
52ea5f2e2a 个人中心修改密码使用密文传输 2024-01-16 10:17:18 +08:00
dataprince
8f1ce9a5df 完善API加解密 2024-01-16 08:49:04 +08:00
dataprince
2868e20c86 升级依赖mapstruct-plus:1.3.5——>1.3.6 2024-01-16 08:47:07 +08:00
dataprince
54d4b8bf16 升级依赖pagehelper:5.3.3——>6.1.0 2024-01-15 11:10:14 +08:00
dataprince
14740e817e 删除废弃TaskExecutorBuilder方法 2024-01-15 09:44:05 +08:00
dataprince
71d1d7bd77 启用虚拟线程后,不再需要原来的线程池功能了 2024-01-14 10:35:31 +08:00
dataprince
c39403a0d2 Revert "web容器从undertow切换到tomcat,更好地支持虚拟线程"
This reverts commit 9c8de8afbf.
2024-01-14 10:22:02 +08:00
dataprince
9c8de8afbf web容器从undertow切换到tomcat,更好地支持虚拟线程 2024-01-14 10:19:30 +08:00
dataprince
a84a3c6049 web容器从undertow切换到tomcat,更好地支持虚拟线程 2024-01-14 08:54:22 +08:00
dataprince
9f4991f32f 打印SQL配置参数化 2024-01-13 08:56:05 +08:00
dataprince
d09b65c396 yml配置文件添加swagger-ui的版本号配置 2024-01-12 16:59:12 +08:00
dataprince
9b1602649e 去掉@MapperScan配置 2024-01-12 16:39:12 +08:00
dataprince
ec9ff68567 完善文档,增加“演示模块”说明 2024-01-12 09:30:40 +08:00
dataprince
58c62ee6b4 升级springdoc依赖到V2.3.0 2024-01-11 11:08:55 +08:00
dataprince
0993d1abf3 更改版本号为V5.0.0 2024-01-10 10:53:12 +08:00
dataprince
52d7c80683 Ruoyi-FlexV5的MySQL数据库完整脚本 2024-01-10 09:51:45 +08:00
dataprince
815da0f1fa Ruoyi-FlexV5的PosgtgreSQL数据库完整脚本 2024-01-09 20:50:11 +08:00
dataprince
578e2ae583 修正部门排序字段错误 2024-01-09 20:47:05 +08:00
dataprince
f72cfbcb3b 更新文档 2024-01-08 17:27:58 +08:00
dataprince
fe75716c7f 乐观锁、逻辑删除功能 2024-01-08 16:03:01 +08:00
dataprince
f3e2afcaa3 乐观锁、逻辑删除功能 2024-01-08 16:01:31 +08:00
dataprince
b0d0aefde8 乐观锁、逻辑删除MySQL数据库升级脚本 2024-01-08 15:52:49 +08:00
dataprince
741b4a842e 乐观锁、逻辑删除PostgreSQL数据库升级脚本 2024-01-08 15:51:13 +08:00
dataprince
f2cad965fd 启用JAVA21虚拟线程功能 2024-01-06 10:06:05 +08:00
dataprince
7a4ef2efdd 依赖版本升级 2024-01-06 09:40:14 +08:00
dataprince
967427c8bc 升级spring-boot依赖到V3.2.1 2024-01-05 14:27:42 +08:00
dataprince
55c8c842dc 升级mybatis-flex依赖到1.7.7,去掉mybatis-spring依赖 2024-01-05 11:43:12 +08:00
dataprince
071ba49c92 代码生成模块支持多租户 2024-01-05 11:06:45 +08:00
dataprince
95599f3350 MySQL数据库多租户功能V4.2.0 to V5.0.0升级脚本 2024-01-04 15:24:56 +08:00
dataprince
714c371ee2 校验租户用户数量 2024-01-04 11:23:00 +08:00
dataprince
873d809df5 完善PostgreSQL数据库多租户功能升级脚本 2024-01-03 21:33:53 +08:00
dataprince
bfe02a50ac PostgreSQL数据库多租户功能V4.2.0 to V5.0.0升级脚本 2024-01-03 17:30:54 +08:00
dataprince
5a1c99cfd5 其它针对租户功能的修改 2024-01-03 17:20:23 +08:00
dataprince
4946f2f45f 增加“租户套餐管理”模块 2024-01-03 17:17:27 +08:00
dataprince
a862b258fc 增加“租户管理”模块 2024-01-03 17:11:45 +08:00
dataprince
a2e904d1f2 添加"客户端管理"模块 2024-01-02 19:50:08 +08:00
dataprince
6e9d42088b 完善“ruoyi-common-tenant“模块 2024-01-01 15:20:26 +08:00
dataprince
617cf85209 添加“ruoyi-common-encrypt”加密模块 2023-12-29 15:19:14 +08:00
dataprince
c14f8d098f 完善ruoyi-common-tenant模块 2023-12-27 10:47:35 +08:00
dataprince
a03742f481 postgresql创建于与mysql等效的的find_in_set函数 2023-12-26 10:26:37 +08:00
dataprince
3693ee1a35 操作日志添加查询条件 2023-12-26 10:25:41 +08:00
dataprince
dd3349bf2e 同步ruoyi-vue-plus的2023-11-17至2023-12-23的更新
(1)fix 修复 OssFactory 并发多创建实例问题
   (2)update 优化 删除无用接口实现
   (3)!454 添加excel多sheet页导出
   (4)update 优化 重构 LoginHelper 将本地存储代码操作封装
   (5)update 优化 删除无用依赖
   (6)update 优化 删除无用注解
   (7)update 优化 更新用户异常提示 使用登录账号
   (8)fix 修复 token 失效后 登录获取用户null问题
   (9)fix 修复 session 多账号共用覆盖问题 改为 tokenSession 独立存储
   (10)add 新增 RedisUtils.setObjectIfExists 如果存在则设置方法
   (11)update transmittable-thread-local 2.14.2 => 2.14.4
   (12)update 优化 删除 hikaricp 官方不推荐使用的配置 jdbc4 协议自带校验方法
   (13)update 优化 开启 redisson 脚本缓存 减少网络传输
   (14)升级依赖update easyexcel 3.3.2 => 3.3.3
     update springboot-admin 3.1.7 => 3.1.8
     update aws-java-sdk-s3 1.12.540 => 1.12.600
   (15)细化oss配置管理权限控制,需要执行更新数据库脚本update.sql
   (16)update 优化 将 OSS配置 改为全局模式
   (17)fix 修复 excel合并注解会根据第一合并列的结果来决定后续的列合并
   (18)fix 修复 MybatisSystemException 空指针问题
   (19)update 优化 删除无用异常类
   (20)update 优化 验证码接口 增加限流配置
   (21)update 优化 丰富RedisUtils对List Set类型的操作
2023-12-25 16:20:32 +08:00
dataprince
64685e74f9 同步ruoyi-vue-plus的2023-11-17至2023-11-23的更新
(1)fix 修复 OssFactory 并发多创建实例问题
   (2)update 优化 删除无用接口实现
   (3)!454 添加excel多sheet页导出
   (4)update 优化 重构 LoginHelper 将本地存储代码操作封装
   (5)update 优化 删除无用依赖
   (6)update 优化 删除无用注解
   (7)update 优化 更新用户异常提示 使用登录账号
   (8)fix 修复 token 失效后 登录获取用户null问题
   (9)fix 修复 session 多账号共用覆盖问题 改为 tokenSession 独立存储
   (10)add 新增 RedisUtils.setObjectIfExists 如果存在则设置方法
   (11)update transmittable-thread-local 2.14.2 => 2.14.4
   (12)update 优化 删除 hikaricp 官方不推荐使用的配置 jdbc4 协议自带校验方法
   (13)update 优化 开启 redisson 脚本缓存 减少网络传输
   (14)升级依赖update easyexcel 3.3.2 => 3.3.3
     update springboot-admin 3.1.7 => 3.1.8
     update aws-java-sdk-s3 1.12.540 => 1.12.600
   (15)细化oss配置管理权限控制,需要执行更新数据库脚本update.sql
   (16)update 优化 将 OSS配置 改为全局模式
   (17)fix 修复 excel合并注解会根据第一合并列的结果来决定后续的列合并
   (18)fix 修复 MybatisSystemException 空指针问题
   (19)update 优化 删除无用异常类
   (20)update 优化 验证码接口 增加限流配置
   (21)update 优化 丰富RedisUtils对List Set类型的操作
2023-12-25 16:16:02 +08:00
dataprince
64fea077c2 简化代码,对于QueryWrapper的操作,不再重复判断条件! 2023-12-24 21:07:51 +08:00
dataprince
c8643dc779 升级依赖Mybatis-Flex到V1.7.6版本 2023-12-24 21:06:29 +08:00
dataprince
abef9c503f 修改powerjob的库结构的错误oid类型为text类型 2023-12-23 17:21:40 +08:00
dataprince
02c55cf5df 更改sql文件夹位置 2023-12-23 17:19:49 +08:00
dataprince
ed30212ddc 使用Java21的新特性:分代ZGC 2023-12-23 12:29:45 +08:00
dataprince
cf81b1f67c 修改yml文件,大部分配置代码放到参数文件中 2023-12-22 16:38:35 +08:00
dataprince
3786e6f078 修正“任务调度”的“任务管理”错误,需要执行udate.sql脚本 2023-12-22 10:51:02 +08:00
dataprince
3d3d17066c 修改版本号:V4.2.0 2023-12-21 08:16:05 +08:00
dataprince
703e3c2b07 powerjob配置参数新增默认PostgreSQL数据源 2023-12-21 08:14:43 +08:00
dataprince
5e8fec31df 更新文档,添加支持PostgreSQL数据库 2023-12-21 08:13:51 +08:00
dataprince
9f28779390 支持PostgreSQL数据库 2023-12-20 19:44:47 +08:00
dataprince
f546a47c61 增加多数据源演示(学生信息表的服务selectPage方法),默认关闭 2023-12-20 14:29:26 +08:00
dataprince
ea1411246e 为方便入门,数据库登录用户、密码不再加密! 2023-12-19 16:32:21 +08:00
dataprince
95e54ad11a 修改delFlag属性为Integer类型 2023-12-19 16:10:22 +08:00
dataprince
7bde7c05bc 修改mysql数据库表的del_flag字段为smallint类型 2023-12-19 16:06:49 +08:00
dataprince
3b804032dc 升级依赖Redisson到V3.25.1,改进 JDK21 虚拟线程兼容性 2023-12-19 09:49:49 +08:00
dataprince
5a72eb7f49 代码生成模块重构 2023-12-18 11:36:48 +08:00
dataprince
751545f4be 代码生成模块数据库表去掉主键自增 2023-12-18 11:33:43 +08:00
dataprince
3c0ea248bc 更新文档 2023-12-16 19:08:32 +08:00
dataprince
7525384034 代码生成更新:生成的sql文件去掉主键自增 2023-12-15 11:48:07 +08:00
dataprince
1b184ffc63 重构menu菜单模块,去掉xml中的sql语句 2023-12-15 11:44:17 +08:00
dataprince
4e7d0af153 sys_menu菜单表结构修改、去掉主键自增 2023-12-15 11:41:13 +08:00
dataprince
ff9d4d61d6 修改数据库名称:由ry-vue修改为ruoyi-flex 2023-12-13 21:52:16 +08:00
dataprince
d9f8eeef36 readme增加“系统特色” 2023-12-12 16:09:24 +08:00
dataprince
ea43076161 增加演示图片 2023-12-11 17:16:47 +08:00
dataprince
e612b206ea 删除弃用的flex-ui前端文件夹 2023-12-11 09:17:17 +08:00
dataprince
0f3c6924fa 更新文档,修改minio、前端独立仓库部分 2023-12-11 09:15:22 +08:00
dataprince
154bc3e187 修正文件管理两个bug:不显示“上传人”的问题、图片无法预览问题 2023-12-11 09:05:20 +08:00
dataprince
731b5e60b2 前后端仓库分离 2023-12-10 11:13:07 +08:00
dataprince
c91a5189af springboot升级到V3.2.0 2023-12-10 08:55:54 +08:00
dataprince
a8e0c3df32 更新文档,修改JDK21部分篇幅 2023-12-09 12:44:42 +08:00
dataprince
13d14fea18 更新jdk到21 2023-12-08 17:02:45 +08:00
dataprince
2d0f571fab springboot升级到V3.1.6 2023-12-08 09:06:58 +08:00
dataprince
653aab3b1e 修改版本号为V4.1.8 2023-12-07 15:22:25 +08:00
dataprince
8a3b9784f5 修正“演示模块”的“路由地址”重复的问题 2023-12-07 14:44:45 +08:00
dataprince
83d968e621 1、同步ruoyi-vue-plus的2023-10-25至2023-11-14的更新
1)update 优化 排除powerjob无用的依赖 减少打包30M体积
   2)fix 修复 代码生成 是否必填与数据库不匹配问题
   3)update 优化 补全操作日志部门数据
   4)update 优化 AddressUtils 兼容linux系统本地ip
   5)fix 修复 普通角色编辑使用内置管理员code越权问题
   6)update 优化 补全代码生成 columnList 接口参数注解缺失
   7)fix 修复 外链带端口出现的异常
   8)update 优化 更改默认日志等级为info 避免日志过多(按需开启debug)
2、同步RuoYi-Vue的2023-10-24到2023-12-5的更新:
   1)update ruoyi-ui/src/permission.js
   2)优化数字金额大写转换精度丢失问题
   3)修复字典表详情页面搜索bug
   4)修复五级路由缓存无效问题
   5)优化缓存监控图表支持跟随屏幕大小自适应调整
   6)update fastjson2
   7)升级oshi到最新版本6.4.8
2023-12-07 09:57:36 +08:00
dataprince
47ed943d61 添加:主子表演示程序,支持mybatis-flex 2023-12-06 11:20:05 +08:00
dataprince
5a9e023985 添加:主子表演示后台数据库结构,支持mybatis-flex 2023-12-06 11:18:15 +08:00
dataprince
968e26c593 完成主子表“代码生成”模块重构,支持mybatis-flex 2023-12-06 11:16:59 +08:00
dataprince
21c040b74e 优化代码生成器的辅助类名生成方式 2023-12-04 16:06:20 +08:00
dataprince
930d5aaa70 树选择框由tree-select升级为el-tree-select 2023-12-03 14:39:49 +08:00
dataprince
afd95d2499 删除ruoyi-common-translation模块 2023-12-03 14:37:14 +08:00
dataprince
41cfdd4f74 通知公告模块“创建者”显示使用用户表的用户名称 2023-12-03 14:34:43 +08:00
dataprince
48043d1261 mybatis-flex升级到V1.7.5版本 2023-12-03 14:31:54 +08:00
dataprince
478c128ba2 使用MinIO存储个人头像 2023-12-03 09:40:49 +08:00
dataprince
c646c95c7b 增加“文件管理”模块,整合MinIO 2023-12-03 09:35:28 +08:00
dataprince
36854b8ea8 增加“文件管理”模块后台数据库结构 2023-12-02 22:20:33 +08:00
dataprince
713b370d42 数据字典根据DICT_SORT升序排列 2023-11-29 09:53:46 +08:00
dataprince
7e4fb37ab6 部门树列表添加数据权限条件过滤 2023-11-27 20:47:05 +08:00
dataprince
568acfda82 修改prod.yml配置文件 2023-11-26 19:15:28 +08:00
dataprince
5e2c826595 添加:树表演示程序,支持mybatis-flex 2023-11-23 11:18:21 +08:00
dataprince
c788f88216 添加:树表演示后台数据库结构,支持mybatis-flex 2023-11-23 11:17:33 +08:00
dataprince
630881c1d7 完成树表重构“代码生成”模块,支持mybatis-flex 2023-11-23 11:16:35 +08:00
dataprince
faebcb7a6a 添加:单表演示程序,支持mybatis-flex 2023-11-22 22:25:17 +08:00
dataprince
f34c143499 添加:单表演示后台数据库结构,支持mybatis-flex 2023-11-22 22:23:54 +08:00
dataprince
82f1600d39 完成单表重构“代码生成”模块,支持mybatis-flex 2023-11-22 22:22:19 +08:00
dataprince
bd4b1720b7 修改文档 2023-11-21 21:36:22 +08:00
dataprince
0bfaed7b67 升级前端文件 2023-11-21 21:26:10 +08:00
dataprince
b2d8576f30 升级前端,状态管理由vuex切换到pinia 2023-11-21 21:21:55 +08:00
dataprince
57c7b3aaeb 完成数据权限重构 2023-11-21 21:17:21 +08:00
dataprince
61af7b3106 升级mybatis-flex到v1.7.4版本 2023-11-21 15:29:31 +08:00
dataprince
cad6506ac8 完善菜单管理页面,修正“菜单图标”画面显示问题 2023-11-17 09:27:14 +08:00
dataprince
ec3eae7bae 完善分配角色前台界面 2023-11-16 10:24:03 +08:00
dataprince
02f2354ee7 完善个人中心模块前台界面 2023-11-15 11:44:56 +08:00
dataprince
249913a2a9 mybatis-flex升级到V1.7.3 2023-10-27 22:41:57 +08:00
dataprince
81ed861819 升级用户导入:加入部门名称 2023-10-26 17:28:16 +08:00
dataprince
29d47ab642 登录不做数据重复提交验证 2023-10-26 11:01:30 +08:00
dataprince
1f5c673d63 升级依赖:oshi到6.4.6、fastjson到2.0.41 2023-10-26 10:53:56 +08:00
dataprince
1db7b15aab BO类去掉@NotNull校验的分组信息 2023-10-26 09:31:30 +08:00
dataprince
716c422a27 BO类去掉在ID字段上的@NotNull校验 2023-10-26 09:10:09 +08:00
dataprince
f2a3dfa070 前端界面文件更新 2023-10-22 10:00:40 +08:00
dataprince
3663f0ea78 同步RuoYi-Vue的2023-8-25到2023-10-9代码 2023-10-21 15:15:38 +08:00
dataprince
f6d699ce2e Spring Boot升级到V3.1.5、redisson升级到V3.23.5 2023-10-21 11:57:32 +08:00
dataprince
a81387422f 数据库增加授权类型等字典数据 2023-10-21 11:56:34 +08:00
dataprince
92189d68c8 操作日志列表新增IP地址查询 2023-10-21 11:54:15 +08:00
dataprince
f6626d7a30 使用mybatis-flex重构“操作日志”模块代码 2023-10-20 20:58:28 +08:00
dataprince
7b0f3591f0 操作日志记录表sys_oper_log去掉自增主键,使用雪花算法 2023-10-20 20:53:12 +08:00
dataprince
a36301fecb 使用mybatis-flex重构“登录日志”模块代码 2023-10-19 21:03:40 +08:00
dataprince
6966236b61 登录日志sys_logininfor去掉自增主键,使用雪花算法 2023-10-19 20:59:06 +08:00
dataprince
e4366f4d53 升级mybatis-flex到V1.7.2、sa-token到V1.37.0 2023-10-18 16:36:42 +08:00
dataprince
bf3f62727f 重构用户表的user_type登录设备类型字段前台界面 2023-10-16 20:26:02 +08:00
dataprince
3354392524 修改用户表的user_type字段结构 2023-10-16 20:23:34 +08:00
dataprince
1fe08a0945 使用mybatis-flex重构“用户管理”模块代码 2023-10-16 19:26:34 +08:00
dataprince
5e147e2f88 数据库用户表结构去掉自增主键:sys_user 2023-10-16 19:20:50 +08:00
dataprince
a17df59f45 5个模块重构保存代码:取消自增主键,使用雪花算法 2023-10-01 11:26:54 +08:00
dataprince
c23d23159a 数据库表结构去掉自增主键:sys_config、sys_notice、sys_post、sys_dict_type、sys_dict_data、sys_dept 2023-10-01 11:23:08 +08:00
dataprince
50da2f8224 使用mybatis-flex重构“角色管理”模块代码 2023-09-30 21:31:21 +08:00
dataprince
39215aaded 数据库角色表取消自增主键,使用雪花算法 2023-09-30 21:28:42 +08:00
dataprince
37183ce262 重构IBaseService,加入批量更新功能 2023-09-30 11:00:17 +08:00
dataprince
891ef9bb09 重构分页查询代码 2023-09-29 08:08:54 +08:00
dataprince
d413baeb98 依赖sa-token升级:1.35.0.RC => 1.36.0 2023-09-28 22:08:54 +08:00
dataprince
6b2bb7fcbe 依赖升级:hutool 5.8.21 => 5.8.22,spring-boot-admin 3.1.5 => 3.1.6, lombok 1.18.28 => 1.18.30 2023-09-27 08:51:03 +08:00
dataprince
a0ca1e9eb8 升级mybatis-flex到V1.6.7 2023-09-26 09:21:09 +08:00
dataprince
107c67f10e 删除注释的不再使用代码 2023-09-25 20:41:36 +08:00
dataprince
5285591fc5 使用mybatis-flex重构“部门管理”模块代码 2023-09-25 20:36:10 +08:00
dataprince
a73f311afc 升级数据库表结构 2023-09-25 20:31:42 +08:00
dataprince
e6679d97eb sa-token回退到V1.35.0.RC 2023-09-24 13:10:44 +08:00
dataprince
0908f93146 升级依赖版本:spring boot升级到V3.1.4,mybatis-flex升级到V1.6.6,sa-token升级到V1.36.0 2023-09-23 19:05:40 +08:00
dataprince
2ea8b0b944 升级powerjob依赖 2023-09-22 08:26:44 +08:00
dataprince
89e8ec72c7 使用mybatis-flex重构“菜单管理”模块代码 2023-09-21 11:40:53 +08:00
dataprince
7a4a0d926d 使用mybatis-flex重构“字典管理”模块代码 2023-09-20 10:35:05 +08:00
dataprince
5212d4fb68 修改字典"sys_user_sex"为"sys_user_gender" 2023-09-20 10:32:43 +08:00
dataprince
a36b4df86b 构建基类服务查询条件 2023-09-19 21:31:51 +08:00
dataprince
cf6c040eee 升级文档及数据库脚本 2023-09-18 20:27:20 +08:00
dataprince
39c3a181e2 使用mybatis-flex重构各模块代码:岗位管理 2023-09-18 11:20:02 +08:00
dataprince
82988f3385 使用mybatis-flex重构各模块代码:岗位管理 2023-09-18 11:19:08 +08:00
dataprince
42a3285e8a powerjob升级到4.3.6 2023-09-18 09:50:27 +08:00
dataprince
499642c0dc 新增保姆级开发文档:《Ruoyi-Flex-Guide.docx》 2023-09-17 11:21:12 +08:00
dataprince
096ee507d0 升级springdoc依赖 2023-09-17 08:33:09 +08:00
dataprince
6c9a2be0d1 修改readme文件 2023-09-16 20:50:48 +08:00
dataprince
66e4b01f52 增加系统截图 2023-09-16 20:43:59 +08:00
dataprince
eeb7723da3 使用mybatis-flex.config文件统一相关默认参数 2023-09-16 11:32:12 +08:00
dataprince
7e1b977b0c GenTable增加remark属性 2023-09-16 11:26:19 +08:00
dataprince
4edd8fa101 使用mybatis-flex重构“通知公告”模块代码 2023-09-15 11:47:16 +08:00
dataprince
a2ec2b31e0 使用mybatis-flex重构“参数设置”模块代码 2023-09-14 22:05:17 +08:00
dataprince
73f8e9f0e9 修正用户新增bug 2023-09-12 11:57:24 +08:00
dataprince
de31954c6f 注册全局数据填充监听器 2023-09-10 19:07:00 +08:00
dataprince
01f30b9e89 删除mybatis-flex.config文件,使用mybatis-flex的默认APT配置 2023-09-10 10:54:42 +08:00
dataprince
687564f377 升级mybatis-flex到V1.6.4 2023-09-07 15:46:41 +08:00
dataprince
bd81c2b1ab 修改版本号,发布新版:V4.1.7 2023-09-05 16:48:56 +08:00
dataprince
ec2781d166 前端代码从vue2升级到vue3 2023-09-05 16:18:41 +08:00
dataprince
79b3e3a751 修正update.sql语句错误 2023-08-31 16:47:23 +08:00
dataprince
3bb20e1d92 修正退出登录的bug:无法返回login页面 2023-08-30 10:22:36 +08:00
837 changed files with 34009 additions and 39345 deletions

22
.gitignore vendored
View File

@ -48,3 +48,25 @@ nbdist/
!*/build/*.xml
.flattened-pom.xml
DS_Store
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
**/*.log
tests/**/coverage/
tests/e2e/reports
selenium-debug.log
# Editor directories and files
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.local
package-lock.json
yarn.lock

View File

@ -1,36 +0,0 @@
# Ruoyi-Flex
#### Description
Ruoyi-Flex是基于RuoYi-Vue v3.8.6进行的扩展集成MyBatis-Flex、JDK17、lombok、Sa-Token、PowerJob、Hutool、OSS、ureport-keep、Flowable、vue3、TypeScript等优秀开源软件准备作为未来5年软件开发的底座。
#### Software Architecture
Software architecture description
#### Installation
1. xxxx
2. xxxx
3. xxxx
#### Instructions
1. xxxx
2. xxxx
3. xxxx
#### Contribution
1. Fork the repository
2. Create Feat_xxx branch
3. Commit your code
4. Create Pull Request
#### Gitee Feature
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
4. The most valuable open source project [GVP](https://gitee.com/gvp)
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

177
README.md
View File

@ -1,90 +1,147 @@
<p align="center">
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
<img alt="logo" src="https://gitee.com/dataprince/ruoyi-flex/raw/master/image/ruoyi-flex-logo.png">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Ruoyi-Flex V4.1.6</h1>
<h4 align="center">Ruoyi-Flex是基于Spring Boot V3平台 前后端分离的Java快速开发框架</h4>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Ruoyi-Flex V5.2.0-SNAPSHOT</h1>
<h4 align="center">Ruoyi-Flex是基于JDK21、Spring Boot V3.2.X+平台 前后端分离的未来8年更快的Java开发框架</h4>
## 平台简介
## 1、平台简介
Ruoyi-Flex是一套全部开源的快速开发平台毫无保留给个人及企业免费使用。基于RuoYi-Vue、RuoYi-Vue-Plus集成MyBatis-Flex、JDK17、SpringBootV3、Lombok、Sa-Token、Hutool、SpringBoot Admin、PowerJob等优秀开源软件
Ruoyi-Flex是一套全部开源的快速开发平台针对”分布式集群与多租户“场景全方位升级使用MIT开源许可协议毫无保留给个人及企业免费使用。基于RuoYi-Vue、RuoYi-Vue-Plus集成MyBatis-Flex、JDK21、SpringBootV3.2.X+、Lombok、Sa-Token、SpringDoc、Hutool、SpringBoot Admin、EasyRetry、PowerJob、Vue3、Element-Plus、AntDesign-Vben、MinIO、Flowable等优秀开源软件,支持PostgreSQL、MySQL开源数据库及其衍生分布式数据库。
* 前端采用Vue、Element UI。
* 后端采用Spring Boot V3、Sa-Token、Redis & Jwt、MyBatis-Flex、PowerJob。
* 权限认证使用Jwt支持多终端认证系统。
* 支持加载动态权限菜单,多方式轻松权限控制。
* 高效率开发,使用代码生成器可以一键生成前后端代码。
## 2、系统特色
Ruoyi-Flex秉承“写的更少、性能更好、出错更低、交流通畅、快速入门” 的理念,为您带来全方位的赋能与提升:
### 1写的更少
借助MyBatis-FlexRuoyi-Flex显著降低了代码输入工作量最高降低了25.85%,参考“演示模块”中的同一功能演示程序源码对比分析(排除相同代码量的控制器、前端代码):
<p align="center">
<img alt="工作量" src="https://gitee.com/dataprince/ruoyi-flex/raw/master/image/workload.JPG">
</p>
除了那些复杂的遗留项目中的统计报表,在绝大部分情况下 Ruoyi-Flex 不需要手写 SQL 语句。
## 内置功能
### 2性能更好
除了集成的JDK21、SpringBootV3.2、MyBatis-Flex的性能提升系统“代码生成”模块生成的代码凡是涉及到后台数据库的多表查询没有采用数据库的LeftJoin、InnerJoin等SQL方式而是使用WithRelation编程装配来取代数据库LeftJoin SQL关联查询数据库不用维护表间外键关系将多表关联SQL语句拆分为对各个单表的主键查询关联无 SQL性能提高10倍。
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
3. 岗位管理:配置系统用户所属担任职务。
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
7. 参数管理:对系统动态配置常用参数。
8. 通知公告:系统通知公告信息发布维护。
9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
10. 登录日志:系统登录日志记录查询包含登录异常。
11. 在线用户:当前系统中活跃用户状态监控。
12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
13. 代码生成前后端代码的生成java、html、xml、sql支持CRUD下载 。
14. 系统接口根据业务代码自动生成相关的api接口文档。
15. 服务监控监视当前系统CPU、内存、磁盘、堆栈等相关信息。
16. 缓存监控:对系统的缓存信息查询,命令统计等。
17. 在线构建器拖动表单元素生成相应的HTML代码。
18. 连接池监视监视当前系统数据库连接池状态可进行分析SQL找出系统性能瓶颈。
### 3出错更低
原来用mybatis开发需要手写SQL语句开发后期需要增加字段修改xml文件是一种灾难一不留神就犯错了而Ruoyi-Flex借助MyBatis-Flex则很好地规避了此问题如果字段输入错误开发环境IDEA就会自动标红报警避免犯错。
### 4交流通畅
“非我族类其心必异”。Ruoyi-Flex集成了一大波国产开源软件MyBatis-Flex、Sa-Token、Hutool、PowerJob、Element-Plus等同根同源交流自然顺畅开发中遇到问题可联系作者快速得到解决。例如同一个领域的安全框架一个中国人只需半天就可学会Sa-Token干活如果是学Spring Security的话七天也不一定能学会。
### 5多端同步
Ruoyi-Flex提供“1+3”端1个后台端、3个前台端熟悉js的可使用flex-elementplus-ui前端熟悉ts的可使用ruoyiflex-elementplus-ts前端既熟悉ts又熟悉antdesign的请使用ruoyiflex-antdesign-vben前端总有一款适合您的前端供您选择
## 演示图
### 6入门快速
Ruoyi-Flex已集成各种开源开发框架扫平了技术障碍可直接上手干活。使用者只需要设计好数据库表结构系统能可视化生成前后端本地代码单表、树表、主子表任你选10分钟就能开发一个模块快速入门开发高效。
## 3、前端项目
Ruoyi-Flex实行前后端分离仓库本项目是java后端部分目前有3个前端项目
### 1ruoyiflex-elementplus-ts
使用elementplus、typescript构建项目地址: [ruoyiflex-elementplus-ts](https://gitee.com/dataprince/ruoyiflex-elementplus-ts)
### 2ruoyiflex-antdesign-vben
使用antdesign、vben、typescript构建项目地址: [ruoyiflex-antdesign-vben](https://gitee.com/dataprince/ruoyiflex-antdesign-vben)
### 3flex-elementplus-ui
使用elementplus、js构建项目地址: [flex-elementplus-ui](https://gitee.com/dataprince/flex-elementplus-ui)
## 4、内置功能
1. 租户管理:系统内租户的管理 如:租户套餐、过期时间、用户数量、企业信息等。
2. 租户套餐管理:系统内租户所能使用的套餐管理 如:套餐内所包含的菜单等。
3. 客户端管理:系统内对接的所有客户端管理 如: pc端、小程序端等支持动态授权登录方式 如: 短信登录、密码登录等 支持动态控制token时效。
4. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
5. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
6. 岗位管理:配置系统用户所属担任职务。
7. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
8. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
9. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
10. 参数管理:对系统动态配置常用参数。
11. 通知公告:系统通知公告信息发布维护。
12. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
13. 登录日志:系统登录日志记录查询包含登录异常。
14. 文件管理引入云存储服务将文件存储到MinIO、七牛、阿里、腾讯等OSS服务器上支持上传、下载。
15. 在线用户:当前系统中活跃用户状态监控。
16. 调度中心集成PowerJob全新一代分布式任务调度与计算框架。
17. 代码生成前后端代码的生成java、html、vue、js支持单表、树表、主子表减少70%以上的开发工作量。
18. 系统接口集成springdoc根据文档注释自动生成相关的api接口文档。
19. 监控中心集成Spring Boot Admin监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等。
20. 缓存监控:对系统的缓存信息查询,命令统计等。
21. 后台数据库支持PostgreSQL、MySQL开源数据库及其衍生分布式数据库。
22. 演示模块mybatis、mybatis-flex两种格式代码的单表、树表、主子表三种类型的演示程序。
23. 实现多租户功能。
24. 实现乐观锁功能。
25. 实现逻辑删除功能。
26. 启用JAVA21虚拟线程、分代ZGC功能。
27. 实现API接口加密功能密码使用密文传输。
## 5、演示图
<table>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/cd1f90be5f2684f4560c9519c0f2a232ee8.jpg"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/1cbcf0e6f257c7d3a063c0e3f2ff989e4b3.jpg"/></td>
<td><img src="https://gitee.com/dataprince/ruoyi-flex/raw/master/image/main.JPG"/></td>
<td><img src="https://gitee.com/dataprince/ruoyi-flex/raw/master/image/manul.JPG"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-8074972883b5ba0622e13246738ebba237a.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-9f88719cdfca9af2e58b352a20e23d43b12.png"/></td>
<td><img src="https://gitee.com/dataprince/ruoyi-flex/raw/master/image/user.JPG"/></td>
<td><img src="https://gitee.com/dataprince/ruoyi-flex/raw/master/image/role.JPG"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-39bf2584ec3a529b0d5a3b70d15c9b37646.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-936ec82d1f4872e1bc980927654b6007307.png"/></td>
<td><img src="https://gitee.com/dataprince/ruoyi-flex/raw/master/image/powerjob.JPG"/></td>
<td><img src="https://gitee.com/dataprince/ruoyi-flex/raw/master/image/springbootadmin.JPG"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-b2d62ceb95d2dd9b3fbe157bb70d26001e9.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-d67451d308b7a79ad6819723396f7c3d77a.png"/></td>
<td><img src="https://gitee.com/dataprince/ruoyi-flex/raw/master/image/oss.JPG"/></td>
<td><img src="https://gitee.com/dataprince/ruoyi-flex/raw/master/image/gen.JPG"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/5e8c387724954459291aafd5eb52b456f53.jpg"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/644e78da53c2e92a95dfda4f76e6d117c4b.jpg"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-8370a0d02977eebf6dbf854c8450293c937.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-49003ed83f60f633e7153609a53a2b644f7.png"/></td>
<td><img src="https://gitee.com/dataprince/ruoyi-flex/raw/master/image/master.JPG"/></td>
<td><img src="https://gitee.com/dataprince/ruoyi-flex/raw/master/image/preview.JPG"/></td>
</tr>
</table>
## 特别鸣谢
## 6、开发文档
本项目提供保姆级开发文档,零基础手把手入门教程,位于/doc文件夹下面
入门必读,请下载到本地查看:《[Ruoyi-Flex开发编译手册.docx](https://gitee.com/dataprince/ruoyi-flex/raw/master/doc/Ruoyi-Flex-Guide.docx)》。
## 7、Ruoyi-Flex交流群
本软件完全开源作者很忙如果您在使用过程中遇到问题请付点小费扫码微信支付99元后申请加入微信群寻求帮助
<table>
<tr>
<td>1、免费QQ交流群</td>
<td>762217712[交流1群]</td>
</tr>
<tr>
<td>2、付费微信VIP群微信扫码支付99元加好友入群</td>
<td><img src="https://gitee.com/dataprince/ruoyi-flex/raw/master/image/dataprince.jpg"/></td>
</tr>
</table>
## 8、开源协议
**为什么推荐使用本项目?**
① 本项目采用比 Apache 2.0 更宽松的 [MIT License](https://gitee.com/dataprince/ruoyi-flex/blob/master/LICENSE) 开源协议,个人与企业可 100% 免费使用不用保留类作者、Copyright 信息。
② 代码全部开源,不会像其它项目一样,只开源部分代码,让你无法了解整个项目的架构设计。
如果这个项目让您有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。
## 9、参与贡献
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
4. 新建 Pull Request
## 10、特别鸣谢
- [RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue)
- [RuoYi-Vue-Plus](https://gitee.com/dromara/RuoYi-Vue-Plus)
- [MyBatis-Flex](https://gitee.com/mybatis-flex/mybatis-flex)
## Ruoyi-Flex交流群
<table>
<tr>
<td>1、普通QQ群 100956531</td>
<td>[Ruoyi-Flex交流一群]</td>
</tr>
<tr>
<td>2、付费微信VIP交流群需加好友捐助99元</td>
<td><img src="https://gitee.com/dataprince/ruoyi-flex/raw/master/image/dataprince-wxcode.jpg"/></td>
</tr>
</table>

BIN
doc/Ruoyi-Flex-Guide.docx Normal file

Binary file not shown.

BIN
doc/~$oyi-Flex-Guide.docx Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

BIN
image/dataprince.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
image/gen.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
image/login.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

BIN
image/main.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

BIN
image/manul.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

BIN
image/master.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

BIN
image/oss.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

BIN
image/powerjob.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
image/preview.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
image/role.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

BIN
image/ruoyi-flex-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
image/springbootadmin.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
image/springdoc.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

BIN
image/user.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

BIN
image/workload.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

2
mybatis-flex.config Normal file
View File

@ -0,0 +1,2 @@
processor.mapper.generateEnable=false
processor.tableDef.propertiesNameStyle=upperCase

195
pom.xml
View File

@ -2,7 +2,7 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<modelVersion>4.0.0</modelVersion>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-flex</artifactId>
@ -13,50 +13,64 @@
<description>Ruoyi-Flex管理系统</description>
<properties>
<revision>4.1.7-SNAPSHOT</revision>
<revision>5.2.0-SNAPSHOT</revision>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
<spring-boot.version>3.1.3</spring-boot.version>
<mybatis-flex.version>1.6.0</mybatis-flex.version>
<satoken.version>1.35.0.RC</satoken.version>
<mysql.version>8.0.33</mysql.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<HikariCP.version>5.0.1</HikariCP.version>
<java.version>21</java.version>
<spring-boot.version>3.2.5</spring-boot.version>
<mybatis-flex.version>1.9.4</mybatis-flex.version>
<satoken.version>1.38.0</satoken.version>
<HikariCP.version>5.1.0</HikariCP.version>
<bitwalker.version>1.21</bitwalker.version>
<caffeine.version>3.1.8</caffeine.version>
<kaptcha.version>2.3.3</kaptcha.version>
<pagehelper.version>5.3.3</pagehelper.version>
<fastjson.version>2.0.34</fastjson.version>
<oshi.version>6.4.4</oshi.version>
<commons.io.version>2.13.0</commons.io.version>
<pagehelper.version>6.1.0</pagehelper.version>
<fastjson.version>2.0.43</fastjson.version>
<oshi.version>6.4.8</oshi.version>
<commons.collections.version>3.2.2</commons.collections.version>
<poi.version>5.2.3</poi.version>
<easyexcel.version>3.3.2</easyexcel.version>
<poi.version>5.2.5</poi.version>
<easyexcel.version>3.3.3</easyexcel.version>
<velocity.version>2.3</velocity.version>
<jwt.version>0.9.1</jwt.version>
<servlet-api.version>6.0.0</servlet-api.version>
<guava.version>32.1.1-jre</guava.version>
<flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>
<springdoc.version>2.2.0</springdoc.version>
<springdoc-openapi-starter-common.version>2.1.0</springdoc-openapi-starter-common.version>
<springdoc.version>2.4.0</springdoc.version>
<springdoc-openapi-starter-common.version>2.4.0</springdoc-openapi-starter-common.version>
<therapi-runtime-javadoc.version>0.15.0</therapi-runtime-javadoc.version>
<snakeyaml.version>1.33</snakeyaml.version>
<lombok.version>1.18.28</lombok.version>
<mapstruct-plus.version>1.3.5</mapstruct-plus.version>
<snakeyaml.version>2.2</snakeyaml.version>
<lombok.version>1.18.30</lombok.version>
<mapstruct-plus.version>1.3.6</mapstruct-plus.version>
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
<hutool.version>5.8.21</hutool.version>
<redisson.version>3.23.3</redisson.version>
<lock4j.version>2.2.4</lock4j.version>
<alibaba-ttl.version>2.14.3</alibaba-ttl.version>
<spring-boot-admin.version>3.1.5</spring-boot-admin.version>
<powerjob.version>4.3.5</powerjob.version>
<hutool.version>5.8.27</hutool.version>
<redisson.version>3.27.2</redisson.version>
<lock4j.version>2.2.7</lock4j.version>
<alibaba-ttl.version>2.14.4</alibaba-ttl.version>
<spring-boot-admin.version>3.2.3</spring-boot-admin.version>
<powerjob.version>4.3.6</powerjob.version>
<easyretry.version>3.2.0</easyretry.version>
<!-- 离线IP地址定位库 -->
<ip2region.version>2.7.0</ip2region.version>
<!-- OSS 配置 -->
<aws.sdk.version>2.25.15</aws.sdk.version>
<aws.crt.version>0.29.13</aws.crt.version>
<!-- 加解密依赖库 -->
<bcprov-jdk.version>1.77</bcprov-jdk.version>
<!-- SMS 配置 -->
<sms4j.version>3.2.0</sms4j.version>
<!-- findbugs消除打包警告 -->
<jsr305.version>3.0.2</jsr305.version>
<!-- 三方授权认证 -->
<justauth.version>1.16.6</justauth.version>
<!-- 插件版本 -->
<maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
<maven-war-plugin.version>3.2.2</maven-war-plugin.version>
<maven-compiler-plugin.verison>3.11.0</maven-compiler-plugin.verison>
<maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
<flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
<flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>
<!--工作流配置-->
<flowable.version>7.0.0</flowable.version>
</properties>
<profiles>
@ -65,7 +79,7 @@
<properties>
<!-- 环境标识,需要与配置文件的名称相对应 -->
<profiles.active>dev</profiles.active>
<logging.level>debug</logging.level>
<logging.level>info</logging.level>
</properties>
<activation>
<!-- 默认环境 -->
@ -93,6 +107,13 @@
<scope>import</scope>
</dependency>
<!-- validation检验-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- hutool 的依赖配置-->
<dependency>
<groupId>cn.hutool</groupId>
@ -102,6 +123,15 @@
<scope>import</scope>
</dependency>
<!-- flowable 的依赖配置-->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-bom</artifactId>
<version>${flowable.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- common 的依赖配置-->
<dependency>
<groupId>com.ruoyi</groupId>
@ -111,9 +141,12 @@
<scope>import</scope>
</dependency>
<!--测试框架-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version>
<scope>test</scope>
</dependency>
<!-- 数据库连接池-->
@ -126,7 +159,7 @@
<!-- mybatis-flex -->
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot-starter</artifactId>
<artifactId>mybatis-flex-spring-boot3-starter</artifactId>
<version>${mybatis-flex.version}</version>
</dependency>
@ -154,11 +187,11 @@
<version>${satoken.version}</version>
</dependency>
<!-- Mysql驱动包 -->
<!-- caffeine缓存 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>${caffeine.version}</version>
</dependency>
<!-- servlet包 -->
@ -189,13 +222,6 @@
<version>${oshi.version}</version>
</dependency>
<!-- io常用工具类 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons.io.version}</version>
</dependency>
<!-- excel工具 -->
<dependency>
<groupId>org.apache.poi</groupId>
@ -321,6 +347,53 @@
<version>${lock4j.version}</version>
</dependency>
<!-- 离线IP地址定位库 ip2region -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>${ip2region.version}</version>
</dependency>
<!-- AWS SDK for Java 2.x -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>${aws.sdk.version}</version>
</dependency>
<!-- 使用AWS基于 CRT 的 S3 客户端 -->
<dependency>
<groupId>software.amazon.awssdk.crt</groupId>
<artifactId>aws-crt</artifactId>
<version>${aws.crt.version}</version>
</dependency>
<!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3-transfer-manager</artifactId>
<version>${aws.sdk.version}</version>
</dependency>
<!-- 加解密依赖库 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>${bcprov-jdk.version}</version>
</dependency>
<!-- findbugs消除打包警告 -->
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>${jsr305.version}</version>
</dependency>
<!-- JustAuth 的依赖配置-->
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
<version>${justauth.version}</version>
</dependency>
<!--Annotation Processor-->
<dependency>
<groupId>org.springframework.boot</groupId>
@ -334,6 +407,13 @@
<version>${alibaba-ttl.version}</version>
</dependency>
<!--短信sms4j-->
<dependency>
<groupId>org.dromara.sms4j</groupId>
<artifactId>sms4j-spring-boot-starter</artifactId>
<version>${sms4j.version}</version>
</dependency>
<!-- spring-boot-admin监控-->
<dependency>
<groupId>de.codecentric</groupId>
@ -358,13 +438,6 @@
<version>${powerjob.version}</version>
</dependency>
<!-- 定时任务-->
<!-- <dependency>-->
<!-- <groupId>com.ruoyi</groupId>-->
<!-- <artifactId>ruoyi-quartz</artifactId>-->
<!-- <version>${revision}</version>-->
<!-- </dependency>-->
<!-- PowerJob定时任务处理器-->
<dependency>
<groupId>com.ruoyi</groupId>
@ -372,6 +445,23 @@
<version>${revision}</version>
</dependency>
<!-- EasyRetry Client -->
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>easy-retry-client-starter</artifactId>
<version>${easyretry.version}</version>
</dependency>
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>easy-retry-client-core</artifactId>
<version>${easyretry.version}</version>
</dependency>
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>easy-retry-client-job-core</artifactId>
<version>${easyretry.version}</version>
</dependency>
<!-- 代码生成-->
<dependency>
<groupId>com.ruoyi</groupId>
@ -393,6 +483,13 @@
<version>${revision}</version>
</dependency>
<!-- 工作流模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-workflow</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>

8
ruoyi-admin/Dockerfile Normal file
View File

@ -0,0 +1,8 @@
# 使用官方的 Java 运行时作为父镜像
FROM registry.cn-qingdao.aliyuncs.com/yuzl1/jdk:21
# 将本地文件复制到容器中
COPY target/ruoyi-admin.jar /ruoyi-admin.jar
# 运行应用
ENTRYPOINT ["java","-jar","/ruoyi-admin.jar"]

View File

@ -27,8 +27,14 @@
<!-- Mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- 三方授权认证 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-social</artifactId>
</dependency>
<!-- PostgreSql -->
@ -37,6 +43,11 @@
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-tenant</artifactId>
</dependency>
<!-- system模块-->
<dependency>
<groupId>com.ruoyi</groupId>
@ -67,11 +78,18 @@
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>
<!-- powerjob 客户端 -->
<!-- <dependency>-->
<!-- <groupId>tech.powerjob</groupId>-->
<!-- <artifactId>powerjob-worker-spring-boot-starter</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>easy-retry-client-starter</artifactId>
</dependency>
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>easy-retry-client-core</artifactId>
</dependency>
<dependency>
<groupId>com.aizuda</groupId>
<artifactId>easy-retry-client-job-core</artifactId>
</dependency>
</dependencies>

View File

@ -2,20 +2,23 @@ package com.ruoyi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
/**
* 启动程序
*
* @author ruoyi
*/
@SpringBootApplication
@SpringBootApplication(exclude = SpringDataWebAutoConfiguration.class)
public class RuoYiApplication
{
public static void main(String[] args)
{
System.setProperty("spring.devtools.restart.enabled", "false");
SpringApplication.run(RuoYiApplication.class, args);
System.out.println("(♥◠‿◠)ノ゙ RuoYi-Flex启动成功 ლ(´ڡ`ლ)゙ \n" +
SpringApplication application = new SpringApplication(RuoYiApplication.class);
application.setApplicationStartup(new BufferingApplicationStartup(2048));
application.run(args);
System.out.println("(♥◠‿◠)ノ゙ RuoYi-Flex-Boot启动成功 ლ(´ڡ`ლ)゙ \n" +
" ███████ ██ ██ ██ ████████ ██ \n" +
"░██░░░░██ ░░██ ██ ░░ ░██░░░░░ ░██ \n" +
"░██ ░██ ██ ██ ██████ ░░████ ██ ░██ ░██ █████ ██ ██\n" +

View File

@ -1,31 +1,48 @@
package com.ruoyi.web.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.mybatisflex.core.query.QueryWrapper;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.core.domain.AjaxResult;
import com.ruoyi.common.core.core.domain.model.LoginUser;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.core.domain.model.SocialLoginBody;
import com.ruoyi.common.core.utils.*;
import com.ruoyi.common.encrypt.annotation.ApiEncrypt;
import com.ruoyi.common.json.utils.JsonUtils;
import com.ruoyi.common.security.utils.LoginHelper;
import com.ruoyi.system.domain.SysMenu;
import com.ruoyi.system.domain.SysUser;
import com.ruoyi.common.social.config.properties.SocialLoginConfigProperties;
import com.ruoyi.common.social.config.properties.SocialProperties;
import com.ruoyi.common.social.utils.SocialUtils;
import com.ruoyi.common.websocket.dto.WebSocketMessageDto;
import com.ruoyi.common.websocket.utils.WebSocketUtils;
import com.ruoyi.system.domain.bo.SysTenantBo;
import com.ruoyi.system.domain.vo.SysClientVo;
import com.ruoyi.system.domain.vo.SysTenantVo;
import com.ruoyi.system.service.*;
import com.ruoyi.web.domain.vo.LoginTenantVo;
import com.ruoyi.web.domain.vo.LoginVo;
import com.ruoyi.web.domain.vo.TenantListVo;
import com.ruoyi.web.service.SysRegisterService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.ruoyi.common.core.core.domain.R;
import com.ruoyi.common.core.core.domain.model.LoginBody;
import com.ruoyi.common.core.core.domain.model.RegisterBody;
import com.ruoyi.common.core.utils.MessageUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.system.domain.SysClient;
import com.ruoyi.web.service.IAuthStrategy;
import com.ruoyi.web.service.SysLoginService;
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.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.net.URL;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static com.ruoyi.system.domain.table.SysClientTableDef.SYS_CLIENT;
@ -37,114 +54,165 @@ import static com.ruoyi.system.domain.table.SysClientTableDef.SYS_CLIENT;
*/
@Slf4j
@SaIgnore
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/auth")
public class AuthController {
@Resource
private final SysLoginService loginService;
private SysLoginService loginService;
@Resource
private final ISysClientService clientService;
private ISysClientService clientService;
@Resource
private final ISysUserService sysUserService;
private SysRegisterService registerService;
@Resource
private final ISysPermissionService permissionService;
private ISysConfigService configService;
@Resource
private ISysMenuService menuService;
private ISysTenantService tenantService;
@Resource
private ISysSocialService socialService;
private final ScheduledExecutorService scheduledExecutorService;
private final SocialProperties socialProperties;
/**
* 登录方法
*
* @param loginBody 登录信息
* @param body 登录信息
* @return 结果
*/
@ApiEncrypt
@PostMapping("/login")
public AjaxResult login(@Validated @RequestBody LoginBody loginBody) {
AjaxResult ajax = AjaxResult.success();
public R<LoginVo> login(@RequestBody String body) {
LoginBody loginBody = JsonUtils.parseObject(body, LoginBody.class);
ValidatorUtils.validate(loginBody);
// 授权类型和客户端id
String clientId = loginBody.getClientId();
String grantType = loginBody.getGrantType();
QueryWrapper query=QueryWrapper.create().from(SYS_CLIENT).where(SYS_CLIENT.CLIENT_ID.eq(clientId));
SysClient client = clientService.getOne(query);
SysClientVo client = clientService.getOneAs(query,SysClientVo.class);
// 查询不到 client client 内不包含 grantType
if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) {
log.info("客户端id: {} 认证类型:{} 异常!.", clientId, grantType);
return AjaxResult.error(MessageUtils.message("auth.grant.type.error"));
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"));
}
// TODO:校验租户
//loginService.checkTenant(loginBody.getTenantId());
// 生成令牌
String token =IAuthStrategy.login(loginBody, client);
ajax.put(Constants.TOKEN, token);
// 校验租户
loginService.checkTenant(loginBody.getTenantId());
// 登录
return ajax;
LoginVo loginVo =IAuthStrategy.login(body, client, grantType);
Long userId = LoginHelper.getUserId();
scheduledExecutorService.schedule(() -> {
WebSocketMessageDto dto = new WebSocketMessageDto();
dto.setMessage("欢迎登录RuoYi-Flex多租户管理系统");
dto.setSessionKeys(List.of(userId));
WebSocketUtils.publishMessage(dto);
}, 3, TimeUnit.SECONDS);
return R.ok(loginVo);
}
/**
* 获取用户信息
* 第三方登录请求
*
* @return 用户信息
* @param source 登录来源
* @return 结果
*/
@GetMapping("/getInfo")
public AjaxResult getInfo() {
LoginUser loginUser = LoginHelper.getLoginUser();
//TODO:多租户 超级管理员 如果重新加载用户信息需清除动态租户
SysUser user = sysUserService.selectUserById(loginUser.getUserId());
// 角色集合
Set<String> roles = permissionService.getRolePermission(user.getUserId());
// 权限集合
Set<String> permissions = permissionService.getMenuPermission(user.getUserId());
AjaxResult ajax = AjaxResult.success();
ajax.put("user", user);
ajax.put("roles", roles);
ajax.put("permissions", permissions);
return ajax;
@GetMapping("/binding/{source}")
public R<String> authBinding(@PathVariable("source") String source) {
SocialLoginConfigProperties obj = socialProperties.getType().get(source);
if (ObjectUtil.isNull(obj)) {
return R.fail(source + "平台账号暂不支持");
}
AuthRequest authRequest = SocialUtils.getAuthRequest(source, socialProperties);
String authorizeUrl = authRequest.authorize(AuthStateUtils.createState());
return R.ok("操作成功", authorizeUrl);
}
/**
* 获取路由信息
* 第三方登录回调业务处理 绑定授权
*
* @return 路由信息
* @param loginBody 请求体
* @return 结果
*/
@GetMapping("/getRouters")
public AjaxResult getRouters()
{
LoginUser loginUser = LoginHelper.getLoginUser();
// 用户信息
SysUser user = sysUserService.selectUserById(loginUser.getUserId());
List<SysMenu> menus = menuService.selectMenuTreeByUserId(user.getUserId());
return AjaxResult.success(menuService.buildMenus(menus));
@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();
}
/**
* 取消授权
*
* @param socialId socialId
*/
@DeleteMapping(value = "/unlock/{socialId}")
public R<Void> unlockSocial(@PathVariable Long socialId) {
Boolean rows = socialService.deleteWithValidById(socialId);
return rows ? R.ok() : R.fail("取消授权失败");
}
/**
* 退出登录
*/
@PostMapping("/logout")
public AjaxResult logout() {
public R<Void> logout() {
loginService.logout();
return AjaxResult.error("退出成功!");
return R.ok("退出成功!");
}
/**
* 用户注册
*/
@PostMapping("LoginHelper.getUserId()")
@PostMapping("/register")
public R<Void> register(@Validated @RequestBody RegisterBody user) {
//if (!configService.selectRegisterEnabled(user.getTenantId())) // TODO注册代码
if (!configService.selectRegisterEnabled(user.getTenantId()))
{
return R.fail("当前系统没有开启注册功能!");
}
// registerService.register(user);
// return R.ok();
registerService.register(user);
return R.ok();
}
/**
* 登录页面租户下拉框
*
* @return 租户列表
*/
@GetMapping("/tenant/list")
public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
List<SysTenantVo> tenantList = tenantService.selectList(new SysTenantBo());
List<TenantListVo> voList = MapstructUtils.convert(tenantList, TenantListVo.class);
// 获取域名
String host;
String referer = request.getHeader("referer");
if (StringUtils.isNotBlank(referer)) {
// 这里从referer中取值是为了本地使用hosts添加虚拟域名方便本地环境调试
host = referer.split("//")[1].split("/")[0];
} else {
host = new URL(request.getRequestURL().toString()).getHost();
}
// 根据域名进行筛选
List<TenantListVo> list = StreamUtils.filter(voList, vo ->
StringUtils.equals(vo.getDomain(), host));
// 返回对象
LoginTenantVo vo = new LoginTenantVo();
vo.setTenantEnabled(true);
vo.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
return R.ok(vo);
}

View File

@ -2,21 +2,35 @@ package com.ruoyi.web.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import java.time.Duration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import com.ruoyi.common.core.annotation.RateLimiter;
import com.ruoyi.common.core.constant.GlobalConstants;
import com.ruoyi.common.core.core.domain.AjaxResult;
import com.ruoyi.common.core.enums.LimitType;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.reflect.ReflectUtils;
import com.ruoyi.common.core.utils.SpringUtils;
import com.ruoyi.common.core.core.domain.R;
import com.ruoyi.common.encrypt.utils.RSAUtils;
import com.ruoyi.common.mail.config.properties.MailProperties;
import com.ruoyi.common.mail.utils.MailUtils;
import com.ruoyi.common.redis.utils.RedisUtils;
import com.ruoyi.common.web.enums.CaptchaType;
import com.ruoyi.web.domain.vo.CaptchaVo;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@ -30,6 +44,7 @@ import com.ruoyi.common.web.config.properties.CaptchaProperties;
* 验证码操作处理
*
* @author ruoyi
* @author 数据小王子
*/
@SaIgnore
@Slf4j
@ -39,17 +54,19 @@ import com.ruoyi.common.web.config.properties.CaptchaProperties;
public class CaptchaController
{
private final CaptchaProperties captchaProperties;
private final MailProperties mailProperties;
/**
* 生成验证码
*/
@GetMapping("/captchaImage")
public AjaxResult getCode() {
@RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
@GetMapping("/auth/code")
public R<CaptchaVo> getCode() {
CaptchaVo captchaVo = new CaptchaVo();
boolean captchaEnabled = captchaProperties.getEnable();
if (!captchaEnabled) {
captchaVo.setCaptchaEnabled(false);
return AjaxResult.success(captchaVo);
return R.ok(captchaVo);
}
// 保存验证码信息
String uuid = IdUtil.simpleUUID();
@ -62,6 +79,7 @@ public class CaptchaController
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
captcha.setGenerator(codeGenerator);
captcha.createCode();
// 如果是数学验证码使用SpEL表达式处理验证码结果
String code = captcha.getCode();
if (isMath) {
ExpressionParser parser = new SpelExpressionParser();
@ -72,8 +90,76 @@ public class CaptchaController
captchaVo.setUuid(uuid);
captchaVo.setImg(captcha.getImageBase64());
return AjaxResult.success(captchaVo);
return R.ok(captchaVo);
}
/**
* 邮箱验证码
*
* @param email 邮箱
*/
@RateLimiter(key = "#email", time = 60, count = 1)
@GetMapping("/resource/email/code")
public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
if (!mailProperties.getEnabled()) {
return R.fail("当前系统没有开启邮箱功能!");
}
String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
try {
MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
} catch (Exception e) {
log.error("验证码短信发送异常 => {}", e.getMessage());
return R.fail(e.getMessage());
}
return R.ok();
}
/**
* 短信验证码
*
* @param phonenumber 用户手机号
*/
@RateLimiter(key = "#phonenumber", time = 60, count = 1)
@GetMapping("/resource/sms/code")
public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
// 验证码模板id 自行处理 (查数据库或写死均可)
String templateId = "";
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put("code", code);
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map);
if (!smsResponse.isSuccess()) {
log.error("验证码短信发送异常 => {}", smsResponse);
return R.fail(smsResponse.getData().toString());
}
return R.ok();
}
@GetMapping("/genKeyPair")
public R genKeyPair() {
Map<String,String> map=new HashMap<>();
try {
log.info("开始生产rsa秘钥");
Map<String, Object> keyPair = RSAUtils.genKeyPair();
String publicKey = RSAUtils.getPublicKey(keyPair);
String privateKey = RSAUtils.getPrivateKey(keyPair);
log.info("privateKey"+privateKey);
String uuid="ruoyi_"+ UUID.randomUUID().toString().replace("-","");
RedisUtils.setCacheMapValue("loginRsa",uuid,privateKey);
RedisUtils.expire("loginRsa",60*60);
log.info("写入redis完成");
map.put("uuidPrivateKey",uuid);
map.put("RSA_PUBLIC_KEY",publicKey);
} catch (Exception e) {
return R.fail("生成RSA秘钥失败,"+e.getMessage());
}
return R.ok(map);
}
}

View File

@ -0,0 +1,25 @@
package com.ruoyi.web.domain.vo;
import lombok.Data;
import java.util.List;
/**
* 登录租户对象
*
* @author Michelle.Chung
*/
@Data
public class LoginTenantVo {
/**
* 租户开关
*/
private Boolean tenantEnabled;
/**
* 租户对象列表
*/
private List<TenantListVo> voList;
}

View File

@ -0,0 +1,31 @@
package com.ruoyi.web.domain.vo;
import com.ruoyi.system.domain.vo.SysTenantVo;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
/**
* 租户列表
*
* @author Lion Li
*/
@Data
@AutoMapper(target = SysTenantVo.class)
public class TenantListVo {
/**
* 租户编号
*/
private String tenantId;
/**
* 企业名称
*/
private String companyName;
/**
* 域名
*/
private String domain;
}

View File

@ -1,26 +1,30 @@
package com.ruoyi.common.security.listener;
package com.ruoyi.web.listener;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.core.domain.dto.UserOnlineDTO;
import com.ruoyi.common.core.core.domain.model.LoginUser;
import com.ruoyi.common.core.enums.UserType;
import com.ruoyi.common.redis.utils.RedisUtils;
import com.ruoyi.common.security.utils.LoginHelper;
import com.ruoyi.common.core.utils.ip.AddressUtils;
import com.ruoyi.common.core.utils.ServletUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.core.domain.dto.UserOnlineDTO;
import com.ruoyi.common.core.utils.MessageUtils;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.SpringUtils;
import com.ruoyi.common.core.utils.ip.AddressUtils;
import com.ruoyi.common.log.event.LogininforEvent;
import com.ruoyi.common.redis.utils.RedisUtils;
import com.ruoyi.common.security.utils.LoginHelper;
import com.ruoyi.common.tenant.helper.TenantHelper;
import com.ruoyi.web.service.SysLoginService;
import org.springframework.stereotype.Component;
import java.time.Duration;
/**
* 用户行为 自定义侦听器
* 用户行为 侦听器的实现
*
* @author Lion Li
*/
@ -30,35 +34,46 @@ import java.time.Duration;
public class UserActionListener implements SaTokenListener {
private final SaTokenConfig tokenConfig;
private final SysLoginService loginService;
/**
* 每次登录时触发
*/
@Override
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
UserType userType = UserType.getUserType(loginId.toString());
if (userType == UserType.SYS_USER) {
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = ServletUtils.getClientIP();
LoginUser user = LoginHelper.getLoginUser();
UserOnlineDTO dto = new UserOnlineDTO();
dto.setIpaddr(ip);
dto.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
dto.setBrowser(userAgent.getBrowser().getName());
dto.setOs(userAgent.getOs().getName());
dto.setLoginTime(System.currentTimeMillis());
dto.setTokenId(tokenValue);
dto.setUserName(user.getUsername());
dto.setDeptName(user.getDeptName());
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = ServletUtils.getClientIP();
UserOnlineDTO dto = new UserOnlineDTO();
dto.setIpaddr(ip);
dto.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
dto.setBrowser(userAgent.getBrowser().getName());
dto.setOs(userAgent.getOs().getName());
dto.setLoginTime(System.currentTimeMillis());
dto.setTokenId(tokenValue);
String username = (String) loginModel.getExtra(LoginHelper.USER_NAME_KEY);
Long tenantId = (Long) loginModel.getExtra(LoginHelper.TENANT_KEY);
dto.setUserName(username);
dto.setClientKey((String) loginModel.getExtra(LoginHelper.CLIENT_KEY));
dto.setDeviceType(loginModel.getDevice());
dto.setDeptName((String) loginModel.getExtra(LoginHelper.DEPT_NAME_KEY));
TenantHelper.dynamic(tenantId, () -> {
if(tokenConfig.getTimeout() == -1) {
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
} else {
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(tokenConfig.getTimeout()));
}
log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
} else if (userType == UserType.APP_USER) {
// app端 自行根据业务编写
}
});
// 记录登录日志
LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username);
logininforEvent.setStatus(Constants.LOGIN_SUCCESS);
logininforEvent.setMessage(MessageUtils.message("user.login.success"));
logininforEvent.setRequest(ServletUtils.getRequest());
SpringUtils.context().publishEvent(logininforEvent);
// 更新登录信息
loginService.recordLoginInfo((Long) loginModel.getExtra(LoginHelper.USER_KEY), ip);
log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
}
/**

View File

@ -6,6 +6,8 @@ import com.ruoyi.common.core.core.domain.model.LoginBody;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.utils.SpringUtils;
import com.ruoyi.system.domain.SysClient;
import com.ruoyi.system.domain.vo.SysClientVo;
import com.ruoyi.web.domain.vo.LoginVo;
/**
* 授权策略
@ -18,28 +20,29 @@ public interface IAuthStrategy {
/**
* 登录
*
* @param body 登录对象
* @param client 授权管理视图对象
* @param grantType 授权类型
* @return 登录验证信息
*/
static String login(LoginBody loginBody, SysClient client) {
static LoginVo login(String body, SysClientVo client, String grantType) {
// 授权类型和客户端id
String clientId = loginBody.getClientId();
String grantType = loginBody.getGrantType();
String beanName = grantType + BASE_NAME;
if (!SpringUtils.containsBean(beanName)) {
throw new ServiceException("授权类型不正确!");
}
IAuthStrategy instance = SpringUtils.getBean(beanName);
instance.validate(loginBody);
return instance.login(clientId, loginBody, client);
return instance.login(body,client);
}
/**
* 参数校验
*/
void validate(LoginBody loginBody);
/**
* 登录
*
* @param body 登录对象
* @param client 授权管理视图对象
* @return 登录验证信息
*/
String login(String clientId, LoginBody loginBody, SysClient client);
LoginVo login(String body, SysClientVo client);
}

View File

@ -3,31 +3,37 @@ package com.ruoyi.web.service;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.core.constant.*;
import com.ruoyi.common.core.core.domain.dto.RoleDTO;
import com.ruoyi.common.core.enums.LoginType;
import com.ruoyi.common.core.enums.TenantStatus;
import com.ruoyi.common.core.exception.user.*;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.SpringUtils;
import com.ruoyi.common.log.event.LogininforEvent;
import com.ruoyi.common.redis.utils.RedisUtils;
import com.ruoyi.common.security.utils.LoginHelper;
import com.ruoyi.common.tenant.exception.TenantException;
import com.ruoyi.common.tenant.helper.TenantHelper;
import com.ruoyi.system.domain.SysUser;
import com.ruoyi.system.domain.vo.SysUserVo;
import com.ruoyi.system.service.ISysPermissionService;
import com.ruoyi.system.domain.bo.SysSocialBo;
import com.ruoyi.system.domain.bo.SysUserBo;
import com.ruoyi.system.domain.vo.*;
import com.ruoyi.system.service.*;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import me.zhyd.oauth.model.AuthUser;
import com.ruoyi.common.core.core.domain.model.LoginUser;
import com.ruoyi.common.core.utils.DateUtils;
import com.ruoyi.common.core.utils.MessageUtils;
import com.ruoyi.system.service.ISysUserService;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Value;
import java.time.Duration;
import java.util.Date;
import java.util.List;
import java.util.function.Supplier;
@ -35,6 +41,7 @@ import java.util.function.Supplier;
* 登录校验方法
*
* @author ruoyi
* @author 数据小王子
*/
@RequiredArgsConstructor
@Slf4j
@ -47,11 +54,75 @@ public class SysLoginService {
@Value("${user.password.lockTime}")
private Integer lockTime;
private final ISysPermissionService permissionService;
@Resource
private ISysPermissionService permissionService;
@Autowired
@Resource
private ISysSocialService sysSocialService;
@Resource
private ISysUserService userService;
@Resource
private ISysDeptService deptService;
@Resource
private ISysRoleService roleService;
@Resource
private ISysTenantService tenantService;
/**
* 绑定第三方用户
*
* @param authUserData 授权响应实体
* @return 统一响应实体
*/
public void socialRegister(AuthUser authUserData) {
String authId = authUserData.getSource() + authUserData.getUuid();
// 第三方用户信息
SysSocialBo bo = BeanUtil.toBean(authUserData, SysSocialBo.class);
BeanUtil.copyProperties(authUserData.getToken(), bo);
bo.setUserId(LoginHelper.getUserId());
bo.setAuthId(authId);
bo.setOpenId(authUserData.getUuid());
bo.setUserName(authUserData.getUsername());
bo.setNickName(authUserData.getNickname());
// 查询是否已经绑定用户
List<SysSocialVo> list = sysSocialService.selectListByAuthId(authId);
if (CollUtil.isEmpty(list)) {
// 没有绑定用户, 新增用户信息
sysSocialService.insertByBo(bo);
} else {
// 更新用户信息
bo.setSocialId(list.get(0).getSocialId());
sysSocialService.updateByBo(bo);
}
}
/**
* 退出登录
*/
public void logout() {
try {
LoginUser loginUser = LoginHelper.getLoginUser();
if (ObjectUtil.isNull(loginUser)) {
return;
}
if (LoginHelper.isSuperAdmin()) {
// 超级管理员 登出清除动态租户
TenantHelper.clearDynamic();
}
recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
} catch (NotLoginException ignored) {
} finally {
try {
StpUtil.logout();
} catch (NotLoginException ignored) {
}
}
}
/**
* 登录校验
*/
@ -89,23 +160,34 @@ public class SysLoginService {
/**
* 校验租户
*
* @param tenantId 租户ID
* @param tenantId 租户编号
*/
public void checkTenant(String tenantId) {
if (!TenantHelper.isEnable()) {
return;
}
public void checkTenant(Long tenantId) {
if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
return;
}
if (ObjectUtil.isNull(tenantId)) {
throw new TenantException("tenant.number.not.blank");
}
SysTenantVo tenant = tenantService.selectById(tenantId);
//TODO:完善heckTenant功能
if (ObjectUtil.isNull(tenant)) {
log.info("登录租户:{} 不存在.", tenantId);
throw new TenantException("tenant.not.exists");
} else if (TenantStatus.DISABLE.getCode().equals(tenant.getStatus())) {
log.info("登录租户:{} 已被停用.", tenantId);
throw new TenantException("tenant.blocked");
} else if (ObjectUtil.isNotNull(tenant.getExpireTime())
&& new Date().after(tenant.getExpireTime())) {
log.info("登录租户:{} 已超过有效期.", tenantId);
throw new TenantException("tenant.expired");
}
}
/**
* 构建登录用户
*/
public LoginUser buildLoginUser(SysUser user) {
public LoginUser buildLoginUser(SysUserVo user) {
LoginUser loginUser = new LoginUser();
loginUser.setTenantId(user.getTenantId());
loginUser.setUserId(user.getUserId());
@ -125,13 +207,15 @@ public class SysLoginService {
* 记录登录信息
*
* @param userId 用户ID
* @param version 乐观锁
*/
public void recordLoginInfo(Long userId) {
SysUser sysUser = new SysUser();
public void recordLoginInfo(Long userId,Integer version) {
SysUserBo sysUser = new SysUserBo();
sysUser.setUserId(userId);
sysUser.setLoginIp(ServletUtils.getClientIP());
sysUser.setLoginDate(DateUtils.getNowDate());
sysUser.setUpdateBy(userId);
sysUser.setVersion(version);
userService.updateUserProfile(sysUser);
}
@ -154,22 +238,24 @@ public class SysLoginService {
}
/**
* 退出登录
* 记录登录信息
*
* @param userId 用户ID
*/
public void logout() {
try {
LoginUser loginUser = LoginHelper.getLoginUser();
if (TenantHelper.isEnable() && LoginHelper.isSuperAdmin()) {
// 超级管理员 登出清除动态租户
TenantHelper.clearDynamic();
}
recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
} catch (NotLoginException ignored) {
} finally {
try {
StpUtil.logout();
} catch (NotLoginException ignored) {
}
public void recordLoginInfo(Long userId, String ip) {
SysUserVo sysUserVo = userService.selectUserById(userId);
if (ObjectUtil.isNull(sysUserVo)) {
return;
}
SysUser sysUser = new SysUser();
sysUser.setUserId(userId);
sysUser.setLoginIp(ip);
sysUser.setLoginDate(DateUtils.getNowDate());
sysUser.setUpdateBy(userId);
sysUser.setVersion(sysUserVo.getVersion());
userService.updateById(sysUser);
}
}

View File

@ -8,6 +8,7 @@ import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.SpringUtils;
import com.ruoyi.common.log.event.LogininforEvent;
import com.ruoyi.common.redis.utils.RedisUtils;
import com.ruoyi.common.tenant.helper.TenantHelper;
import com.ruoyi.common.web.config.properties.CaptchaProperties;
import com.ruoyi.system.domain.SysUser;
import com.ruoyi.system.domain.bo.SysUserBo;
@ -30,8 +31,7 @@ import org.springframework.stereotype.Service;
*/
@RequiredArgsConstructor
@Service
public class SysRegisterService
{
public class SysRegisterService {
@Resource
private ISysUserService userService;
@ -43,33 +43,36 @@ public class SysRegisterService
/**
* 注册
*/
public void register(RegisterBody registerBody)
{
public void register(RegisterBody registerBody) {
Long tenantId = registerBody.getTenantId();
String username = registerBody.getUsername();
String password = registerBody.getPassword();
// 校验用户类型是否存在
String userType = UserType.getUserType(registerBody.getUserType()).getUserType();
TenantHelper.dynamic(tenantId, () -> {
String username = registerBody.getUsername();
String password = registerBody.getPassword();
// 校验用户类型是否存在
String userType = UserType.getUserType(registerBody.getUserType()).getUserType();
boolean captchaEnabled = captchaProperties.getEnable();
// 验证码开关
if (captchaEnabled) {
validateCaptcha(tenantId, username, registerBody.getCode(), registerBody.getUuid());
}
SysUser sysUser = new SysUser();
sysUser.setUserName(username);
sysUser.setNickName(username);
sysUser.setPassword(BCrypt.hashpw(password));
sysUser.setUserType(userType);
boolean captchaEnabled = captchaProperties.getEnable();
// 验证码开关
if (captchaEnabled) {
validateCaptcha(tenantId, username, registerBody.getCode(), registerBody.getUuid());
}
SysUserBo sysUser = new SysUserBo();
sysUser.setUserName(username);
sysUser.setNickName(username);
sysUser.setPassword(BCrypt.hashpw(password));
sysUser.setUserType(userType);
if (!userService.checkUserNameUnique(sysUser)) {
throw new UserException("user.register.save.error", username);
}
boolean regFlag = userService.registerUser(sysUser, tenantId);
if (!regFlag) {
throw new UserException("user.register.error");
}
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
boolean unique = userService.checkUserNameUnique(sysUser);
if (!unique) {
throw new UserException("user.register.save.error", username);
}
boolean regFlag = userService.registerUser(sysUser, tenantId);
if (!regFlag) {
throw new UserException("user.register.error");
}
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
});
}
/**
@ -81,7 +84,7 @@ public class SysRegisterService
* @param uuid 唯一标识
*/
public void validateCaptcha(Long 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

@ -3,12 +3,17 @@ package com.ruoyi.web.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.core.core.domain.AjaxResult;
import com.ruoyi.common.core.core.domain.model.EmailLoginBody;
import com.ruoyi.common.json.utils.JsonUtils;
import com.ruoyi.system.domain.vo.SysClientVo;
import com.ruoyi.system.domain.vo.SysUserVo;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.web.domain.vo.LoginVo;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.constant.GlobalConstants;
import com.ruoyi.common.core.core.domain.model.LoginBody;
import com.ruoyi.common.core.core.domain.model.LoginUser;
import com.ruoyi.common.core.enums.LoginType;
import com.ruoyi.common.core.enums.UserStatus;
@ -17,12 +22,8 @@ import com.ruoyi.common.core.exception.user.UserException;
import com.ruoyi.common.core.utils.MessageUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.ValidatorUtils;
import com.ruoyi.common.core.validate.auth.EmailGroup;
import com.ruoyi.common.redis.utils.RedisUtils;
import com.ruoyi.common.security.utils.LoginHelper;
import com.ruoyi.system.domain.SysClient;
import com.ruoyi.system.domain.SysUser;
import com.ruoyi.system.mapper.SysUserMapper;
import com.ruoyi.web.service.IAuthStrategy;
import com.ruoyi.web.service.SysLoginService;
import org.springframework.stereotype.Service;
@ -37,45 +38,42 @@ import org.springframework.stereotype.Service;
@RequiredArgsConstructor
public class EmailAuthStrategy implements IAuthStrategy {
@Resource
private final SysLoginService loginService;
private final SysUserMapper userMapper;
@Resource
private ISysUserService userService;
@Override
public void validate(LoginBody loginBody) {
ValidatorUtils.validate(loginBody, EmailGroup.class);
}
@Override
public String login(String clientId, LoginBody loginBody, SysClient client) {
public LoginVo login(String body, SysClientVo client) {
EmailLoginBody loginBody = JsonUtils.parseObject(body, EmailLoginBody.class);
ValidatorUtils.validate(loginBody);
Long tenantId = loginBody.getTenantId();
String email = loginBody.getEmail();
String emailCode = loginBody.getEmailCode();
// 通过邮箱查找用户
SysUser user = loadUserByEmail(tenantId, email);
SysUserVo user = loadUserByEmail(tenantId, email);
loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
LoginUser loginUser = loginService.buildLoginUser(user);
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, clientId);
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token
LoginHelper.login(loginUser, model);
loginService.recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
loginService.recordLoginInfo(user.getUserId());
// LoginVo loginVo = new LoginVo();
// loginVo.setAccessToken(StpUtil.getTokenValue());
// loginVo.setExpireIn(StpUtil.getTokenTimeout());
// loginVo.setClientId(clientId);
// return loginVo;
return StpUtil.getTokenValue();
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
return loginVo;
}
/**
@ -90,12 +88,8 @@ public class EmailAuthStrategy implements IAuthStrategy {
return code.equals(emailCode);
}
private SysUser loadUserByEmail(Long tenantId, String email) {
// SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
// .select(SysUser::getEmail, SysUser::getStatus)
// .eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId)
// .eq(SysUser::getEmail, email));
SysUser user =userMapper.selectUserByEmail(email);
private SysUserVo loadUserByEmail(Long tenantId, String email) {
SysUserVo user = userService.selectTenantUserByEmail(tenantId, email);
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", email);
throw new UserException("user.not.exists", email);
@ -103,10 +97,8 @@ public class EmailAuthStrategy implements IAuthStrategy {
log.info("登录用户:{} 已被停用.", email);
throw new UserException("user.blocked", email);
}
// if (TenantHelper.isEnable()) {
// return userMapper.selectTenantUserByEmail(email, tenantId);
// }
return user;
}
}

View File

@ -4,14 +4,15 @@ 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 com.ruoyi.common.core.core.domain.AjaxResult;
import com.ruoyi.common.core.core.domain.model.PasswordLoginBody;
import com.ruoyi.common.json.utils.JsonUtils;
import com.ruoyi.system.domain.vo.SysClientVo;
import com.ruoyi.system.service.ISysUserService;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.constant.GlobalConstants;
import com.ruoyi.common.core.core.domain.model.LoginBody;
import com.ruoyi.common.core.core.domain.model.LoginUser;
import com.ruoyi.common.core.enums.LoginType;
import com.ruoyi.common.core.enums.UserStatus;
@ -21,14 +22,10 @@ import com.ruoyi.common.core.exception.user.UserException;
import com.ruoyi.common.core.utils.MessageUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.ValidatorUtils;
import com.ruoyi.common.core.validate.auth.PasswordGroup;
import com.ruoyi.common.redis.utils.RedisUtils;
import com.ruoyi.common.security.utils.LoginHelper;
import com.ruoyi.common.web.config.properties.CaptchaProperties;
import com.ruoyi.system.domain.SysClient;
import com.ruoyi.system.domain.SysUser;
import com.ruoyi.system.domain.vo.SysUserVo;
import com.ruoyi.system.mapper.SysUserMapper;
import com.ruoyi.web.domain.vo.LoginVo;
import com.ruoyi.web.service.IAuthStrategy;
import com.ruoyi.web.service.SysLoginService;
@ -45,19 +42,15 @@ import org.springframework.stereotype.Service;
public class PasswordAuthStrategy implements IAuthStrategy {
private final CaptchaProperties captchaProperties;
private final SysLoginService loginService;
@Resource
private SysLoginService loginService;
@Resource
private ISysUserService userService;
//private final SysUserMapper userMapper;
@Override
public void validate(LoginBody loginBody) {
ValidatorUtils.validate(loginBody, PasswordGroup.class);
}
@Override
public String login(String clientId, LoginBody loginBody, SysClient client) {
public LoginVo login(String body, SysClientVo client) {
PasswordLoginBody loginBody = JsonUtils.parseObject(body, PasswordLoginBody.class);
ValidatorUtils.validate(loginBody);
Long tenantId = loginBody.getTenantId();
String username = loginBody.getUsername();
String password = loginBody.getPassword();
@ -70,31 +63,28 @@ public class PasswordAuthStrategy implements IAuthStrategy {
validateCaptcha(tenantId, username, code, uuid);
}
SysUser user = loadUserByUsername(tenantId, username);
SysUserVo user = loadUserByUsername(tenantId, username);
loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
// 此处可根据登录用户的数据不同 自行创建 loginUser
LoginUser loginUser = loginService.buildLoginUser(user);
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, clientId);
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
// 生成token
LoginHelper.login(loginUser, model);
loginService.recordLogininfor(loginUser.getTenantId(), username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
loginService.recordLoginInfo(user.getUserId());
// LoginVo loginVo = new LoginVo();
// loginVo.setAccessToken(StpUtil.getTokenValue());
// loginVo.setExpireIn(StpUtil.getTokenTimeout());
// loginVo.setClientId(clientId);
// return loginVo;
return StpUtil.getTokenValue();
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
return loginVo;
}
/**
@ -118,20 +108,16 @@ public class PasswordAuthStrategy implements IAuthStrategy {
}
}
private SysUser loadUserByUsername(Long tenantId, String username) {
//TODO:以后根据tenantId条件过滤查询
SysUser user = userService.selectUserByUserName(username);
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new UserException("user.not.exists", username);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new UserException("user.blocked", username);
}
// if (TenantHelper.isEnable()) {
// return userMapper.selectTenantUserByUserName(username, tenantId);
// }
return user;
private SysUserVo loadUserByUsername(Long tenantId, String username) {
SysUserVo user = userService.selectTenantUserByUserName(tenantId,username);
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new UserException("user.not.exists", username);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new UserException("user.blocked", username);
}
return user;
}
}

View File

@ -0,0 +1,103 @@
package com.ruoyi.web.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.core.core.domain.model.LoginUser;
import com.ruoyi.common.core.core.domain.model.SmsLoginBody;
import com.ruoyi.system.service.ISysUserService;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.constant.GlobalConstants;
import com.ruoyi.common.core.enums.LoginType;
import com.ruoyi.common.core.enums.UserStatus;
import com.ruoyi.common.core.exception.user.CaptchaExpireException;
import com.ruoyi.common.core.exception.user.UserException;
import com.ruoyi.common.core.utils.MessageUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.ValidatorUtils;
import com.ruoyi.common.json.utils.JsonUtils;
import com.ruoyi.common.redis.utils.RedisUtils;
import com.ruoyi.common.security.utils.LoginHelper;
import com.ruoyi.system.domain.vo.SysClientVo;
import com.ruoyi.system.domain.vo.SysUserVo;
import com.ruoyi.web.domain.vo.LoginVo;
import com.ruoyi.web.service.IAuthStrategy;
import com.ruoyi.web.service.SysLoginService;
import org.springframework.stereotype.Service;
/**
* 短信认证策略
*
* @author Michelle.Chung
*/
@Slf4j
@Service("sms" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class SmsAuthStrategy implements IAuthStrategy {
@Resource
private SysLoginService loginService;
@Resource
private ISysUserService userService;
@Override
public LoginVo login(String body, SysClientVo client) {
SmsLoginBody loginBody = JsonUtils.parseObject(body, SmsLoginBody.class);
ValidatorUtils.validate(loginBody);
Long tenantId = loginBody.getTenantId();
String phonenumber = loginBody.getPhonenumber();
String smsCode = loginBody.getSmsCode();
// 通过手机号查找用户
SysUserVo user = loadUserByPhonenumber(tenantId, phonenumber);
loginService.checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
LoginUser loginUser = loginService.buildLoginUser(user);
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
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 boolean validateSmsCode(Long tenantId, String phonenumber, String smsCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber);
if (StringUtils.isBlank(code)) {
loginService.recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
return code.equals(smsCode);
}
private SysUserVo loadUserByPhonenumber(Long tenantId, String phonenumber) {
SysUserVo user = userService.selectTenantUserByPhonenumber(tenantId, phonenumber);
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", phonenumber);
throw new UserException("user.not.exists", phonenumber);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", phonenumber);
throw new UserException("user.blocked", phonenumber);
}
return user;
}
}

View File

@ -0,0 +1,117 @@
package com.ruoyi.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.util.ObjectUtil;
import com.ruoyi.system.service.ISysUserService;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import com.ruoyi.common.core.core.domain.model.LoginUser;
import com.ruoyi.common.core.core.domain.model.SocialLoginBody;
import com.ruoyi.common.core.enums.UserStatus;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.exception.user.UserException;
import com.ruoyi.common.core.utils.ValidatorUtils;
import com.ruoyi.common.json.utils.JsonUtils;
import com.ruoyi.common.security.utils.LoginHelper;
import com.ruoyi.common.social.config.properties.SocialProperties;
import com.ruoyi.common.social.utils.SocialUtils;
import com.ruoyi.system.domain.vo.SysClientVo;
import com.ruoyi.system.domain.vo.SysSocialVo;
import com.ruoyi.system.domain.vo.SysUserVo;
import com.ruoyi.system.service.ISysSocialService;
import com.ruoyi.web.domain.vo.LoginVo;
import com.ruoyi.web.service.IAuthStrategy;
import com.ruoyi.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;
@Resource
private ISysSocialService sysSocialService;
@Resource
private ISysUserService userService;
@Resource
private 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();
List<SysSocialVo> list = sysSocialService.selectListByAuthId(authUserData.getSource() + authUserData.getUuid());
if (CollUtil.isEmpty(list)) {
throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!");
}
Optional<SysSocialVo> opt = list.stream().filter(x -> x.getTenantId().equals(loginBody.getTenantId())).findAny();
if (opt.isEmpty()) {
throw new ServiceException("对不起,你没有权限登录当前租户!");
}
SysSocialVo social = opt.get();
// 查找用户
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(Long tenantId, Long userId) {
SysUserVo user = userService.selectTenantUserById(tenantId, 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

@ -0,0 +1,93 @@
package com.ruoyi.web.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.ObjectUtil;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.ruoyi.common.core.core.domain.model.XcxLoginBody;
import com.ruoyi.common.core.core.domain.model.XcxLoginUser;
import com.ruoyi.common.core.enums.UserStatus;
import com.ruoyi.common.core.utils.ValidatorUtils;
import com.ruoyi.common.json.utils.JsonUtils;
import com.ruoyi.common.security.utils.LoginHelper;
import com.ruoyi.system.domain.vo.SysClientVo;
import com.ruoyi.system.domain.vo.SysUserVo;
import com.ruoyi.web.domain.vo.LoginVo;
import com.ruoyi.web.service.IAuthStrategy;
import com.ruoyi.web.service.SysLoginService;
import org.springframework.stereotype.Service;
/**
* 小程序认证策略
*
* @author Michelle.Chung
*/
@Slf4j
@Service("xcx" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class XcxAuthStrategy implements IAuthStrategy {
@Resource
private SysLoginService loginService;
@Override
public LoginVo login(String body, SysClientVo client) {
XcxLoginBody loginBody = JsonUtils.parseObject(body, XcxLoginBody.class);
ValidatorUtils.validate(loginBody);
// xcxCode 小程序调用 wx.login 授权后获取
String xcxCode = loginBody.getXcxCode();
// 多个小程序识别使用
String appid = loginBody.getAppid();
// todo 以下自行实现
// 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key openid
String openid = "";
// 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可
SysUserVo user = loadUserByOpenid(openid);
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
XcxLoginUser loginUser = new XcxLoginUser();
loginUser.setTenantId(user.getTenantId());
loginUser.setUserId(user.getUserId());
loginUser.setUsername(user.getUserName());
loginUser.setNickname(user.getNickName());
loginUser.setUserType(user.getUserType());
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
loginUser.setOpenid(openid);
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());
loginVo.setOpenid(openid);
return loginVo;
}
private SysUserVo loadUserByOpenid(String openid) {
// 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
// todo 自行实现 userService.selectUserByOpenid(openid);
SysUserVo user = new SysUserVo();
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", openid);
// todo 用户不存在 业务逻辑自行实现
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", openid);
// todo 用户已被停用 业务逻辑自行实现
}
return user;
}
}

View File

@ -1,4 +1,4 @@
restart.include.json=/com.alibaba.fastjson.*.jar
restart.include.json=/com.alibaba.fastjson2.*.jar
restart.include.mapper=/mapper-[\\w-\\.].jar
restart.include.pagehelper=/pagehelper-[\\w-\\.].jar
restart.include.mybatis-flex=/mybatis-flex-[\\w-\\.]+jar

View File

@ -1,70 +1,43 @@
# 数据源及mybatis、mybatis-flex配置
--- # 数据源配置
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
hikari:
# 最大连接池数量
maximum-pool-size: 20
# 最小空闲线程数量
minimum-idle: 10
# 配置获取连接等待超时的时间
connectionTimeout: 30000
# 校验超时时间
validationTimeout: 5000
# 空闲连接存活最大时间默认10分钟
idleTimeout: 600000
# 此属性控制池中连接的最长生命周期值0表示无限生命周期默认30分钟
maxLifetime: 1800000
# 多久检查一次连接的活性
keepaliveTime: 30000
mybatis-flex:
# 搜索指定包别名
typeAliasesPackage: com.ruoyi.**.domain
# 不支持多包, 如有需要可在注解配置 或 提升扫包等级com.**.**.mapper
mapperPackage: com.ruoyi.**.mapper
# 配置mapper的扫描找到所有的mapper.xml映射文件
mapperLocations: classpath*:mapper/**/*Mapper.xml
#本部分Configuration的配置都为 MyBatis 原生支持的配置,有关配置请参考:https://mybatis.org/mybatis-3/zh/configuration.html#%E8%AE%BE%E7%BD%AE%EF%BC%88settings%EF%BC%89
configuration:
# 自动驼峰命名规则camel case映射
mapUnderscoreToCamelCase: true
# MyBatis 自动映射策略
# NONE不启用 PARTIAL只对非嵌套 resultMap 自动映射 FULL对所有 resultMap 自动映射
autoMappingBehavior: FULL
# MyBatis 自动映射时未知列或未知属性处理策
# NONE不做处理 WARNING打印相关警告 FAILING抛出异常和详细信息
autoMappingUnknownColumnBehavior: NONE
# 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
# 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl
# 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
logImpl: org.apache.ibatis.logging.slf4j.Slf4jImpl
cacheEnabled: true
useGeneratedKeys: true
defaultExecutorType: SIMPLE
# MyBatis-Flex全局配置
global-config:
# 是否控制台打印 MyBatis-Flex 的 LOGO 及版本号
print-banner: true
# 逻辑删除数据存在标记值
normal-value-of-logic-delete: 0
# 逻辑删除数据存在标记值
deleted-value-of-logic-delete: 1
# sql审计
audit_enable: true
# sql打印
sql_print: true
datasource:
# 数据源-1
PrimaryDS:
# 指定为HikariDataSource
type: com.zaxxer.hikari.HikariDataSource
ds1:
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
type: ${spring.datasource.type}
# mysql数据库
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
username: root123
password: Root@369---0000
hikari:
#连接池名
pool-name: HikariCP-PrimaryDS
#最小空闲连接数
minimum-idle: 5
# 空闲连接存活最大时间默认10分钟
idle-timeout: 600000
# 连接池最大连接数默认是10
maximum-pool-size: 10
# 此属性控制从池返回的连接的默认自动提交行为,默认值true
auto-commit: true
# 此属性控制池中连接的最长生命周期值0表示无限生命周期默认30分钟
max-lifetime: 1800000
# 数据库连接超时时间,默认30秒
connection-timeout: 30000
# 校验超时时间
validationTimeout: 5000
# 多久检查一次连接的活性
keepaliveTime: 30000
# 连接测试query
connection-test-query: SELECT 1
url: jdbc:mysql://localhost:3306/ruoyi-flex?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
username: root
password: Root@369
#postgresql数据库
# driver-class-name: org.postgresql.Driver
# url: jdbc:postgresql://localhost:5432/ruoyi-flex?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
# username: postgres
# password: postgres@369
# redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
spring.data:
@ -104,22 +77,10 @@ redisson:
# 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50
--- # Actuator 监控端点的配置项
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: ALWAYS
logfile:
external-file: ./logs/ruoyi-monitor.log
--- # 监控中心配置
--- # 监控中心客户端配置
spring.boot.admin.client:
# 增加客户端开关
enabled: true
enabled: false
url: http://localhost:9090/admin
instance:
service-host-type: IP
@ -130,7 +91,7 @@ spring.boot.admin.client:
powerjob:
worker:
# 如何开启调度中心请查看文档教程
enabled: true
enabled: false
# 需要先在 powerjob 登录页执行应用注册后才能使用
app-name: ruoyi-worker
# 28080 端口 随着主应用端口飘逸 避免集群冲突
@ -138,8 +99,149 @@ powerjob:
protocol: http
server-address: 127.0.0.1:7700
store-strategy: disk
enable-test-mode: false
allow-lazy-connect-server: false
max-appended-wf-context-length: 4096
max-result-length: 4096
--- # easy-retry 配置
easy-retry:
enabled: false
# 需要在EasyRetry后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
group-name: "ruoyi_group"
# EasyRetry接入验证令牌 详见 script/sql/easy_retry.sql `er_group_config` 表
token: "ER_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
server:
host: 127.0.0.1
port: 1788
# 详见 script/sql/easy_retry.sql `er_namespace` 表
namespace: ${spring.profiles.active}
--- # 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: 91436b7940090**********d67eea73acbf61b6b590751a98
client-secret: 02c6fcfd70342980cd8**********c754d7a264c4e125f9ba915ac
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

@ -1,73 +1,43 @@
# 临时文件存储位置 避免临时文件被系统清理报错
spring.servlet.multipart.location: /ruoyi/server/temp
# 数据源及mybatis、mybatis-flex配置
--- # 数据源配置
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
hikari:
# 最大连接池数量
maximum-pool-size: 20
# 最小空闲线程数量
minimum-idle: 10
# 配置获取连接等待超时的时间
connectionTimeout: 30000
# 校验超时时间
validationTimeout: 5000
# 空闲连接存活最大时间默认10分钟
idleTimeout: 600000
# 此属性控制池中连接的最长生命周期值0表示无限生命周期默认30分钟
maxLifetime: 1800000
# 多久检查一次连接的活性
keepaliveTime: 30000
mybatis-flex:
# 搜索指定包别名
typeAliasesPackage: com.ruoyi.**.domain
# 配置mapper的扫描找到所有的mapper.xml映射文件
mapper-locations: classpath*:mapper/**/*Mapper.xml
cacheEnabled: true
useGeneratedKeys: true
defaultExecutorType: SIMPLE
#本部分Configuration的配置都为 MyBatis 原生支持的配置,有关配置请参考:https://mybatis.org/mybatis-3/zh/configuration.html#%E8%AE%BE%E7%BD%AE%EF%BC%88settings%EF%BC%89
configuration:
# 自动驼峰命名规则camel case映射
mapUnderscoreToCamelCase: true
# MyBatis 自动映射策略
# NONE不启用 PARTIAL只对非嵌套 resultMap 自动映射 FULL对所有 resultMap 自动映射
autoMappingBehavior: FULL
# MyBatis 自动映射时未知列或未知属性处理策
# NONE不做处理 WARNING打印相关警告 FAILING抛出异常和详细信息
autoMappingUnknownColumnBehavior: NONE
# 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
# 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl
# 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
logImpl: org.apache.ibatis.logging.slf4j.Slf4jImpl
# MyBatis-Flex全局配置
global-config:
# 是否控制台打印 MyBatis-Flex 的 LOGO 及版本号
print-banner: false
# 逻辑删除数据存在标记值
normal-value-of-logic-delete: 0
# 逻辑删除数据存在标记值
deleted-value-of-logic-delete: 1
# sql审计
audit_enable: false
# sql打印
sql_print: false
datasource:
# 数据源-1
PrimaryDS:
# 指定为HikariDataSource
type: com.zaxxer.hikari.HikariDataSource
ds1:
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
type: ${spring.datasource.type}
# mysql数据库
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
username: root123
password: Root@369---0000
hikari:
#连接池名
pool-name: HikariCP-PrimaryDS
#最小空闲连接数
minimum-idle: 10
# 空闲连接存活最大时间默认10分钟
idle-timeout: 600000
# 连接池最大连接数默认是10
maximum-pool-size: 20
# 此属性控制从池返回的连接的默认自动提交行为,默认值true
auto-commit: true
# 此属性控制池中连接的最长生命周期值0表示无限生命周期默认30分钟
max-lifetime: 1800000
# 数据库连接超时时间,默认30秒
connection-timeout: 30000
# 校验超时时间
validationTimeout: 5000
# 多久检查一次连接的活性
keepaliveTime: 30000
# 连接测试query
connection-test-query: SELECT 1
url: jdbc:mysql://localhost:3306/ruoyi-flex?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true
username: root
password: Root@369
#postgresql数据库
# driver-class-name: org.postgresql.Driver
# url: jdbc:postgresql://localhost:5432/ruoyi-flex?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
# username: postgres
# password: postgres@369
# redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
spring.data:
@ -107,19 +77,7 @@ redisson:
# 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50
--- # Actuator 监控端点的配置项
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: ALWAYS
logfile:
external-file: ./logs/ruoyi-monitor.log
--- # 监控中心配置
--- # 监控中心客户端配置
spring.boot.admin.client:
# 增加客户端开关
enabled: true
@ -136,12 +94,155 @@ powerjob:
enabled: true
# 需要先在 powerjob 登录页执行应用注册后才能使用
app-name: ruoyi-worker
enable-test-mode: false
max-appended-wf-context-length: 4096
max-result-length: 4096
# 28080 端口 随着主应用端口飘逸 避免集群冲突
port: 2${server.port}
protocol: http
server-address: 127.0.0.1:7700
store-strategy: disk
allow-lazy-connect-server: false
max-appended-wf-context-length: 4096
max-result-length: 4096
--- # easy-retry 配置
easy-retry:
enabled: false
# 需要在EasyRetry后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
group-name: "ruoyi_group"
# EasyRetry接入验证令牌 详见 script/sql/easy_retry.sql `er_group_config` 表
token: "ER_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
server:
host: 127.0.0.1
port: 1788
# 详见 script/sql/easy_retry.sql `er_namespace` 表
namespace: ${spring.profiles.active}
--- # 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: 91436b7940090**********d67eea73acbf61b6b590751a98
client-secret: 02c6fcfd70342980cd8**********c754d7a264c4e125f9ba915ac
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

@ -1,15 +1,15 @@
# 项目相关配置
ruoyi:
# 名称
name: RuoYi-Flex
name: Ruoyi-Flex
# 版本
version: 4.1.6
version: ${revision}
# 版权年份
copyrightYear: 2023
copyrightYear: 2023 ~ 2024
# 实例演示开关
demoEnabled: true
# 文件路径 示例( Windows配置D:/ruoyi/uploadPathLinux配置 /home/ruoyi/uploadPath
profile: D:/ruoyi/uploadPath
profile: /home/ruoyi/uploadPath
# 获取ip地址开关
addressEnabled: false
@ -33,27 +33,35 @@ server:
# 应用的访问路径
context-path: /
# tomcat web容器配置
tomcat:
# tomcat的URI编码
uri-encoding: UTF-8
# 连接数满后的排队数默认为100
accept-count: 1000
# undertow web容器配置
undertow:
# HTTP post内容的最大大小。当值为-1时默认值为大小是无限的
max-http-post-size: -1
# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
# 每块buffer的空间大小,越小的空间被利用越充分
buffer-size: 512
# 是否分配的直接内存
direct-buffers: true
threads:
# 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
io: 8
# 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
worker: 256
# undertow:
# # HTTP post内容的最大大小。当值为-1时默认值为大小是无限的
# max-http-post-size: -1
# # 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
# # 每块buffer的空间大小,越小的空间被利用越充分
# buffer-size: 512
# # 是否分配的直接内存
# direct-buffers: true
# threads:
# # 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
# io: 8
# # 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
# worker: 256
# 日志配置
logging:
level:
com.ruoyi: debug
com.ruoyi: @logging.level@
org.springframework: warn
tech.powerjob.worker.background: warn
org.mybatis.spring.mapper: error
config: classpath:logback.xml
# 用户配置
@ -68,6 +76,10 @@ user:
spring:
application:
name: ${ruoyi.name}
threads:
# 启用JAVA21虚拟线程
virtual:
enabled: true
# 资源信息
messages:
# 国际化资源文件路径
@ -101,30 +113,90 @@ spring:
# 热部署开关
enabled: true
# token配置
token:
# 令牌自定义标识
header: Authorization
# 令牌密钥
secret: abcdefghijklmnopqrstuvwxyz
# 令牌有效期默认30分钟
expireTime: 30
# PageHelper分页插件
pagehelper:
helperDialect: mysql
#helperDialect: mysql、postgresql pagehelper分页插件会自动检测当前的数据库链接自动选择合适的分页方式。
supportMethodsArguments: true
params: count=countSql
# MyBatisFlex公共配置
# https://mybatis-flex.com/zh/base/configuration.html
mybatis-flex:
# 搜索指定包别名
type-aliases-package: com.ruoyi.**.domain
# 多包名使用 例如 org.dromara.**.mapper,org.xxx.**.mapper
mapper-package: com.ruoyi.**.mapper
# 配置mapper的扫描找到所有的mapper.xml映射文件
mapper-locations: classpath*:mapper/**/*Mapper.xml
configuration:
## 以下为mybatis原生配置 https://mybatis.org/mybatis-3/zh/configuration.html
# 自动驼峰命名规则camel case映射
map_underscore_to_camel_case: true
# MyBatis 自动映射策略
# NONE不启用 PARTIAL只对非嵌套 resultMap 自动映射 FULL对所有 resultMap 自动映射
auto_mapping_behavior: FULL
# MyBatis 自动映射时未知列或未知属性处理策
# NONE不做处理 WARNING打印相关警告 FAILING抛出异常和详细信息
auto_mapping_unknown_column_behavior: NONE
# 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
# 关闭日志记录 org.apache.ibatis.logging.nologging.NoLoggingImpl
# 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
#log_impl: org.apache.ibatis.logging.stdout.StdOutImpl
logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
cacheEnabled: true
global-config:
# 是否控制台打印 MyBatis-Flex 的 LOGO 及版本号
print-banner: true
# 全局的 ID 生成策略配置:雪花算法
key-config:
key-type: Generator
value: snowFlakeId
# 逻辑未删除值
normal-value-of-logic-delete: 0
# 逻辑已删除值(框架表均使用此值 禁止随意修改)
deleted-value-of-logic-delete: 1
# 默认的逻辑删除字段
logic-delete-column: del_flag
# 默认的多租户字段
tenant-column: tenant_id
# 默认的乐观锁字段
version-column: version
# 数据加密
mybatis-encryptor:
# 是否开启加密
enable: false
# 默认加密算法
algorithm: BASE64
# 编码方式 BASE64/HEX。默认BASE64
encode: BASE64
# 安全秘钥 对称算法的秘钥 如AESSM4
password:
# 公私钥 非对称算法的公私钥 如SM2RSA
publicKey:
privateKey:
# api接口加密
api-decrypt:
# 是否开启全局接口加密
enabled: true
# AES 加密头标识
headerFlag: encrypt-key
# 响应加密公钥 非对称算法的公私钥 如SM2RSA 使用者请自行更换
# 对应前端解密私钥 MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=
publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJnNwrj4hi/y3CCJu868ghCG5dUj8wZK++RNlTLcXoMmdZWEQ/u02RgD5LyLAXGjLOjbMtC+/J9qofpSGTKSx/MCAwEAAQ==
# 请求解密私钥 非对称算法的公私钥 如SM2RSA 使用者请自行更换
# 对应前端加密公钥 MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==
privateKey: MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKNPuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gAkM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWowcSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99EcvDQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthhYhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3UP8iWi1Qw0Y=
# SpringDoc配置
springdoc:
#需要扫描的包,可以配置多个,使用逗号分割
packages-to-scan: com.ruoyi
paths-to-exclude: #配置不包含在swagger文档中的api
- /api/test/**
- /api/mockito/data
swagger-ui:
enabled: true #开启/禁止swagger,prod可以设置为false
version: 5.11.8 #指定swagger-ui的版本号
disable-swagger-default-url: true #禁用default petstore url
path: /swagger-ui.html #swagger页面
persistAuthorization: true # 持久化认证数据,如果设置为 true它会保留授权数据并且不会在浏览器关闭/刷新时丢失
@ -147,20 +219,24 @@ springdoc:
email: 738981257@qq.com
url: https://gitee.com/dataprince/ruoyi-flex
components:
# 鉴权方式配置
# 鉴权方式配置
security-schemes:
apiKey:
type: APIKEY
in: HEADER
name: ${sa-token.token-name}
group-configs:
- group: 1.演示模块
packages-to-scan: com.ruoyi.demo
- group: 2.通用模块
- group: 1.web模块
packages-to-scan: com.ruoyi.web
- group: 2.演示模块
packages-to-scan:
- com.ruoyi.demo
- com.ruoyi.mf
- group: 3.通用模块
packages-to-scan: com.ruoyi.common
- group: 3.系统模块
- group: 4.系统模块
packages-to-scan: com.ruoyi.system
- group: 4.代码生成模块
- group: 5.代码生成模块
packages-to-scan: com.ruoyi.generator
# 防止XSS攻击
@ -172,15 +248,6 @@ xss:
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*,/demo/*
# 全局线程池相关配置
thread-pool:
# 是否开启线程池
enabled: false
# 队列最大长度
queueCapacity: 128
# 线程池维护线程所允许的空闲时间
keepAliveSeconds: 300
# 分布式锁 lock4j 全局配置
lock4j:
# 获取分布式锁超时时间,默认为 3000 毫秒
@ -217,6 +284,7 @@ security:
- /**/*.html
- /**/*.css
- /**/*.js
- /profile/**
# 公共路径
- /favicon.ico
- /error
@ -232,21 +300,8 @@ security:
- /captchaImage
- /captcha/get
- /captcha/check
# 多租户配置
tenant:
# 是否开启
enable: false
# 排除表
excludes:
- sys_menu
- sys_tenant
- sys_tenant_package
- sys_role_dept
- sys_role_menu
- sys_user_post
- sys_user_role
- sys_client
- /genKeyPair
- /job/**
--- # Actuator 监控端点的配置项
management:

View File

@ -29,6 +29,7 @@ user.notfound=请重新登录
user.forcelogout=管理员强制退出,请重新登录
user.unknown.error=未知错误,请重新登录
auth.grant.type.error=认证权限类型错误
auth.grant.type.blocked=认证权限类型已禁用
auth.grant.type.not.blank=认证权限类型不能为空
auth.clientid.not.blank=认证客户端id不能为空
##文件上传消息
@ -49,7 +50,10 @@ sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}
email.code.not.blank=邮箱验证码不能为空
email.code.retry.limit.count=邮箱验证码输入错误{0}次
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
xcx.code.not.blank=小程序code不能为空
xcx.code.not.blank=小程序[code]不能为空
social.source.not.blank=第三方登录平台[source]不能为空
social.code.not.blank=第三方登录平台[code]不能为空
social.state.not.blank=第三方登录平台[state]不能为空
##租户
tenant.number.not.blank=租户编号不能为空
tenant.not.exists=对不起, 您的租户不存在,请联系管理员

View File

@ -29,6 +29,7 @@ user.notfound=Please login again
user.forcelogout=The administrator is forced to exitplease login again
user.unknown.error=Unknown error, please login again
auth.grant.type.error=Auth grant type error
auth.grant.type.blocked=Auth grant type disabled
auth.grant.type.not.blank=Auth grant type cannot be blank
auth.clientid.not.blank=Auth clientid cannot be blank
##文件上传消息
@ -49,7 +50,10 @@ sms.code.retry.limit.exceed=Sms code input error {0} times, account locked for {
email.code.not.blank=Email code cannot be blank
email.code.retry.limit.count=Email code input error {0} times
email.code.retry.limit.exceed=Email code input error {0} times, account locked for {1} minutes
xcx.code.not.blank=Mini program code cannot be blank
xcx.code.not.blank=Mini program [code] cannot be blank
social.source.not.blank=Social login platform [source] cannot be blank
social.code.not.blank=Social login platform [code] cannot be blank
social.state.not.blank=Social login platform [state] cannot be blank
##租户
tenant.number.not.blank=Tenant number cannot be blank
tenant.not.exists=Sorry, your tenant does not exist. Please contact the administrator

View File

@ -29,6 +29,7 @@ user.notfound=请重新登录
user.forcelogout=管理员强制退出,请重新登录
user.unknown.error=未知错误,请重新登录
auth.grant.type.error=认证权限类型错误
auth.grant.type.blocked=认证权限类型已禁用
auth.grant.type.not.blank=认证权限类型不能为空
auth.clientid.not.blank=认证客户端id不能为空
##文件上传消息
@ -49,7 +50,10 @@ sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}
email.code.not.blank=邮箱验证码不能为空
email.code.retry.limit.count=邮箱验证码输入错误{0}次
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
xcx.code.not.blank=小程序code不能为空
xcx.code.not.blank=小程序[code]不能为空
social.source.not.blank=第三方登录平台[source]不能为空
social.code.not.blank=第三方登录平台[code]不能为空
social.state.not.blank=第三方登录平台[state]不能为空
##租户
tenant.number.not.blank=租户编号不能为空
tenant.not.exists=对不起, 您的租户不存在,请联系管理员

Binary file not shown.

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志存放路径 -->
<property name="log.path" value="/home/ruoyi/logs" />
<property name="log.path" value="./home/ruoyi/logs" />
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
@ -11,7 +11,7 @@
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-info.log</file>
@ -34,7 +34,7 @@
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
@ -56,7 +56,7 @@
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 用户访问日志输出 -->
<appender name="sys-user" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-user.log</file>
@ -70,7 +70,7 @@
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统模块日志级别控制 -->
<logger name="com.ruoyi" level="info" />
<!-- Spring日志级别控制 -->
@ -79,15 +79,15 @@
<root level="info">
<appender-ref ref="console" />
</root>
<!--系统操作日志-->
<root level="info">
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
</root>
<!--系统用户操作日志-->
<logger name="sys-user" level="info">
<appender-ref ref="sys-user"/>
</logger>
</configuration>
</configuration>

View File

@ -13,18 +13,23 @@
<modules>
<module>ruoyi-common-bom</module>
<module>ruoyi-common-core</module>
<module>ruoyi-common-encrypt</module>
<module>ruoyi-common-excel</module>
<module>ruoyi-common-job</module>
<module>ruoyi-common-json</module>
<module>ruoyi-common-log</module>
<module>ruoyi-common-mail</module>
<module>ruoyi-common-orm</module>
<module>ruoyi-common-oss</module>
<module>ruoyi-common-ratelimiter</module>
<module>ruoyi-common-redis</module>
<module>ruoyi-common-security</module>
<module>ruoyi-common-sms</module>
<module>ruoyi-common-social</module>
<module>ruoyi-common-springdoc</module>
<module>ruoyi-common-tenant</module>
<module>ruoyi-common-translation</module>
<module>ruoyi-common-web</module>
<module>ruoyi-common-websocket</module>
</modules>
<artifactId>ruoyi-common</artifactId>

View File

@ -14,7 +14,7 @@
</description>
<properties>
<revision>4.1.7-SNAPSHOT</revision>
<revision>5.2.0-SNAPSHOT</revision>
</properties>
<dependencyManagement>
@ -26,6 +26,13 @@
<version>${revision}</version>
</dependency>
<!-- 加解密模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-encrypt</artifactId>
<version>${revision}</version>
</dependency>
<!-- excel模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
@ -54,6 +61,13 @@
<version>${revision}</version>
</dependency>
<!-- 邮件模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-mail</artifactId>
<version>${revision}</version>
</dependency>
<!-- 数据库映射模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
@ -61,6 +75,13 @@
<version>${revision}</version>
</dependency>
<!-- oss对象存储服务模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-oss</artifactId>
<version>${revision}</version>
</dependency>
<!-- 限流公共服务 -->
<dependency>
<groupId>com.ruoyi</groupId>
@ -82,6 +103,20 @@
<version>${revision}</version>
</dependency>
<!-- 短信模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-sms</artifactId>
<version>${revision}</version>
</dependency>
<!-- 三方授权认证模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-social</artifactId>
<version>${revision}</version>
</dependency>
<!-- 接口模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
@ -96,13 +131,6 @@
<version>${revision}</version>
</dependency>
<!-- 翻译模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-translation</artifactId>
<version>${revision}</version>
</dependency>
<!-- web服务 -->
<dependency>
<groupId>com.ruoyi</groupId>
@ -110,6 +138,13 @@
<version>${revision}</version>
</dependency>
<!-- WebSocket模块 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-websocket</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@ -60,12 +60,6 @@
<artifactId>fastjson2</artifactId>
</dependency>
<!-- io常用工具类 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<!-- excel工具 -->
<dependency>
<groupId>org.apache.poi</groupId>
@ -131,12 +125,6 @@
<artifactId>hutool-extra</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-json</artifactId>
<scope>provided</scope>
</dependency>
<!-- mapstruct-plus -->
<dependency>
<groupId>io.github.linpeilie</groupId>
@ -149,6 +137,22 @@
<artifactId>pagehelper</artifactId>
</dependency>
<!-- 离线IP地址定位库 -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
</dependency>
<!-- findbugs消除打包警告 -->
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
</dependency>
</dependencies>

View File

@ -2,6 +2,7 @@ package com.ruoyi.common.core.config;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.scheduling.annotation.EnableAsync;
/**
* 程序注解配置
@ -11,7 +12,7 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy;
@AutoConfiguration
// 表示通过aop框架暴露该代理对象,AopContext能够访问
@EnableAspectJAutoProxy(exposeProxy = true)
// 指定要扫描的Mapper类的包的路径
@EnableAsync(proxyTargetClass = true)
public class ApplicationConfig
{

View File

@ -5,6 +5,8 @@ import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.utils.SpringUtils;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.task.VirtualThreadTaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
@ -13,10 +15,10 @@ import java.util.concurrent.Executor;
/**
* 异步配置
*
* <p>
* 如果未使用虚拟线程则生效
* @author Lion Li
*/
@EnableAsync(proxyTargetClass = true)
@AutoConfiguration
public class AsyncConfig implements AsyncConfigurer {
@ -25,6 +27,9 @@ public class AsyncConfig implements AsyncConfigurer {
*/
@Override
public Executor getAsyncExecutor() {
if(SpringUtils.isVirtual()) {
return new VirtualThreadTaskExecutor("async-");
}
return SpringUtils.getBean("scheduledExecutorService");
}

View File

@ -10,7 +10,6 @@ import org.springframework.stereotype.Component;
*
* @author ruoyi
*/
@Data
@Component
@ConfigurationProperties(prefix = "ruoyi")
public class RuoYiConfig {
@ -43,11 +42,46 @@ public class RuoYiConfig {
/**
* 获取地址开关
*/
@Getter
private static boolean addressEnabled;
public void setAddressEnabled(boolean addressEnabled) {
RuoYiConfig.addressEnabled = addressEnabled;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getVersion()
{
return version;
}
public void setVersion(String version)
{
this.version = version;
}
public String getCopyrightYear()
{
return copyrightYear;
}
public void setCopyrightYear(String copyrightYear)
{
this.copyrightYear = copyrightYear;
}
public boolean isDemoEnabled()
{
return demoEnabled;
}
public void setDemoEnabled(boolean demoEnabled)
{
this.demoEnabled = demoEnabled;
}
public static String getProfile()
@ -55,6 +89,22 @@ public class RuoYiConfig {
return profile;
}
public void setProfile(String profile)
{
RuoYiConfig.profile = profile;
}
public static boolean isAddressEnabled()
{
return addressEnabled;
}
public void setAddressEnabled(boolean addressEnabled)
{
RuoYiConfig.addressEnabled = addressEnabled;
}
/**
* 获取导入上传路径
*/

View File

@ -1,15 +1,11 @@
package com.ruoyi.common.core.config;
import com.ruoyi.common.core.config.properties.ThreadPoolProperties;
import com.ruoyi.common.core.utils.Threads;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
@ -21,7 +17,6 @@ import java.util.concurrent.ThreadPoolExecutor;
**/
@Slf4j
@AutoConfiguration
@EnableConfigurationProperties(ThreadPoolProperties.class)
public class ThreadPoolConfig
{
/**
@ -31,18 +26,6 @@ public class ThreadPoolConfig
private ScheduledExecutorService scheduledExecutorService;
@Bean(name = "threadPoolTaskExecutor")
@ConditionalOnProperty(prefix = "thread-pool", name = "enabled", havingValue = "true")
public ThreadPoolTaskExecutor threadPoolTaskExecutor(ThreadPoolProperties threadPoolProperties) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(core);
executor.setMaxPoolSize(core * 2);
executor.setQueueCapacity(threadPoolProperties.getQueueCapacity());
executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
/**
* 执行周期性或定时任务
*/

View File

@ -1,30 +0,0 @@
package com.ruoyi.common.core.config.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 线程池 配置属性
*
* @author Lion Li
*/
@Data
@ConfigurationProperties(prefix = "thread-pool")
public class ThreadPoolProperties {
/**
* 是否开启线程池
*/
private boolean enabled;
/**
* 队列最大长度
*/
private int queueCapacity;
/**
* 线程池维护线程所允许的空闲时间
*/
private int keepAliveSeconds;
}

View File

@ -35,6 +35,11 @@ public interface CacheNames {
*/
String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";
/**
* 客户端
*/
String SYS_CLIENT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_client#30d";
/**
* 用户账户
*/
@ -53,7 +58,7 @@ public interface CacheNames {
/**
* OSS配置
*/
String SYS_OSS_CONFIG = "sys_oss_config";
String SYS_OSS_CONFIG = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss_config";
/**
* 在线用户

View File

@ -4,7 +4,7 @@ import io.jsonwebtoken.Claims;
/**
* 通用常量信息
*
*
* @author ruoyi
*/
public class Constants
@ -63,7 +63,7 @@ public class Constants
* 登录失败
*/
public static final String LOGIN_FAIL = "Error";
/**
* 验证码有效期分钟
*/
@ -80,68 +80,14 @@ public class Constants
public static final String TOKEN = "token";
/**
* 令牌前缀
* 顶级部门id
*/
public static final String TOKEN_PREFIX = "Bearer ";
/**
* 令牌前缀
*/
public static final String LOGIN_USER_KEY = "login_user_key";
/**
* 用户ID
*/
public static final String JWT_USERID = "userid";
/**
* 用户名称
*/
public static final String JWT_USERNAME = Claims.SUBJECT;
/**
* 用户头像
*/
public static final String JWT_AVATAR = "avatar";
/**
* 创建时间
*/
public static final String JWT_CREATED = "created";
/**
* 用户权限
*/
public static final String JWT_AUTHORITIES = "authorities";
public static final Long TOP_PARENT_ID = 0L;
/**
* 资源映射路径 前缀
*/
public static final String RESOURCE_PREFIX = "/profile";
/**
* RMI 远程方法调用
*/
public static final String LOOKUP_RMI = "rmi:";
/**
* LDAP 远程方法调用
*/
public static final String LOOKUP_LDAP = "ldap:";
/**
* LDAPS 远程方法调用
*/
public static final String LOOKUP_LDAPS = "ldaps:";
/**
* 定时任务白名单配置仅允许访问的包名如其他需要可以自行添加
*/
public static final String[] JOB_WHITELIST_STR = { "com.ruoyi" };
/**
* 定时任务违规的字符
*/
public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
"org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config" };
}

View File

@ -2,116 +2,196 @@ package com.ruoyi.common.core.constant;
/**
* 代码生成通用常量
*
*
* @author ruoyi
*/
public class GenConstants
{
/** 单表(增删改查) */
public class GenConstants {
/**
* 单表增删改查
*/
public static final String TPL_CRUD = "crud";
/** 树表(增删改查) */
/**
* 树表增删改查
*/
public static final String TPL_TREE = "tree";
/** 主子表(增删改查) */
/**
* 主子表增删改查
*/
public static final String TPL_SUB = "sub";
/** 树编码字段 */
/**
* 树编码字段
*/
public static final String TREE_CODE = "treeCode";
/** 树父编码字段 */
/**
* 树父编码字段
*/
public static final String TREE_PARENT_CODE = "treeParentCode";
/** 树名称字段 */
/**
* 树名称字段
*/
public static final String TREE_NAME = "treeName";
/** 上级菜单ID字段 */
/**
* 上级菜单ID字段
*/
public static final String PARENT_MENU_ID = "parentMenuId";
/** 上级菜单名称字段 */
/**
* 上级菜单名称字段
*/
public static final String PARENT_MENU_NAME = "parentMenuName";
/** 数据库字符串类型 */
public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2" };
/**
* 数据库字符串类型
*/
public static final String[] COLUMNTYPE_STR = {"char", "varchar", "nvarchar", "enum", "set", "nchar", "nvarchar", "varchar2", "nvarchar2", "character", "character varying"};
/** 数据库文本类型 */
public static final String[] COLUMNTYPE_TEXT = { "tinytext", "text", "mediumtext", "longtext" };
/**
* 数据库文本类型
*/
public static final String[] COLUMNTYPE_TEXT = {"tinytext", "text", "mediumtext", "longtext", "binary", "varbinary", "blob",
"ntext", "image", "bytea"};
/** 数据库时间类型 */
public static final String[] COLUMNTYPE_TIME = { "datetime", "time", "date", "timestamp" };
/**
* 数据库时间类型
*/
public static final String[] COLUMNTYPE_TIME = {"datetime", "time", "date", "timestamp", "timestamp without time zone", "year", "interval",
"smalldatetime", "datetime2", "datetimeoffset"};
/** 数据库数字类型 */
public static final String[] COLUMNTYPE_NUMBER = { "tinyint", "smallint", "mediumint", "int", "number", "integer",
"bit", "bigint", "float", "double", "decimal" };
/**
* 数据库integer类型
*/
public static final String[] COLUMNTYPE_INTEGER = {"tinyint", "smallint", "mediumint", "int", "number", "integer", "bit"};
/** 页面不需要编辑字段 */
public static final String[] COLUMNNAME_NOT_EDIT = { "id", "create_by", "create_time", "del_flag" };
/**
* 数据库数字类型
*/
public static final String[] COLUMNTYPE_NUMBER = {"tinyint", "smallint", "mediumint", "int", "number", "integer",
"bit", "bigint", "float", "double", "decimal", "numeric", "real", "double precision",
"smallserial", "serial", "bigserial", "money", "smallmoney"};
/** 页面不需要显示的列表字段 */
public static final String[] COLUMNNAME_NOT_LIST = { "id", "create_by", "create_time", "del_flag", "update_by",
"update_time" };
/**
* BO对象 不需要添加字段
*/
public static final String[] COLUMNNAME_NOT_ADD = {"tenant_id", "version", "del_flag", "create_by", "create_time", "update_by", "update_time"};
/** 页面不需要查询字段 */
public static final String[] COLUMNNAME_NOT_QUERY = { "id", "create_by", "create_time", "del_flag", "update_by",
"update_time", "remark" };
/**
* BO对象 不需要编辑字段
*/
public static final String[] COLUMNNAME_NOT_EDIT = {"tenant_id","del_flag", "create_by", "create_time", "update_by", "update_time"};
/** Entity基类字段 */
public static final String[] BASE_ENTITY = { "createBy", "createTime", "updateBy", "updateTime", "remark" };
/**
* VO对象 不需要显示的列表字段
*/
public static final String[] COLUMNNAME_NOT_LIST = {"tenant_id", "version", "del_flag", "create_by", "create_time", "update_by", "update_time"};
/** Tree基类字段 */
public static final String[] TREE_ENTITY = { "parentName", "parentId", "orderNum", "ancestors", "children" };
/**
* BO对象 不需要查询字段
*/
public static final String[] COLUMNNAME_NOT_QUERY = {"tenant_id", "version", "del_flag", "create_by", "create_time", "update_by", "update_time", "remark", "id"};
/** 文本框 */
/**
* Entity基类字段
*/
public static final String[] BASE_ENTITY = {"tenantId", "version", "createBy", "createTime", "updateBy", "updateTime"};
/**
* Tree基类字段
*/
public static final String[] TREE_ENTITY = {"parentName", "parentId", "orderNum", "ancestors", "children"};
/**
* 文本框
*/
public static final String HTML_INPUT = "input";
/** 文本域 */
/**
* 文本域
*/
public static final String HTML_TEXTAREA = "textarea";
/** 下拉框 */
/**
* 下拉框
*/
public static final String HTML_SELECT = "select";
/** 单选框 */
/**
* 单选框
*/
public static final String HTML_RADIO = "radio";
/** 复选框 */
/**
* 复选框
*/
public static final String HTML_CHECKBOX = "checkbox";
/** 日期控件 */
/**
* 日期控件
*/
public static final String HTML_DATETIME = "datetime";
/** 图片上传控件 */
/**
* 图片上传控件
*/
public static final String HTML_IMAGE_UPLOAD = "imageUpload";
/** 文件上传控件 */
/**
* 文件上传控件
*/
public static final String HTML_FILE_UPLOAD = "fileUpload";
/** 富文本控件 */
/**
* 富文本控件
*/
public static final String HTML_EDITOR = "editor";
/** 字符串类型 */
/**
* 字符串类型
*/
public static final String TYPE_STRING = "String";
/** 整型 */
/**
* 整型
*/
public static final String TYPE_INTEGER = "Integer";
/** 长整型 */
/**
* 长整型
*/
public static final String TYPE_LONG = "Long";
/** 浮点型 */
/**
* 浮点型
*/
public static final String TYPE_DOUBLE = "Double";
/** 高精度计算类型 */
/**
* 高精度计算类型
*/
public static final String TYPE_BIGDECIMAL = "BigDecimal";
/** 时间类型 */
/**
* 时间类型
*/
public static final String TYPE_DATE = "Date";
/** 模糊查询 */
/**
* 模糊查询
*/
public static final String QUERY_LIKE = "LIKE";
/** 相等查询 */
/**
* 相等查询
*/
public static final String QUERY_EQ = "EQ";
/** 需要 */
/**
* 需要
*/
public static final String REQUIRE = "1";
}

View File

@ -0,0 +1,49 @@
package com.ruoyi.common.core.constant;
import cn.hutool.core.lang.RegexPool;
/**
* 常用正则表达式字符串
* <p>
* 常用正则表达式集合更多正则见: https://any86.github.io/any-rule/
*
* @author Feng
*/
public interface RegexConstants extends RegexPool {
/**
* 字典类型必须以字母开头且只能为小写字母数字下滑线
*/
public static final String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$";
/**
* 身份证号码后6位
*/
public static final String ID_CARD_LAST_6 = "^(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$";
/**
* QQ号码
*/
public static final String QQ_NUMBER = "^[1-9][0-9]\\d{4,9}$";
/**
* 邮政编码
*/
public static final String POSTAL_CODE = "^[1-9]\\d{5}$";
/**
* 注册账号
*/
public static final String ACCOUNT = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$";
/**
* 密码包含至少8个字符包括大写字母小写字母数字和特殊字符
*/
public static final String PASSWORD = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
/**
* 通用状态0表示正常1表示停用
*/
public static final String STATUS = "^[01]$";
}

View File

@ -4,6 +4,7 @@ package com.ruoyi.common.core.constant;
* 租户常量信息
*
* @author Lion Li
* @author 数据小王子
*/
public interface TenantConstants {
@ -25,21 +26,21 @@ public interface TenantConstants {
/**
* 超级管理员角色 roleKey
*/
String SUPER_ADMIN_ROLE_KEY = "superadmin";
String SUPER_ADMIN_ROLE_KEY = "SuperAminRole";
/**
* 租户管理员角色 roleKey
*/
String TENANT_ADMIN_ROLE_KEY = "admin";
String TENANT_ADMIN_ROLE_KEY = "AdminRole";
/**
* 租户管理员角色名称
*/
String TENANT_ADMIN_ROLE_NAME = "管理员";
String TENANT_ADMIN_ROLE_NAME = "管理员角色";
/**
* 默认租户ID
*/
String DEFAULT_TENANT_ID = "000000";
Long DEFAULT_TENANT_ID = 1L;
}

View File

@ -61,11 +61,6 @@ public class UserConstants {
*/
public static final String POST_DISABLE = "1";
/**
* 字典正常状态
*/
public static final String DICT_NORMAL = "0";
/**
* 是否为系统默认
*/
@ -81,6 +76,16 @@ public class UserConstants {
*/
public static final String NO_FRAME = "1";
/**
* 菜单正常状态
*/
public static final String MENU_NORMAL = "0";
/**
* 菜单停用状态
*/
public static final String MENU_DISABLE = "1";
/**
* 菜单类型目录
*/

View File

@ -34,6 +34,16 @@ public class UserOnlineDTO implements Serializable {
*/
private String userName;
/**
* 客户端
*/
private String clientKey;
/**
* 设备类型
*/
private String deviceType;
/**
* 登录IP地址
*/

View File

@ -11,13 +11,7 @@ import lombok.Data;
*/
@Data
public class EmailLoginBody {
/**
* 租户ID
*/
@NotBlank(message = "{tenant.number.not.blank}")
private String tenantId;
public class EmailLoginBody extends LoginBody {
/**
* 邮箱

View File

@ -40,7 +40,6 @@ public class LoginBody
/**
* 租户ID
*/
//@NotBlank(message = "{tenant.number.not.blank}")
private Long tenantId;
/**

View File

@ -111,6 +111,16 @@ public class LoginUser
*/
private Long roleId;
/**
* 客户端
*/
private String clientKey;
/**
* 设备类型
*/
private String deviceType;
/**
* 获取登录id
*/

View File

@ -0,0 +1,33 @@
package com.ruoyi.common.core.core.domain.model;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.validator.constraints.Length;
import static com.ruoyi.common.core.constant.UserConstants.*;
/**
* 密码登录对象
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class PasswordLoginBody extends LoginBody {
/**
* 用户名
*/
@NotBlank(message = "{user.username.not.blank}")
@Length(min = USERNAME_MIN_LENGTH, max = USERNAME_MAX_LENGTH, message = "{user.username.length.valid}")
private String username;
/**
* 用户密码
*/
@NotBlank(message = "{user.password.not.blank}")
@Length(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}")
private String password;
}

View File

@ -0,0 +1,29 @@
package com.ruoyi.common.core.core.domain.model;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 短信登录对象
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SmsLoginBody extends LoginBody {
/**
* 手机号
*/
@NotBlank(message = "{user.phonenumber.not.blank}")
private String phonenumber;
/**
* 短信code
*/
@NotBlank(message = "{sms.code.not.blank}")
private String smsCode;
}

View File

@ -0,0 +1,35 @@
package com.ruoyi.common.core.core.domain.model;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 三方登录对象
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SocialLoginBody extends LoginBody {
/**
* 第三方登录平台
*/
@NotBlank(message = "{social.source.not.blank}")
private String source;
/**
* 第三方登录code
*/
@NotBlank(message = "{social.code.not.blank}")
private String socialCode;
/**
* 第三方登录socialState
*/
@NotBlank(message = "{social.state.not.blank}")
private String socialState;
}

View File

@ -0,0 +1,28 @@
package com.ruoyi.common.core.core.domain.model;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 三方登录对象
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class XcxLoginBody extends LoginBody {
/**
* 小程序id(多个小程序时使用)
*/
private String appid;
/**
* 小程序code
*/
@NotBlank(message = "{xcx.code.not.blank}")
private String xcxCode;
}

View File

@ -0,0 +1,27 @@
package com.ruoyi.common.core.core.domain.model;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.Serial;
/**
* 小程序登录用户身份权限
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class XcxLoginUser extends LoginUser {
@Serial
private static final long serialVersionUID = 1L;
/**
* openid
*/
private String openid;
}

View File

@ -1,98 +0,0 @@
package com.ruoyi.common.core.core.page;
import cn.hutool.http.HttpStatus;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 表格分页数据对象
*
* @author ruoyi
*/
public class TableDataInfo implements Serializable
{
@Serial
private static final long serialVersionUID = 1L;
/** 总记录数 */
private long total;
/** 列表数据 */
private List<?> rows;
/** 消息状态码 */
private int code;
/** 消息内容 */
private String msg;
/**
* 表格数据对象
*/
public TableDataInfo()
{
}
/**
* 分页
*
* @param list 列表数据
* @param total 总记录数
*/
public TableDataInfo(List<?> list, int total)
{
this.rows = list;
this.total = total;
}
public static TableDataInfo build(List<?> list) {
TableDataInfo rspData = new TableDataInfo();
rspData.setCode(HttpStatus.HTTP_OK);
rspData.setMsg("查询成功");
rspData.setRows(list);
rspData.setTotal(list.size());
return rspData;
}
public long getTotal()
{
return total;
}
public void setTotal(long total)
{
this.total = total;
}
public List<?> getRows()
{
return rows;
}
public void setRows(List<?> rows)
{
this.rows = rows;
}
public int getCode()
{
return code;
}
public void setCode(int code)
{
this.code = code;
}
public String getMsg()
{
return msg;
}
public void setMsg(String msg)
{
this.msg = msg;
}
}

View File

@ -2,6 +2,7 @@ package com.ruoyi.common.core.core.text;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.text.NumberFormat;
@ -977,7 +978,12 @@ public class Convert
String s = "";
for (int i = 0; i < fraction.length; i++)
{
s += (digit[(int) (Math.floor(n * 10 * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", "");
// 优化double计算精度丢失问题
BigDecimal nNum = new BigDecimal(n);
BigDecimal decimal = new BigDecimal(10);
BigDecimal scale = nNum.multiply(decimal).setScale(2, RoundingMode.HALF_EVEN);
double d = scale.doubleValue();
s += (digit[(int) (Math.floor(d * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", "");
}
if (s.length() < 1)
{

View File

@ -0,0 +1,30 @@
package com.ruoyi.common.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 用户状态
*
* @author LionLi
*/
@Getter
@AllArgsConstructor
public enum TenantStatus {
/**
* 正常
*/
OK("0", "正常"),
/**
* 停用
*/
DISABLE("1", "停用"),
/**
* 删除
*/
DELETED("2", "删除");
private final String code;
private final String info;
}

View File

@ -1,15 +0,0 @@
package com.ruoyi.common.core.exception;
/**
* 演示模式异常
*
* @author ruoyi
*/
public class DemoModeException extends RuntimeException
{
private static final long serialVersionUID = 1L;
public DemoModeException()
{
}
}

View File

@ -1,60 +0,0 @@
package com.ruoyi.common.core.exception;
import java.io.Serial;
/**
* 全局异常
*
* @author ruoyi
*/
public class GlobalException extends RuntimeException
{
@Serial
private static final long serialVersionUID = 1L;
/**
* 错误提示
*/
private String message;
/**
* 错误明细内部调试错误
*
*/
private String detailMessage;
/**
* 空构造方法避免反序列化问题
*/
public GlobalException()
{
}
public GlobalException(String message)
{
this.message = message;
}
public String getDetailMessage()
{
return detailMessage;
}
public GlobalException setDetailMessage(String detailMessage)
{
this.detailMessage = detailMessage;
return this;
}
@Override
public String getMessage()
{
return message;
}
public GlobalException setMessage(String message)
{
this.message = message;
return this;
}
}

View File

@ -1,29 +0,0 @@
package com.ruoyi.common.core.exception;
import java.io.Serial;
/**
* 工具类异常
*
* @author ruoyi
*/
public class UtilException extends RuntimeException
{
@Serial
private static final long serialVersionUID = 8247610319171014183L;
public UtilException(Throwable e)
{
super(e.getMessage(), e);
}
public UtilException(String message)
{
super(message);
}
public UtilException(String message, Throwable throwable)
{
super(message, throwable);
}
}

View File

@ -4,7 +4,7 @@ import java.io.Serial;
/**
* 黑名单IP异常类
*
*
* @author ruoyi
*/
public class BlackListException extends UserException
@ -14,6 +14,6 @@ public class BlackListException extends UserException
public BlackListException()
{
super("login.blocked", null);
super("login.blocked", (Object)null);
}
}

View File

@ -4,7 +4,7 @@ import java.io.Serial;
/**
* 验证码错误异常类
*
*
* @author ruoyi
*/
public class CaptchaException extends UserException
@ -14,6 +14,6 @@ public class CaptchaException extends UserException
public CaptchaException()
{
super("user.jcaptcha.error", null);
super("user.jcaptcha.error", (Object)null);
}
}

View File

@ -4,7 +4,7 @@ import java.io.Serial;
/**
* 验证码失效异常类
*
*
* @author ruoyi
*/
public class CaptchaExpireException extends UserException
@ -14,6 +14,6 @@ public class CaptchaExpireException extends UserException
public CaptchaExpireException()
{
super("user.jcaptcha.expire", null);
super("user.jcaptcha.expire", (Object)null);
}
}

View File

@ -4,7 +4,7 @@ import java.io.Serial;
/**
* 用户不存在异常类
*
*
* @author ruoyi
*/
public class UserNotExistsException extends UserException
@ -14,6 +14,6 @@ public class UserNotExistsException extends UserException
public UserNotExistsException()
{
super("user.not.exists", null);
super("user.not.exists", (Object)null);
}
}

View File

@ -1,19 +0,0 @@
package com.ruoyi.common.core.exception.user;
import java.io.Serial;
/**
* 用户密码不正确或不符合规范异常类
*
* @author ruoyi
*/
public class UserPasswordNotMatchException extends UserException
{
@Serial
private static final long serialVersionUID = 1L;
public UserPasswordNotMatchException()
{
super("user.password.not.match", null);
}
}

View File

@ -1,19 +0,0 @@
package com.ruoyi.common.core.exception.user;
import java.io.Serial;
/**
* 用户错误最大次数异常类
*
* @author ruoyi
*/
public class UserPasswordRetryLimitExceedException extends UserException
{
@Serial
private static final long serialVersionUID = 1L;
public UserPasswordRetryLimitExceedException(int retryLimitCount, int lockTime)
{
super("user.password.retry.limit.exceed", new Object[] { retryLimitCount, lockTime });
}
}

View File

@ -0,0 +1,52 @@
package com.ruoyi.common.core.factory;
import cn.hutool.core.lang.PatternPool;
import com.ruoyi.common.core.constant.RegexConstants;
import java.util.regex.Pattern;
/**
* 正则表达式模式池工厂
* <p>初始化的时候将正则表达式加入缓存池当中</p>
* <p>提高正则表达式的性能避免重复编译相同的正则表达式</p>
*
* @author 21001
*/
public class RegexPatternPoolFactory extends PatternPool {
/**
* 字典类型必须以字母开头且只能为小写字母数字下滑线
*/
public static final Pattern DICTIONARY_TYPE = get(RegexConstants.DICTIONARY_TYPE);
/**
* 身份证号码后6位
*/
public static final Pattern ID_CARD_LAST_6 = get(RegexConstants.ID_CARD_LAST_6);
/**
* QQ号码
*/
public static final Pattern QQ_NUMBER = get(RegexConstants.QQ_NUMBER);
/**
* 邮政编码
*/
public static final Pattern POSTAL_CODE = get(RegexConstants.POSTAL_CODE);
/**
* 注册账号
*/
public static final Pattern ACCOUNT = get(RegexConstants.ACCOUNT);
/**
* 密码包含至少8个字符包括大写字母小写字母数字和特殊字符
*/
public static final Pattern PASSWORD = get(RegexConstants.PASSWORD);
/**
* 通用状态0表示正常1表示停用
*/
public static final Pattern STATUS = get(RegexConstants.STATUS);
}

View File

@ -0,0 +1,18 @@
package com.ruoyi.common.core.service;
/**
* 通用 OSS服务
*
* @author Lion Li
*/
public interface OssService {
/**
* 通过ossId查询对应的url
*
* @param ossIds ossId串逗号分隔
* @return url串逗号分隔
*/
String selectUrlByIds(String ossIds);
}

View File

@ -3,7 +3,9 @@ package com.ruoyi.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;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
/**
@ -98,4 +100,8 @@ public final class SpringUtils extends SpringUtil
return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null;
}
public static boolean isVirtual() {
return Threading.VIRTUAL.isActive(getBean(Environment.class));
}
}

View File

@ -26,9 +26,21 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
{
public static final String SEPARATOR = ",";
public static final String SLASH = "/";
/** 空字符串 */
private static final String NULLSTR = "";
/**
* 获取参数不为空值
*
* @param str defaultValue 要判断的value
* @return value 返回值
*/
public static String blankToDefault(String str, String defaultValue) {
return StrUtil.blankToDefault(str, defaultValue);
}
/**
* 获取参数不为空值
*
@ -384,48 +396,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
*/
public static String toUnderScoreCase(String str)
{
if (str == null)
{
return null;
}
StringBuilder sb = new StringBuilder();
// 前置字符是否大写
boolean preCharIsUpperCase = true;
// 当前字符是否大写
boolean curreCharIsUpperCase = true;
// 下一字符是否大写
boolean nexteCharIsUpperCase = true;
for (int i = 0; i < str.length(); i++)
{
char c = str.charAt(i);
if (i > 0)
{
preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1));
}
else
{
preCharIsUpperCase = false;
}
curreCharIsUpperCase = Character.isUpperCase(c);
if (i < (str.length() - 1))
{
nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1));
}
if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase)
{
sb.append(SEPARATOR);
}
else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase)
{
sb.append(SEPARATOR);
}
sb.append(Character.toLowerCase(c));
}
return sb.toString();
return StrUtil.toUnderlineCase(str);
}
/**

View File

@ -0,0 +1,35 @@
package com.ruoyi.common.core.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.lang.tree.parser.NodeParser;
import com.ruoyi.common.core.utils.reflect.ReflectUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 扩展 hutool TreeUtil 封装系统树构建
*
* @author Lion Li
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class TreeBuildUtils extends TreeUtil {
/**
* 根据前端定制差异化字段
*/
public static final TreeNodeConfig DEFAULT_CONFIG = TreeNodeConfig.DEFAULT_CONFIG.setNameKey("label");
public static <T, K> List<Tree<K>> build(List<T> list, NodeParser<T, K> nodeParser) {
if (CollUtil.isEmpty(list)) {
return null;
}
K k = ReflectUtils.invokeGetter(list.get(0), "parentId");
return TreeUtil.build(list, k, DEFAULT_CONFIG, nodeParser);
}
}

View File

@ -1,56 +1,33 @@
package com.ruoyi.common.core.utils.ip;
import cn.hutool.core.net.NetUtil;
import cn.hutool.http.HtmlUtil;
import com.ruoyi.common.core.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.core.config.RuoYiConfig;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.utils.http.HttpUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* 获取地址类
*
*
* @author ruoyi
*/
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class AddressUtils
{
private static final Logger log = LoggerFactory.getLogger(AddressUtils.class);
// IP地址查询
public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp";
// 未知地址
public static final String UNKNOWN = "XX XX";
public static String getRealAddressByIP(String ip)
{
public static String getRealAddressByIP(String ip) {
if (StringUtils.isBlank(ip)) {
return UNKNOWN;
}
// 内网不查询
if (IpUtils.internalIp(ip))
{
ip = StringUtils.contains(ip, "0:0:0:0:0:0:0:1") ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
if (NetUtil.isInnerIP(ip)) {
return "内网IP";
}
if (RuoYiConfig.isAddressEnabled())
{
try
{
String rspStr = HttpUtils.sendGet(IP_URL, "ip=" + ip + "&json=true", Constants.GBK);
if (StringUtils.isEmpty(rspStr))
{
log.error("获取地理位置异常 {}", ip);
return UNKNOWN;
}
JSONObject obj = JSON.parseObject(rspStr);
String region = obj.getString("pro");
String city = obj.getString("city");
return String.format("%s %s", region, city);
}
catch (Exception e)
{
log.error("获取地理位置异常 {}", ip);
}
}
return UNKNOWN;
return RegionUtils.getCityInfo(ip);
}
}

View File

@ -1,383 +0,0 @@
package com.ruoyi.common.core.utils.ip;
import java.net.InetAddress;
import java.net.UnknownHostException;
import com.ruoyi.common.core.utils.StringUtils;
import jakarta.servlet.http.HttpServletRequest;
import com.ruoyi.common.core.utils.ServletUtils;
/**
* 获取IP方法
*
* @author ruoyi
*/
public class IpUtils
{
public final static String REGX_0_255 = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)";
// 匹配 ip
public final static String REGX_IP = "((" + REGX_0_255 + "\\.){3}" + REGX_0_255 + ")";
public final static String REGX_IP_WILDCARD = "(((\\*\\.){3}\\*)|(" + REGX_0_255 + "(\\.\\*){3})|(" + REGX_0_255 + "\\." + REGX_0_255 + ")(\\.\\*){2}" + "|((" + REGX_0_255 + "\\.){3}\\*))";
// 匹配网段
public final static String REGX_IP_SEG = "(" + REGX_IP + "\\-" + REGX_IP + ")";
/**
* 获取客户端IP
*
* @return IP地址
*/
public static String getIpAddr()
{
return getIpAddr(ServletUtils.getRequest());
}
/**
* 获取客户端IP
*
* @param request 请求对象
* @return IP地址
*/
public static String getIpAddr(HttpServletRequest request)
{
if (request == null)
{
return "unknown";
}
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("X-Forwarded-For");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getRemoteAddr();
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
}
/**
* 检查是否为内部IP地址
*
* @param ip IP地址
* @return 结果
*/
public static boolean internalIp(String ip)
{
byte[] addr = textToNumericFormatV4(ip);
return internalIp(addr) || "127.0.0.1".equals(ip);
}
/**
* 检查是否为内部IP地址
*
* @param addr byte地址
* @return 结果
*/
private static boolean internalIp(byte[] addr)
{
if (StringUtils.isNull(addr) || addr.length < 2)
{
return true;
}
final byte b0 = addr[0];
final byte b1 = addr[1];
// 10.x.x.x/8
final byte SECTION_1 = 0x0A;
// 172.16.x.x/12
final byte SECTION_2 = (byte) 0xAC;
final byte SECTION_3 = (byte) 0x10;
final byte SECTION_4 = (byte) 0x1F;
// 192.168.x.x/16
final byte SECTION_5 = (byte) 0xC0;
final byte SECTION_6 = (byte) 0xA8;
switch (b0)
{
case SECTION_1:
return true;
case SECTION_2:
if (b1 >= SECTION_3 && b1 <= SECTION_4)
{
return true;
}
case SECTION_5:
switch (b1)
{
case SECTION_6:
return true;
}
default:
return false;
}
}
/**
* 将IPv4地址转换成字节
*
* @param text IPv4地址
* @return byte 字节
*/
public static byte[] textToNumericFormatV4(String text)
{
if (text.length() == 0)
{
return null;
}
byte[] bytes = new byte[4];
String[] elements = text.split("\\.", -1);
try
{
long l;
int i;
switch (elements.length)
{
case 1:
l = Long.parseLong(elements[0]);
if ((l < 0L) || (l > 4294967295L))
{
return null;
}
bytes[0] = (byte) (int) (l >> 24 & 0xFF);
bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF);
bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 2:
l = Integer.parseInt(elements[0]);
if ((l < 0L) || (l > 255L))
{
return null;
}
bytes[0] = (byte) (int) (l & 0xFF);
l = Integer.parseInt(elements[1]);
if ((l < 0L) || (l > 16777215L))
{
return null;
}
bytes[1] = (byte) (int) (l >> 16 & 0xFF);
bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 3:
for (i = 0; i < 2; ++i)
{
l = Integer.parseInt(elements[i]);
if ((l < 0L) || (l > 255L))
{
return null;
}
bytes[i] = (byte) (int) (l & 0xFF);
}
l = Integer.parseInt(elements[2]);
if ((l < 0L) || (l > 65535L))
{
return null;
}
bytes[2] = (byte) (int) (l >> 8 & 0xFF);
bytes[3] = (byte) (int) (l & 0xFF);
break;
case 4:
for (i = 0; i < 4; ++i)
{
l = Integer.parseInt(elements[i]);
if ((l < 0L) || (l > 255L))
{
return null;
}
bytes[i] = (byte) (int) (l & 0xFF);
}
break;
default:
return null;
}
}
catch (NumberFormatException e)
{
return null;
}
return bytes;
}
/**
* 获取IP地址
*
* @return 本地IP地址
*/
public static String getHostIp()
{
try
{
return InetAddress.getLocalHost().getHostAddress();
}
catch (UnknownHostException e)
{
}
return "127.0.0.1";
}
/**
* 获取主机名
*
* @return 本地主机名
*/
public static String getHostName()
{
try
{
return InetAddress.getLocalHost().getHostName();
}
catch (UnknownHostException e)
{
}
return "未知";
}
/**
* 从多级反向代理中获得第一个非unknown IP地址
*
* @param ip 获得的IP地址
* @return 第一个非unknown IP地址
*/
public static String getMultistageReverseProxyIp(String ip)
{
// 多级反向代理检测
if (ip != null && ip.indexOf(",") > 0)
{
final String[] ips = ip.trim().split(",");
for (String subIp : ips)
{
if (false == isUnknown(subIp))
{
ip = subIp;
break;
}
}
}
return StringUtils.substring(ip, 0, 255);
}
/**
* 检测给定字符串是否为未知多用于检测HTTP请求相关
*
* @param checkString 被检测的字符串
* @return 是否未知
*/
public static boolean isUnknown(String checkString)
{
return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString);
}
/**
* 是否为IP
*/
public static boolean isIP(String ip)
{
return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP);
}
/**
* 是否为IP *为间隔的通配符地址
*/
public static boolean isIpWildCard(String ip)
{
return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP_WILDCARD);
}
/**
* 检测参数是否在ip通配符里
*/
public static boolean ipIsInWildCardNoCheck(String ipWildCard, String ip)
{
String[] s1 = ipWildCard.split("\\.");
String[] s2 = ip.split("\\.");
boolean isMatchedSeg = true;
for (int i = 0; i < s1.length && !s1[i].equals("*"); i++)
{
if (!s1[i].equals(s2[i]))
{
isMatchedSeg = false;
break;
}
}
return isMatchedSeg;
}
/**
* 是否为特定格式如:10.10.10.1-10.10.10.99的ip段字符串
*/
public static boolean isIPSegment(String ipSeg)
{
return StringUtils.isNotBlank(ipSeg) && ipSeg.matches(REGX_IP_SEG);
}
/**
* 判断ip是否在指定网段中
*/
public static boolean ipIsInNetNoCheck(String iparea, String ip)
{
int idx = iparea.indexOf('-');
String[] sips = iparea.substring(0, idx).split("\\.");
String[] sipe = iparea.substring(idx + 1).split("\\.");
String[] sipt = ip.split("\\.");
long ips = 0L, ipe = 0L, ipt = 0L;
for (int i = 0; i < 4; ++i)
{
ips = ips << 8 | Integer.parseInt(sips[i]);
ipe = ipe << 8 | Integer.parseInt(sipe[i]);
ipt = ipt << 8 | Integer.parseInt(sipt[i]);
}
if (ips > ipe)
{
long t = ips;
ips = ipe;
ipe = t;
}
return ips <= ipt && ipt <= ipe;
}
/**
* 校验ip是否符合过滤串规则
*
* @param filter 过滤IP列表,支持后缀'*'通配,支持网段如:`10.10.10.1-10.10.10.99`
* @param ip 校验IP地址
* @return boolean 结果
*/
public static boolean isMatchedIp(String filter, String ip)
{
if (StringUtils.isEmpty(filter) || StringUtils.isEmpty(ip))
{
return false;
}
String[] ips = filter.split(";");
for (String iStr : ips)
{
if (isIP(iStr) && iStr.equals(ip))
{
return true;
}
else if (isIpWildCard(iStr) && ipIsInWildCardNoCheck(iStr, ip))
{
return true;
}
else if (isIPSegment(iStr) && ipIsInNetNoCheck(iStr, ip))
{
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,67 @@
package com.ruoyi.common.core.utils.ip;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.utils.file.FileUtils;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;
import java.io.File;
/**
* 根据ip地址定位工具类离线方式
* 参考地址<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a>
*
* @author lishuyan
*/
@Slf4j
public class RegionUtils {
private static final Searcher SEARCHER;
static {
String fileName = "/ip2region.xdb";
File existFile = FileUtils.file(FileUtil.getTmpDir() + FileUtil.FILE_SEPARATOR + fileName);
if (!FileUtils.exist(existFile)) {
ClassPathResource fileStream = new ClassPathResource(fileName);
if (ObjectUtil.isEmpty(fileStream.getStream())) {
throw new ServiceException("RegionUtils初始化失败原因IP地址库数据不存在");
}
FileUtils.writeFromStream(fileStream.getStream(), existFile);
}
String dbPath = existFile.getPath();
// 1 dbPath 加载整个 xdb 到内存
byte[] cBuff;
try {
cBuff = Searcher.loadContentFromFile(dbPath);
} catch (Exception e) {
throw new ServiceException("RegionUtils初始化失败原因从ip2region.xdb文件加载内容失败" + e.getMessage());
}
// 2使用上述的 cBuff 创建一个完全基于内存的查询对象
try {
SEARCHER = Searcher.newWithBuffer(cBuff);
} catch (Exception e) {
throw new ServiceException("RegionUtils初始化失败原因" + e.getMessage());
}
}
/**
* 根据IP地址离线获取城市
*/
public static String getCityInfo(String ip) {
try {
ip = ip.trim();
// 3执行查询
String region = SEARCHER.search(ip);
return region.replace("0|", "").replace("|0", "");
} catch (Exception e) {
log.error("IP地址离线获取城市异常 {}", ip);
return "未知";
}
}
}

View File

@ -76,7 +76,6 @@ import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ruoyi.common.core.config.RuoYiConfig;
import com.ruoyi.common.core.exception.UtilException;
import com.ruoyi.common.core.utils.DateUtils;
import com.ruoyi.common.core.utils.file.FileTypeUtils;
import com.ruoyi.common.core.utils.file.FileUtils;
@ -540,7 +539,7 @@ public class ExcelUtil<T> {
return AjaxResult.success(filename);
} catch (Exception e) {
log.error("导出Excel异常{}", e.getMessage());
throw new UtilException("导出Excel失败请联系网站管理员");
throw new IllegalArgumentException("导出Excel失败请联系网站管理员");
} finally {
IOUtils.closeQuietly(wb);
IOUtils.closeQuietly(out);

View File

@ -0,0 +1,30 @@
package com.ruoyi.common.core.utils.regex;
import cn.hutool.core.util.ReUtil;
import com.ruoyi.common.core.constant.RegexConstants;
/**
* 正则相关工具类
*
* @author Feng
*/
public final class RegexUtils extends ReUtil {
/**
* 从输入字符串中提取匹配的部分如果没有匹配则返回默认值
*
* @param input 要提取的输入字符串
* @param regex 用于匹配的正则表达式可以使用 {@link RegexConstants} 中定义的常量
* @param defaultInput 如果没有匹配时返回的默认值
* @return 如果找到匹配的部分则返回匹配的部分否则返回默认值
*/
public static String extractFromString(String input, String regex, String defaultInput) {
try {
return ReUtil.get(regex, input, 1);
} catch (Exception e) {
return defaultInput;
}
}
}

View File

@ -0,0 +1,105 @@
package com.ruoyi.common.core.utils.regex;
import cn.hutool.core.exceptions.ValidateException;
import cn.hutool.core.lang.Validator;
import com.ruoyi.common.core.factory.RegexPatternPoolFactory;
import java.util.regex.Pattern;
/**
* 正则字段校验器
* 主要验证字段非空是否为满足指定格式等
*
* @author Feng
*/
public class RegexValidator extends Validator {
/**
* 字典类型必须以字母开头且只能为小写字母数字下滑线
*/
public static final Pattern DICTIONARY_TYPE = RegexPatternPoolFactory.DICTIONARY_TYPE;
/**
* 身份证号码后6位
*/
public static final Pattern ID_CARD_LAST_6 = RegexPatternPoolFactory.ID_CARD_LAST_6;
/**
* QQ号码
*/
public static final Pattern QQ_NUMBER = RegexPatternPoolFactory.QQ_NUMBER;
/**
* 邮政编码
*/
public static final Pattern POSTAL_CODE = RegexPatternPoolFactory.POSTAL_CODE;
/**
* 注册账号
*/
public static final Pattern ACCOUNT = RegexPatternPoolFactory.ACCOUNT;
/**
* 密码包含至少8个字符包括大写字母小写字母数字和特殊字符
*/
public static final Pattern PASSWORD = RegexPatternPoolFactory.PASSWORD;
/**
* 通用状态0表示正常1表示停用
*/
public static final Pattern STATUS = RegexPatternPoolFactory.STATUS;
/**
* 检查输入的账号是否匹配预定义的规则
*
* @param value 要验证的账号
* @return 如果账号符合规则返回 true否则返回 false
*/
public static boolean isAccount(CharSequence value) {
return isMatchRegex(ACCOUNT, value);
}
/**
* 验证输入的账号是否符合规则如果不符合则抛出 ValidateException 异常
*
* @param value 要验证的账号
* @param errorMsg 验证失败时抛出的异常消息
* @param <T> CharSequence 的子类型
* @return 如果验证通过返回输入的账号
* @throws ValidateException 如果验证失败
*/
public static <T extends CharSequence> T validateAccount(T value, String errorMsg) throws ValidateException {
if (!isAccount(value)) {
throw new ValidateException(errorMsg);
}
return value;
}
/**
* 检查输入的状态是否匹配预定义的规则
*
* @param value 要验证的状态
* @return 如果状态符合规则返回 true否则返回 false
*/
public static boolean isStatus(CharSequence value) {
return isMatchRegex(STATUS, value);
}
/**
* 验证输入的状态是否符合规则如果不符合则抛出 ValidateException 异常
*
* @param value 要验证的状态
* @param errorMsg 验证失败时抛出的异常消息
* @param <T> CharSequence 的子类型
* @return 如果验证通过返回输入的状态
* @throws ValidateException 如果验证失败
*/
public static <T extends CharSequence> T validateStatus(T value, String errorMsg) throws ValidateException {
if (!isStatus(value)) {
throw new ValidateException(errorMsg);
}
return value;
}
}

View File

@ -1,11 +1,10 @@
package com.ruoyi.common.core.utils.sql;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.exception.UtilException;
/**
* sql操作工具类
*
*
* @author ruoyi
*/
public class SqlUtil
@ -32,11 +31,11 @@ public class SqlUtil
{
if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value))
{
throw new UtilException("参数不符合规范,不能进行查询");
throw new IllegalArgumentException("参数不符合规范,不能进行查询");
}
if (StringUtils.length(value) > ORDER_BY_MAX_LENGTH)
{
throw new UtilException("参数已超过最大限制,不能进行查询");
throw new IllegalArgumentException("参数已超过最大限制,不能进行查询");
}
return value;
}
@ -63,7 +62,7 @@ public class SqlUtil
{
if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1)
{
throw new UtilException("参数存在SQL注入风险");
throw new IllegalArgumentException("参数存在SQL注入风险");
}
}
}

View File

@ -5,7 +5,6 @@ import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import com.ruoyi.common.core.exception.UtilException;
/**
* 提供通用唯一识别码universally unique identifierUUID实现
@ -33,7 +32,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
/**
* 私有构造
*
*
* @param data 数据
*/
private UUID(byte[] data)
@ -67,7 +66,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
/**
* 获取类型 4伪随机生成的UUID 的静态工厂
*
*
* @return 随机生成的 {@code UUID}
*/
public static UUID fastUUID()
@ -77,7 +76,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
/**
* 获取类型 4伪随机生成的UUID 的静态工厂 使用加密的强伪随机数生成器生成该 UUID
*
*
* @return 随机生成的 {@code UUID}
*/
public static UUID randomUUID()
@ -87,7 +86,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
/**
* 获取类型 4伪随机生成的UUID 的静态工厂 使用加密的强伪随机数生成器生成该 UUID
*
*
* @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码否则可以得到更好的性能
* @return 随机生成的 {@code UUID}
*/
@ -289,7 +288,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
*
* <p>
* UUID 的字符串表示形式由此 BNF 描述
*
*
* <pre>
* {@code
* UUID = <time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node>
@ -302,7 +301,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
* hexDigit = [0-9a-fA-F]
* }
* </pre>
*
*
* </blockquote>
*
* @return {@code UUID} 的字符串表现形式
@ -319,7 +318,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
*
* <p>
* UUID 的字符串表示形式由此 BNF 描述
*
*
* <pre>
* {@code
* UUID = <time_low>-<time_mid>-<time_high_and_version>-<variant_and_sequence>-<node>
@ -332,7 +331,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
* hexDigit = [0-9a-fA-F]
* }
* </pre>
*
*
* </blockquote>
*
* @param isSimple 是否简单模式简单模式为不带'-'的UUID字符串
@ -432,7 +431,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
// Private method start
/**
* 返回指定数字对应的hex值
*
*
* @param val
* @param digits
* @return
@ -456,7 +455,7 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
/**
* 获取{@link SecureRandom}类提供加密的强随机数生成器 (RNG)
*
*
* @return {@link SecureRandom}
*/
public static SecureRandom getSecureRandom()
@ -467,14 +466,14 @@ public final class UUID implements java.io.Serializable, Comparable<UUID>
}
catch (NoSuchAlgorithmException e)
{
throw new UtilException(e);
throw new IllegalArgumentException(e);
}
}
/**
* 获取随机数生成器对象<br>
* ThreadLocalRandom是JDK 7之后提供并发产生随机数能够解决多个线程发生的竞争争夺
*
*
* @return {@link ThreadLocalRandom}
*/
public static ThreadLocalRandom getRandom()

View File

@ -1,34 +1,20 @@
package com.ruoyi.common.core.xss;
import com.ruoyi.common.core.utils.StringUtils;
import cn.hutool.core.util.ReUtil;
import cn.hutool.http.HtmlUtil;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 自定义xss校验注解实现
*
*
* @author ruoyi
*/
public class XssValidator implements ConstraintValidator<Xss, String>
{
private static final String HTML_PATTERN = "<(\\S*?)[^>]*>.*?|<.*? />";
@Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext)
{
if (StringUtils.isBlank(value))
{
return true;
}
return !containsHtml(value);
return !ReUtil.contains(HtmlUtil.RE_HTML_MARK, value);
}
public static boolean containsHtml(String value)
{
Pattern pattern = Pattern.compile(HTML_PATTERN);
Matcher matcher = pattern.matcher(value);
return matcher.matches();
}
}
}

View File

@ -0,0 +1,46 @@
<?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>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-common-encrypt</artifactId>
<description>
ruoyi-common-encrypt 数据加解密模块
</description>
<dependencies>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
</dependency>
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common-orm</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,20 @@
package com.ruoyi.common.encrypt.annotation;
import java.lang.annotation.*;
/**
* 强制加密注解
*
* @author Michelle.Chung
*/
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiEncrypt {
/**
* 响应加密忽略默认不加密 true 时加密
*/
boolean response() default false;
}

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