mirror of
https://github.com/dromara/RuoYi-Vue-Plus.git
synced 2025-09-24 07:19:46 +08:00
Compare commits
215 Commits
Author | SHA1 | Date | |
---|---|---|---|
d22b2a10df | |||
957a4d1fcd | |||
49ef8378fe | |||
cd531f1d39 | |||
92f73a4a72 | |||
a4e3f7ea5e | |||
26b4561a71 | |||
dbe276a33b | |||
4ab4e1685c | |||
aab87d322c | |||
79ee168293 | |||
10e4b0618c | |||
7c3316e116 | |||
8460316632 | |||
5d356aa6c4 | |||
a776d28294 | |||
a002a4e7a1 | |||
79ec850eca | |||
d1889c42a3 | |||
a7ea096319 | |||
4e3fc7002d | |||
1752695751 | |||
2b89c3f8d0 | |||
6b387b2456 | |||
ffc971cf92 | |||
887d5e85d0 | |||
8c603ff8d7 | |||
a0831dda45 | |||
336b2e8cc3 | |||
cea4855f57 | |||
9fc043b105 | |||
d729c8ecde | |||
b726a91cdb | |||
05d5d9be2c | |||
c40a8b2f0b | |||
8232908b3f | |||
1db0bc83b2 | |||
74a0ec1ec3 | |||
1228e8f3ea | |||
737838d92f | |||
c054029cfc | |||
62bbd78033 | |||
82a5ed632f | |||
52ddccba3e | |||
1a12aecd49 | |||
777ae645c5 | |||
21c87eee9a | |||
0c8ac12e4d | |||
cf871d9387 | |||
90fb26fbf1 | |||
fdfca0b33a | |||
facd3e351f | |||
a4ad56f0eb | |||
b9e5914bab | |||
553fca28a2 | |||
97caabe0a2 | |||
122f2770b2 | |||
748c95b30f | |||
e0672fc753 | |||
5a1523564b | |||
0c2fe34d92 | |||
2dde42168f | |||
a5c2093c76 | |||
ea74803ccc | |||
9df837f047 | |||
d6758dc47b | |||
5a8dc8e1cf | |||
eac7f1b4e2 | |||
3749e7e724 | |||
b709bc0214 | |||
a09414110e | |||
053dc50c4d | |||
5382722867 | |||
5e51077347 | |||
6d44069364 | |||
e1e3843ec0 | |||
15905b7022 | |||
1c5ae2f168 | |||
f29e0223a7 | |||
9fbe3cf399 | |||
e4f1da30fc | |||
21deab4bf1 | |||
d7d7dcbcf7 | |||
3f680385a9 | |||
7ecf4bbf1c | |||
ae65985fbc | |||
2a34c3ebb2 | |||
3b46f8c8cf | |||
7c2efb1aef | |||
ea25474529 | |||
33e1d34ce5 | |||
142fb33d81 | |||
ee6c0388da | |||
c171817d6a | |||
71dddee146 | |||
d456ff64f1 | |||
9e78fcccf7 | |||
878cd7e9f0 | |||
5c9721cfac | |||
31502dccc7 | |||
538aa8d908 | |||
00003b2c57 | |||
a2c238d466 | |||
d89f147c54 | |||
53cf1b2013 | |||
564ab331d7 | |||
a690ece164 | |||
b50904c6ff | |||
70aa14ecf8 | |||
c37b92978a | |||
ef39ad7107 | |||
48d3ef9818 | |||
5bf901cdcd | |||
8e99dd306a | |||
07fdc240d7 | |||
023ceaaf91 | |||
9e551a0b2a | |||
7c3f3523ea | |||
40eac07789 | |||
5868fadbf5 | |||
124bcc4bba | |||
e71d6fa983 | |||
7129ad4fac | |||
16923cc86a | |||
57dd6831d3 | |||
8aa60abb1f | |||
7a9f51fc7a | |||
3761473967 | |||
34031cae8d | |||
26abb98747 | |||
c2f67b4a77 | |||
4d925a4d62 | |||
4e62054bd1 | |||
159e30c982 | |||
fe40d7db32 | |||
c0eeafb5cd | |||
5626b97a19 | |||
0f95e9393d | |||
5de1ffff90 | |||
33698ee448 | |||
5e5d478cf2 | |||
778096d100 | |||
bba163f7b4 | |||
d4f792810e | |||
d14ee59912 | |||
be5d69d5a5 | |||
97c36674e4 | |||
1f1564fad9 | |||
c79e053bea | |||
92e9ed771b | |||
800c6c8ff3 | |||
865627fdad | |||
192537672e | |||
384f9528e7 | |||
7334d91d6b | |||
5fc76b6426 | |||
4d8a45204c | |||
2de9397db8 | |||
bfc73ed214 | |||
8bf741fd5b | |||
460545a75e | |||
a93b30ec91 | |||
34bac1add9 | |||
f028cb76fc | |||
5a4be5fba1 | |||
23245b78ca | |||
13ac302525 | |||
96a62a3564 | |||
7adf702283 | |||
279c8e014a | |||
b7517cbbd4 | |||
45eac02f4f | |||
a6b7c3afe6 | |||
e99e4f6c58 | |||
bcb97bc406 | |||
ad01406fc1 | |||
15eb08c065 | |||
2340556091 | |||
65c54184e8 | |||
9dcb7c6a12 | |||
0c6faa751a | |||
b465cb34de | |||
21c12a791a | |||
723a0b6d9c | |||
c4ef053958 | |||
055d1f3bb2 | |||
fe27d8920a | |||
df65670d3d | |||
2623d0b343 | |||
c0e0b41d13 | |||
8763bfa3d3 | |||
71180584da | |||
319a89e320 | |||
0673cf8849 | |||
b537899e62 | |||
7b679e60e0 | |||
bb475a6088 | |||
a217c495d1 | |||
bdb86e2b3a | |||
e8700ac44b | |||
d80f6ab695 | |||
381be5a1a1 | |||
214cbac9a6 | |||
906a031172 | |||
236dd6e054 | |||
eb17eb6559 | |||
2746af21f0 | |||
78abb617ce | |||
3c57c0e7f9 | |||
95c01301f6 | |||
934bbe8bd7 | |||
718a010c0f | |||
a87071b834 | |||
2c598f93ab | |||
0937093851 |
@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
<configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
||||||
<deployment type="dockerfile">
|
<deployment type="dockerfile">
|
||||||
<settings>
|
<settings>
|
||||||
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.3.0" />
|
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.4.0" />
|
||||||
<option name="buildOnly" value="true" />
|
<option name="buildOnly" value="true" />
|
||||||
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
|
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
|
||||||
</settings>
|
</settings>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
<configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
||||||
<deployment type="dockerfile">
|
<deployment type="dockerfile">
|
||||||
<settings>
|
<settings>
|
||||||
<option name="imageTag" value="ruoyi/ruoyi-server:5.3.0" />
|
<option name="imageTag" value="ruoyi/ruoyi-server:5.4.0" />
|
||||||
<option name="buildOnly" value="true" />
|
<option name="buildOnly" value="true" />
|
||||||
<option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
|
<option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
|
||||||
</settings>
|
</settings>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
<configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
||||||
<deployment type="dockerfile">
|
<deployment type="dockerfile">
|
||||||
<settings>
|
<settings>
|
||||||
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.3.0" />
|
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.4.0" />
|
||||||
<option name="buildOnly" value="true" />
|
<option name="buildOnly" value="true" />
|
||||||
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
|
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
|
||||||
</settings>
|
</settings>
|
||||||
|
16
README.md
16
README.md
@ -7,10 +7,10 @@
|
|||||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus)
|
[](https://gitee.com/dromara/RuoYi-Vue-Plus)
|
||||||
[](https://github.com/dromara/RuoYi-Vue-Plus)
|
[](https://github.com/dromara/RuoYi-Vue-Plus)
|
||||||
[](https://gitcode.com/dromara/RuoYi-Vue-Plus)
|
[](https://gitcode.com/dromara/RuoYi-Vue-Plus)
|
||||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE)
|
[](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/5.X/LICENSE)
|
||||||
[](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
|
[](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
|
||||||
<br>
|
<br>
|
||||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus)
|
[](https://gitee.com/dromara/RuoYi-Vue-Plus)
|
||||||
[]()
|
[]()
|
||||||
[]()
|
[]()
|
||||||
[]()
|
[]()
|
||||||
@ -22,10 +22,12 @@
|
|||||||
|
|
||||||
> 系统演示: [传送门](https://plus-doc.dromara.org/#/common/demo_system)
|
> 系统演示: [传送门](https://plus-doc.dromara.org/#/common/demo_system)
|
||||||
|
|
||||||
> 官方前端项目地址: [plus-ui](https://gitee.com/JavaLionLi/plus-ui)<br>
|
> 官方前端项目地址: [gitee](https://gitee.com/JavaLionLi/plus-ui) - [github](https://github.com/JavaLionLi/plus-ui) - [gitcode](https://gitcode.com/dromara/plus-ui)<br>
|
||||||
> 成员前端项目地址: 基于vben5 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)
|
> 成员前端项目地址: 基于vben5 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)<br>
|
||||||
|
> 成员前端项目地址: 基于soybean [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)<br>
|
||||||
|
> 成员项目地址: 删除多租户与工作流 [RuoYi-Vue-Plus-Single](https://gitee.com/ColorDreams/RuoYi-Vue-Plus-Single)<br>
|
||||||
|
|
||||||
> 文档地址: [plus-doc](https://plus-doc.dromara.org)
|
> 文档地址: [plus-doc](https://plus-doc.dromara.org) 文档在华为云上如果打不开大概率是DNS问题 可以尝试切换网络等方式(或者科学上网)
|
||||||
|
|
||||||
## 赞助商
|
## 赞助商
|
||||||
|
|
||||||
@ -34,6 +36,7 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
|
|||||||
数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br>
|
数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br>
|
||||||
引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
|
引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
|
||||||
<font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br>
|
<font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br>
|
||||||
|
Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br>
|
||||||
[如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group)
|
[如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group)
|
||||||
|
|
||||||
# 本框架与RuoYi的功能差异
|
# 本框架与RuoYi的功能差异
|
||||||
@ -75,7 +78,7 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
|
|||||||
| 邮件 | 采用 mail-api 通用协议支持大部分邮件厂商 | 不支持 |
|
| 邮件 | 采用 mail-api 通用协议支持大部分邮件厂商 | 不支持 |
|
||||||
| 接口文档 | 采用 SpringDoc、javadoc 无注解零入侵基于java注释<br/>只需把注释写好 无需再写一大堆的文档注解了 | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成 |
|
| 接口文档 | 采用 SpringDoc、javadoc 无注解零入侵基于java注释<br/>只需把注释写好 无需再写一大堆的文档注解了 | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成 |
|
||||||
| 校验框架 | 采用 Validation 支持注解与工具类校验 注解支持国际化 | 仅支持注解 且注解不支持国际化 |
|
| 校验框架 | 采用 Validation 支持注解与工具类校验 注解支持国际化 | 仅支持注解 且注解不支持国际化 |
|
||||||
| Excel框架 | 采用 Alibaba EasyExcel 基于插件化<br/>框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等 | 基于 POI 手写实现 功能有限 复杂 扩展性差 |
|
| Excel框架 | 采用 FastExcel(原Alibaba EasyExcel) 基于插件化<br/>框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等 | 基于 POI 手写实现 功能有限 复杂 扩展性差 |
|
||||||
| 工作流支持 | 支持各种复杂审批 转办 委派 加减签 会签 或签 票签 等功能 | 无 |
|
| 工作流支持 | 支持各种复杂审批 转办 委派 加减签 会签 或签 票签 等功能 | 无 |
|
||||||
| 工具类框架 | 采用 Hutool、Lombok 上百种工具覆盖90%的使用需求 基于注解自动生成 get set 等简化框架大量代码 | 手写工具稳定性差易出问题 工具数量有限 代码臃肿需自己手写 get set 等 |
|
| 工具类框架 | 采用 Hutool、Lombok 上百种工具覆盖90%的使用需求 基于注解自动生成 get set 等简化框架大量代码 | 手写工具稳定性差易出问题 工具数量有限 代码臃肿需自己手写 get set 等 |
|
||||||
| 监控框架 | 采用 SpringBoot-Admin 基于SpringBoot官方 actuator 探针机制<br/>实时监控服务状态 框架还为其扩展了在线日志查看监控 | 无 |
|
| 监控框架 | 采用 SpringBoot-Admin 基于SpringBoot官方 actuator 探针机制<br/>实时监控服务状态 框架还为其扩展了在线日志查看监控 | 无 |
|
||||||
@ -113,7 +116,6 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
|
|||||||
| 系统接口 | 根据业务代码自动生成相关的api接口文档 | 支持 | 支持 |
|
| 系统接口 | 根据业务代码自动生成相关的api接口文档 | 支持 | 支持 |
|
||||||
| 服务监控 | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等 | 支持 | 仅支持单机CPU、内存、磁盘监控 |
|
| 服务监控 | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等 | 支持 | 仅支持单机CPU、内存、磁盘监控 |
|
||||||
| 缓存监控 | 对系统的缓存信息查询,命令统计等。 | 支持 | 支持 |
|
| 缓存监控 | 对系统的缓存信息查询,命令统计等。 | 支持 | 支持 |
|
||||||
| 在线构建器 | 拖动表单元素生成相应的HTML代码。 | 支持 | 支持 |
|
|
||||||
| 使用案例 | 系统的一些功能案例 | 支持 | 不支持 |
|
| 使用案例 | 系统的一些功能案例 | 支持 | 不支持 |
|
||||||
|
|
||||||
## 参考文档
|
## 参考文档
|
||||||
|
55
pom.xml
55
pom.xml
@ -13,44 +13,43 @@
|
|||||||
<description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
|
<description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>5.3.0</revision>
|
<revision>5.4.0</revision>
|
||||||
<spring-boot.version>3.4.1</spring-boot.version>
|
<spring-boot.version>3.4.6</spring-boot.version>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
<java.version>17</java.version>
|
<java.version>17</java.version>
|
||||||
<mybatis.version>3.5.16</mybatis.version>
|
<mybatis.version>3.5.16</mybatis.version>
|
||||||
<springdoc.version>2.8.3</springdoc.version>
|
<springdoc.version>2.8.8</springdoc.version>
|
||||||
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
|
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
|
||||||
<easyexcel.version>4.0.3</easyexcel.version>
|
<fastexcel.version>1.2.0</fastexcel.version>
|
||||||
<velocity.version>2.3</velocity.version>
|
<velocity.version>2.3</velocity.version>
|
||||||
<satoken.version>1.39.0</satoken.version>
|
<satoken.version>1.42.0</satoken.version>
|
||||||
<mybatis-plus.version>3.5.10</mybatis-plus.version>
|
<mybatis-plus.version>3.5.12</mybatis-plus.version>
|
||||||
<p6spy.version>3.9.1</p6spy.version>
|
<p6spy.version>3.9.1</p6spy.version>
|
||||||
<hutool.version>5.8.35</hutool.version>
|
<hutool.version>5.8.35</hutool.version>
|
||||||
<spring-boot-admin.version>3.4.1</spring-boot-admin.version>
|
<spring-boot-admin.version>3.4.7</spring-boot-admin.version>
|
||||||
<redisson.version>3.43.0</redisson.version>
|
<redisson.version>3.45.1</redisson.version>
|
||||||
<lock4j.version>2.2.7</lock4j.version>
|
<lock4j.version>2.2.7</lock4j.version>
|
||||||
<dynamic-ds.version>4.3.1</dynamic-ds.version>
|
<dynamic-ds.version>4.3.1</dynamic-ds.version>
|
||||||
<snailjob.version>1.3.0</snailjob.version>
|
<snailjob.version>1.5.0</snailjob.version>
|
||||||
<mapstruct-plus.version>1.4.6</mapstruct-plus.version>
|
<mapstruct-plus.version>1.4.8</mapstruct-plus.version>
|
||||||
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
|
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
|
||||||
<lombok.version>1.18.36</lombok.version>
|
<lombok.version>1.18.36</lombok.version>
|
||||||
<bouncycastle.version>1.76</bouncycastle.version>
|
<bouncycastle.version>1.80</bouncycastle.version>
|
||||||
<justauth.version>1.16.7</justauth.version>
|
<justauth.version>1.16.7</justauth.version>
|
||||||
<!-- 离线IP地址定位库 -->
|
<!-- 离线IP地址定位库 -->
|
||||||
<ip2region.version>2.7.0</ip2region.version>
|
<ip2region.version>2.7.0</ip2region.version>
|
||||||
|
|
||||||
<!-- OSS 配置 -->
|
<!-- OSS 配置 -->
|
||||||
<aws.sdk.version>2.28.22</aws.sdk.version>
|
<aws.sdk.version>2.28.22</aws.sdk.version>
|
||||||
<aws.crt.version>0.31.3</aws.crt.version>
|
|
||||||
<!-- SMS 配置 -->
|
<!-- SMS 配置 -->
|
||||||
<sms4j.version>3.3.3</sms4j.version>
|
<sms4j.version>3.3.4</sms4j.version>
|
||||||
<!-- 限制框架中的fastjson版本 -->
|
<!-- 限制框架中的fastjson版本 -->
|
||||||
<fastjson.version>1.2.83</fastjson.version>
|
<fastjson.version>1.2.83</fastjson.version>
|
||||||
<!-- 面向运行时的D-ORM依赖 -->
|
<!-- 面向运行时的D-ORM依赖 -->
|
||||||
<anyline.version>8.7.2-20250101</anyline.version>
|
<anyline.version>8.7.2-20250101</anyline.version>
|
||||||
<!--工作流配置-->
|
<!-- 工作流配置 -->
|
||||||
<warm-flow.version>1.6.6</warm-flow.version>
|
<warm-flow.version>1.7.3</warm-flow.version>
|
||||||
|
|
||||||
<!-- 插件版本 -->
|
<!-- 插件版本 -->
|
||||||
<maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
|
<maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
|
||||||
@ -58,6 +57,8 @@
|
|||||||
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
|
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
|
||||||
<maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
|
<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.3.0</flatten-maven-plugin.version>
|
||||||
|
<!-- 打包默认跳过测试 -->
|
||||||
|
<skipTests>true</skipTests>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
@ -165,9 +166,9 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba</groupId>
|
<groupId>cn.idev.excel</groupId>
|
||||||
<artifactId>easyexcel</artifactId>
|
<artifactId>fastexcel</artifactId>
|
||||||
<version>${easyexcel.version}</version>
|
<version>${fastexcel.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- velocity代码生成使用模板 -->
|
<!-- velocity代码生成使用模板 -->
|
||||||
@ -245,18 +246,18 @@
|
|||||||
<artifactId>s3</artifactId>
|
<artifactId>s3</artifactId>
|
||||||
<version>${aws.sdk.version}</version>
|
<version>${aws.sdk.version}</version>
|
||||||
</dependency>
|
</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 传输管理器 -->
|
<!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>software.amazon.awssdk</groupId>
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
<artifactId>s3-transfer-manager</artifactId>
|
<artifactId>s3-transfer-manager</artifactId>
|
||||||
<version>${aws.sdk.version}</version>
|
<version>${aws.sdk.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
|
<artifactId>netty-nio-client</artifactId>
|
||||||
|
<version>${aws.sdk.version}</version>
|
||||||
|
</dependency>
|
||||||
<!--短信sms4j-->
|
<!--短信sms4j-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.dromara.sms4j</groupId>
|
<groupId>org.dromara.sms4j</groupId>
|
||||||
@ -320,12 +321,6 @@
|
|||||||
<version>${ip2region.version}</version>
|
<version>${ip2region.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>commons-io</groupId>
|
|
||||||
<artifactId>commons-io</artifactId>
|
|
||||||
<version>2.15.0</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba</groupId>
|
<groupId>com.alibaba</groupId>
|
||||||
<artifactId>fastjson</artifactId>
|
<artifactId>fastjson</artifactId>
|
||||||
|
@ -11,17 +11,18 @@ RUN mkdir -p /ruoyi/server/logs \
|
|||||||
|
|
||||||
WORKDIR /ruoyi/server
|
WORKDIR /ruoyi/server
|
||||||
|
|
||||||
ENV SERVER_PORT=8080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
|
ENV SERVER_PORT=8080 SNAIL_PORT=28080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
|
||||||
|
|
||||||
EXPOSE ${SERVER_PORT}
|
EXPOSE ${SERVER_PORT}
|
||||||
|
# 暴露 snail job 客户端端口 用于定时任务调度中心通信
|
||||||
|
EXPOSE ${SNAIL_PORT}
|
||||||
|
|
||||||
ADD ./target/ruoyi-admin.jar ./app.jar
|
ADD ./target/ruoyi-admin.jar ./app.jar
|
||||||
# 工作流字体文件
|
|
||||||
ADD ./zhFonts/ /usr/share/fonts/zhFonts/
|
|
||||||
|
|
||||||
SHELL ["/bin/bash", "-c"]
|
SHELL ["/bin/bash", "-c"]
|
||||||
|
|
||||||
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
|
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
|
||||||
|
-Dsnail-job.port=${SNAIL_PORT} \
|
||||||
# 应用名称 如果想区分集群节点监控 改成不同的名称即可
|
# 应用名称 如果想区分集群节点监控 改成不同的名称即可
|
||||||
#-Dskywalking.agent.service_name=ruoyi-server \
|
#-Dskywalking.agent.service_name=ruoyi-server \
|
||||||
#-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \
|
#-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \
|
||||||
|
@ -11,6 +11,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.dromara.common.core.constant.Constants;
|
import org.dromara.common.core.constant.Constants;
|
||||||
import org.dromara.common.core.constant.GlobalConstants;
|
import org.dromara.common.core.constant.GlobalConstants;
|
||||||
import org.dromara.common.core.domain.R;
|
import org.dromara.common.core.domain.R;
|
||||||
|
import org.dromara.common.core.exception.ServiceException;
|
||||||
import org.dromara.common.core.utils.SpringUtils;
|
import org.dromara.common.core.utils.SpringUtils;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
||||||
@ -79,12 +80,21 @@ public class CaptchaController {
|
|||||||
*
|
*
|
||||||
* @param email 邮箱
|
* @param email 邮箱
|
||||||
*/
|
*/
|
||||||
@RateLimiter(key = "#email", time = 60, count = 1)
|
|
||||||
@GetMapping("/resource/email/code")
|
@GetMapping("/resource/email/code")
|
||||||
public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
|
public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
|
||||||
if (!mailProperties.getEnabled()) {
|
if (!mailProperties.getEnabled()) {
|
||||||
return R.fail("当前系统没有开启邮箱功能!");
|
return R.fail("当前系统没有开启邮箱功能!");
|
||||||
}
|
}
|
||||||
|
SpringUtils.getAopProxy(this).emailCodeImpl(email);
|
||||||
|
return R.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮箱验证码
|
||||||
|
* 独立方法避免验证码关闭之后仍然走限流
|
||||||
|
*/
|
||||||
|
@RateLimiter(key = "#email", time = 60, count = 1)
|
||||||
|
public void emailCodeImpl(String email) {
|
||||||
String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
|
String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
|
||||||
String code = RandomUtil.randomNumbers(4);
|
String code = RandomUtil.randomNumbers(4);
|
||||||
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||||
@ -92,23 +102,30 @@ public class CaptchaController {
|
|||||||
MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
|
MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("验证码短信发送异常 => {}", e.getMessage());
|
log.error("验证码短信发送异常 => {}", e.getMessage());
|
||||||
return R.fail(e.getMessage());
|
throw new ServiceException(e.getMessage());
|
||||||
}
|
}
|
||||||
return R.ok();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成验证码
|
* 生成验证码
|
||||||
*/
|
*/
|
||||||
@RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
|
|
||||||
@GetMapping("/auth/code")
|
@GetMapping("/auth/code")
|
||||||
public R<CaptchaVo> getCode() {
|
public R<CaptchaVo> getCode() {
|
||||||
CaptchaVo captchaVo = new CaptchaVo();
|
|
||||||
boolean captchaEnabled = captchaProperties.getEnable();
|
boolean captchaEnabled = captchaProperties.getEnable();
|
||||||
if (!captchaEnabled) {
|
if (!captchaEnabled) {
|
||||||
|
CaptchaVo captchaVo = new CaptchaVo();
|
||||||
captchaVo.setCaptchaEnabled(false);
|
captchaVo.setCaptchaEnabled(false);
|
||||||
return R.ok(captchaVo);
|
return R.ok(captchaVo);
|
||||||
}
|
}
|
||||||
|
return R.ok(SpringUtils.getAopProxy(this).getCodeImpl());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成验证码
|
||||||
|
* 独立方法避免验证码关闭之后仍然走限流
|
||||||
|
*/
|
||||||
|
@RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
|
||||||
|
public CaptchaVo getCodeImpl() {
|
||||||
// 保存验证码信息
|
// 保存验证码信息
|
||||||
String uuid = IdUtil.simpleUUID();
|
String uuid = IdUtil.simpleUUID();
|
||||||
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
|
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
|
||||||
@ -128,9 +145,10 @@ public class CaptchaController {
|
|||||||
code = exp.getValue(String.class);
|
code = exp.getValue(String.class);
|
||||||
}
|
}
|
||||||
RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||||
|
CaptchaVo captchaVo = new CaptchaVo();
|
||||||
captchaVo.setUuid(uuid);
|
captchaVo.setUuid(uuid);
|
||||||
captchaVo.setImg(captcha.getImageBase64());
|
captchaVo.setImg(captcha.getImageBase64());
|
||||||
return R.ok(captchaVo);
|
return captchaVo;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package org.dromara.web.controller;
|
package org.dromara.web.controller;
|
||||||
|
|
||||||
import cn.dev33.satoken.annotation.SaIgnore;
|
import cn.dev33.satoken.annotation.SaIgnore;
|
||||||
import org.dromara.common.core.config.RuoYiConfig;
|
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.dromara.common.core.utils.SpringUtils;
|
||||||
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
@ -17,16 +17,12 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
@RestController
|
@RestController
|
||||||
public class IndexController {
|
public class IndexController {
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统基础配置
|
|
||||||
*/
|
|
||||||
private final RuoYiConfig ruoyiConfig;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 访问首页,提示语
|
* 访问首页,提示语
|
||||||
*/
|
*/
|
||||||
@GetMapping("/")
|
@GetMapping("/")
|
||||||
public String index() {
|
public String index() {
|
||||||
return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", ruoyiConfig.getName(), ruoyiConfig.getVersion());
|
return StringUtils.format("欢迎使用{}后台管理框架,请通过前端地址访问。", SpringUtils.getApplicationName());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
package org.dromara.web.listener;
|
package org.dromara.web.listener;
|
||||||
|
|
||||||
import cn.dev33.satoken.config.SaTokenConfig;
|
|
||||||
import cn.dev33.satoken.listener.SaTokenListener;
|
import cn.dev33.satoken.listener.SaTokenListener;
|
||||||
import cn.dev33.satoken.stp.SaLoginModel;
|
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||||
import cn.hutool.core.convert.Convert;
|
import cn.hutool.core.convert.Convert;
|
||||||
import cn.hutool.http.useragent.UserAgent;
|
import cn.hutool.http.useragent.UserAgent;
|
||||||
import cn.hutool.http.useragent.UserAgentUtil;
|
import cn.hutool.http.useragent.UserAgentUtil;
|
||||||
@ -35,14 +34,13 @@ import java.time.Duration;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class UserActionListener implements SaTokenListener {
|
public class UserActionListener implements SaTokenListener {
|
||||||
|
|
||||||
private final SaTokenConfig tokenConfig;
|
|
||||||
private final SysLoginService loginService;
|
private final SysLoginService loginService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 每次登录时触发
|
* 每次登录时触发
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
|
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter) {
|
||||||
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
|
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
|
||||||
String ip = ServletUtils.getClientIP();
|
String ip = ServletUtils.getClientIP();
|
||||||
UserOnlineDTO dto = new UserOnlineDTO();
|
UserOnlineDTO dto = new UserOnlineDTO();
|
||||||
@ -52,17 +50,17 @@ public class UserActionListener implements SaTokenListener {
|
|||||||
dto.setOs(userAgent.getOs().getName());
|
dto.setOs(userAgent.getOs().getName());
|
||||||
dto.setLoginTime(System.currentTimeMillis());
|
dto.setLoginTime(System.currentTimeMillis());
|
||||||
dto.setTokenId(tokenValue);
|
dto.setTokenId(tokenValue);
|
||||||
String username = (String) loginModel.getExtra(LoginHelper.USER_NAME_KEY);
|
String username = (String) loginParameter.getExtra(LoginHelper.USER_NAME_KEY);
|
||||||
String tenantId = (String) loginModel.getExtra(LoginHelper.TENANT_KEY);
|
String tenantId = (String) loginParameter.getExtra(LoginHelper.TENANT_KEY);
|
||||||
dto.setUserName(username);
|
dto.setUserName(username);
|
||||||
dto.setClientKey((String) loginModel.getExtra(LoginHelper.CLIENT_KEY));
|
dto.setClientKey((String) loginParameter.getExtra(LoginHelper.CLIENT_KEY));
|
||||||
dto.setDeviceType(loginModel.getDevice());
|
dto.setDeviceType(loginParameter.getDeviceType());
|
||||||
dto.setDeptName((String) loginModel.getExtra(LoginHelper.DEPT_NAME_KEY));
|
dto.setDeptName((String) loginParameter.getExtra(LoginHelper.DEPT_NAME_KEY));
|
||||||
TenantHelper.dynamic(tenantId, () -> {
|
TenantHelper.dynamic(tenantId, () -> {
|
||||||
if(tokenConfig.getTimeout() == -1) {
|
if(loginParameter.getTimeout() == -1) {
|
||||||
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
|
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
|
||||||
} else {
|
} else {
|
||||||
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(tokenConfig.getTimeout()));
|
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(loginParameter.getTimeout()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// 记录登录日志
|
// 记录登录日志
|
||||||
@ -74,7 +72,7 @@ public class UserActionListener implements SaTokenListener {
|
|||||||
logininforEvent.setRequest(ServletUtils.getRequest());
|
logininforEvent.setRequest(ServletUtils.getRequest());
|
||||||
SpringUtils.context().publishEvent(logininforEvent);
|
SpringUtils.context().publishEvent(logininforEvent);
|
||||||
// 更新登录信息
|
// 更新登录信息
|
||||||
loginService.recordLoginInfo((Long) loginModel.getExtra(LoginHelper.USER_KEY), ip);
|
loginService.recordLoginInfo((Long) loginParameter.getExtra(LoginHelper.USER_KEY), ip);
|
||||||
log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
|
log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package org.dromara.web.service;
|
package org.dromara.web.service;
|
||||||
|
|
||||||
import cn.dev33.satoken.secure.BCrypt;
|
import cn.hutool.crypto.digest.BCrypt;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.dromara.common.core.constant.Constants;
|
import org.dromara.common.core.constant.Constants;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package org.dromara.web.service.impl;
|
package org.dromara.web.service.impl;
|
||||||
|
|
||||||
import cn.dev33.satoken.stp.SaLoginModel;
|
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -58,8 +58,8 @@ public class EmailAuthStrategy implements IAuthStrategy {
|
|||||||
});
|
});
|
||||||
loginUser.setClientKey(client.getClientKey());
|
loginUser.setClientKey(client.getClientKey());
|
||||||
loginUser.setDeviceType(client.getDeviceType());
|
loginUser.setDeviceType(client.getDeviceType());
|
||||||
SaLoginModel model = new SaLoginModel();
|
SaLoginParameter model = new SaLoginParameter();
|
||||||
model.setDevice(client.getDeviceType());
|
model.setDeviceType(client.getDeviceType());
|
||||||
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
||||||
// 例如: 后台用户30分钟过期 app用户1天过期
|
// 例如: 后台用户30分钟过期 app用户1天过期
|
||||||
model.setTimeout(client.getTimeout());
|
model.setTimeout(client.getTimeout());
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package org.dromara.web.service.impl;
|
package org.dromara.web.service.impl;
|
||||||
|
|
||||||
import cn.dev33.satoken.secure.BCrypt;
|
|
||||||
import cn.dev33.satoken.stp.SaLoginModel;
|
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.crypto.digest.BCrypt;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -70,8 +70,8 @@ public class PasswordAuthStrategy implements IAuthStrategy {
|
|||||||
});
|
});
|
||||||
loginUser.setClientKey(client.getClientKey());
|
loginUser.setClientKey(client.getClientKey());
|
||||||
loginUser.setDeviceType(client.getDeviceType());
|
loginUser.setDeviceType(client.getDeviceType());
|
||||||
SaLoginModel model = new SaLoginModel();
|
SaLoginParameter model = new SaLoginParameter();
|
||||||
model.setDevice(client.getDeviceType());
|
model.setDeviceType(client.getDeviceType());
|
||||||
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
||||||
// 例如: 后台用户30分钟过期 app用户1天过期
|
// 例如: 后台用户30分钟过期 app用户1天过期
|
||||||
model.setTimeout(client.getTimeout());
|
model.setTimeout(client.getTimeout());
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package org.dromara.web.service.impl;
|
package org.dromara.web.service.impl;
|
||||||
|
|
||||||
import cn.dev33.satoken.stp.SaLoginModel;
|
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -58,8 +58,8 @@ public class SmsAuthStrategy implements IAuthStrategy {
|
|||||||
});
|
});
|
||||||
loginUser.setClientKey(client.getClientKey());
|
loginUser.setClientKey(client.getClientKey());
|
||||||
loginUser.setDeviceType(client.getDeviceType());
|
loginUser.setDeviceType(client.getDeviceType());
|
||||||
SaLoginModel model = new SaLoginModel();
|
SaLoginParameter model = new SaLoginParameter();
|
||||||
model.setDevice(client.getDeviceType());
|
model.setDeviceType(client.getDeviceType());
|
||||||
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
||||||
// 例如: 后台用户30分钟过期 app用户1天过期
|
// 例如: 后台用户30分钟过期 app用户1天过期
|
||||||
model.setTimeout(client.getTimeout());
|
model.setTimeout(client.getTimeout());
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package org.dromara.web.service.impl;
|
package org.dromara.web.service.impl;
|
||||||
|
|
||||||
import cn.dev33.satoken.stp.SaLoginModel;
|
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.map.MapUtil;
|
import cn.hutool.core.map.MapUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
@ -99,8 +99,8 @@ public class SocialAuthStrategy implements IAuthStrategy {
|
|||||||
});
|
});
|
||||||
loginUser.setClientKey(client.getClientKey());
|
loginUser.setClientKey(client.getClientKey());
|
||||||
loginUser.setDeviceType(client.getDeviceType());
|
loginUser.setDeviceType(client.getDeviceType());
|
||||||
SaLoginModel model = new SaLoginModel();
|
SaLoginParameter model = new SaLoginParameter();
|
||||||
model.setDevice(client.getDeviceType());
|
model.setDeviceType(client.getDeviceType());
|
||||||
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
||||||
// 例如: 后台用户30分钟过期 app用户1天过期
|
// 例如: 后台用户30分钟过期 app用户1天过期
|
||||||
model.setTimeout(client.getTimeout());
|
model.setTimeout(client.getTimeout());
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package org.dromara.web.service.impl;
|
package org.dromara.web.service.impl;
|
||||||
|
|
||||||
import cn.dev33.satoken.stp.SaLoginModel;
|
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -76,8 +76,8 @@ public class XcxAuthStrategy implements IAuthStrategy {
|
|||||||
loginUser.setDeviceType(client.getDeviceType());
|
loginUser.setDeviceType(client.getDeviceType());
|
||||||
loginUser.setOpenid(openid);
|
loginUser.setOpenid(openid);
|
||||||
|
|
||||||
SaLoginModel model = new SaLoginModel();
|
SaLoginParameter model = new SaLoginParameter();
|
||||||
model.setDevice(client.getDeviceType());
|
model.setDeviceType(client.getDeviceType());
|
||||||
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
||||||
// 例如: 后台用户30分钟过期 app用户1天过期
|
// 例如: 后台用户30分钟过期 app用户1天过期
|
||||||
model.setTimeout(client.getTimeout());
|
model.setTimeout(client.getTimeout());
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
spring.boot.admin.client:
|
spring.boot.admin.client:
|
||||||
# 增加客户端开关
|
# 增加客户端开关
|
||||||
enabled: true
|
enabled: true
|
||||||
url: http://localhost:9090
|
url: http://localhost:9090/admin
|
||||||
instance:
|
instance:
|
||||||
service-host-type: IP
|
service-host-type: IP
|
||||||
metadata:
|
metadata:
|
||||||
@ -120,8 +120,8 @@ redisson:
|
|||||||
nettyThreads: 8
|
nettyThreads: 8
|
||||||
# 单节点配置
|
# 单节点配置
|
||||||
singleServerConfig:
|
singleServerConfig:
|
||||||
# 客户端名称
|
# 客户端名称 不能用中文
|
||||||
clientName: ${ruoyi.name}
|
clientName: RuoYi-Vue-Plus
|
||||||
# 最小空闲连接数
|
# 最小空闲连接数
|
||||||
connectionMinimumIdleSize: 8
|
connectionMinimumIdleSize: 8
|
||||||
# 连接池大小
|
# 连接池大小
|
||||||
@ -263,3 +263,10 @@ justauth:
|
|||||||
client-id: 10**********6
|
client-id: 10**********6
|
||||||
client-secret: 1f7d08**********5b7**********29e
|
client-secret: 1f7d08**********5b7**********29e
|
||||||
redirect-uri: ${justauth.address}/social-callback?source=gitlab
|
redirect-uri: ${justauth.address}/social-callback?source=gitlab
|
||||||
|
gitea:
|
||||||
|
# 前端改动 https://gitee.com/JavaLionLi/plus-ui/pulls/204
|
||||||
|
# gitea 服务器地址
|
||||||
|
server-url: https://demo.gitea.com
|
||||||
|
client-id: 10**********6
|
||||||
|
client-secret: 1f7d08**********5b7**********29e
|
||||||
|
redirect-uri: ${justauth.address}/social-callback?source=gitea
|
||||||
|
@ -5,7 +5,7 @@ spring.servlet.multipart.location: /ruoyi/server/temp
|
|||||||
spring.boot.admin.client:
|
spring.boot.admin.client:
|
||||||
# 增加客户端开关
|
# 增加客户端开关
|
||||||
enabled: true
|
enabled: true
|
||||||
url: http://localhost:9090
|
url: http://localhost:9090/admin
|
||||||
instance:
|
instance:
|
||||||
service-host-type: IP
|
service-host-type: IP
|
||||||
metadata:
|
metadata:
|
||||||
@ -123,8 +123,8 @@ redisson:
|
|||||||
nettyThreads: 32
|
nettyThreads: 32
|
||||||
# 单节点配置
|
# 单节点配置
|
||||||
singleServerConfig:
|
singleServerConfig:
|
||||||
# 客户端名称
|
# 客户端名称 不能用中文
|
||||||
clientName: ${ruoyi.name}
|
clientName: RuoYi-Vue-Plus
|
||||||
# 最小空闲连接数
|
# 最小空闲连接数
|
||||||
connectionMinimumIdleSize: 32
|
connectionMinimumIdleSize: 32
|
||||||
# 连接池大小
|
# 连接池大小
|
||||||
@ -265,3 +265,10 @@ justauth:
|
|||||||
client-id: 10**********6
|
client-id: 10**********6
|
||||||
client-secret: 1f7d08**********5b7**********29e
|
client-secret: 1f7d08**********5b7**********29e
|
||||||
redirect-uri: ${justauth.address}/social-callback?source=gitlab
|
redirect-uri: ${justauth.address}/social-callback?source=gitlab
|
||||||
|
gitea:
|
||||||
|
# 前端改动 https://gitee.com/JavaLionLi/plus-ui/pulls/204
|
||||||
|
# gitea 服务器地址
|
||||||
|
server-url: https://demo.gitea.com
|
||||||
|
client-id: 10**********6
|
||||||
|
client-secret: 1f7d08**********5b7**********29e
|
||||||
|
redirect-uri: ${justauth.address}/social-callback?source=gitea
|
||||||
|
@ -1,24 +1,3 @@
|
|||||||
# 项目相关配置
|
|
||||||
ruoyi:
|
|
||||||
# 名称
|
|
||||||
name: RuoYi-Vue-Plus
|
|
||||||
# 版本
|
|
||||||
version: ${revision}
|
|
||||||
# 版权年份
|
|
||||||
copyrightYear: 2024
|
|
||||||
|
|
||||||
captcha:
|
|
||||||
enable: true
|
|
||||||
# 页面 <参数设置> 可开启关闭 验证码校验
|
|
||||||
# 验证码类型 math 数组计算 char 字符验证
|
|
||||||
type: MATH
|
|
||||||
# line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
|
|
||||||
category: CIRCLE
|
|
||||||
# 数字验证码位数
|
|
||||||
numberLength: 1
|
|
||||||
# 字符验证码长度
|
|
||||||
charLength: 4
|
|
||||||
|
|
||||||
# 开发环境配置
|
# 开发环境配置
|
||||||
server:
|
server:
|
||||||
# 服务器的HTTP端口,默认为8080
|
# 服务器的HTTP端口,默认为8080
|
||||||
@ -41,6 +20,18 @@ server:
|
|||||||
# 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
|
# 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
|
||||||
worker: 256
|
worker: 256
|
||||||
|
|
||||||
|
captcha:
|
||||||
|
# 是否启用验证码校验
|
||||||
|
enable: true
|
||||||
|
# 验证码类型 math 数组计算 char 字符验证
|
||||||
|
type: MATH
|
||||||
|
# line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
|
||||||
|
category: CIRCLE
|
||||||
|
# 数字验证码位数
|
||||||
|
numberLength: 1
|
||||||
|
# 字符验证码长度
|
||||||
|
charLength: 4
|
||||||
|
|
||||||
# 日志配置
|
# 日志配置
|
||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
@ -61,7 +52,7 @@ user:
|
|||||||
# Spring配置
|
# Spring配置
|
||||||
spring:
|
spring:
|
||||||
application:
|
application:
|
||||||
name: ${ruoyi.name}
|
name: RuoYi-Vue-Plus
|
||||||
threads:
|
threads:
|
||||||
# 开启虚拟线程 仅jdk21可用
|
# 开启虚拟线程 仅jdk21可用
|
||||||
virtual:
|
virtual:
|
||||||
@ -119,7 +110,7 @@ security:
|
|||||||
- /error
|
- /error
|
||||||
- /*/api-docs
|
- /*/api-docs
|
||||||
- /*/api-docs/**
|
- /*/api-docs/**
|
||||||
- /warm-flow-ui/token-name
|
- /warm-flow-ui/config
|
||||||
|
|
||||||
# 多租户配置
|
# 多租户配置
|
||||||
tenant:
|
tenant:
|
||||||
@ -186,12 +177,9 @@ springdoc:
|
|||||||
api-docs:
|
api-docs:
|
||||||
# 是否开启接口文档
|
# 是否开启接口文档
|
||||||
enabled: true
|
enabled: true
|
||||||
# swagger-ui:
|
|
||||||
# # 持久化认证数据
|
|
||||||
# persistAuthorization: true
|
|
||||||
info:
|
info:
|
||||||
# 标题
|
# 标题
|
||||||
title: '标题:${ruoyi.name}多租户管理系统_接口文档'
|
title: '标题:RuoYi-Vue-Plus多租户管理系统_接口文档'
|
||||||
# 描述
|
# 描述
|
||||||
description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
|
description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
|
||||||
# 版本
|
# 版本
|
||||||
@ -228,7 +216,6 @@ xss:
|
|||||||
# 排除链接(多个用逗号分隔)
|
# 排除链接(多个用逗号分隔)
|
||||||
excludeUrls:
|
excludeUrls:
|
||||||
- /system/notice
|
- /system/notice
|
||||||
- /warm-flow/save-xml
|
|
||||||
|
|
||||||
# 全局线程池相关配置
|
# 全局线程池相关配置
|
||||||
# 如使用JDK21请直接使用虚拟线程 不要开启此配置
|
# 如使用JDK21请直接使用虚拟线程 不要开启此配置
|
||||||
@ -281,3 +268,11 @@ warm-flow:
|
|||||||
ui: true
|
ui: true
|
||||||
# 默认Authorization,如果有多个token,用逗号分隔
|
# 默认Authorization,如果有多个token,用逗号分隔
|
||||||
token-name: ${sa-token.token-name},clientid
|
token-name: ${sa-token.token-name},clientid
|
||||||
|
# 流程状态对应的三元色
|
||||||
|
chart-status-color:
|
||||||
|
## 未办理
|
||||||
|
- 62,62,62
|
||||||
|
## 待办理
|
||||||
|
- 255,205,23
|
||||||
|
## 已办理
|
||||||
|
- 157,255,0
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package org.dromara.test;
|
package org.dromara.test;
|
||||||
|
|
||||||
import org.dromara.common.core.config.RuoYiConfig;
|
import org.dromara.common.web.config.properties.CaptchaProperties;
|
||||||
import org.junit.jupiter.api.*;
|
import org.junit.jupiter.api.*;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
@ -17,19 +17,19 @@ import java.util.concurrent.TimeUnit;
|
|||||||
public class DemoUnitTest {
|
public class DemoUnitTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private RuoYiConfig ruoYiConfig;
|
private CaptchaProperties captchaProperties;
|
||||||
|
|
||||||
@DisplayName("测试 @SpringBootTest @Test @DisplayName 注解")
|
@DisplayName("测试 @SpringBootTest @Test @DisplayName 注解")
|
||||||
@Test
|
@Test
|
||||||
public void testTest() {
|
public void testTest() {
|
||||||
System.out.println(ruoYiConfig);
|
System.out.println(captchaProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Disabled
|
@Disabled
|
||||||
@DisplayName("测试 @Disabled 注解")
|
@DisplayName("测试 @Disabled 注解")
|
||||||
@Test
|
@Test
|
||||||
public void testDisabled() {
|
public void testDisabled() {
|
||||||
System.out.println(ruoYiConfig);
|
System.out.println(captchaProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Timeout(value = 2L, unit = TimeUnit.SECONDS)
|
@Timeout(value = 2L, unit = TimeUnit.SECONDS)
|
||||||
@ -37,7 +37,7 @@ public class DemoUnitTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testTimeout() throws InterruptedException {
|
public void testTimeout() throws InterruptedException {
|
||||||
Thread.sleep(3000);
|
Thread.sleep(3000);
|
||||||
System.out.println(ruoYiConfig);
|
System.out.println(captchaProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
3f2ee348-0303-40ca-bf03-03f48d2d2141
|
|
Binary file not shown.
@ -1,4 +0,0 @@
|
|||||||
3
|
|
||||||
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso10646-1
|
|
||||||
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso8859-1
|
|
||||||
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-koi8-r
|
|
@ -1,4 +0,0 @@
|
|||||||
3
|
|
||||||
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso10646-1
|
|
||||||
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso8859-1
|
|
||||||
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-koi8-r
|
|
@ -14,7 +14,7 @@
|
|||||||
</description>
|
</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>5.3.0</revision>
|
<revision>5.4.0</revision>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
package org.dromara.common.core.config;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 读取项目相关配置
|
|
||||||
*
|
|
||||||
* @author Lion Li
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@Component
|
|
||||||
@ConfigurationProperties(prefix = "ruoyi")
|
|
||||||
public class RuoYiConfig {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 项目名称
|
|
||||||
*/
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 版本
|
|
||||||
*/
|
|
||||||
private String version;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 版权年份
|
|
||||||
*/
|
|
||||||
private String copyrightYear;
|
|
||||||
|
|
||||||
}
|
|
@ -3,6 +3,7 @@ package org.dromara.common.core.config;
|
|||||||
import jakarta.validation.Validator;
|
import jakarta.validation.Validator;
|
||||||
import org.hibernate.validator.HibernateValidator;
|
import org.hibernate.validator.HibernateValidator;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
|
||||||
import org.springframework.context.MessageSource;
|
import org.springframework.context.MessageSource;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||||
@ -14,11 +15,11 @@ import java.util.Properties;
|
|||||||
*
|
*
|
||||||
* @author Lion Li
|
* @author Lion Li
|
||||||
*/
|
*/
|
||||||
@AutoConfiguration
|
@AutoConfiguration(before = ValidationAutoConfiguration.class)
|
||||||
public class ValidatorConfig {
|
public class ValidatorConfig {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 配置校验框架 快速返回模式
|
* 配置校验框架 快速失败模式
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public Validator validator(MessageSource messageSource) {
|
public Validator validator(MessageSource messageSource) {
|
||||||
@ -28,7 +29,7 @@ public class ValidatorConfig {
|
|||||||
// 设置使用 HibernateValidator 校验器
|
// 设置使用 HibernateValidator 校验器
|
||||||
factoryBean.setProviderClass(HibernateValidator.class);
|
factoryBean.setProviderClass(HibernateValidator.class);
|
||||||
Properties properties = new Properties();
|
Properties properties = new Properties();
|
||||||
// 设置 快速异常返回
|
// 设置快速失败模式(fail-fast),即校验过程中一旦遇到失败,立即停止并返回错误
|
||||||
properties.setProperty("hibernate.validator.fail_fast", "true");
|
properties.setProperty("hibernate.validator.fail_fast", "true");
|
||||||
factoryBean.setValidationProperties(properties);
|
factoryBean.setValidationProperties(properties);
|
||||||
// 加载配置
|
// 加载配置
|
||||||
|
@ -3,13 +3,14 @@ package org.dromara.common.core.constant;
|
|||||||
/**
|
/**
|
||||||
* 缓存组名称常量
|
* 缓存组名称常量
|
||||||
* <p>
|
* <p>
|
||||||
* key 格式为 cacheNames#ttl#maxIdleTime#maxSize
|
* key 格式为 cacheNames#ttl#maxIdleTime#maxSize#local
|
||||||
* <p>
|
* <p>
|
||||||
* ttl 过期时间 如果设置为0则不过期 默认为0
|
* ttl 过期时间 如果设置为0则不过期 默认为0
|
||||||
* maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
|
* maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
|
||||||
* maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
|
* maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
|
||||||
|
* local 默认开启本地缓存为1 关闭本地缓存为0
|
||||||
* <p>
|
* <p>
|
||||||
* 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500
|
* 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500、test#1h#0#500#0
|
||||||
*
|
*
|
||||||
* @author Lion Li
|
* @author Lion Li
|
||||||
*/
|
*/
|
||||||
@ -30,6 +31,11 @@ public interface CacheNames {
|
|||||||
*/
|
*/
|
||||||
String SYS_DICT = "sys_dict";
|
String SYS_DICT = "sys_dict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据字典类型
|
||||||
|
*/
|
||||||
|
String SYS_DICT_TYPE = "sys_dict_type";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 租户
|
* 租户
|
||||||
*/
|
*/
|
||||||
|
@ -17,9 +17,14 @@ public interface RegexConstants extends RegexPool {
|
|||||||
String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$";
|
String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 权限标识必须符合 tool:build:list 格式,或者空字符串
|
* 权限标识必须符合以下格式:
|
||||||
|
* 1. 标准格式:xxx:yyy:zzz
|
||||||
|
* - 第一部分(xxx):只能包含字母、数字和下划线(_),不能使用 `*`
|
||||||
|
* - 第二部分(yyy):可以包含字母、数字、下划线(_)和 `*`
|
||||||
|
* - 第三部分(zzz):可以包含字母、数字、下划线(_)和 `*`
|
||||||
|
* 2. 允许空字符串(""),表示没有权限标识
|
||||||
*/
|
*/
|
||||||
String PERMISSION_STRING = "^(|^[a-zA-Z0-9_]+:[a-zA-Z0-9_]+:[a-zA-Z0-9_]+)$";
|
String PERMISSION_STRING = "^$|^[a-zA-Z0-9_]+:[a-zA-Z0-9_*]+:[a-zA-Z0-9_*]+$";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 身份证号码(后6位)
|
* 身份证号码(后6位)
|
||||||
|
@ -72,4 +72,9 @@ public interface SystemConstants {
|
|||||||
*/
|
*/
|
||||||
Long SUPER_ADMIN_ID = 1L;
|
Long SUPER_ADMIN_ID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根部门祖级列表
|
||||||
|
*/
|
||||||
|
String ROOT_DEPT_ANCESTORS = "0";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ import java.io.Serializable;
|
|||||||
*
|
*
|
||||||
* @author AprilWind
|
* @author AprilWind
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
public class DeptDTO implements Serializable {
|
public class DeptDTO implements Serializable {
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
package org.dromara.common.core.domain.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典数据DTO
|
||||||
|
*
|
||||||
|
* @author AprilWind
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class DictDataDTO implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典标签
|
||||||
|
*/
|
||||||
|
private String dictLabel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典键值
|
||||||
|
*/
|
||||||
|
private String dictValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否默认(Y是 N否)
|
||||||
|
*/
|
||||||
|
private String isDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package org.dromara.common.core.domain.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典类型DTO
|
||||||
|
*
|
||||||
|
* @author AprilWind
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class DictTypeDTO implements Serializable {
|
||||||
|
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典主键
|
||||||
|
*/
|
||||||
|
private Long dictId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典名称
|
||||||
|
*/
|
||||||
|
private String dictName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典类型
|
||||||
|
*/
|
||||||
|
private String dictType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备注
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
}
|
@ -35,7 +35,7 @@ public class RoleDTO implements Serializable {
|
|||||||
private String roleKey;
|
private String roleKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限)
|
* 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限 6:部门及以下或本人数据权限)
|
||||||
*/
|
*/
|
||||||
private String dataScope;
|
private String dataScope;
|
||||||
|
|
||||||
|
@ -33,7 +33,22 @@ public class ProcessEvent implements Serializable {
|
|||||||
private String businessId;
|
private String businessId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态
|
* 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
|
||||||
|
*/
|
||||||
|
private Integer nodeType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程节点编码
|
||||||
|
*/
|
||||||
|
private String nodeCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程节点名称
|
||||||
|
*/
|
||||||
|
private String nodeName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程状态
|
||||||
*/
|
*/
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
@ -45,6 +60,6 @@ public class ProcessEvent implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* 当为true时为申请人节点办理
|
* 当为true时为申请人节点办理
|
||||||
*/
|
*/
|
||||||
private boolean submit;
|
private Boolean submit;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import java.io.Serial;
|
|||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流程办理监听
|
* 流程任务监听
|
||||||
*
|
*
|
||||||
* @author may
|
* @author may
|
||||||
*/
|
*/
|
||||||
@ -27,10 +27,20 @@ public class ProcessTaskEvent implements Serializable {
|
|||||||
private String flowCode;
|
private String flowCode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 审批节点编码
|
* 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
|
||||||
|
*/
|
||||||
|
private Integer nodeType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程节点编码
|
||||||
*/
|
*/
|
||||||
private String nodeCode;
|
private String nodeCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程节点名称
|
||||||
|
*/
|
||||||
|
private String nodeName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 任务id
|
* 任务id
|
||||||
*/
|
*/
|
||||||
@ -41,4 +51,9 @@ public class ProcessTaskEvent implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private String businessId;
|
private String businessId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程状态
|
||||||
|
*/
|
||||||
|
private String status;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,14 +18,14 @@ public class PasswordLoginBody extends LoginBody {
|
|||||||
* 用户名
|
* 用户名
|
||||||
*/
|
*/
|
||||||
@NotBlank(message = "{user.username.not.blank}")
|
@NotBlank(message = "{user.username.not.blank}")
|
||||||
@Length(min = 2, max = 20, message = "{user.username.length.valid}")
|
@Length(min = 2, max = 30, message = "{user.username.length.valid}")
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户密码
|
* 用户密码
|
||||||
*/
|
*/
|
||||||
@NotBlank(message = "{user.password.not.blank}")
|
@NotBlank(message = "{user.password.not.blank}")
|
||||||
@Length(min = 5, max = 20, message = "{user.password.length.valid}")
|
@Length(min = 5, max = 30, message = "{user.password.length.valid}")
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,14 +18,14 @@ public class RegisterBody extends LoginBody {
|
|||||||
* 用户名
|
* 用户名
|
||||||
*/
|
*/
|
||||||
@NotBlank(message = "{user.username.not.blank}")
|
@NotBlank(message = "{user.username.not.blank}")
|
||||||
@Length(min = 2, max = 20, message = "{user.username.length.valid}")
|
@Length(min = 2, max = 30, message = "{user.username.length.valid}")
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户密码
|
* 用户密码
|
||||||
*/
|
*/
|
||||||
@NotBlank(message = "{user.password.not.blank}")
|
@NotBlank(message = "{user.password.not.blank}")
|
||||||
@Length(min = 5, max = 20, message = "{user.password.length.valid}")
|
@Length(min = 5, max = 30, message = "{user.password.length.valid}")
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
private String userType;
|
private String userType;
|
||||||
|
@ -5,7 +5,6 @@ import lombok.Getter;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 设备类型
|
* 设备类型
|
||||||
* 针对一套 用户体系
|
|
||||||
*
|
*
|
||||||
* @author Lion Li
|
* @author Lion Li
|
||||||
*/
|
*/
|
||||||
@ -29,9 +28,12 @@ public enum DeviceType {
|
|||||||
XCX("xcx"),
|
XCX("xcx"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* social第三方端
|
* 第三方社交登录平台
|
||||||
*/
|
*/
|
||||||
SOCIAL("social");
|
SOCIAL("social");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备标识
|
||||||
|
*/
|
||||||
private final String device;
|
private final String device;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
package org.dromara.common.core.enums;
|
package org.dromara.common.core.enums;
|
||||||
|
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设备类型
|
* 用户类型
|
||||||
* 针对多套 用户体系
|
|
||||||
*
|
*
|
||||||
* @author Lion Li
|
* @author Lion Li
|
||||||
*/
|
*/
|
||||||
@ -15,15 +14,18 @@ import lombok.Getter;
|
|||||||
public enum UserType {
|
public enum UserType {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pc端
|
* 后台系统用户
|
||||||
*/
|
*/
|
||||||
SYS_USER("sys_user"),
|
SYS_USER("sys_user"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* app端
|
* 移动客户端用户
|
||||||
*/
|
*/
|
||||||
APP_USER("app_user");
|
APP_USER("app_user");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户类型标识(用于 token、权限识别等)
|
||||||
|
*/
|
||||||
private final String userType;
|
private final String userType;
|
||||||
|
|
||||||
public static UserType getUserType(String str) {
|
public static UserType getUserType(String str) {
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
package org.dromara.common.core.service;
|
package org.dromara.common.core.service;
|
||||||
|
|
||||||
|
import org.dromara.common.core.domain.dto.DictDataDTO;
|
||||||
|
import org.dromara.common.core.domain.dto.DictTypeDTO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,4 +68,20 @@ public interface DictService {
|
|||||||
*/
|
*/
|
||||||
Map<String, String> getAllDictByDictType(String dictType);
|
Map<String, String> getAllDictByDictType(String dictType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字典类型查询详细信息
|
||||||
|
*
|
||||||
|
* @param dictType 字典类型
|
||||||
|
* @return 字典类型详细信息
|
||||||
|
*/
|
||||||
|
DictTypeDTO getDictType(String dictType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字典类型查询字典数据列表
|
||||||
|
*
|
||||||
|
* @param dictType 字典类型
|
||||||
|
* @return 字典数据列表
|
||||||
|
*/
|
||||||
|
List<DictDataDTO> getDictData(String dictType);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
package org.dromara.common.core.service;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户权限处理
|
||||||
|
*
|
||||||
|
* @author Lion Li
|
||||||
|
*/
|
||||||
|
public interface PermissionService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取角色数据权限
|
||||||
|
*
|
||||||
|
* @param userId 用户id
|
||||||
|
* @return 角色权限信息
|
||||||
|
*/
|
||||||
|
Set<String> getRolePermission(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取菜单数据权限
|
||||||
|
*
|
||||||
|
* @param userId 用户id
|
||||||
|
* @return 菜单权限信息
|
||||||
|
*/
|
||||||
|
Set<String> getMenuPermission(Long userId);
|
||||||
|
|
||||||
|
}
|
@ -3,6 +3,7 @@ package org.dromara.common.core.service;
|
|||||||
import org.dromara.common.core.domain.dto.UserDTO;
|
import org.dromara.common.core.domain.dto.UserDTO;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用 用户服务
|
* 通用 用户服务
|
||||||
@ -91,4 +92,36 @@ public interface UserService {
|
|||||||
*/
|
*/
|
||||||
List<UserDTO> selectUsersByPostIds(List<Long> postIds);
|
List<UserDTO> selectUsersByPostIds(List<Long> postIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户 ID 列表查询用户名称映射关系
|
||||||
|
*
|
||||||
|
* @param userIds 用户 ID 列表
|
||||||
|
* @return Map,其中 key 为用户 ID,value 为对应的用户名称
|
||||||
|
*/
|
||||||
|
Map<Long, String> selectUserNamesByIds(List<Long> userIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据角色 ID 列表查询角色名称映射关系
|
||||||
|
*
|
||||||
|
* @param roleIds 角色 ID 列表
|
||||||
|
* @return Map,其中 key 为角色 ID,value 为对应的角色名称
|
||||||
|
*/
|
||||||
|
Map<Long, String> selectRoleNamesByIds(List<Long> roleIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据部门 ID 列表查询部门名称映射关系
|
||||||
|
*
|
||||||
|
* @param deptIds 部门 ID 列表
|
||||||
|
* @return Map,其中 key 为部门 ID,value 为对应的部门名称
|
||||||
|
*/
|
||||||
|
Map<Long, String> selectDeptNamesByIds(List<Long> deptIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据岗位 ID 列表查询岗位名称映射关系
|
||||||
|
*
|
||||||
|
* @param postIds 岗位 ID 列表
|
||||||
|
* @return Map,其中 key 为岗位 ID,value 为对应的岗位名称
|
||||||
|
*/
|
||||||
|
Map<Long, String> selectPostNamesByIds(List<Long> postIds);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -78,9 +78,18 @@ public interface WorkflowService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 办理任务
|
* 办理任务
|
||||||
|
* 系统后台发起审批 无用户信息 需要忽略权限
|
||||||
|
* completeTask.getVariables().put("ignore", true);
|
||||||
*
|
*
|
||||||
* @param completeTask 参数
|
* @param completeTask 参数
|
||||||
* @return 结果
|
|
||||||
*/
|
*/
|
||||||
boolean completeTask(CompleteTaskDTO completeTask);
|
boolean completeTask(CompleteTaskDTO completeTask);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 办理任务
|
||||||
|
*
|
||||||
|
* @param taskId 任务ID
|
||||||
|
* @param message 办理意见
|
||||||
|
*/
|
||||||
|
boolean completeTask(Long taskId, String message);
|
||||||
}
|
}
|
||||||
|
@ -175,14 +175,27 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算两个日期之间的天数差(以毫秒为单位)
|
* 计算两个时间之间的时间差,并以指定单位返回(绝对值)
|
||||||
*
|
*
|
||||||
* @param date1 第一个日期
|
* @param start 起始时间
|
||||||
* @param date2 第二个日期
|
* @param end 结束时间
|
||||||
* @return 两个日期之间的天数差的绝对值
|
* @param unit 所需返回的时间单位(DAYS、HOURS、MINUTES、SECONDS、MILLISECONDS、MICROSECONDS、NANOSECONDS)
|
||||||
|
* @return 时间差的绝对值,以指定单位表示
|
||||||
*/
|
*/
|
||||||
public static int differentDaysByMillisecond(Date date1, Date date2) {
|
public static long difference(Date start, Date end, TimeUnit unit) {
|
||||||
return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
|
// 计算时间差,单位为毫秒,取绝对值避免负数
|
||||||
|
long diffInMillis = Math.abs(end.getTime() - start.getTime());
|
||||||
|
|
||||||
|
// 根据目标单位转换时间差
|
||||||
|
return switch (unit) {
|
||||||
|
case DAYS -> diffInMillis / TimeUnit.DAYS.toMillis(1);
|
||||||
|
case HOURS -> diffInMillis / TimeUnit.HOURS.toMillis(1);
|
||||||
|
case MINUTES -> diffInMillis / TimeUnit.MINUTES.toMillis(1);
|
||||||
|
case SECONDS -> diffInMillis / TimeUnit.SECONDS.toMillis(1);
|
||||||
|
case MILLISECONDS -> diffInMillis;
|
||||||
|
case MICROSECONDS -> TimeUnit.MILLISECONDS.toMicros(diffInMillis);
|
||||||
|
case NANOSECONDS -> TimeUnit.MILLISECONDS.toNanos(diffInMillis);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
package org.dromara.common.core.utils;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.PatternPool;
|
||||||
|
import cn.hutool.core.net.NetUtil;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.dromara.common.core.utils.regex.RegexUtils;
|
||||||
|
|
||||||
|
import java.net.Inet6Address;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增强网络相关工具类
|
||||||
|
*
|
||||||
|
* @author 秋辞未寒
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class NetUtils extends NetUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为IPv6地址
|
||||||
|
*
|
||||||
|
* @param ip IP地址
|
||||||
|
* @return 是否为IPv6地址
|
||||||
|
*/
|
||||||
|
public static boolean isIPv6(String ip) {
|
||||||
|
try {
|
||||||
|
// 判断是否为IPv6地址
|
||||||
|
return InetAddress.getByName(ip) instanceof Inet6Address;
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断IPv6地址是否为内网地址
|
||||||
|
* <br><br>
|
||||||
|
* 以下地址将归类为本地地址,如有业务场景有需要,请根据需求自行处理:
|
||||||
|
* <pre>
|
||||||
|
* 通配符地址 0:0:0:0:0:0:0:0
|
||||||
|
* 链路本地地址 fe80::/10
|
||||||
|
* 唯一本地地址 fec0::/10
|
||||||
|
* 环回地址 ::1
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param ip IP地址
|
||||||
|
* @return 是否为内网地址
|
||||||
|
*/
|
||||||
|
public static boolean isInnerIPv6(String ip) {
|
||||||
|
try {
|
||||||
|
// 判断是否为IPv6地址
|
||||||
|
if (InetAddress.getByName(ip) instanceof Inet6Address inet6Address) {
|
||||||
|
// isAnyLocalAddress 判断是否为通配符地址,通常不会将其视为内网地址,根据业务场景自行处理判断
|
||||||
|
// isLinkLocalAddress 判断是否为链路本地地址,通常不算内网地址,是否划分归属于内网需要根据业务场景自行处理判断
|
||||||
|
// isLoopbackAddress 判断是否为环回地址,与IPv4的 127.0.0.1 同理,用于表示本机
|
||||||
|
// isSiteLocalAddress 判断是否为本地站点地址,IPv6唯一本地地址(Unique Local Addresses,简称ULA)
|
||||||
|
if (inet6Address.isAnyLocalAddress()
|
||||||
|
|| inet6Address.isLinkLocalAddress()
|
||||||
|
|| inet6Address.isLoopbackAddress()
|
||||||
|
|| inet6Address.isSiteLocalAddress()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
// 注意,isInnerIPv6方法和isIPv6方法的适用范围不同,所以此处不能忽略其异常信息。
|
||||||
|
throw new IllegalArgumentException("Invalid IPv6 address!", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否为IPv4地址
|
||||||
|
*
|
||||||
|
* @param ip IP地址
|
||||||
|
* @return 是否为IPv4地址
|
||||||
|
*/
|
||||||
|
public static boolean isIPv4(String ip) {
|
||||||
|
return RegexUtils.isMatch(PatternPool.IPV4, ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -6,6 +6,7 @@ import cn.hutool.core.lang.Validator;
|
|||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import org.springframework.util.AntPathMatcher;
|
import org.springframework.util.AntPathMatcher;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -318,6 +319,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
|
|||||||
.stream()
|
.stream()
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.map(mapper)
|
.map(mapper)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -338,4 +340,26 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将字符串从源字符集转换为目标字符集
|
||||||
|
*
|
||||||
|
* @param input 原始字符串
|
||||||
|
* @param fromCharset 源字符集
|
||||||
|
* @param toCharset 目标字符集
|
||||||
|
* @return 转换后的字符串
|
||||||
|
*/
|
||||||
|
public static String convert(String input, Charset fromCharset, Charset toCharset) {
|
||||||
|
if (isBlank(input)) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// 从源字符集获取字节
|
||||||
|
byte[] bytes = input.getBytes(fromCharset);
|
||||||
|
// 使用目标字符集解码
|
||||||
|
return new String(bytes, toCharset);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package org.dromara.common.core.utils.ip;
|
package org.dromara.common.core.utils.ip;
|
||||||
|
|
||||||
import cn.hutool.core.net.NetUtil;
|
|
||||||
import cn.hutool.http.HtmlUtil;
|
import cn.hutool.http.HtmlUtil;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.dromara.common.core.utils.NetUtils;
|
||||||
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取地址类
|
* 获取地址类
|
||||||
@ -20,14 +20,24 @@ public class AddressUtils {
|
|||||||
public static final String UNKNOWN = "XX XX";
|
public static final String UNKNOWN = "XX XX";
|
||||||
|
|
||||||
public static String getRealAddressByIP(String ip) {
|
public static String getRealAddressByIP(String ip) {
|
||||||
if (StringUtils.isBlank(ip)) {
|
// 处理空串并过滤HTML标签
|
||||||
|
ip = HtmlUtil.cleanHtmlTag(StringUtils.blankToDefault(ip,""));
|
||||||
|
boolean isIPv6 = NetUtils.isIPv6(ip);
|
||||||
|
// 判断是否为IPv4或IPv6,如果不是则返回未知地址
|
||||||
|
if (!NetUtils.isIPv4(ip) && !isIPv6) {
|
||||||
return UNKNOWN;
|
return UNKNOWN;
|
||||||
}
|
}
|
||||||
// 内网不查询
|
// 内网不查询
|
||||||
ip = StringUtils.contains(ip, "0:0:0:0:0:0:0:1") ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
|
if (NetUtils.isInnerIPv6(ip) || NetUtils.isInnerIP(ip)) {
|
||||||
if (NetUtil.isInnerIP(ip)) {
|
|
||||||
return "内网IP";
|
return "内网IP";
|
||||||
}
|
}
|
||||||
|
// 不支持IPv6,不再进行没有必要的IP地址信息的解析,直接返回
|
||||||
|
if (isIPv6) {
|
||||||
|
log.warn("ip2region不支持IPV6地址解析:{}", ip);
|
||||||
|
// 如有需要,可自行实现IPv6地址信息解析逻辑,并在这里返回
|
||||||
|
return "未知";
|
||||||
|
}
|
||||||
return RegionUtils.getCityInfo(ip);
|
return RegionUtils.getCityInfo(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
package org.dromara.common.core.utils.ip;
|
package org.dromara.common.core.utils.ip;
|
||||||
|
|
||||||
import cn.hutool.core.io.FileUtil;
|
import cn.hutool.core.io.resource.NoResourceException;
|
||||||
import cn.hutool.core.io.resource.ClassPathResource;
|
import cn.hutool.core.io.resource.ResourceUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
|
||||||
import org.dromara.common.core.exception.ServiceException;
|
|
||||||
import org.dromara.common.core.utils.file.FileUtils;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.dromara.common.core.exception.ServiceException;
|
||||||
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
import org.lionsoul.ip2region.xdb.Searcher;
|
import org.lionsoul.ip2region.xdb.Searcher;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据ip地址定位工具类,离线方式
|
* 根据ip地址定位工具类,离线方式
|
||||||
* 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a>
|
* 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a>
|
||||||
@ -19,31 +16,19 @@ import java.io.File;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class RegionUtils {
|
public class RegionUtils {
|
||||||
|
|
||||||
|
// IP地址库文件名称
|
||||||
|
public static final String IP_XDB_FILENAME = "ip2region.xdb";
|
||||||
|
|
||||||
private static final Searcher SEARCHER;
|
private static final Searcher SEARCHER;
|
||||||
|
|
||||||
static {
|
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 {
|
try {
|
||||||
cBuff = Searcher.loadContentFromFile(dbPath);
|
// 1、将 ip2region 数据库文件 xdb 从 ClassPath 加载到内存。
|
||||||
} catch (Exception e) {
|
// 2、基于加载到内存的 xdb 数据创建一个 Searcher 查询对象。
|
||||||
throw new ServiceException("RegionUtils初始化失败,原因:从ip2region.xdb文件加载内容失败!" + e.getMessage());
|
SEARCHER = Searcher.newWithBuffer(ResourceUtil.readBytes(IP_XDB_FILENAME));
|
||||||
}
|
log.info("RegionUtils初始化成功,加载IP地址库数据成功!");
|
||||||
// 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
|
} catch (NoResourceException e) {
|
||||||
try {
|
throw new ServiceException("RegionUtils初始化失败,原因:IP地址库数据不存在!");
|
||||||
SEARCHER = Searcher.newWithBuffer(cBuff);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ServiceException("RegionUtils初始化失败,原因:" + e.getMessage());
|
throw new ServiceException("RegionUtils初始化失败,原因:" + e.getMessage());
|
||||||
}
|
}
|
||||||
@ -54,9 +39,8 @@ public class RegionUtils {
|
|||||||
*/
|
*/
|
||||||
public static String getCityInfo(String ip) {
|
public static String getCityInfo(String ip) {
|
||||||
try {
|
try {
|
||||||
ip = ip.trim();
|
|
||||||
// 3、执行查询
|
// 3、执行查询
|
||||||
String region = SEARCHER.search(ip);
|
String region = SEARCHER.search(StringUtils.trim(ip));
|
||||||
return region.replace("0|", "").replace("|0", "");
|
return region.replace("0|", "").replace("|0", "");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("IP地址离线获取城市异常 {}", ip);
|
log.error("IP地址离线获取城市异常 {}", ip);
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
package org.dromara.common.core.validate.dicts;
|
||||||
|
|
||||||
|
import jakarta.validation.Constraint;
|
||||||
|
import jakarta.validation.Payload;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典项校验注解
|
||||||
|
*
|
||||||
|
* @author AprilWind
|
||||||
|
*/
|
||||||
|
@Constraint(validatedBy = DictPatternValidator.class)
|
||||||
|
@Target({ElementType.FIELD, ElementType.PARAMETER})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface DictPattern {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典类型,如 "sys_user_sex"
|
||||||
|
*/
|
||||||
|
String dictType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分隔符
|
||||||
|
*/
|
||||||
|
String separator();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认校验失败提示信息
|
||||||
|
*/
|
||||||
|
String message() default "字典值无效";
|
||||||
|
|
||||||
|
Class<?>[] groups() default {};
|
||||||
|
|
||||||
|
Class<? extends Payload>[] payload() default {};
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package org.dromara.common.core.validate.dicts;
|
||||||
|
|
||||||
|
import jakarta.validation.ConstraintValidator;
|
||||||
|
import jakarta.validation.ConstraintValidatorContext;
|
||||||
|
import org.dromara.common.core.service.DictService;
|
||||||
|
import org.dromara.common.core.utils.SpringUtils;
|
||||||
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义字典值校验器
|
||||||
|
*
|
||||||
|
* @author AprilWind
|
||||||
|
*/
|
||||||
|
public class DictPatternValidator implements ConstraintValidator<DictPattern, String> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典类型
|
||||||
|
*/
|
||||||
|
private String dictType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分隔符
|
||||||
|
*/
|
||||||
|
private String separator = ",";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化校验器,提取注解上的字典类型
|
||||||
|
*
|
||||||
|
* @param annotation 注解实例
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void initialize(DictPattern annotation) {
|
||||||
|
this.dictType = annotation.dictType();
|
||||||
|
if (StringUtils.isNotBlank(annotation.separator())) {
|
||||||
|
this.separator = annotation.separator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验字段值是否为指定字典类型中的合法值
|
||||||
|
*
|
||||||
|
* @param value 被校验的字段值
|
||||||
|
* @param context 校验上下文(可用于构建错误信息)
|
||||||
|
* @return true 表示校验通过(合法字典值),false 表示不通过
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||||||
|
if (StringUtils.isBlank(dictType) || StringUtils.isBlank(value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String dictLabel = SpringUtils.getBean(DictService.class).getDictLabel(dictType, value, separator);
|
||||||
|
return StringUtils.isNotBlank(dictLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,37 +1,37 @@
|
|||||||
package org.dromara.common.core.validate.enumd;
|
package org.dromara.common.core.validate.enumd;
|
||||||
|
|
||||||
import jakarta.validation.ConstraintValidator;
|
import jakarta.validation.ConstraintValidator;
|
||||||
import jakarta.validation.ConstraintValidatorContext;
|
import jakarta.validation.ConstraintValidatorContext;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义枚举校验注解实现
|
* 自定义枚举校验注解实现
|
||||||
*
|
*
|
||||||
* @author 秋辞未寒
|
* @author 秋辞未寒
|
||||||
* @date 2024-12-09
|
* @date 2024-12-09
|
||||||
*/
|
*/
|
||||||
public class EnumPatternValidator implements ConstraintValidator<EnumPattern, String> {
|
public class EnumPatternValidator implements ConstraintValidator<EnumPattern, String> {
|
||||||
|
|
||||||
private EnumPattern annotation;;
|
private EnumPattern annotation;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(EnumPattern annotation) {
|
public void initialize(EnumPattern annotation) {
|
||||||
ConstraintValidator.super.initialize(annotation);
|
ConstraintValidator.super.initialize(annotation);
|
||||||
this.annotation = annotation;
|
this.annotation = annotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
|
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
|
||||||
if (StringUtils.isNotBlank(value)) {
|
if (StringUtils.isNotBlank(value)) {
|
||||||
String fieldName = annotation.fieldName();
|
String fieldName = annotation.fieldName();
|
||||||
for (Object e : annotation.type().getEnumConstants()) {
|
for (Object e : annotation.type().getEnumConstants()) {
|
||||||
if (value.equals(ReflectUtils.invokeGetter(e, fieldName))) {
|
if (value.equals(ReflectUtils.invokeGetter(e, fieldName))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
org.dromara.common.core.config.ApplicationConfig
|
org.dromara.common.core.config.ApplicationConfig
|
||||||
org.dromara.common.core.config.AsyncConfig
|
org.dromara.common.core.config.AsyncConfig
|
||||||
org.dromara.common.core.config.RuoYiConfig
|
|
||||||
org.dromara.common.core.config.ThreadPoolConfig
|
org.dromara.common.core.config.ThreadPoolConfig
|
||||||
org.dromara.common.core.config.ValidatorConfig
|
org.dromara.common.core.config.ValidatorConfig
|
||||||
org.dromara.common.core.utils.SpringUtils
|
org.dromara.common.core.utils.SpringUtils
|
||||||
|
@ -30,7 +30,7 @@ import java.util.Optional;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Swagger 文档配置
|
* 接口文档配置
|
||||||
*
|
*
|
||||||
* @author Lion Li
|
* @author Lion Li
|
||||||
*/
|
*/
|
||||||
|
@ -76,12 +76,14 @@ public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper {
|
|||||||
String encryptPassword = EncryptUtils.encryptByRsa(encryptAes, publicKey);
|
String encryptPassword = EncryptUtils.encryptByRsa(encryptAes, publicKey);
|
||||||
|
|
||||||
// 设置响应头
|
// 设置响应头
|
||||||
|
// vue版本需要设置
|
||||||
servletResponse.addHeader("Access-Control-Expose-Headers", headerFlag);
|
servletResponse.addHeader("Access-Control-Expose-Headers", headerFlag);
|
||||||
servletResponse.setHeader(headerFlag, encryptPassword);
|
|
||||||
servletResponse.setHeader("Access-Control-Allow-Origin", "*");
|
servletResponse.setHeader("Access-Control-Allow-Origin", "*");
|
||||||
servletResponse.setHeader("Access-Control-Allow-Methods", "*");
|
servletResponse.setHeader("Access-Control-Allow-Methods", "*");
|
||||||
|
servletResponse.setHeader(headerFlag, encryptPassword);
|
||||||
servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.toString());
|
servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.toString());
|
||||||
|
|
||||||
|
|
||||||
// 获取原始内容
|
// 获取原始内容
|
||||||
String originalBody = this.getContent();
|
String originalBody = this.getContent();
|
||||||
// 对内容进行加密
|
// 对内容进行加密
|
||||||
|
@ -108,7 +108,7 @@ public class EncryptUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sm4加密
|
* SM4加密(Base64编码)
|
||||||
*
|
*
|
||||||
* @param data 待加密数据
|
* @param data 待加密数据
|
||||||
* @param password 秘钥字符串
|
* @param password 秘钥字符串
|
||||||
@ -127,11 +127,11 @@ public class EncryptUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sm4加密
|
* SM4加密(Hex编码)
|
||||||
*
|
*
|
||||||
* @param data 待加密数据
|
* @param data 待加密数据
|
||||||
* @param password 秘钥字符串
|
* @param password 秘钥字符串
|
||||||
* @return 加密后字符串, 采用Base64编码
|
* @return 加密后字符串, 采用Hex编码
|
||||||
*/
|
*/
|
||||||
public static String encryptBySm4Hex(String data, String password) {
|
public static String encryptBySm4Hex(String data, String password) {
|
||||||
if (StrUtil.isBlank(password)) {
|
if (StrUtil.isBlank(password)) {
|
||||||
@ -148,7 +148,7 @@ public class EncryptUtils {
|
|||||||
/**
|
/**
|
||||||
* sm4解密
|
* sm4解密
|
||||||
*
|
*
|
||||||
* @param data 待解密数据
|
* @param data 待解密数据(可以是Base64或Hex编码)
|
||||||
* @param password 秘钥字符串
|
* @param password 秘钥字符串
|
||||||
* @return 解密后字符串
|
* @return 解密后字符串
|
||||||
*/
|
*/
|
||||||
|
@ -22,8 +22,8 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba</groupId>
|
<groupId>cn.idev.excel</groupId>
|
||||||
<artifactId>easyexcel</artifactId>
|
<artifactId>fastexcel</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
@ -2,12 +2,12 @@ package org.dromara.common.excel.convert;
|
|||||||
|
|
||||||
import cn.hutool.core.convert.Convert;
|
import cn.hutool.core.convert.Convert;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.alibaba.excel.converters.Converter;
|
import cn.idev.excel.converters.Converter;
|
||||||
import com.alibaba.excel.enums.CellDataTypeEnum;
|
import cn.idev.excel.enums.CellDataTypeEnum;
|
||||||
import com.alibaba.excel.metadata.GlobalConfiguration;
|
import cn.idev.excel.metadata.GlobalConfiguration;
|
||||||
import com.alibaba.excel.metadata.data.ReadCellData;
|
import cn.idev.excel.metadata.data.ReadCellData;
|
||||||
import com.alibaba.excel.metadata.data.WriteCellData;
|
import cn.idev.excel.metadata.data.WriteCellData;
|
||||||
import com.alibaba.excel.metadata.property.ExcelContentProperty;
|
import cn.idev.excel.metadata.property.ExcelContentProperty;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
@ -3,12 +3,12 @@ package org.dromara.common.excel.convert;
|
|||||||
import cn.hutool.core.annotation.AnnotationUtil;
|
import cn.hutool.core.annotation.AnnotationUtil;
|
||||||
import cn.hutool.core.convert.Convert;
|
import cn.hutool.core.convert.Convert;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.alibaba.excel.converters.Converter;
|
import cn.idev.excel.converters.Converter;
|
||||||
import com.alibaba.excel.enums.CellDataTypeEnum;
|
import cn.idev.excel.enums.CellDataTypeEnum;
|
||||||
import com.alibaba.excel.metadata.GlobalConfiguration;
|
import cn.idev.excel.metadata.GlobalConfiguration;
|
||||||
import com.alibaba.excel.metadata.data.ReadCellData;
|
import cn.idev.excel.metadata.data.ReadCellData;
|
||||||
import com.alibaba.excel.metadata.data.WriteCellData;
|
import cn.idev.excel.metadata.data.WriteCellData;
|
||||||
import com.alibaba.excel.metadata.property.ExcelContentProperty;
|
import cn.idev.excel.metadata.property.ExcelContentProperty;
|
||||||
import org.dromara.common.excel.annotation.ExcelDictFormat;
|
import org.dromara.common.excel.annotation.ExcelDictFormat;
|
||||||
import org.dromara.common.core.service.DictService;
|
import org.dromara.common.core.service.DictService;
|
||||||
import org.dromara.common.core.utils.SpringUtils;
|
import org.dromara.common.core.utils.SpringUtils;
|
||||||
|
@ -3,12 +3,12 @@ package org.dromara.common.excel.convert;
|
|||||||
import cn.hutool.core.annotation.AnnotationUtil;
|
import cn.hutool.core.annotation.AnnotationUtil;
|
||||||
import cn.hutool.core.convert.Convert;
|
import cn.hutool.core.convert.Convert;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.alibaba.excel.converters.Converter;
|
import cn.idev.excel.converters.Converter;
|
||||||
import com.alibaba.excel.enums.CellDataTypeEnum;
|
import cn.idev.excel.enums.CellDataTypeEnum;
|
||||||
import com.alibaba.excel.metadata.GlobalConfiguration;
|
import cn.idev.excel.metadata.GlobalConfiguration;
|
||||||
import com.alibaba.excel.metadata.data.ReadCellData;
|
import cn.idev.excel.metadata.data.ReadCellData;
|
||||||
import com.alibaba.excel.metadata.data.WriteCellData;
|
import cn.idev.excel.metadata.data.WriteCellData;
|
||||||
import com.alibaba.excel.metadata.property.ExcelContentProperty;
|
import cn.idev.excel.metadata.property.ExcelContentProperty;
|
||||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
||||||
import org.dromara.common.excel.annotation.ExcelEnumFormat;
|
import org.dromara.common.excel.annotation.ExcelEnumFormat;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@ -3,11 +3,11 @@ package org.dromara.common.excel.core;
|
|||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.util.ReflectUtil;
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.alibaba.excel.annotation.ExcelProperty;
|
import cn.idev.excel.annotation.ExcelProperty;
|
||||||
import com.alibaba.excel.metadata.Head;
|
import cn.idev.excel.metadata.Head;
|
||||||
import com.alibaba.excel.write.handler.WorkbookWriteHandler;
|
import cn.idev.excel.write.handler.WorkbookWriteHandler;
|
||||||
import com.alibaba.excel.write.handler.context.WorkbookWriteHandlerContext;
|
import cn.idev.excel.write.handler.context.WorkbookWriteHandlerContext;
|
||||||
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
|
import cn.idev.excel.write.merge.AbstractMergeStrategy;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
@ -112,7 +112,13 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements Workbook
|
|||||||
}
|
}
|
||||||
map.put(field, new RepeatCell(val, i));
|
map.put(field, new RepeatCell(val, i));
|
||||||
} else if (i == list.size() - 1) {
|
} else if (i == list.size() - 1) {
|
||||||
if (i > repeatCell.getCurrent() && isMerge(list, i, field)) {
|
if (!isMerge(list, i, field)) {
|
||||||
|
// 如果最后一行不能合并,检查之前的数据是否需要合并
|
||||||
|
if (i - repeatCell.getCurrent() > 1) {
|
||||||
|
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
|
||||||
|
}
|
||||||
|
} else if (i > repeatCell.getCurrent()) {
|
||||||
|
// 如果最后一行可以合并,则直接合并到最后
|
||||||
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
|
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
|
||||||
}
|
}
|
||||||
} else if (!isMerge(list, i, field)) {
|
} else if (!isMerge(list, i, field)) {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package org.dromara.common.excel.core;
|
package org.dromara.common.excel.core;
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.alibaba.excel.context.AnalysisContext;
|
import cn.idev.excel.context.AnalysisContext;
|
||||||
import com.alibaba.excel.event.AnalysisEventListener;
|
import cn.idev.excel.event.AnalysisEventListener;
|
||||||
import com.alibaba.excel.exception.ExcelAnalysisException;
|
import cn.idev.excel.exception.ExcelAnalysisException;
|
||||||
import com.alibaba.excel.exception.ExcelDataConvertException;
|
import cn.idev.excel.exception.ExcelDataConvertException;
|
||||||
import org.dromara.common.core.utils.StreamUtils;
|
import org.dromara.common.core.utils.StreamUtils;
|
||||||
import org.dromara.common.core.utils.ValidatorUtils;
|
import org.dromara.common.core.utils.ValidatorUtils;
|
||||||
import org.dromara.common.json.utils.JsonUtils;
|
import org.dromara.common.json.utils.JsonUtils;
|
||||||
|
@ -5,12 +5,12 @@ import cn.hutool.core.util.ArrayUtil;
|
|||||||
import cn.hutool.core.util.EnumUtil;
|
import cn.hutool.core.util.EnumUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.alibaba.excel.metadata.FieldCache;
|
import cn.idev.excel.metadata.FieldCache;
|
||||||
import com.alibaba.excel.metadata.FieldWrapper;
|
import cn.idev.excel.metadata.FieldWrapper;
|
||||||
import com.alibaba.excel.util.ClassUtils;
|
import cn.idev.excel.util.ClassUtils;
|
||||||
import com.alibaba.excel.write.handler.SheetWriteHandler;
|
import cn.idev.excel.write.handler.SheetWriteHandler;
|
||||||
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
|
import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
|
||||||
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
|
import cn.idev.excel.write.metadata.holder.WriteWorkbookHolder;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.poi.ss.usermodel.*;
|
import org.apache.poi.ss.usermodel.*;
|
||||||
import org.apache.poi.ss.util.CellRangeAddressList;
|
import org.apache.poi.ss.util.CellRangeAddressList;
|
||||||
@ -175,7 +175,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
List<String> firstOptions = options.getOptions();
|
List<String> firstOptions = options.getOptions();
|
||||||
Map<String, List<String>> secoundOptionsMap = options.getNextOptions();
|
Map<String, List<String>> secoundOptionsMap = options.getNextOptions();
|
||||||
|
|
||||||
// 采用按行填充数据的方式,避免EasyExcel出现数据无法写入的问题
|
// 采用按行填充数据的方式,避免出现数据无法写入的问题
|
||||||
// Attempting to write a row in the range that is already written to disk
|
// Attempting to write a row in the range that is already written to disk
|
||||||
|
|
||||||
// 使用ArrayList记载数据,防止乱序
|
// 使用ArrayList记载数据,防止乱序
|
||||||
@ -291,9 +291,11 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
* @param value 下拉选可选值
|
* @param value 下拉选可选值
|
||||||
*/
|
*/
|
||||||
private void dropDownWithSheet(DataValidationHelper helper, Workbook workbook, Sheet sheet, Integer celIndex, List<String> value) {
|
private void dropDownWithSheet(DataValidationHelper helper, Workbook workbook, Sheet sheet, Integer celIndex, List<String> value) {
|
||||||
|
//由于poi的写出相关问题,超过100个会被临时写进硬盘,导致后续内存合并会出Attempting to write a row[] in the range [] that is already written to disk
|
||||||
|
String tmpOptionsSheetName = OPTIONS_SHEET_NAME + "_" + currentOptionsColumnIndex;
|
||||||
// 创建下拉数据表
|
// 创建下拉数据表
|
||||||
Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)))
|
Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(tmpOptionsSheetName)))
|
||||||
.orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)));
|
.orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(tmpOptionsSheetName)));
|
||||||
// 将下拉表隐藏
|
// 将下拉表隐藏
|
||||||
workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), true);
|
workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), true);
|
||||||
// 完善纵向的一级选项数据表
|
// 完善纵向的一级选项数据表
|
||||||
@ -302,9 +304,9 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
// 获取每一选项行,如果没有则创建
|
// 获取每一选项行,如果没有则创建
|
||||||
Row row = Optional.ofNullable(simpleDataSheet.getRow(i))
|
Row row = Optional.ofNullable(simpleDataSheet.getRow(i))
|
||||||
.orElseGet(() -> simpleDataSheet.createRow(finalI));
|
.orElseGet(() -> simpleDataSheet.createRow(finalI));
|
||||||
// 获取本级选项对应的选项列,如果没有则创建
|
// 获取本级选项对应的选项列,如果没有则创建。上述采用多个sheet,默认索引为1列
|
||||||
Cell cell = Optional.ofNullable(row.getCell(currentOptionsColumnIndex))
|
Cell cell = Optional.ofNullable(row.getCell(0))
|
||||||
.orElseGet(() -> row.createCell(currentOptionsColumnIndex));
|
.orElseGet(() -> row.createCell(0));
|
||||||
// 设置值
|
// 设置值
|
||||||
cell.setCellValue(value.get(i));
|
cell.setCellValue(value.get(i));
|
||||||
}
|
}
|
||||||
@ -312,13 +314,13 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
// 创建名称管理器
|
// 创建名称管理器
|
||||||
Name name = workbook.createName();
|
Name name = workbook.createName();
|
||||||
// 设置名称管理器的别名
|
// 设置名称管理器的别名
|
||||||
String nameName = String.format("%s_%d", OPTIONS_SHEET_NAME, celIndex);
|
String nameName = String.format("%s_%d", tmpOptionsSheetName, celIndex);
|
||||||
name.setNameName(nameName);
|
name.setNameName(nameName);
|
||||||
// 以纵向第一列创建一级下拉拼接引用位置
|
// 以纵向第一列创建一级下拉拼接引用位置
|
||||||
String function = String.format("%s!$%s$1:$%s$%d",
|
String function = String.format("%s!$%s$1:$%s$%d",
|
||||||
OPTIONS_SHEET_NAME,
|
tmpOptionsSheetName,
|
||||||
getExcelColumnName(currentOptionsColumnIndex),
|
getExcelColumnName(0),
|
||||||
getExcelColumnName(currentOptionsColumnIndex),
|
getExcelColumnName(0),
|
||||||
value.size());
|
value.size());
|
||||||
// 设置名称管理器的引用位置
|
// 设置名称管理器的引用位置
|
||||||
name.setRefersToFormula(function);
|
name.setRefersToFormula(function);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package org.dromara.common.excel.core;
|
package org.dromara.common.excel.core;
|
||||||
|
|
||||||
import com.alibaba.excel.read.listener.ReadListener;
|
import cn.idev.excel.read.listener.ReadListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Excel 导入监听
|
* Excel 导入监听
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
package org.dromara.common.excel.handler;
|
package org.dromara.common.excel.handler;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import com.alibaba.excel.metadata.data.DataFormatData;
|
import cn.idev.excel.metadata.data.DataFormatData;
|
||||||
import com.alibaba.excel.metadata.data.WriteCellData;
|
import cn.idev.excel.metadata.data.WriteCellData;
|
||||||
import com.alibaba.excel.util.StyleUtil;
|
import cn.idev.excel.util.StyleUtil;
|
||||||
import com.alibaba.excel.write.handler.CellWriteHandler;
|
import cn.idev.excel.write.handler.CellWriteHandler;
|
||||||
import com.alibaba.excel.write.handler.SheetWriteHandler;
|
import cn.idev.excel.write.handler.SheetWriteHandler;
|
||||||
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
|
import cn.idev.excel.write.handler.context.CellWriteHandlerContext;
|
||||||
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
|
import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
|
||||||
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
|
import cn.idev.excel.write.metadata.style.WriteCellStyle;
|
||||||
import com.alibaba.excel.write.metadata.style.WriteFont;
|
import cn.idev.excel.write.metadata.style.WriteFont;
|
||||||
import org.apache.poi.ss.usermodel.*;
|
import org.apache.poi.ss.usermodel.*;
|
||||||
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
|
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
|
||||||
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
|
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
|
||||||
|
@ -3,13 +3,13 @@ package org.dromara.common.excel.utils;
|
|||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.io.resource.ClassPathResource;
|
import cn.hutool.core.io.resource.ClassPathResource;
|
||||||
import cn.hutool.core.util.IdUtil;
|
import cn.hutool.core.util.IdUtil;
|
||||||
import com.alibaba.excel.EasyExcel;
|
import cn.idev.excel.FastExcel;
|
||||||
import com.alibaba.excel.ExcelWriter;
|
import cn.idev.excel.ExcelWriter;
|
||||||
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
|
import cn.idev.excel.write.builder.ExcelWriterSheetBuilder;
|
||||||
import com.alibaba.excel.write.metadata.WriteSheet;
|
import cn.idev.excel.write.metadata.WriteSheet;
|
||||||
import com.alibaba.excel.write.metadata.fill.FillConfig;
|
import cn.idev.excel.write.metadata.fill.FillConfig;
|
||||||
import com.alibaba.excel.write.metadata.fill.FillWrapper;
|
import cn.idev.excel.write.metadata.fill.FillWrapper;
|
||||||
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
import cn.idev.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
||||||
import jakarta.servlet.ServletOutputStream;
|
import jakarta.servlet.ServletOutputStream;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
@ -43,7 +43,7 @@ public class ExcelUtil {
|
|||||||
* @return 转换后集合
|
* @return 转换后集合
|
||||||
*/
|
*/
|
||||||
public static <T> List<T> importExcel(InputStream is, Class<T> clazz) {
|
public static <T> List<T> importExcel(InputStream is, Class<T> clazz) {
|
||||||
return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
|
return FastExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ public class ExcelUtil {
|
|||||||
*/
|
*/
|
||||||
public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate) {
|
public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate) {
|
||||||
DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
|
DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
|
||||||
EasyExcel.read(is, clazz, listener).sheet().doRead();
|
FastExcel.read(is, clazz, listener).sheet().doRead();
|
||||||
return listener.getExcelResult();
|
return listener.getExcelResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ public class ExcelUtil {
|
|||||||
* @return 转换后集合
|
* @return 转换后集合
|
||||||
*/
|
*/
|
||||||
public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener) {
|
public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener) {
|
||||||
EasyExcel.read(is, clazz, listener).sheet().doRead();
|
FastExcel.read(is, clazz, listener).sheet().doRead();
|
||||||
return listener.getExcelResult();
|
return listener.getExcelResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +186,7 @@ public class ExcelUtil {
|
|||||||
*/
|
*/
|
||||||
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge,
|
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge,
|
||||||
OutputStream os, List<DropDownOptions> options) {
|
OutputStream os, List<DropDownOptions> options) {
|
||||||
ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
|
ExcelWriterSheetBuilder builder = FastExcel.write(os, clazz)
|
||||||
.autoCloseStream(false)
|
.autoCloseStream(false)
|
||||||
// 自动适配
|
// 自动适配
|
||||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
||||||
@ -215,6 +215,9 @@ public class ExcelUtil {
|
|||||||
*/
|
*/
|
||||||
public static <T> void exportTemplate(List<T> data, String filename, String templatePath, HttpServletResponse response) {
|
public static <T> void exportTemplate(List<T> data, String filename, String templatePath, HttpServletResponse response) {
|
||||||
try {
|
try {
|
||||||
|
if (CollUtil.isEmpty(data)) {
|
||||||
|
throw new IllegalArgumentException("数据为空");
|
||||||
|
}
|
||||||
resetResponse(filename, response);
|
resetResponse(filename, response);
|
||||||
ServletOutputStream os = response.getOutputStream();
|
ServletOutputStream os = response.getOutputStream();
|
||||||
exportTemplate(data, templatePath, os);
|
exportTemplate(data, templatePath, os);
|
||||||
@ -233,21 +236,19 @@ public class ExcelUtil {
|
|||||||
* @param os 输出流
|
* @param os 输出流
|
||||||
*/
|
*/
|
||||||
public static <T> void exportTemplate(List<T> data, String templatePath, OutputStream os) {
|
public static <T> void exportTemplate(List<T> data, String templatePath, OutputStream os) {
|
||||||
if (CollUtil.isEmpty(data)) {
|
|
||||||
throw new IllegalArgumentException("数据为空");
|
|
||||||
}
|
|
||||||
ClassPathResource templateResource = new ClassPathResource(templatePath);
|
ClassPathResource templateResource = new ClassPathResource(templatePath);
|
||||||
ExcelWriter excelWriter = EasyExcel.write(os)
|
ExcelWriter excelWriter = FastExcel.write(os)
|
||||||
.withTemplate(templateResource.getStream())
|
.withTemplate(templateResource.getStream())
|
||||||
.autoCloseStream(false)
|
.autoCloseStream(false)
|
||||||
// 大数值自动转换 防止失真
|
// 大数值自动转换 防止失真
|
||||||
.registerConverter(new ExcelBigNumberConvert())
|
.registerConverter(new ExcelBigNumberConvert())
|
||||||
.registerWriteHandler(new DataWriteHandler(data.get(0).getClass()))
|
.registerWriteHandler(new DataWriteHandler(data.get(0).getClass()))
|
||||||
.build();
|
.build();
|
||||||
WriteSheet writeSheet = EasyExcel.writerSheet().build();
|
WriteSheet writeSheet = FastExcel.writerSheet().build();
|
||||||
|
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
|
||||||
// 单表多数据导出 模板格式为 {.属性}
|
// 单表多数据导出 模板格式为 {.属性}
|
||||||
for (T d : data) {
|
for (T d : data) {
|
||||||
excelWriter.fill(d, writeSheet);
|
excelWriter.fill(d, fillConfig, writeSheet);
|
||||||
}
|
}
|
||||||
excelWriter.finish();
|
excelWriter.finish();
|
||||||
}
|
}
|
||||||
@ -264,6 +265,9 @@ public class ExcelUtil {
|
|||||||
*/
|
*/
|
||||||
public static void exportTemplateMultiList(Map<String, Object> data, String filename, String templatePath, HttpServletResponse response) {
|
public static void exportTemplateMultiList(Map<String, Object> data, String filename, String templatePath, HttpServletResponse response) {
|
||||||
try {
|
try {
|
||||||
|
if (CollUtil.isEmpty(data)) {
|
||||||
|
throw new IllegalArgumentException("数据为空");
|
||||||
|
}
|
||||||
resetResponse(filename, response);
|
resetResponse(filename, response);
|
||||||
ServletOutputStream os = response.getOutputStream();
|
ServletOutputStream os = response.getOutputStream();
|
||||||
exportTemplateMultiList(data, templatePath, os);
|
exportTemplateMultiList(data, templatePath, os);
|
||||||
@ -284,6 +288,9 @@ public class ExcelUtil {
|
|||||||
*/
|
*/
|
||||||
public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String filename, String templatePath, HttpServletResponse response) {
|
public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String filename, String templatePath, HttpServletResponse response) {
|
||||||
try {
|
try {
|
||||||
|
if (CollUtil.isEmpty(data)) {
|
||||||
|
throw new IllegalArgumentException("数据为空");
|
||||||
|
}
|
||||||
resetResponse(filename, response);
|
resetResponse(filename, response);
|
||||||
ServletOutputStream os = response.getOutputStream();
|
ServletOutputStream os = response.getOutputStream();
|
||||||
exportTemplateMultiSheet(data, templatePath, os);
|
exportTemplateMultiSheet(data, templatePath, os);
|
||||||
@ -302,17 +309,14 @@ public class ExcelUtil {
|
|||||||
* @param os 输出流
|
* @param os 输出流
|
||||||
*/
|
*/
|
||||||
public static void exportTemplateMultiList(Map<String, Object> data, String templatePath, OutputStream os) {
|
public static void exportTemplateMultiList(Map<String, Object> data, String templatePath, OutputStream os) {
|
||||||
if (CollUtil.isEmpty(data)) {
|
|
||||||
throw new IllegalArgumentException("数据为空");
|
|
||||||
}
|
|
||||||
ClassPathResource templateResource = new ClassPathResource(templatePath);
|
ClassPathResource templateResource = new ClassPathResource(templatePath);
|
||||||
ExcelWriter excelWriter = EasyExcel.write(os)
|
ExcelWriter excelWriter = FastExcel.write(os)
|
||||||
.withTemplate(templateResource.getStream())
|
.withTemplate(templateResource.getStream())
|
||||||
.autoCloseStream(false)
|
.autoCloseStream(false)
|
||||||
// 大数值自动转换 防止失真
|
// 大数值自动转换 防止失真
|
||||||
.registerConverter(new ExcelBigNumberConvert())
|
.registerConverter(new ExcelBigNumberConvert())
|
||||||
.build();
|
.build();
|
||||||
WriteSheet writeSheet = EasyExcel.writerSheet().build();
|
WriteSheet writeSheet = FastExcel.writerSheet().build();
|
||||||
for (Map.Entry<String, Object> map : data.entrySet()) {
|
for (Map.Entry<String, Object> map : data.entrySet()) {
|
||||||
// 设置列表后续还有数据
|
// 设置列表后续还有数据
|
||||||
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
|
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
|
||||||
@ -320,7 +324,7 @@ public class ExcelUtil {
|
|||||||
// 多表导出必须使用 FillWrapper
|
// 多表导出必须使用 FillWrapper
|
||||||
excelWriter.fill(new FillWrapper(map.getKey(), (Collection<?>) map.getValue()), fillConfig, writeSheet);
|
excelWriter.fill(new FillWrapper(map.getKey(), (Collection<?>) map.getValue()), fillConfig, writeSheet);
|
||||||
} else {
|
} else {
|
||||||
excelWriter.fill(map.getValue(), writeSheet);
|
excelWriter.fill(map.getValue(), fillConfig, writeSheet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
excelWriter.finish();
|
excelWriter.finish();
|
||||||
@ -336,18 +340,15 @@ public class ExcelUtil {
|
|||||||
* @param os 输出流
|
* @param os 输出流
|
||||||
*/
|
*/
|
||||||
public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String templatePath, OutputStream os) {
|
public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String templatePath, OutputStream os) {
|
||||||
if (CollUtil.isEmpty(data)) {
|
|
||||||
throw new IllegalArgumentException("数据为空");
|
|
||||||
}
|
|
||||||
ClassPathResource templateResource = new ClassPathResource(templatePath);
|
ClassPathResource templateResource = new ClassPathResource(templatePath);
|
||||||
ExcelWriter excelWriter = EasyExcel.write(os)
|
ExcelWriter excelWriter = FastExcel.write(os)
|
||||||
.withTemplate(templateResource.getStream())
|
.withTemplate(templateResource.getStream())
|
||||||
.autoCloseStream(false)
|
.autoCloseStream(false)
|
||||||
// 大数值自动转换 防止失真
|
// 大数值自动转换 防止失真
|
||||||
.registerConverter(new ExcelBigNumberConvert())
|
.registerConverter(new ExcelBigNumberConvert())
|
||||||
.build();
|
.build();
|
||||||
for (int i = 0; i < data.size(); i++) {
|
for (int i = 0; i < data.size(); i++) {
|
||||||
WriteSheet writeSheet = EasyExcel.writerSheet(i).build();
|
WriteSheet writeSheet = FastExcel.writerSheet(i).build();
|
||||||
for (Map.Entry<String, Object> map : data.get(i).entrySet()) {
|
for (Map.Entry<String, Object> map : data.get(i).entrySet()) {
|
||||||
// 设置列表后续还有数据
|
// 设置列表后续还有数据
|
||||||
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
|
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
|
||||||
|
@ -4,8 +4,9 @@ import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
|||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||||
import org.dromara.common.json.handler.BigNumberSerializer;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.dromara.common.json.handler.BigNumberSerializer;
|
||||||
|
import org.dromara.common.json.handler.CustomDateDeserializer;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||||
@ -15,6 +16,7 @@ import java.math.BigDecimal;
|
|||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,6 +40,7 @@ public class JacksonConfig {
|
|||||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
|
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
|
||||||
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
|
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
|
||||||
|
javaTimeModule.addDeserializer(Date.class, new CustomDateDeserializer());
|
||||||
builder.modules(javaTimeModule);
|
builder.modules(javaTimeModule);
|
||||||
builder.timeZone(TimeZone.getDefault());
|
builder.timeZone(TimeZone.getDefault());
|
||||||
log.info("初始化 jackson 配置");
|
log.info("初始化 jackson 配置");
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package org.dromara.common.json.handler;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||||
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义 Date 类型反序列化处理器(支持多种格式)
|
||||||
|
*
|
||||||
|
* @author AprilWind
|
||||||
|
*/
|
||||||
|
public class CustomDateDeserializer extends JsonDeserializer<Date> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 反序列化逻辑:将字符串转换为 Date 对象
|
||||||
|
*
|
||||||
|
* @param p JSON 解析器,用于获取字符串值
|
||||||
|
* @param ctxt 上下文环境(可用于获取更多配置)
|
||||||
|
* @return 转换后的 Date 对象,若为空字符串返回 null
|
||||||
|
* @throws IOException 当字符串格式非法或转换失败时抛出
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||||
|
return DateUtil.parse(p.getText());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package org.dromara.common.mybatis.core.page;
|
package org.dromara.common.mybatis.core.page;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.http.HttpStatus;
|
import cn.hutool.http.HttpStatus;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@ -88,4 +89,19 @@ public class TableDataInfo<T> implements Serializable {
|
|||||||
return rspData;
|
return rspData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据原始数据列表和分页参数,构建表格分页数据对象(用于假分页)
|
||||||
|
*
|
||||||
|
* @param list 原始数据列表(全部数据)
|
||||||
|
* @param page 分页参数对象(包含当前页码、每页大小等)
|
||||||
|
* @return 构造好的分页结果 TableDataInfo<T>
|
||||||
|
*/
|
||||||
|
public static <T> TableDataInfo<T> build(List<T> list, IPage<T> page) {
|
||||||
|
if (CollUtil.isEmpty(list)) {
|
||||||
|
return TableDataInfo.build();
|
||||||
|
}
|
||||||
|
List<T> pageList = CollUtil.page((int) page.getCurrent() - 1, (int) page.getSize(), list);
|
||||||
|
return new TableDataInfo<>(pageList, list.size());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package org.dromara.common.mybatis.handler;
|
package org.dromara.common.mybatis.handler;
|
||||||
|
|
||||||
|
import cn.hutool.http.HttpStatus;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.dromara.common.core.domain.R;
|
import org.dromara.common.core.domain.R;
|
||||||
@ -25,7 +26,7 @@ public class MybatisExceptionHandler {
|
|||||||
public R<Void> handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) {
|
public R<Void> handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) {
|
||||||
String requestURI = request.getRequestURI();
|
String requestURI = request.getRequestURI();
|
||||||
log.error("请求地址'{}',数据库中已存在记录'{}'", requestURI, e.getMessage());
|
log.error("请求地址'{}',数据库中已存在记录'{}'", requestURI, e.getMessage());
|
||||||
return R.fail("数据库中已存在该记录,请联系管理员确认");
|
return R.fail(HttpStatus.HTTP_CONFLICT, "数据库中已存在该记录,请联系管理员确认");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,12 +36,12 @@ public class MybatisExceptionHandler {
|
|||||||
public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
|
public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
|
||||||
String requestURI = request.getRequestURI();
|
String requestURI = request.getRequestURI();
|
||||||
String message = e.getMessage();
|
String message = e.getMessage();
|
||||||
if (StringUtils.contains("CannotFindDataSourceException", message)) {
|
if (StringUtils.contains(message, "CannotFindDataSourceException")) {
|
||||||
log.error("请求地址'{}', 未找到数据源", requestURI);
|
log.error("请求地址'{}', 未找到数据源", requestURI);
|
||||||
return R.fail("未找到数据源,请联系管理员确认");
|
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "未找到数据源,请联系管理员确认");
|
||||||
}
|
}
|
||||||
log.error("请求地址'{}', Mybatis系统异常", requestURI, e);
|
log.error("请求地址'{}', Mybatis系统异常", requestURI, e);
|
||||||
return R.fail(message);
|
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
|
|||||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@ -229,10 +230,7 @@ public class PlusDataPermissionHandler {
|
|||||||
// 获取资源对应的类对象
|
// 获取资源对应的类对象
|
||||||
Class<?> clazz = Resources.classForName(classMetadata.getClassName());
|
Class<?> clazz = Resources.classForName(classMetadata.getClassName());
|
||||||
// 查找类中的特定注解
|
// 查找类中的特定注解
|
||||||
if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) {
|
findAnnotation(clazz);
|
||||||
DataPermission dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class);
|
|
||||||
dataPermissionCacheMap.put(clazz.getName(), dataPermission);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -240,6 +238,29 @@ public class PlusDataPermissionHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在指定的类中查找特定的注解 DataPermission,并将带有这个注解的方法或类存储到 dataPermissionCacheMap 中
|
||||||
|
*
|
||||||
|
* @param clazz 要查找的类
|
||||||
|
*/
|
||||||
|
private void findAnnotation(Class<?> clazz) {
|
||||||
|
DataPermission dataPermission;
|
||||||
|
for (Method method : clazz.getMethods()) {
|
||||||
|
if (method.isDefault() || method.isVarArgs()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String mappedStatementId = clazz.getName() + "." + method.getName();
|
||||||
|
if (AnnotationUtil.hasAnnotation(method, DataPermission.class)) {
|
||||||
|
dataPermission = AnnotationUtil.getAnnotation(method, DataPermission.class);
|
||||||
|
dataPermissionCacheMap.put(mappedStatementId, dataPermission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) {
|
||||||
|
dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class);
|
||||||
|
dataPermissionCacheMap.put(clazz.getName(), dataPermission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据映射语句 ID 或类名获取对应的 DataPermission 注解对象
|
* 根据映射语句 ID 或类名获取对应的 DataPermission 注解对象
|
||||||
*
|
*
|
||||||
@ -251,6 +272,10 @@ public class PlusDataPermissionHandler {
|
|||||||
if (DataPermissionHelper.getPermission() != null) {
|
if (DataPermissionHelper.getPermission() != null) {
|
||||||
return DataPermissionHelper.getPermission();
|
return DataPermissionHelper.getPermission();
|
||||||
}
|
}
|
||||||
|
// 检查缓存中是否包含映射语句 ID 对应的 DataPermission 注解对象
|
||||||
|
if (dataPermissionCacheMap.containsKey(mapperId)) {
|
||||||
|
return dataPermissionCacheMap.get(mapperId);
|
||||||
|
}
|
||||||
// 如果缓存中不包含映射语句 ID 对应的 DataPermission 注解对象,则尝试使用类名作为键查找
|
// 如果缓存中不包含映射语句 ID 对应的 DataPermission 注解对象,则尝试使用类名作为键查找
|
||||||
String clazzName = mapperId.substring(0, mapperId.lastIndexOf("."));
|
String clazzName = mapperId.substring(0, mapperId.lastIndexOf("."));
|
||||||
if (dataPermissionCacheMap.containsKey(clazzName)) {
|
if (dataPermissionCacheMap.containsKey(clazzName)) {
|
||||||
|
@ -31,11 +31,6 @@
|
|||||||
<groupId>software.amazon.awssdk</groupId>
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
<artifactId>s3</artifactId>
|
<artifactId>s3</artifactId>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 -->
|
|
||||||
<exclusion>
|
|
||||||
<groupId>software.amazon.awssdk</groupId>
|
|
||||||
<artifactId>netty-nio-client</artifactId>
|
|
||||||
</exclusion>
|
|
||||||
<!-- 将基于 CRT 的 HTTP 客户端从类路径中移除 -->
|
<!-- 将基于 CRT 的 HTTP 客户端从类路径中移除 -->
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>software.amazon.awssdk</groupId>
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
@ -54,10 +49,10 @@
|
|||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 使用AWS基于 CRT 的 S3 客户端 -->
|
<!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>software.amazon.awssdk.crt</groupId>
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
<artifactId>aws-crt</artifactId>
|
<artifactId>netty-nio-client</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
|
<!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
|
||||||
|
@ -8,7 +8,7 @@ import org.dromara.common.core.utils.StringUtils;
|
|||||||
import org.dromara.common.core.utils.file.FileUtils;
|
import org.dromara.common.core.utils.file.FileUtils;
|
||||||
import org.dromara.common.oss.constant.OssConstant;
|
import org.dromara.common.oss.constant.OssConstant;
|
||||||
import org.dromara.common.oss.entity.UploadResult;
|
import org.dromara.common.oss.entity.UploadResult;
|
||||||
import org.dromara.common.oss.enumd.AccessPolicyType;
|
import org.dromara.common.oss.enums.AccessPolicyType;
|
||||||
import org.dromara.common.oss.exception.OssException;
|
import org.dromara.common.oss.exception.OssException;
|
||||||
import org.dromara.common.oss.properties.OssProperties;
|
import org.dromara.common.oss.properties.OssProperties;
|
||||||
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
|
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
|
||||||
@ -16,10 +16,10 @@ import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
|
|||||||
import software.amazon.awssdk.core.ResponseInputStream;
|
import software.amazon.awssdk.core.ResponseInputStream;
|
||||||
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
|
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
|
||||||
import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody;
|
import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody;
|
||||||
|
import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
|
||||||
import software.amazon.awssdk.regions.Region;
|
import software.amazon.awssdk.regions.Region;
|
||||||
import software.amazon.awssdk.services.s3.S3AsyncClient;
|
import software.amazon.awssdk.services.s3.S3AsyncClient;
|
||||||
import software.amazon.awssdk.services.s3.S3Configuration;
|
import software.amazon.awssdk.services.s3.S3Configuration;
|
||||||
import software.amazon.awssdk.services.s3.crt.S3CrtHttpConfiguration;
|
|
||||||
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
|
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
|
||||||
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
|
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
|
||||||
import software.amazon.awssdk.transfer.s3.S3TransferManager;
|
import software.amazon.awssdk.transfer.s3.S3TransferManager;
|
||||||
@ -84,18 +84,14 @@ public class OssClient {
|
|||||||
// MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问
|
// MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问
|
||||||
boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);
|
boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);
|
||||||
|
|
||||||
// 创建AWS基于 CRT 的 S3 客户端
|
// 创建AWS基于 Netty 的 S3 客户端
|
||||||
this.client = S3AsyncClient.crtBuilder()
|
this.client = S3AsyncClient.builder()
|
||||||
.credentialsProvider(credentialsProvider)
|
.credentialsProvider(credentialsProvider)
|
||||||
.endpointOverride(URI.create(getEndpoint()))
|
.endpointOverride(URI.create(getEndpoint()))
|
||||||
.region(of())
|
.region(of())
|
||||||
.targetThroughputInGbps(20.0)
|
|
||||||
.minimumPartSizeInBytes(10 * 1025 * 1024L)
|
|
||||||
.checksumValidationEnabled(false)
|
|
||||||
.forcePathStyle(isStyle)
|
.forcePathStyle(isStyle)
|
||||||
.httpConfiguration(S3CrtHttpConfiguration.builder()
|
.httpClient(NettyNioAsyncHttpClient.builder()
|
||||||
.connectionTimeout(Duration.ofSeconds(60)) // 设置连接超时
|
.connectionTimeout(Duration.ofSeconds(60)).build())
|
||||||
.build())
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
//AWS基于 CRT 的 S3 AsyncClient 实例用作 S3 传输管理器的底层客户端
|
//AWS基于 CRT 的 S3 AsyncClient 实例用作 S3 传输管理器的底层客户端
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package org.dromara.common.oss.enumd;
|
package org.dromara.common.oss.enums;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
@ -78,6 +78,7 @@ public class CaffeineCacheDecorator implements Cache {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
|
CAFFEINE.invalidateAll();
|
||||||
cache.clear();
|
cache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,18 +145,25 @@ public class PlusSpringCacheManager implements CacheManager {
|
|||||||
if (array.length > 3) {
|
if (array.length > 3) {
|
||||||
config.setMaxSize(Integer.parseInt(array[3]));
|
config.setMaxSize(Integer.parseInt(array[3]));
|
||||||
}
|
}
|
||||||
|
int local = 1;
|
||||||
if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
|
if (array.length > 4) {
|
||||||
return createMap(name, config);
|
local = Integer.parseInt(array[4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return createMapCache(name, config);
|
if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
|
||||||
|
return createMap(name, config, local);
|
||||||
|
}
|
||||||
|
|
||||||
|
return createMapCache(name, config, local);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Cache createMap(String name, CacheConfig config) {
|
private Cache createMap(String name, CacheConfig config, int local) {
|
||||||
RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
|
RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
|
||||||
|
|
||||||
Cache cache = new CaffeineCacheDecorator(name, new RedissonCache(map, allowNullValues));
|
Cache cache = new RedissonCache(map, allowNullValues);
|
||||||
|
if (local == 1) {
|
||||||
|
cache = new CaffeineCacheDecorator(name, cache);
|
||||||
|
}
|
||||||
if (transactionAware) {
|
if (transactionAware) {
|
||||||
cache = new TransactionAwareCacheDecorator(cache);
|
cache = new TransactionAwareCacheDecorator(cache);
|
||||||
}
|
}
|
||||||
@ -167,10 +174,13 @@ public class PlusSpringCacheManager implements CacheManager {
|
|||||||
return cache;
|
return cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Cache createMapCache(String name, CacheConfig config) {
|
private Cache createMapCache(String name, CacheConfig config, int local) {
|
||||||
RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
|
RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
|
||||||
|
|
||||||
Cache cache = new CaffeineCacheDecorator(name, new RedissonCache(map, config, allowNullValues));
|
Cache cache = new RedissonCache(map, config, allowNullValues);
|
||||||
|
if (local == 1) {
|
||||||
|
cache = new CaffeineCacheDecorator(name, cache);
|
||||||
|
}
|
||||||
if (transactionAware) {
|
if (transactionAware) {
|
||||||
cache = new TransactionAwareCacheDecorator(cache);
|
cache = new TransactionAwareCacheDecorator(cache);
|
||||||
}
|
}
|
||||||
|
@ -1,165 +1,180 @@
|
|||||||
package org.dromara.common.redis.utils;
|
package org.dromara.common.redis.utils;
|
||||||
|
|
||||||
import cn.hutool.core.date.DatePattern;
|
import cn.hutool.core.date.DatePattern;
|
||||||
import cn.hutool.core.date.DateUtil;
|
import cn.hutool.core.date.DateUtil;
|
||||||
import org.dromara.common.core.utils.SpringUtils;
|
import lombok.AccessLevel;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.AccessLevel;
|
import org.dromara.common.core.utils.SpringUtils;
|
||||||
import lombok.NoArgsConstructor;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
import org.redisson.api.RIdGenerator;
|
import org.redisson.api.RIdGenerator;
|
||||||
import org.redisson.api.RedissonClient;
|
import org.redisson.api.RedissonClient;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发号器工具类
|
* 发号器工具类
|
||||||
*
|
*
|
||||||
* @author 秋辞未寒
|
* @author 秋辞未寒
|
||||||
* @date 2024-12-10
|
* @date 2024-12-10
|
||||||
*/
|
*/
|
||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
public class SequenceUtils {
|
public class SequenceUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认初始值
|
* 默认初始值
|
||||||
*/
|
*/
|
||||||
public static final Long DEFAULT_INIT_VALUE = 1L;
|
public static final Long DEFAULT_INIT_VALUE = 1L;
|
||||||
/**
|
|
||||||
* 默认步长
|
/**
|
||||||
*/
|
* 默认步长
|
||||||
public static final Long DEFAULT_STEP_VALUE = 1L;
|
*/
|
||||||
/**
|
public static final Long DEFAULT_STEP_VALUE = 1L;
|
||||||
* 默认过期时间-天
|
|
||||||
*/
|
/**
|
||||||
public static final Duration DEFAULT_EXPIRE_TIME_DAY = Duration.ofDays(1);
|
* 默认过期时间-天
|
||||||
/**
|
*/
|
||||||
* 默认过期时间-分钟
|
public static final Duration DEFAULT_EXPIRE_TIME_DAY = Duration.ofDays(1);
|
||||||
*/
|
|
||||||
public static final Duration DEFAULT_EXPIRE_TIME_MINUTE = Duration.ofMinutes(1);
|
/**
|
||||||
|
* 默认过期时间-分钟
|
||||||
/**
|
*/
|
||||||
* 获取Redisson客户端实例
|
public static final Duration DEFAULT_EXPIRE_TIME_MINUTE = Duration.ofMinutes(1);
|
||||||
*/
|
|
||||||
private static final RedissonClient REDISSON_CLIENT = SpringUtils.getBean(RedissonClient.class);
|
/**
|
||||||
|
* 获取Redisson客户端实例
|
||||||
/**
|
*/
|
||||||
* 获取ID生成器
|
private static final RedissonClient REDISSON_CLIENT = SpringUtils.getBean(RedissonClient.class);
|
||||||
*
|
|
||||||
* @param key 业务key
|
/**
|
||||||
* @param expireTime 过期时间
|
* 获取ID生成器
|
||||||
* @param initValue ID初始值
|
*
|
||||||
* @param stepValue ID步长
|
* @param key 业务key
|
||||||
* @return ID生成器
|
* @param expireTime 过期时间
|
||||||
*/
|
* @param initValue ID初始值
|
||||||
private static RIdGenerator getIdGenerator(String key, Duration expireTime, Long initValue, Long stepValue) {
|
* @param stepValue ID步长
|
||||||
if (initValue == null || initValue <= 0) {
|
* @return ID生成器
|
||||||
initValue = DEFAULT_INIT_VALUE;
|
*/
|
||||||
}
|
private static RIdGenerator getIdGenerator(String key, Duration expireTime, Long initValue, Long stepValue) {
|
||||||
if (stepValue == null || stepValue <= 0) {
|
if (initValue == null || initValue <= 0) {
|
||||||
stepValue = DEFAULT_STEP_VALUE;
|
initValue = DEFAULT_INIT_VALUE;
|
||||||
}
|
}
|
||||||
RIdGenerator idGenerator = REDISSON_CLIENT.getIdGenerator(key);
|
if (stepValue == null || stepValue <= 0) {
|
||||||
// 设置初始值和步长
|
stepValue = DEFAULT_STEP_VALUE;
|
||||||
idGenerator.tryInit(initValue, stepValue);
|
}
|
||||||
// 设置过期时间
|
RIdGenerator idGenerator = REDISSON_CLIENT.getIdGenerator(key);
|
||||||
idGenerator.expire(expireTime);
|
// 设置初始值和步长
|
||||||
return idGenerator;
|
idGenerator.tryInit(initValue, stepValue);
|
||||||
}
|
// 设置过期时间
|
||||||
|
idGenerator.expire(expireTime);
|
||||||
/**
|
return idGenerator;
|
||||||
* 获取指定业务key的唯一id
|
}
|
||||||
*
|
|
||||||
* @param key 业务key
|
/**
|
||||||
* @param expireTime 过期时间
|
* 获取指定业务key的唯一id
|
||||||
* @param initValue ID初始值
|
*
|
||||||
* @param stepValue ID步长
|
* @param key 业务key
|
||||||
* @return 唯一id
|
* @param expireTime 过期时间
|
||||||
*/
|
* @param initValue ID初始值
|
||||||
public static long nextId(String key, Duration expireTime, Long initValue, Long stepValue) {
|
* @param stepValue ID步长
|
||||||
return getIdGenerator(key, expireTime, initValue, stepValue).nextId();
|
* @return 唯一id
|
||||||
}
|
*/
|
||||||
|
public static long nextId(String key, Duration expireTime, Long initValue, Long stepValue) {
|
||||||
/**
|
return getIdGenerator(key, expireTime, initValue, stepValue).nextId();
|
||||||
* 获取指定业务key的唯一id字符串
|
}
|
||||||
*
|
|
||||||
* @param key 业务key
|
/**
|
||||||
* @param expireTime 过期时间
|
* 获取指定业务key的唯一id字符串
|
||||||
* @param initValue ID初始值
|
*
|
||||||
* @param stepValue ID步长
|
* @param key 业务key
|
||||||
* @return 唯一id
|
* @param expireTime 过期时间
|
||||||
*/
|
* @param initValue ID初始值
|
||||||
public static String nextIdStr(String key, Duration expireTime, Long initValue, Long stepValue) {
|
* @param stepValue ID步长
|
||||||
return String.valueOf(nextId(key, expireTime, initValue, stepValue));
|
* @return 唯一id
|
||||||
}
|
*/
|
||||||
|
public static String nextIdStr(String key, Duration expireTime, Long initValue, Long stepValue) {
|
||||||
/**
|
return String.valueOf(nextId(key, expireTime, initValue, stepValue));
|
||||||
* 获取指定业务key的唯一id (ID初始值=1,ID步长=1)
|
}
|
||||||
*
|
|
||||||
* @param key 业务key
|
/**
|
||||||
* @param expireTime 过期时间
|
* 获取指定业务key的唯一id (ID初始值=1,ID步长=1)
|
||||||
* @return 唯一id
|
*
|
||||||
*/
|
* @param key 业务key
|
||||||
public static long nextId(String key, Duration expireTime) {
|
* @param expireTime 过期时间
|
||||||
return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
|
* @return 唯一id
|
||||||
}
|
*/
|
||||||
|
public static long nextId(String key, Duration expireTime) {
|
||||||
/**
|
return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
|
||||||
* 获取指定业务key的唯一id字符串 (ID初始值=1,ID步长=1)
|
}
|
||||||
*
|
|
||||||
* @param key 业务key
|
/**
|
||||||
* @param expireTime 过期时间
|
* 获取指定业务key的唯一id字符串 (ID初始值=1,ID步长=1)
|
||||||
* @return 唯一id
|
*
|
||||||
*/
|
* @param key 业务key
|
||||||
public static String nextIdStr(String key, Duration expireTime) {
|
* @param expireTime 过期时间
|
||||||
return String.valueOf(nextId(key, expireTime));
|
* @return 唯一id
|
||||||
}
|
*/
|
||||||
|
public static String nextIdStr(String key, Duration expireTime) {
|
||||||
/**
|
return String.valueOf(nextId(key, expireTime));
|
||||||
* 获取 yyyyMMdd 开头的唯一id
|
}
|
||||||
*
|
|
||||||
* @return 唯一id
|
/**
|
||||||
*/
|
* 获取指定业务key的唯一id字符串 (ID初始值=1,ID步长=1),不足位数自动补零
|
||||||
public static String nextIdDate() {
|
*
|
||||||
return nextIdDate("");
|
* @param key 业务key
|
||||||
}
|
* @param expireTime 过期时间
|
||||||
|
* @param width 位数,不足左补0
|
||||||
/**
|
* @return 补零后的唯一id字符串
|
||||||
* 获取 prefix + yyyyMMdd 开头的唯一id
|
*/
|
||||||
*
|
public static String nextPaddedIdStr(String key, Duration expireTime, Integer width) {
|
||||||
* @param prefix 业务前缀
|
return StringUtils.leftPad(nextIdStr(key, expireTime), width, '0');
|
||||||
* @return 唯一id
|
}
|
||||||
*/
|
|
||||||
public static String nextIdDate(String prefix) {
|
/**
|
||||||
// 前缀+日期 构建 prefixKey
|
* 获取 yyyyMMdd 开头的唯一id
|
||||||
String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_FORMATTER));
|
*
|
||||||
// 获取下一个id
|
* @return 唯一id
|
||||||
long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_DAY, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
|
*/
|
||||||
// 返回完整id
|
public static String nextIdDate() {
|
||||||
return StringUtils.format("{}{}", prefixKey, nextId);
|
return nextIdDate("");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 yyyyMMddHHmmss 开头的唯一id
|
* 获取 prefix + yyyyMMdd 开头的唯一id
|
||||||
*
|
*
|
||||||
* @return 唯一id
|
* @param prefix 业务前缀
|
||||||
*/
|
* @return 唯一id
|
||||||
public static String nextIdDateTime() {
|
*/
|
||||||
return nextIdDateTime("");
|
public static String nextIdDate(String prefix) {
|
||||||
}
|
// 前缀+日期 构建 prefixKey
|
||||||
|
String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_FORMATTER));
|
||||||
/**
|
// 获取下一个id
|
||||||
* 获取 prefix + yyyyMMddHHmmss 开头的唯一id
|
long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_DAY, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
|
||||||
*
|
// 返回完整id
|
||||||
* @param prefix 业务前缀
|
return StringUtils.format("{}{}", prefixKey, nextId);
|
||||||
* @return 唯一id
|
}
|
||||||
*/
|
|
||||||
public static String nextIdDateTime(String prefix) {
|
/**
|
||||||
// 前缀+日期时间 构建 prefixKey
|
* 获取 yyyyMMddHHmmss 开头的唯一id
|
||||||
String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_FORMATTER));
|
*
|
||||||
// 获取下一个id
|
* @return 唯一id
|
||||||
long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_MINUTE, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
|
*/
|
||||||
// 返回完整id
|
public static String nextIdDateTime() {
|
||||||
return StringUtils.format("{}{}", prefixKey, nextId);
|
return nextIdDateTime("");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
/**
|
||||||
|
* 获取 prefix + yyyyMMddHHmmss 开头的唯一id
|
||||||
|
*
|
||||||
|
* @param prefix 业务前缀
|
||||||
|
* @return 唯一id
|
||||||
|
*/
|
||||||
|
public static String nextIdDateTime(String prefix) {
|
||||||
|
// 前缀+日期时间 构建 prefixKey
|
||||||
|
String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_FORMATTER));
|
||||||
|
// 获取下一个id
|
||||||
|
long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_MINUTE, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
|
||||||
|
// 返回完整id
|
||||||
|
return StringUtils.format("{}{}", prefixKey, nextId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package org.dromara.common.satoken.core.dao;
|
package org.dromara.common.satoken.core.dao;
|
||||||
|
|
||||||
import cn.dev33.satoken.dao.SaTokenDao;
|
import cn.dev33.satoken.dao.auto.SaTokenDaoBySessionFollowObject;
|
||||||
import cn.dev33.satoken.util.SaFoxUtil;
|
import cn.dev33.satoken.util.SaFoxUtil;
|
||||||
import com.github.benmanes.caffeine.cache.Cache;
|
import com.github.benmanes.caffeine.cache.Cache;
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
@ -16,10 +16,12 @@ import java.util.concurrent.TimeUnit;
|
|||||||
* Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一)
|
* Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一)
|
||||||
* <p>
|
* <p>
|
||||||
* 采用 caffeine + redis 多级缓存 优化并发查询效率
|
* 采用 caffeine + redis 多级缓存 优化并发查询效率
|
||||||
|
* <p>
|
||||||
|
* SaTokenDaoBySessionFollowObject 是 SaTokenDao 子集简化了session方法处理
|
||||||
*
|
*
|
||||||
* @author Lion Li
|
* @author Lion Li
|
||||||
*/
|
*/
|
||||||
public class PlusSaTokenDao implements SaTokenDao {
|
public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
||||||
|
|
||||||
private static final Cache<String, Object> CAFFEINE = Caffeine.newBuilder()
|
private static final Cache<String, Object> CAFFEINE = Caffeine.newBuilder()
|
||||||
// 设置最后一次写入或访问后经过固定时间过期
|
// 设置最后一次写入或访问后经过固定时间过期
|
||||||
@ -85,7 +87,8 @@ public class PlusSaTokenDao implements SaTokenDao {
|
|||||||
@Override
|
@Override
|
||||||
public long getTimeout(String key) {
|
public long getTimeout(String key) {
|
||||||
long timeout = RedisUtils.getTimeToLive(key);
|
long timeout = RedisUtils.getTimeToLive(key);
|
||||||
return timeout < 0 ? timeout : timeout / 1000;
|
// 加1的目的 解决sa-token使用秒 redis是毫秒导致1秒的精度问题 手动补偿
|
||||||
|
return timeout < 0 ? timeout : timeout / 1000 + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -106,6 +109,19 @@ public class PlusSaTokenDao implements SaTokenDao {
|
|||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 Object (指定反序列化类型),如无返空
|
||||||
|
*
|
||||||
|
* @param key 键名称
|
||||||
|
* @return object
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked cast")
|
||||||
|
@Override
|
||||||
|
public <T> T getObject(String key, Class<T> classType) {
|
||||||
|
Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
|
||||||
|
return (T) o;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 写入Object,并设定存活时间 (单位: 秒)
|
* 写入Object,并设定存活时间 (单位: 秒)
|
||||||
*/
|
*/
|
||||||
@ -152,7 +168,8 @@ public class PlusSaTokenDao implements SaTokenDao {
|
|||||||
@Override
|
@Override
|
||||||
public long getObjectTimeout(String key) {
|
public long getObjectTimeout(String key) {
|
||||||
long timeout = RedisUtils.getTimeToLive(key);
|
long timeout = RedisUtils.getTimeToLive(key);
|
||||||
return timeout < 0 ? timeout : timeout / 1000;
|
// 加1的目的 解决sa-token使用秒 redis是毫秒导致1秒的精度问题 手动补偿
|
||||||
|
return timeout < 0 ? timeout : timeout / 1000 + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -163,7 +180,6 @@ public class PlusSaTokenDao implements SaTokenDao {
|
|||||||
RedisUtils.expire(key, Duration.ofSeconds(timeout));
|
RedisUtils.expire(key, Duration.ofSeconds(timeout));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 搜索数据
|
* 搜索数据
|
||||||
*/
|
*/
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
package org.dromara.common.satoken.core.service;
|
package org.dromara.common.satoken.core.service;
|
||||||
|
|
||||||
import cn.dev33.satoken.stp.StpInterface;
|
import cn.dev33.satoken.stp.StpInterface;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import org.dromara.common.core.domain.model.LoginUser;
|
import org.dromara.common.core.domain.model.LoginUser;
|
||||||
import org.dromara.common.core.enums.UserType;
|
import org.dromara.common.core.enums.UserType;
|
||||||
|
import org.dromara.common.core.exception.ServiceException;
|
||||||
|
import org.dromara.common.core.service.PermissionService;
|
||||||
|
import org.dromara.common.core.utils.SpringUtils;
|
||||||
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
import org.dromara.common.satoken.utils.LoginHelper;
|
import org.dromara.common.satoken.utils.LoginHelper;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -21,13 +26,21 @@ public class SaPermissionImpl implements StpInterface {
|
|||||||
@Override
|
@Override
|
||||||
public List<String> getPermissionList(Object loginId, String loginType) {
|
public List<String> getPermissionList(Object loginId, String loginType) {
|
||||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||||
|
if (ObjectUtil.isNull(loginUser) || !loginUser.getLoginId().equals(loginId)) {
|
||||||
|
PermissionService permissionService = getPermissionService();
|
||||||
|
if (ObjectUtil.isNotNull(permissionService)) {
|
||||||
|
List<String> list = StringUtils.splitList(loginId.toString(), ":");
|
||||||
|
return new ArrayList<>(permissionService.getMenuPermission(Long.parseLong(list.get(1))));
|
||||||
|
} else {
|
||||||
|
throw new ServiceException("PermissionService 实现类不存在");
|
||||||
|
}
|
||||||
|
}
|
||||||
UserType userType = UserType.getUserType(loginUser.getUserType());
|
UserType userType = UserType.getUserType(loginUser.getUserType());
|
||||||
if (userType == UserType.SYS_USER) {
|
if (userType == UserType.APP_USER) {
|
||||||
return new ArrayList<>(loginUser.getMenuPermission());
|
|
||||||
} else if (userType == UserType.APP_USER) {
|
|
||||||
// 其他端 自行根据业务编写
|
// 其他端 自行根据业务编写
|
||||||
}
|
}
|
||||||
return new ArrayList<>();
|
// SYS_USER 默认返回权限
|
||||||
|
return new ArrayList<>(loginUser.getMenuPermission());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,12 +49,29 @@ public class SaPermissionImpl implements StpInterface {
|
|||||||
@Override
|
@Override
|
||||||
public List<String> getRoleList(Object loginId, String loginType) {
|
public List<String> getRoleList(Object loginId, String loginType) {
|
||||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||||
|
if (ObjectUtil.isNull(loginUser) || !loginUser.getLoginId().equals(loginId)) {
|
||||||
|
PermissionService permissionService = getPermissionService();
|
||||||
|
if (ObjectUtil.isNotNull(permissionService)) {
|
||||||
|
List<String> list = StringUtils.splitList(loginId.toString(), ":");
|
||||||
|
return new ArrayList<>(permissionService.getRolePermission(Long.parseLong(list.get(1))));
|
||||||
|
} else {
|
||||||
|
throw new ServiceException("PermissionService 实现类不存在");
|
||||||
|
}
|
||||||
|
}
|
||||||
UserType userType = UserType.getUserType(loginUser.getUserType());
|
UserType userType = UserType.getUserType(loginUser.getUserType());
|
||||||
if (userType == UserType.SYS_USER) {
|
if (userType == UserType.APP_USER) {
|
||||||
return new ArrayList<>(loginUser.getRolePermission());
|
|
||||||
} else if (userType == UserType.APP_USER) {
|
|
||||||
// 其他端 自行根据业务编写
|
// 其他端 自行根据业务编写
|
||||||
}
|
}
|
||||||
return new ArrayList<>();
|
// SYS_USER 默认返回权限
|
||||||
|
return new ArrayList<>(loginUser.getRolePermission());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PermissionService getPermissionService() {
|
||||||
|
try {
|
||||||
|
return SpringUtils.getBean(PermissionService.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package org.dromara.common.satoken.utils;
|
package org.dromara.common.satoken.utils;
|
||||||
|
|
||||||
import cn.dev33.satoken.session.SaSession;
|
import cn.dev33.satoken.session.SaSession;
|
||||||
import cn.dev33.satoken.stp.SaLoginModel;
|
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.convert.Convert;
|
import cn.hutool.core.convert.Convert;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
@ -47,8 +47,8 @@ public class LoginHelper {
|
|||||||
* @param loginUser 登录用户信息
|
* @param loginUser 登录用户信息
|
||||||
* @param model 配置参数
|
* @param model 配置参数
|
||||||
*/
|
*/
|
||||||
public static void login(LoginUser loginUser, SaLoginModel model) {
|
public static void login(LoginUser loginUser, SaLoginParameter model) {
|
||||||
model = ObjectUtil.defaultIfNull(model, new SaLoginModel());
|
model = ObjectUtil.defaultIfNull(model, new SaLoginParameter());
|
||||||
StpUtil.login(loginUser.getLoginId(),
|
StpUtil.login(loginUser.getLoginId(),
|
||||||
model.setExtra(TENANT_KEY, loginUser.getTenantId())
|
model.setExtra(TENANT_KEY, loginUser.getTenantId())
|
||||||
.setExtra(USER_KEY, loginUser.getUserId())
|
.setExtra(USER_KEY, loginUser.getUserId())
|
||||||
@ -63,23 +63,25 @@ public class LoginHelper {
|
|||||||
/**
|
/**
|
||||||
* 获取用户(多级缓存)
|
* 获取用户(多级缓存)
|
||||||
*/
|
*/
|
||||||
public static LoginUser getLoginUser() {
|
@SuppressWarnings("unchecked cast")
|
||||||
|
public static <T extends LoginUser> T getLoginUser() {
|
||||||
SaSession session = StpUtil.getTokenSession();
|
SaSession session = StpUtil.getTokenSession();
|
||||||
if (ObjectUtil.isNull(session)) {
|
if (ObjectUtil.isNull(session)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (LoginUser) session.get(LOGIN_USER_KEY);
|
return (T) session.get(LOGIN_USER_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户基于token
|
* 获取用户基于token
|
||||||
*/
|
*/
|
||||||
public static LoginUser getLoginUser(String token) {
|
@SuppressWarnings("unchecked cast")
|
||||||
|
public static <T extends LoginUser> T getLoginUser(String token) {
|
||||||
SaSession session = StpUtil.getTokenSessionByToken(token);
|
SaSession session = StpUtil.getTokenSessionByToken(token);
|
||||||
if (ObjectUtil.isNull(session)) {
|
if (ObjectUtil.isNull(session)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (LoginUser) session.get(LOGIN_USER_KEY);
|
return (T) session.get(LOGIN_USER_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -191,7 +193,11 @@ public class LoginHelper {
|
|||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
public static boolean isTenantAdmin() {
|
public static boolean isTenantAdmin() {
|
||||||
return Convert.toBool(isTenantAdmin(getLoginUser().getRolePermission()));
|
LoginUser loginUser = getLoginUser();
|
||||||
|
if (loginUser == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Convert.toBool(isTenantAdmin(loginUser.getRolePermission()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,13 +11,13 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.dromara.common.core.constant.HttpStatus;
|
import org.dromara.common.core.constant.HttpStatus;
|
||||||
import org.dromara.common.core.exception.SseException;
|
|
||||||
import org.dromara.common.core.utils.ServletUtils;
|
import org.dromara.common.core.utils.ServletUtils;
|
||||||
import org.dromara.common.core.utils.SpringUtils;
|
import org.dromara.common.core.utils.SpringUtils;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
import org.dromara.common.satoken.utils.LoginHelper;
|
import org.dromara.common.satoken.utils.LoginHelper;
|
||||||
import org.dromara.common.security.config.properties.SecurityProperties;
|
import org.dromara.common.security.config.properties.SecurityProperties;
|
||||||
import org.dromara.common.security.handler.AllUrlHandler;
|
import org.dromara.common.security.handler.AllUrlHandler;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@ -37,6 +37,8 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|||||||
public class SecurityConfig implements WebMvcConfigurer {
|
public class SecurityConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
private final SecurityProperties securityProperties;
|
private final SecurityProperties securityProperties;
|
||||||
|
@Value("${sse.path}")
|
||||||
|
private String ssePath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注册sa-token的拦截器
|
* 注册sa-token的拦截器
|
||||||
@ -54,15 +56,7 @@ public class SecurityConfig implements WebMvcConfigurer {
|
|||||||
.check(() -> {
|
.check(() -> {
|
||||||
HttpServletRequest request = ServletUtils.getRequest();
|
HttpServletRequest request = ServletUtils.getRequest();
|
||||||
// 检查是否登录 是否有token
|
// 检查是否登录 是否有token
|
||||||
try {
|
StpUtil.checkLogin();
|
||||||
StpUtil.checkLogin();
|
|
||||||
} catch (NotLoginException e) {
|
|
||||||
if (request.getRequestURI().contains("sse")) {
|
|
||||||
throw new SseException(e.getMessage(), e.getCode());
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 header 与 param 里的 clientid 与 token 里的是否一致
|
// 检查 header 与 param 里的 clientid 与 token 里的是否一致
|
||||||
String headerCid = request.getHeader(LoginHelper.CLIENT_KEY);
|
String headerCid = request.getHeader(LoginHelper.CLIENT_KEY);
|
||||||
@ -84,7 +78,8 @@ public class SecurityConfig implements WebMvcConfigurer {
|
|||||||
});
|
});
|
||||||
})).addPathPatterns("/**")
|
})).addPathPatterns("/**")
|
||||||
// 排除不需要拦截的路径
|
// 排除不需要拦截的路径
|
||||||
.excludePathPatterns(securityProperties.getExcludes());
|
.excludePathPatterns(securityProperties.getExcludes())
|
||||||
|
.excludePathPatterns(ssePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,92 @@
|
|||||||
|
package org.dromara.common.social.gitea;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.Dict;
|
||||||
|
import cn.hutool.http.HttpRequest;
|
||||||
|
import cn.hutool.http.HttpResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.zhyd.oauth.cache.AuthStateCache;
|
||||||
|
import me.zhyd.oauth.config.AuthConfig;
|
||||||
|
import me.zhyd.oauth.exception.AuthException;
|
||||||
|
import me.zhyd.oauth.model.AuthCallback;
|
||||||
|
import me.zhyd.oauth.model.AuthToken;
|
||||||
|
import me.zhyd.oauth.model.AuthUser;
|
||||||
|
import me.zhyd.oauth.request.AuthDefaultRequest;
|
||||||
|
import org.dromara.common.core.utils.SpringUtils;
|
||||||
|
import org.dromara.common.json.utils.JsonUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lcry
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class AuthGiteaRequest extends AuthDefaultRequest {
|
||||||
|
|
||||||
|
public static final String SERVER_URL = SpringUtils.getProperty("justauth.type.gitea.server-url");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设定归属域
|
||||||
|
*/
|
||||||
|
public AuthGiteaRequest(AuthConfig config) {
|
||||||
|
super(config, AuthGiteaSource.GITEA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthGiteaRequest(AuthConfig config, AuthStateCache authStateCache) {
|
||||||
|
super(config, AuthGiteaSource.GITEA, authStateCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthToken getAccessToken(AuthCallback authCallback) {
|
||||||
|
String body = doPostAuthorizationCode(authCallback.getCode());
|
||||||
|
Dict object = JsonUtils.parseMap(body);
|
||||||
|
// oauth/token 验证异常
|
||||||
|
if (object.containsKey("error")) {
|
||||||
|
throw new AuthException(object.getStr("error_description"));
|
||||||
|
}
|
||||||
|
// user 验证异常
|
||||||
|
if (object.containsKey("message")) {
|
||||||
|
throw new AuthException(object.getStr("message"));
|
||||||
|
}
|
||||||
|
return AuthToken.builder()
|
||||||
|
.accessToken(object.getStr("access_token"))
|
||||||
|
.refreshToken(object.getStr("refresh_token"))
|
||||||
|
.idToken(object.getStr("id_token"))
|
||||||
|
.tokenType(object.getStr("token_type"))
|
||||||
|
.scope(object.getStr("scope"))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String doPostAuthorizationCode(String code) {
|
||||||
|
HttpRequest request = HttpRequest.post(source.accessToken())
|
||||||
|
.form("client_id", config.getClientId())
|
||||||
|
.form("client_secret", config.getClientSecret())
|
||||||
|
.form("grant_type", "authorization_code")
|
||||||
|
.form("code", code)
|
||||||
|
.form("redirect_uri", config.getRedirectUri());
|
||||||
|
HttpResponse response = request.execute();
|
||||||
|
return response.body();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthUser getUserInfo(AuthToken authToken) {
|
||||||
|
String body = doGetUserInfo(authToken);
|
||||||
|
Dict object = JsonUtils.parseMap(body);
|
||||||
|
// oauth/token 验证异常
|
||||||
|
if (object.containsKey("error")) {
|
||||||
|
throw new AuthException(object.getStr("error_description"));
|
||||||
|
}
|
||||||
|
// user 验证异常
|
||||||
|
if (object.containsKey("message")) {
|
||||||
|
throw new AuthException(object.getStr("message"));
|
||||||
|
}
|
||||||
|
return AuthUser.builder()
|
||||||
|
.uuid(object.getStr("sub"))
|
||||||
|
.username(object.getStr("name"))
|
||||||
|
.nickname(object.getStr("preferred_username"))
|
||||||
|
.avatar(object.getStr("picture"))
|
||||||
|
.email(object.getStr("email"))
|
||||||
|
.token(authToken)
|
||||||
|
.source(source.toString())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package org.dromara.common.social.gitea;
|
||||||
|
|
||||||
|
import me.zhyd.oauth.config.AuthSource;
|
||||||
|
import me.zhyd.oauth.request.AuthDefaultRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gitea Oauth2 默认接口说明
|
||||||
|
*
|
||||||
|
* @author lcry
|
||||||
|
*/
|
||||||
|
public enum AuthGiteaSource implements AuthSource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自己搭建的 gitea 私服
|
||||||
|
*/
|
||||||
|
GITEA {
|
||||||
|
/**
|
||||||
|
* 授权的api
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String authorize() {
|
||||||
|
return AuthGiteaRequest.SERVER_URL + "/login/oauth/authorize";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取accessToken的api
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String accessToken() {
|
||||||
|
return AuthGiteaRequest.SERVER_URL + "/login/oauth/access_token";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户信息的api
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String userInfo() {
|
||||||
|
return AuthGiteaRequest.SERVER_URL + "/login/oauth/userinfo";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台对应的 AuthRequest 实现类,必须继承自 {@link AuthDefaultRequest}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Class<? extends AuthDefaultRequest> getTargetClass() {
|
||||||
|
return AuthGiteaRequest.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ import me.zhyd.oauth.request.*;
|
|||||||
import org.dromara.common.core.utils.SpringUtils;
|
import org.dromara.common.core.utils.SpringUtils;
|
||||||
import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
|
import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
|
||||||
import org.dromara.common.social.config.properties.SocialProperties;
|
import org.dromara.common.social.config.properties.SocialProperties;
|
||||||
|
import org.dromara.common.social.gitea.AuthGiteaRequest;
|
||||||
import org.dromara.common.social.maxkey.AuthMaxKeyRequest;
|
import org.dromara.common.social.maxkey.AuthMaxKeyRequest;
|
||||||
import org.dromara.common.social.topiam.AuthTopIamRequest;
|
import org.dromara.common.social.topiam.AuthTopIamRequest;
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ public class SocialUtils {
|
|||||||
.redirectUri(obj.getRedirectUri())
|
.redirectUri(obj.getRedirectUri())
|
||||||
.scopes(obj.getScopes());
|
.scopes(obj.getScopes());
|
||||||
return switch (source.toLowerCase()) {
|
return switch (source.toLowerCase()) {
|
||||||
case "dingtalk" -> new AuthDingTalkRequest(builder.build(), STATE_CACHE);
|
case "dingtalk" -> new AuthDingTalkV2Request(builder.build(), STATE_CACHE);
|
||||||
case "baidu" -> new AuthBaiduRequest(builder.build(), STATE_CACHE);
|
case "baidu" -> new AuthBaiduRequest(builder.build(), STATE_CACHE);
|
||||||
case "github" -> new AuthGithubRequest(builder.build(), STATE_CACHE);
|
case "github" -> new AuthGithubRequest(builder.build(), STATE_CACHE);
|
||||||
case "gitee" -> new AuthGiteeRequest(builder.build(), STATE_CACHE);
|
case "gitee" -> new AuthGiteeRequest(builder.build(), STATE_CACHE);
|
||||||
@ -60,12 +61,13 @@ public class SocialUtils {
|
|||||||
case "renren" -> new AuthRenrenRequest(builder.build(), STATE_CACHE);
|
case "renren" -> new AuthRenrenRequest(builder.build(), STATE_CACHE);
|
||||||
case "stack_overflow" -> new AuthStackOverflowRequest(builder.stackOverflowKey(obj.getStackOverflowKey()).build(), STATE_CACHE);
|
case "stack_overflow" -> new AuthStackOverflowRequest(builder.stackOverflowKey(obj.getStackOverflowKey()).build(), STATE_CACHE);
|
||||||
case "huawei" -> new AuthHuaweiV3Request(builder.build(), STATE_CACHE);
|
case "huawei" -> new AuthHuaweiV3Request(builder.build(), STATE_CACHE);
|
||||||
case "wechat_enterprise" -> new AuthWeChatEnterpriseQrcodeRequest(builder.agentId(obj.getAgentId()).build(), STATE_CACHE);
|
case "wechat_enterprise" -> new AuthWeChatEnterpriseQrcodeV2Request(builder.agentId(obj.getAgentId()).build(), STATE_CACHE);
|
||||||
case "gitlab" -> new AuthGitlabRequest(builder.build(), STATE_CACHE);
|
case "gitlab" -> new AuthGitlabRequest(builder.build(), STATE_CACHE);
|
||||||
case "wechat_mp" -> new AuthWeChatMpRequest(builder.build(), STATE_CACHE);
|
case "wechat_mp" -> new AuthWeChatMpRequest(builder.build(), STATE_CACHE);
|
||||||
case "aliyun" -> new AuthAliyunRequest(builder.build(), STATE_CACHE);
|
case "aliyun" -> new AuthAliyunRequest(builder.build(), STATE_CACHE);
|
||||||
case "maxkey" -> new AuthMaxKeyRequest(builder.build(), STATE_CACHE);
|
case "maxkey" -> new AuthMaxKeyRequest(builder.build(), STATE_CACHE);
|
||||||
case "topiam" -> new AuthTopIamRequest(builder.build(), STATE_CACHE);
|
case "topiam" -> new AuthTopIamRequest(builder.build(), STATE_CACHE);
|
||||||
|
case "gitea" -> new AuthGiteaRequest(builder.build(), STATE_CACHE);
|
||||||
default -> throw new AuthException("未获取到有效的Auth配置");
|
default -> throw new AuthException("未获取到有效的Auth配置");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ public class SseController implements DisposableBean {
|
|||||||
*/
|
*/
|
||||||
@GetMapping(value = "${sse.path}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
@GetMapping(value = "${sse.path}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||||
public SseEmitter connect() {
|
public SseEmitter connect() {
|
||||||
|
StpUtil.checkLogin();
|
||||||
String tokenValue = StpUtil.getTokenValue();
|
String tokenValue = StpUtil.getTokenValue();
|
||||||
Long userId = LoginHelper.getUserId();
|
Long userId = LoginHelper.getUserId();
|
||||||
return sseEmitterManager.connect(userId, tokenValue);
|
return sseEmitterManager.connect(userId, tokenValue);
|
||||||
|
@ -44,9 +44,24 @@ public class SseEmitterManager {
|
|||||||
emitters.put(token, emitter);
|
emitters.put(token, emitter);
|
||||||
|
|
||||||
// 当 emitter 完成、超时或发生错误时,从映射表中移除对应的 token
|
// 当 emitter 完成、超时或发生错误时,从映射表中移除对应的 token
|
||||||
emitter.onCompletion(() -> emitters.remove(token));
|
emitter.onCompletion(() -> {
|
||||||
emitter.onTimeout(() -> emitters.remove(token));
|
SseEmitter remove = emitters.remove(token);
|
||||||
emitter.onError((e) -> emitters.remove(token));
|
if (remove != null) {
|
||||||
|
remove.complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
emitter.onTimeout(() -> {
|
||||||
|
SseEmitter remove = emitters.remove(token);
|
||||||
|
if (remove != null) {
|
||||||
|
remove.complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
emitter.onError((e) -> {
|
||||||
|
SseEmitter remove = emitters.remove(token);
|
||||||
|
if (remove != null) {
|
||||||
|
remove.complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 向客户端发送一条连接成功的事件
|
// 向客户端发送一条连接成功的事件
|
||||||
@ -65,6 +80,9 @@ public class SseEmitterManager {
|
|||||||
* @param token 用户的唯一令牌,用于识别具体的连接
|
* @param token 用户的唯一令牌,用于识别具体的连接
|
||||||
*/
|
*/
|
||||||
public void disconnect(Long userId, String token) {
|
public void disconnect(Long userId, String token) {
|
||||||
|
if (userId == null || token == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.get(userId);
|
Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.get(userId);
|
||||||
if (MapUtil.isNotEmpty(emitters)) {
|
if (MapUtil.isNotEmpty(emitters)) {
|
||||||
try {
|
try {
|
||||||
@ -103,7 +121,10 @@ public class SseEmitterManager {
|
|||||||
.name("message")
|
.name("message")
|
||||||
.data(message));
|
.data(message));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
emitters.remove(entry.getKey());
|
SseEmitter remove = emitters.remove(entry.getKey());
|
||||||
|
if (remove != null) {
|
||||||
|
remove.complete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -26,7 +26,7 @@ public class SseMessageUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 向指定的WebSocket会话发送消息
|
* 向指定的SSE会话发送消息
|
||||||
*
|
*
|
||||||
* @param userId 要发送消息的用户id
|
* @param userId 要发送消息的用户id
|
||||||
* @param message 要发送的消息内容
|
* @param message 要发送的消息内容
|
||||||
|
@ -81,6 +81,17 @@ public class TenantSaTokenDao extends PlusSaTokenDao {
|
|||||||
return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
|
return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 Object (指定反序列化类型),如无返空
|
||||||
|
*
|
||||||
|
* @param key 键名称
|
||||||
|
* @return object
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public <T> T getObject(String key, Class<T> classType) {
|
||||||
|
return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key, classType);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 写入Object,并设定存活时间 (单位: 秒)
|
* 写入Object,并设定存活时间 (单位: 秒)
|
||||||
*/
|
*/
|
||||||
@ -137,7 +148,6 @@ public class TenantSaTokenDao extends PlusSaTokenDao {
|
|||||||
RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout));
|
RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 搜索数据
|
* 搜索数据
|
||||||
*/
|
*/
|
||||||
|
@ -48,7 +48,7 @@ public class PlusTenantLineHandler implements TenantLineHandler {
|
|||||||
"gen_table_column"
|
"gen_table_column"
|
||||||
);
|
);
|
||||||
tables.addAll(excludes);
|
tables.addAll(excludes);
|
||||||
return tables.contains(tableName);
|
return StringUtils.containsAnyIgnoreCase(tableName, tables.toArray(new String[0]));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package org.dromara.common.web.handler;
|
|||||||
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.http.HttpStatus;
|
import cn.hutool.http.HttpStatus;
|
||||||
|
import com.fasterxml.jackson.core.JsonParseException;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.validation.ConstraintViolation;
|
import jakarta.validation.ConstraintViolation;
|
||||||
@ -14,6 +15,7 @@ import org.dromara.common.core.exception.base.BaseException;
|
|||||||
import org.dromara.common.core.utils.StreamUtils;
|
import org.dromara.common.core.utils.StreamUtils;
|
||||||
import org.dromara.common.json.utils.JsonUtils;
|
import org.dromara.common.json.utils.JsonUtils;
|
||||||
import org.springframework.context.support.DefaultMessageSourceResolvable;
|
import org.springframework.context.support.DefaultMessageSourceResolvable;
|
||||||
|
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||||
import org.springframework.validation.BindException;
|
import org.springframework.validation.BindException;
|
||||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
@ -180,4 +182,24 @@ public class GlobalExceptionHandler {
|
|||||||
return R.fail(message);
|
return R.fail(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON 解析异常(Jackson 在处理 JSON 格式出错时抛出)
|
||||||
|
* 可能是请求体格式非法,也可能是服务端反序列化失败
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(JsonParseException.class)
|
||||||
|
public R<Void> handleJsonParseException(JsonParseException e, HttpServletRequest request) {
|
||||||
|
String requestURI = request.getRequestURI();
|
||||||
|
log.error("请求地址'{}' 发生 JSON 解析异常: {}", requestURI, e.getMessage());
|
||||||
|
return R.fail(HttpStatus.HTTP_BAD_REQUEST, "请求数据格式错误(JSON 解析失败):" + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求体读取异常(通常是请求参数格式非法、字段类型不匹配等)
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||||
|
public R<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException e, HttpServletRequest request) {
|
||||||
|
log.error("请求地址'{}', 参数解析失败: {}", request.getRequestURI(), e.getMessage());
|
||||||
|
return R.fail(HttpStatus.HTTP_BAD_REQUEST, "请求参数格式错误:" + e.getMostSpecificCause().getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -59,9 +59,6 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
<version>${spring-boot.version}</version>
|
<version>${spring-boot.version}</version>
|
||||||
<configuration>
|
|
||||||
<!-- <fork>true</fork> <!– 如果没有该配置,devtools不会生效 –>-->
|
|
||||||
</configuration>
|
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<goals>
|
<goals>
|
||||||
|
@ -19,6 +19,7 @@ spring:
|
|||||||
admin:
|
admin:
|
||||||
ui:
|
ui:
|
||||||
title: RuoYi-Vue-Plus服务监控中心
|
title: RuoYi-Vue-Plus服务监控中心
|
||||||
|
context-path: /admin
|
||||||
|
|
||||||
--- # Actuator 监控端点的配置项
|
--- # Actuator 监控端点的配置项
|
||||||
management:
|
management:
|
||||||
@ -37,7 +38,7 @@ spring.boot.admin.client:
|
|||||||
# 增加客户端开关
|
# 增加客户端开关
|
||||||
enabled: true
|
enabled: true
|
||||||
# 设置 Spring Boot Admin Server 地址
|
# 设置 Spring Boot Admin Server 地址
|
||||||
url: http://localhost:9090
|
url: http://localhost:9090/admin
|
||||||
instance:
|
instance:
|
||||||
service-host-type: IP
|
service-host-type: IP
|
||||||
metadata:
|
metadata:
|
||||||
|
@ -44,7 +44,7 @@ public class ActuatorAuthFilter implements Filter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 验证用户名和密码
|
// 验证用户名和密码
|
||||||
if (!username.equals(split[0]) && password.equals(split[1])) {
|
if (!username.equals(split[0]) || !password.equals(split[1])) {
|
||||||
response.setHeader("WWW-Authenticate", "Basic realm=\"realm\"");
|
response.setHeader("WWW-Authenticate", "Basic realm=\"realm\"");
|
||||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
|
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
|
||||||
return;
|
return;
|
||||||
|
@ -22,28 +22,15 @@ snail-job:
|
|||||||
job-pull-page-size: 1000
|
job-pull-page-size: 1000
|
||||||
# 服务器端口
|
# 服务器端口
|
||||||
server-port: 17888
|
server-port: 17888
|
||||||
# 一个客户端每秒最多接收的重试数量指令
|
|
||||||
limiter: 1000
|
|
||||||
# 号段模式下步长配置
|
|
||||||
step: 100
|
|
||||||
# 日志保存时间(单位: day)
|
# 日志保存时间(单位: day)
|
||||||
log-storage: 90
|
log-storage: 7
|
||||||
# 回调配置
|
|
||||||
callback:
|
|
||||||
#回调最大执行次数
|
|
||||||
max-count: 288
|
|
||||||
#间隔时间
|
|
||||||
trigger-interval: 900
|
|
||||||
# 重试每次拉取的次数
|
|
||||||
retry-max-pull-count: 10
|
|
||||||
# RPC通讯类型: netty,grpc
|
|
||||||
rpc-type: grpc
|
rpc-type: grpc
|
||||||
|
|
||||||
--- # 监控中心配置
|
--- # 监控中心配置
|
||||||
spring.boot.admin.client:
|
spring.boot.admin.client:
|
||||||
# 增加客户端开关
|
# 增加客户端开关
|
||||||
enabled: true
|
enabled: true
|
||||||
url: http://localhost:9090
|
url: http://localhost:9090/admin
|
||||||
instance:
|
instance:
|
||||||
service-host-type: IP
|
service-host-type: IP
|
||||||
metadata:
|
metadata:
|
||||||
|
@ -22,28 +22,15 @@ snail-job:
|
|||||||
job-pull-page-size: 1000
|
job-pull-page-size: 1000
|
||||||
# 服务器端口
|
# 服务器端口
|
||||||
server-port: 17888
|
server-port: 17888
|
||||||
# 一个客户端每秒最多接收的重试数量指令
|
|
||||||
limiter: 1000
|
|
||||||
# 号段模式下步长配置
|
|
||||||
step: 100
|
|
||||||
# 日志保存时间(单位: day)
|
# 日志保存时间(单位: day)
|
||||||
log-storage: 90
|
log-storage: 7
|
||||||
# 回调配置
|
|
||||||
callback:
|
|
||||||
#回调最大执行次数
|
|
||||||
max-count: 288
|
|
||||||
#间隔时间
|
|
||||||
trigger-interval: 900
|
|
||||||
# 重试每次拉取的次数
|
|
||||||
retry-max-pull-count: 10
|
|
||||||
# RPC通讯类型: netty,grpc
|
|
||||||
rpc-type: grpc
|
rpc-type: grpc
|
||||||
|
|
||||||
--- # 监控中心配置
|
--- # 监控中心配置
|
||||||
spring.boot.admin.client:
|
spring.boot.admin.client:
|
||||||
# 增加客户端开关
|
# 增加客户端开关
|
||||||
enabled: true
|
enabled: true
|
||||||
url: http://localhost:9090
|
url: http://localhost:9090/admin
|
||||||
instance:
|
instance:
|
||||||
service-host-type: IP
|
service-host-type: IP
|
||||||
metadata:
|
metadata:
|
||||||
|
@ -40,7 +40,7 @@ public class RedisCacheController {
|
|||||||
* <p>
|
* <p>
|
||||||
* cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数
|
* cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数
|
||||||
*/
|
*/
|
||||||
@Cacheable(cacheNames = "demo:cache#60s#10m#20", key = "#key", condition = "#key != null")
|
@Cacheable(cacheNames = "demo:cache#60s#10m#20#1", key = "#key", condition = "#key != null")
|
||||||
@GetMapping("/test1")
|
@GetMapping("/test1")
|
||||||
public R<String> test1(String key, String value) {
|
public R<String> test1(String key, String value) {
|
||||||
return R.ok("操作成功", value);
|
return R.ok("操作成功", value);
|
||||||
|
@ -18,7 +18,7 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/demo/websocket")
|
@RequestMapping("/demo/websocket")
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class WeSocketController {
|
public class WebSocketController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发布消息
|
* 发布消息
|
@ -1,6 +1,6 @@
|
|||||||
package org.dromara.demo.domain.bo;
|
package org.dromara.demo.domain.bo;
|
||||||
|
|
||||||
import com.alibaba.excel.annotation.ExcelProperty;
|
import cn.idev.excel.annotation.ExcelProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package org.dromara.demo.domain.vo;
|
package org.dromara.demo.domain.vo;
|
||||||
|
|
||||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||||
import com.alibaba.excel.annotation.ExcelProperty;
|
import cn.idev.excel.annotation.ExcelProperty;
|
||||||
import jakarta.validation.constraints.NotEmpty;
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package org.dromara.demo.domain.vo;
|
package org.dromara.demo.domain.vo;
|
||||||
|
|
||||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||||
import com.alibaba.excel.annotation.ExcelProperty;
|
import cn.idev.excel.annotation.ExcelProperty;
|
||||||
import org.dromara.common.excel.annotation.ExcelNotation;
|
import org.dromara.common.excel.annotation.ExcelNotation;
|
||||||
import org.dromara.common.excel.annotation.ExcelRequired;
|
import org.dromara.common.excel.annotation.ExcelRequired;
|
||||||
import org.dromara.common.translation.annotation.Translation;
|
import org.dromara.common.translation.annotation.Translation;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package org.dromara.demo.domain.vo;
|
package org.dromara.demo.domain.vo;
|
||||||
|
|
||||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||||
import com.alibaba.excel.annotation.ExcelProperty;
|
import cn.idev.excel.annotation.ExcelProperty;
|
||||||
import org.dromara.demo.domain.TestTree;
|
import org.dromara.demo.domain.TestTree;
|
||||||
import io.github.linpeilie.annotations.AutoMapper;
|
import io.github.linpeilie.annotations.AutoMapper;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user