mirror of
https://github.com/dromara/RuoYi-Vue-Plus.git
synced 2025-11-25 02:16:46 +08:00
Compare commits
287 Commits
v5.3.1
...
4672d7de4d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4672d7de4d | ||
|
|
87ab6e1744 | ||
|
|
ae0a03728b | ||
|
|
6fc82a59f1 | ||
|
|
6c33fa48ec | ||
|
|
343d5d21d8 | ||
|
|
0ba909c52e | ||
|
|
808ce9c25a | ||
|
|
4351fc5239 | ||
|
|
a545f7fc44 | ||
|
|
acfcdf4d9a | ||
|
|
9683252783 | ||
|
|
49c00e162b | ||
|
|
076a0a44fa | ||
|
|
0d93589d99 | ||
|
|
54a8189e27 | ||
|
|
84f17011ad | ||
|
|
f47bd39644 | ||
|
|
89f9617ccb | ||
|
|
bf10a13088 | ||
|
|
f2e0361fb6 | ||
|
|
554152635d | ||
|
|
b379574637 | ||
|
|
6a556cc6ff | ||
|
|
a6950275ad | ||
|
|
58b1bf5c33 | ||
|
|
c85f693ca6 | ||
|
|
5f466fd0c4 | ||
|
|
127eaf936c | ||
|
|
fcd8556076 | ||
|
|
0512781513 | ||
|
|
2472359adb | ||
|
|
29d4bb4e59 | ||
|
|
cce95424ce | ||
|
|
8d7358e663 | ||
|
|
240f10ab45 | ||
|
|
48213bc9c9 | ||
|
|
3995d9699d | ||
|
|
ecd4e3eaf0 | ||
|
|
2e3a42c669 | ||
|
|
82997fc6cd | ||
|
|
0dce571270 | ||
|
|
9375578925 | ||
|
|
e19ccf5064 | ||
|
|
1cea7b72d7 | ||
|
|
5da9ddf5e3 | ||
|
|
93ee01c6b9 | ||
|
|
acd30fda3c | ||
|
|
3f62a76cc8 | ||
|
|
b0b4e573f6 | ||
|
|
de61899eed | ||
|
|
3a9bdb36f1 | ||
|
|
b815b8e574 | ||
|
|
45edee4e63 | ||
|
|
868bc492a2 | ||
|
|
90fef1bb17 | ||
|
|
d79b48ea99 | ||
|
|
f6993a1491 | ||
|
|
6b0b7382a6 | ||
|
|
c41add355f | ||
|
|
74e3d232f5 | ||
|
|
03fca40c7d | ||
|
|
b2ad257bd8 | ||
|
|
7e4f0d73f4 | ||
|
|
2ec802f17f | ||
|
|
e0ce662c28 | ||
|
|
3d9ed1b92f | ||
|
|
d4e6e70c43 | ||
|
|
2095a96e67 | ||
|
|
328b61b252 | ||
|
|
781463417c | ||
|
|
446a14b928 | ||
|
|
6c2518640b | ||
|
|
d8d138092f | ||
|
|
ec31b736c7 | ||
|
|
e7467b2c5c | ||
|
|
8050e2f1b1 | ||
|
|
7de4559b4a | ||
|
|
fc9c0d7657 | ||
|
|
ab3037dc4f | ||
|
|
8281b838b9 | ||
|
|
2bc7171abd | ||
|
|
4f99487d24 | ||
|
|
a7cddc8d40 | ||
|
|
f3c4c02d73 | ||
|
|
eb631360f4 | ||
|
|
a62bf04428 | ||
|
|
7147f81b42 | ||
|
|
c9098563ca | ||
|
|
d4a8c25eab | ||
|
|
0ddba506bf | ||
|
|
d02bea85cb | ||
|
|
d27c58bfe8 | ||
|
|
34bb51f5c0 | ||
|
|
64c37aaec6 | ||
|
|
3de036adde | ||
|
|
e0df8c15d8 | ||
|
|
176793e15b | ||
|
|
589ec1fdbc | ||
|
|
b421c8d017 | ||
|
|
e2200bac71 | ||
|
|
f8950d1e20 | ||
|
|
9dfe9f610d | ||
|
|
17610e8721 | ||
|
|
f29b787767 | ||
|
|
9775283a24 | ||
|
|
debc73d7d4 | ||
|
|
d501e82541 | ||
|
|
3002585e63 | ||
|
|
60aca2eef3 | ||
|
|
5baf342478 | ||
|
|
3f9919fbee | ||
|
|
682d8b0099 | ||
|
|
bbabffe191 | ||
|
|
6722f2eeed | ||
|
|
5a9728c868 | ||
|
|
eea96e87d9 | ||
|
|
e659740cb8 | ||
|
|
314909a536 | ||
|
|
8f5d60f543 | ||
|
|
1ad0d5387b | ||
|
|
770c3bd03e | ||
|
|
4b04a4bf09 | ||
|
|
1598447f6b | ||
|
|
a8a1db4463 | ||
|
|
4b47053dcf | ||
|
|
03054fc1e8 | ||
|
|
9dce540a09 | ||
|
|
534182deff | ||
|
|
4577c45110 | ||
|
|
0796791ec9 | ||
|
|
84baac0a4f | ||
|
|
74d257a610 | ||
|
|
cb8fa6ff9a | ||
|
|
c157012807 | ||
|
|
ffa01bdb3a | ||
|
|
3fa572f0a8 | ||
|
|
f868de1b7b | ||
|
|
fb785dc17f | ||
|
|
f3d475438f | ||
|
|
d7af327248 | ||
|
|
bd88e27c82 | ||
|
|
83b6addbba | ||
|
|
d51f3b9f4e | ||
|
|
9256432532 | ||
|
|
97d3a31aba | ||
|
|
d9cc85187a | ||
|
|
2ff2d89b2d | ||
|
|
be2e5059fd | ||
|
|
fad91f01ff | ||
|
|
8f95374cef | ||
|
|
7471fa7ee0 | ||
|
|
529f1e5dbb | ||
|
|
eff131a1ed | ||
|
|
60b0faa3c6 | ||
|
|
b2d694b90b | ||
|
|
fecc564099 | ||
|
|
297e920179 | ||
|
|
ea9379a52f | ||
|
|
0b0f2ee8ea | ||
|
|
6d2f104a43 | ||
|
|
2e50e30778 | ||
|
|
daf79683b3 | ||
|
|
5849ddc160 | ||
|
|
c88367939c | ||
|
|
a748d0d62c | ||
|
|
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 |
@@ -2,7 +2,7 @@
|
||||
<configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
||||
<deployment type="dockerfile">
|
||||
<settings>
|
||||
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.3.1" />
|
||||
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.4.1" />
|
||||
<option name="buildOnly" value="true" />
|
||||
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
|
||||
</settings>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
||||
<deployment type="dockerfile">
|
||||
<settings>
|
||||
<option name="imageTag" value="ruoyi/ruoyi-server:5.3.1" />
|
||||
<option name="imageTag" value="ruoyi/ruoyi-server:5.4.1" />
|
||||
<option name="buildOnly" value="true" />
|
||||
<option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
|
||||
</settings>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
||||
<deployment type="dockerfile">
|
||||
<settings>
|
||||
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.3.1" />
|
||||
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.4.1" />
|
||||
<option name="buildOnly" value="true" />
|
||||
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
|
||||
</settings>
|
||||
|
||||
18
README.md
18
README.md
@@ -7,10 +7,10 @@
|
||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus)
|
||||
[](https://github.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)
|
||||
<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)
|
||||
|
||||
> 官方前端项目地址: [plus-ui](https://gitee.com/JavaLionLi/plus-ui)<br>
|
||||
> 成员前端项目地址: 基于vben5 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)
|
||||
> 官方前端项目地址: [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)<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) 国内加速: [plus-doc.top](https://plus-doc.top)
|
||||
|
||||
## 赞助商
|
||||
|
||||
@@ -34,6 +36,9 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
|
||||
数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br>
|
||||
引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
|
||||
<font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br>
|
||||
Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br>
|
||||
aizuda flowlong 工作流 - https://gitee.com/aizuda/flowlong <br>
|
||||
|
||||
[如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group)
|
||||
|
||||
# 本框架与RuoYi的功能差异
|
||||
@@ -75,7 +80,7 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
|
||||
| 邮件 | 采用 mail-api 通用协议支持大部分邮件厂商 | 不支持 |
|
||||
| 接口文档 | 采用 SpringDoc、javadoc 无注解零入侵基于java注释<br/>只需把注释写好 无需再写一大堆的文档注解了 | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成 |
|
||||
| 校验框架 | 采用 Validation 支持注解与工具类校验 注解支持国际化 | 仅支持注解 且注解不支持国际化 |
|
||||
| Excel框架 | 采用 Alibaba EasyExcel 基于插件化<br/>框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等 | 基于 POI 手写实现 功能有限 复杂 扩展性差 |
|
||||
| Excel框架 | 采用 FastExcel(原Alibaba EasyExcel) 基于插件化<br/>框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等 | 基于 POI 手写实现 功能有限 复杂 扩展性差 |
|
||||
| 工作流支持 | 支持各种复杂审批 转办 委派 加减签 会签 或签 票签 等功能 | 无 |
|
||||
| 工具类框架 | 采用 Hutool、Lombok 上百种工具覆盖90%的使用需求 基于注解自动生成 get set 等简化框架大量代码 | 手写工具稳定性差易出问题 工具数量有限 代码臃肿需自己手写 get set 等 |
|
||||
| 监控框架 | 采用 SpringBoot-Admin 基于SpringBoot官方 actuator 探针机制<br/>实时监控服务状态 框架还为其扩展了在线日志查看监控 | 无 |
|
||||
@@ -113,7 +118,6 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
|
||||
| 系统接口 | 根据业务代码自动生成相关的api接口文档 | 支持 | 支持 |
|
||||
| 服务监控 | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等 | 支持 | 仅支持单机CPU、内存、磁盘监控 |
|
||||
| 缓存监控 | 对系统的缓存信息查询,命令统计等。 | 支持 | 支持 |
|
||||
| 在线构建器 | 拖动表单元素生成相应的HTML代码。 | 支持 | 支持 |
|
||||
| 使用案例 | 系统的一些功能案例 | 支持 | 不支持 |
|
||||
|
||||
## 参考文档
|
||||
|
||||
91
pom.xml
91
pom.xml
@@ -13,33 +13,32 @@
|
||||
<description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
|
||||
|
||||
<properties>
|
||||
<revision>5.3.1</revision>
|
||||
<spring-boot.version>3.4.4</spring-boot.version>
|
||||
<revision>5.4.1</revision>
|
||||
<spring-boot.version>3.5.4</spring-boot.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>17</java.version>
|
||||
<mybatis.version>3.5.16</mybatis.version>
|
||||
<springdoc.version>2.8.5</springdoc.version>
|
||||
<springdoc.version>2.8.9</springdoc.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>
|
||||
<satoken.version>1.40.0</satoken.version>
|
||||
<mybatis-plus.version>3.5.11</mybatis-plus.version>
|
||||
<satoken.version>1.44.0</satoken.version>
|
||||
<mybatis-plus.version>3.5.12</mybatis-plus.version>
|
||||
<p6spy.version>3.9.1</p6spy.version>
|
||||
<hutool.version>5.8.35</hutool.version>
|
||||
<spring-boot-admin.version>3.4.5</spring-boot-admin.version>
|
||||
<redisson.version>3.45.1</redisson.version>
|
||||
<hutool.version>5.8.38</hutool.version>
|
||||
<spring-boot-admin.version>3.5.1</spring-boot-admin.version>
|
||||
<redisson.version>3.50.0</redisson.version>
|
||||
<lock4j.version>2.2.7</lock4j.version>
|
||||
<dynamic-ds.version>4.3.1</dynamic-ds.version>
|
||||
<snailjob.version>1.4.0</snailjob.version>
|
||||
<mapstruct-plus.version>1.4.6</mapstruct-plus.version>
|
||||
<snailjob.version>1.7.0</snailjob.version>
|
||||
<mapstruct-plus.version>1.4.8</mapstruct-plus.version>
|
||||
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
|
||||
<lombok.version>1.18.36</lombok.version>
|
||||
<bouncycastle.version>1.76</bouncycastle.version>
|
||||
<lombok.version>1.18.38</lombok.version>
|
||||
<bouncycastle.version>1.80</bouncycastle.version>
|
||||
<justauth.version>1.16.7</justauth.version>
|
||||
<!-- 离线IP地址定位库 -->
|
||||
<ip2region.version>2.7.0</ip2region.version>
|
||||
|
||||
<!-- OSS 配置 -->
|
||||
<aws.sdk.version>2.28.22</aws.sdk.version>
|
||||
<!-- SMS 配置 -->
|
||||
@@ -47,15 +46,15 @@
|
||||
<!-- 限制框架中的fastjson版本 -->
|
||||
<fastjson.version>1.2.83</fastjson.version>
|
||||
<!-- 面向运行时的D-ORM依赖 -->
|
||||
<anyline.version>8.7.2-20250101</anyline.version>
|
||||
<!--工作流配置-->
|
||||
<warm-flow.version>1.6.8</warm-flow.version>
|
||||
<anyline.version>8.7.2-20250603</anyline.version>
|
||||
<!-- 工作流配置 -->
|
||||
<warm-flow.version>1.8.0-m1</warm-flow.version>
|
||||
|
||||
<!-- 插件版本 -->
|
||||
<maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
|
||||
<maven-war-plugin.version>3.2.2</maven-war-plugin.version>
|
||||
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
|
||||
<maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
|
||||
<maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
|
||||
<maven-war-plugin.version>3.4.0</maven-war-plugin.version>
|
||||
<maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
|
||||
<maven-surefire-plugin.version>3.5.3</maven-surefire-plugin.version>
|
||||
<flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
|
||||
<!-- 打包默认跳过测试 -->
|
||||
<skipTests>true</skipTests>
|
||||
@@ -119,25 +118,6 @@
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Warm-Flow国产工作流引擎, 在线文档:http://warm-flow.cn/ -->
|
||||
<dependency>
|
||||
<groupId>org.dromara.warm</groupId>
|
||||
<artifactId>warm-flow-mybatis-plus-sb3-starter</artifactId>
|
||||
<version>${warm-flow.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.dromara.warm</groupId>
|
||||
<artifactId>warm-flow-plugin-ui-sb-web</artifactId>
|
||||
<version>${warm-flow.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JustAuth 的依赖配置-->
|
||||
<dependency>
|
||||
<groupId>me.zhyd.oauth</groupId>
|
||||
<artifactId>JustAuth</artifactId>
|
||||
<version>${justauth.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- common 的依赖配置-->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
@@ -166,9 +146,9 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>easyexcel</artifactId>
|
||||
<version>${easyexcel.version}</version>
|
||||
<groupId>cn.idev.excel</groupId>
|
||||
<artifactId>fastexcel</artifactId>
|
||||
<version>${fastexcel.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- velocity代码生成使用模板 -->
|
||||
@@ -314,6 +294,25 @@
|
||||
<version>${mapstruct-plus.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Warm-Flow国产工作流引擎, 在线文档:http://warm-flow.cn/ -->
|
||||
<dependency>
|
||||
<groupId>org.dromara.warm</groupId>
|
||||
<artifactId>warm-flow-mybatis-plus-sb3-starter</artifactId>
|
||||
<version>${warm-flow.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.dromara.warm</groupId>
|
||||
<artifactId>warm-flow-plugin-ui-sb-web</artifactId>
|
||||
<version>${warm-flow.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JustAuth 的依赖配置-->
|
||||
<dependency>
|
||||
<groupId>me.zhyd.oauth</groupId>
|
||||
<artifactId>JustAuth</artifactId>
|
||||
<version>${justauth.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 离线IP地址定位库 ip2region -->
|
||||
<dependency>
|
||||
<groupId>org.lionsoul</groupId>
|
||||
@@ -321,12 +320,6 @@
|
||||
<version>${ip2region.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
|
||||
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
|
||||
#FROM bellsoft/liberica-openjdk-debian:21.0.5-cds
|
||||
FROM bellsoft/liberica-openjdk-rocky:17.0.16-cds
|
||||
#FROM bellsoft/liberica-openjdk-rocky:21.0.8-cds
|
||||
#FROM findepi/graalvm:java17-native
|
||||
|
||||
LABEL maintainer="Lion Li"
|
||||
@@ -11,17 +11,18 @@ RUN mkdir -p /ruoyi/server/logs \
|
||||
|
||||
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}
|
||||
# 暴露 snail job 客户端端口 用于定时任务调度中心通信
|
||||
EXPOSE ${SNAIL_PORT}
|
||||
|
||||
ADD ./target/ruoyi-admin.jar ./app.jar
|
||||
# 工作流字体文件
|
||||
ADD ./zhFonts/ /usr/share/fonts/zhFonts/
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
|
||||
-Dsnail-job.port=${SNAIL_PORT} \
|
||||
# 应用名称 如果想区分集群节点监控 改成不同的名称即可
|
||||
#-Dskywalking.agent.service_name=ruoyi-server \
|
||||
#-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \
|
||||
|
||||
@@ -21,6 +21,8 @@ import org.dromara.common.core.domain.model.SocialLoginBody;
|
||||
import org.dromara.common.core.utils.*;
|
||||
import org.dromara.common.encrypt.annotation.ApiEncrypt;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.common.ratelimiter.annotation.RateLimiter;
|
||||
import org.dromara.common.ratelimiter.enums.LimitType;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
|
||||
import org.dromara.common.social.config.properties.SocialProperties;
|
||||
@@ -198,6 +200,7 @@ public class AuthController {
|
||||
*
|
||||
* @return 租户列表
|
||||
*/
|
||||
@RateLimiter(time = 60, count = 20, limitType = LimitType.IP)
|
||||
@GetMapping("/tenant/list")
|
||||
public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
|
||||
// 返回对象
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package org.dromara.web.listener;
|
||||
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.listener.SaTokenListener;
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.http.useragent.UserAgent;
|
||||
import cn.hutool.http.useragent.UserAgentUtil;
|
||||
@@ -35,14 +34,13 @@ import java.time.Duration;
|
||||
@Slf4j
|
||||
public class UserActionListener implements SaTokenListener {
|
||||
|
||||
private final SaTokenConfig tokenConfig;
|
||||
private final SysLoginService loginService;
|
||||
|
||||
/**
|
||||
* 每次登录时触发
|
||||
*/
|
||||
@Override
|
||||
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
|
||||
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter) {
|
||||
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
|
||||
String ip = ServletUtils.getClientIP();
|
||||
UserOnlineDTO dto = new UserOnlineDTO();
|
||||
@@ -52,17 +50,17 @@ public class UserActionListener implements SaTokenListener {
|
||||
dto.setOs(userAgent.getOs().getName());
|
||||
dto.setLoginTime(System.currentTimeMillis());
|
||||
dto.setTokenId(tokenValue);
|
||||
String username = (String) loginModel.getExtra(LoginHelper.USER_NAME_KEY);
|
||||
String tenantId = (String) loginModel.getExtra(LoginHelper.TENANT_KEY);
|
||||
String username = (String) loginParameter.getExtra(LoginHelper.USER_NAME_KEY);
|
||||
String tenantId = (String) loginParameter.getExtra(LoginHelper.TENANT_KEY);
|
||||
dto.setUserName(username);
|
||||
dto.setClientKey((String) loginModel.getExtra(LoginHelper.CLIENT_KEY));
|
||||
dto.setDeviceType(loginModel.getDevice());
|
||||
dto.setDeptName((String) loginModel.getExtra(LoginHelper.DEPT_NAME_KEY));
|
||||
dto.setClientKey((String) loginParameter.getExtra(LoginHelper.CLIENT_KEY));
|
||||
dto.setDeviceType(loginParameter.getDeviceType());
|
||||
dto.setDeptName((String) loginParameter.getExtra(LoginHelper.DEPT_NAME_KEY));
|
||||
TenantHelper.dynamic(tenantId, () -> {
|
||||
if(tokenConfig.getTimeout() == -1) {
|
||||
if(loginParameter.getTimeout() == -1) {
|
||||
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
|
||||
} 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());
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -160,6 +158,6 @@ public class UserActionListener implements SaTokenListener {
|
||||
* 每次Token续期时触发
|
||||
*/
|
||||
@Override
|
||||
public void doRenewTimeout(String tokenValue, Object loginId, long timeout) {
|
||||
public void doRenewTimeout(String loginType, Object loginId, String tokenValue, long timeout) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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 lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.constant.Constants;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.dromara.web.service.impl;
|
||||
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -58,8 +58,8 @@ public class EmailAuthStrategy implements IAuthStrategy {
|
||||
});
|
||||
loginUser.setClientKey(client.getClientKey());
|
||||
loginUser.setDeviceType(client.getDeviceType());
|
||||
SaLoginModel model = new SaLoginModel();
|
||||
model.setDevice(client.getDeviceType());
|
||||
SaLoginParameter model = new SaLoginParameter();
|
||||
model.setDeviceType(client.getDeviceType());
|
||||
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
||||
// 例如: 后台用户30分钟过期 app用户1天过期
|
||||
model.setTimeout(client.getTimeout());
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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.parameter.SaLoginParameter;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.crypto.digest.BCrypt;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -70,8 +70,8 @@ public class PasswordAuthStrategy implements IAuthStrategy {
|
||||
});
|
||||
loginUser.setClientKey(client.getClientKey());
|
||||
loginUser.setDeviceType(client.getDeviceType());
|
||||
SaLoginModel model = new SaLoginModel();
|
||||
model.setDevice(client.getDeviceType());
|
||||
SaLoginParameter model = new SaLoginParameter();
|
||||
model.setDeviceType(client.getDeviceType());
|
||||
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
||||
// 例如: 后台用户30分钟过期 app用户1天过期
|
||||
model.setTimeout(client.getTimeout());
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.dromara.web.service.impl;
|
||||
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -58,8 +58,8 @@ public class SmsAuthStrategy implements IAuthStrategy {
|
||||
});
|
||||
loginUser.setClientKey(client.getClientKey());
|
||||
loginUser.setDeviceType(client.getDeviceType());
|
||||
SaLoginModel model = new SaLoginModel();
|
||||
model.setDevice(client.getDeviceType());
|
||||
SaLoginParameter model = new SaLoginParameter();
|
||||
model.setDeviceType(client.getDeviceType());
|
||||
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
||||
// 例如: 后台用户30分钟过期 app用户1天过期
|
||||
model.setTimeout(client.getTimeout());
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
package org.dromara.web.service.impl;
|
||||
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.http.Method;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.zhyd.oauth.model.AuthResponse;
|
||||
@@ -68,15 +65,6 @@ public class SocialAuthStrategy implements IAuthStrategy {
|
||||
throw new ServiceException(response.getMsg());
|
||||
}
|
||||
AuthUser authUserData = response.getData();
|
||||
if ("GITEE".equals(authUserData.getSource())) {
|
||||
// 如用户使用 gitee 登录顺手 star 给作者一点支持 拒绝白嫖
|
||||
HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Vue-Plus")
|
||||
.formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
|
||||
.executeAsync();
|
||||
HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Cloud-Plus")
|
||||
.formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
|
||||
.executeAsync();
|
||||
}
|
||||
|
||||
List<SysSocialVo> list = sysSocialService.selectByAuthId(authUserData.getSource() + authUserData.getUuid());
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
@@ -99,8 +87,8 @@ public class SocialAuthStrategy implements IAuthStrategy {
|
||||
});
|
||||
loginUser.setClientKey(client.getClientKey());
|
||||
loginUser.setDeviceType(client.getDeviceType());
|
||||
SaLoginModel model = new SaLoginModel();
|
||||
model.setDevice(client.getDeviceType());
|
||||
SaLoginParameter model = new SaLoginParameter();
|
||||
model.setDeviceType(client.getDeviceType());
|
||||
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
||||
// 例如: 后台用户30分钟过期 app用户1天过期
|
||||
model.setTimeout(client.getTimeout());
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.dromara.web.service.impl;
|
||||
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -76,8 +76,8 @@ public class XcxAuthStrategy implements IAuthStrategy {
|
||||
loginUser.setDeviceType(client.getDeviceType());
|
||||
loginUser.setOpenid(openid);
|
||||
|
||||
SaLoginModel model = new SaLoginModel();
|
||||
model.setDevice(client.getDeviceType());
|
||||
SaLoginParameter model = new SaLoginParameter();
|
||||
model.setDeviceType(client.getDeviceType());
|
||||
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
||||
// 例如: 后台用户30分钟过期 app用户1天过期
|
||||
model.setTimeout(client.getTimeout());
|
||||
|
||||
@@ -27,8 +27,6 @@ snail-job:
|
||||
port: 2${server.port}
|
||||
# 客户端ip指定
|
||||
host:
|
||||
# RPC类型: netty, grpc
|
||||
rpc-type: grpc
|
||||
|
||||
--- # 数据源配置
|
||||
spring:
|
||||
@@ -263,3 +261,10 @@ justauth:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
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
|
||||
|
||||
@@ -30,8 +30,6 @@ snail-job:
|
||||
port: 2${server.port}
|
||||
# 客户端ip指定
|
||||
host:
|
||||
# RPC类型: netty, grpc
|
||||
rpc-type: grpc
|
||||
|
||||
--- # 数据源配置
|
||||
spring:
|
||||
@@ -265,3 +263,10 @@ justauth:
|
||||
client-id: 10**********6
|
||||
client-secret: 1f7d08**********5b7**********29e
|
||||
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
|
||||
|
||||
@@ -21,8 +21,8 @@ server:
|
||||
worker: 256
|
||||
|
||||
captcha:
|
||||
# 是否启用验证码校验
|
||||
enable: true
|
||||
# 页面 <参数设置> 可开启关闭 验证码校验
|
||||
# 验证码类型 math 数组计算 char 字符验证
|
||||
type: MATH
|
||||
# line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
|
||||
@@ -57,6 +57,11 @@ spring:
|
||||
# 开启虚拟线程 仅jdk21可用
|
||||
virtual:
|
||||
enabled: false
|
||||
task:
|
||||
execution:
|
||||
# 从 springboot 3.5 开始 spring自带线程池
|
||||
# 不再需要 AsyncConfig与ThreadPoolConfig 可直接注入线程池使用
|
||||
thread-name-prefix: async-
|
||||
# 资源信息
|
||||
messages:
|
||||
# 国际化资源文件路径
|
||||
@@ -110,7 +115,7 @@ security:
|
||||
- /error
|
||||
- /*/api-docs
|
||||
- /*/api-docs/**
|
||||
- /warm-flow-ui/token-name
|
||||
- /warm-flow-ui/config
|
||||
|
||||
# 多租户配置
|
||||
tenant:
|
||||
@@ -127,6 +132,7 @@ tenant:
|
||||
- sys_user_role
|
||||
- sys_client
|
||||
- sys_oss_config
|
||||
- flow_spel
|
||||
|
||||
# MyBatisPlus配置
|
||||
# https://baomidou.com/config/
|
||||
@@ -177,28 +183,18 @@ springdoc:
|
||||
api-docs:
|
||||
# 是否开启接口文档
|
||||
enabled: true
|
||||
# swagger-ui:
|
||||
# # 持久化认证数据
|
||||
# persistAuthorization: true
|
||||
info:
|
||||
# 标题
|
||||
title: '标题:RuoYi-Vue-Plus多租户管理系统_接口文档'
|
||||
# 描述
|
||||
description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
|
||||
# 版本
|
||||
version: '版本号: ${ruoyi.version}'
|
||||
version: '版本号: ${project.version}'
|
||||
# 作者信息
|
||||
contact:
|
||||
name: Lion Li
|
||||
email: crazylionli@163.com
|
||||
url: https://gitee.com/dromara/RuoYi-Vue-Plus
|
||||
components:
|
||||
# 鉴权方式配置
|
||||
security-schemes:
|
||||
apiKey:
|
||||
type: APIKEY
|
||||
in: HEADER
|
||||
name: ${sa-token.token-name}
|
||||
#这里定义了两个分组,可定义多个,也可以不定义
|
||||
group-configs:
|
||||
- group: 1.演示模块
|
||||
@@ -216,20 +212,10 @@ springdoc:
|
||||
xss:
|
||||
# 过滤开关
|
||||
enabled: true
|
||||
# 排除链接(多个用逗号分隔)
|
||||
# 排除链接
|
||||
excludeUrls:
|
||||
- /system/notice
|
||||
|
||||
# 全局线程池相关配置
|
||||
# 如使用JDK21请直接使用虚拟线程 不要开启此配置
|
||||
thread-pool:
|
||||
# 是否开启线程池
|
||||
enabled: false
|
||||
# 队列最大长度
|
||||
queueCapacity: 128
|
||||
# 线程池维护线程所允许的空闲时间
|
||||
keepAliveSeconds: 300
|
||||
|
||||
--- # 分布式锁 lock4j 全局配置
|
||||
lock4j:
|
||||
# 获取分布式锁超时时间,默认为 3000 毫秒
|
||||
@@ -269,6 +255,8 @@ warm-flow:
|
||||
enabled: true
|
||||
# 是否开启设计器ui
|
||||
ui: true
|
||||
# 是否显示流程图顶部文字
|
||||
top-text-show: true
|
||||
# 默认Authorization,如果有多个token,用逗号分隔
|
||||
token-name: ${sa-token.token-name},clientid
|
||||
# 流程状态对应的三元色
|
||||
|
||||
@@ -17,6 +17,7 @@ user.username.length.valid=账户长度必须在{min}到{max}个字符之间
|
||||
user.password.not.blank=用户密码不能为空
|
||||
user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
|
||||
user.password.not.valid=* 5-50个字符
|
||||
user.password.format.valid=密码必须包含大写字母、小写字母、数字和特殊字符
|
||||
user.email.not.valid=邮箱格式错误
|
||||
user.email.not.blank=邮箱不能为空
|
||||
user.phonenumber.not.blank=用户手机号不能为空
|
||||
|
||||
@@ -17,6 +17,7 @@ user.username.length.valid=Account length must be between {min} and {max} charac
|
||||
user.password.not.blank=Password cannot be empty
|
||||
user.password.length.valid=Password length must be between {min} and {max} characters
|
||||
user.password.not.valid=* 5-50 characters
|
||||
user.password.format.valid=Password must contain uppercase, lowercase, digit, and special character
|
||||
user.email.not.valid=Mailbox format error
|
||||
user.email.not.blank=Mailbox cannot be blank
|
||||
user.phonenumber.not.blank=Phone number cannot be blank
|
||||
|
||||
@@ -17,6 +17,7 @@ user.username.length.valid=账户长度必须在{min}到{max}个字符之间
|
||||
user.password.not.blank=用户密码不能为空
|
||||
user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
|
||||
user.password.not.valid=* 5-50个字符
|
||||
user.password.format.valid=密码必须包含大写字母、小写字母、数字和特殊字符
|
||||
user.email.not.valid=邮箱格式错误
|
||||
user.email.not.blank=邮箱不能为空
|
||||
user.phonenumber.not.blank=用户手机号不能为空
|
||||
|
||||
@@ -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>
|
||||
|
||||
<properties>
|
||||
<revision>5.3.1</revision>
|
||||
<revision>5.4.1</revision>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
package org.dromara.common.core.config;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.core.task.VirtualThreadTaskExecutor;
|
||||
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* 异步配置
|
||||
* <p>
|
||||
* 如果未使用虚拟线程则生效
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@AutoConfiguration
|
||||
public class AsyncConfig implements AsyncConfigurer {
|
||||
|
||||
/**
|
||||
* 自定义 @Async 注解使用系统线程池
|
||||
*/
|
||||
@Override
|
||||
public Executor getAsyncExecutor() {
|
||||
if(SpringUtils.isVirtual()) {
|
||||
return new VirtualThreadTaskExecutor("async-");
|
||||
}
|
||||
return SpringUtils.getBean("scheduledExecutorService");
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步执行异常处理
|
||||
*/
|
||||
@Override
|
||||
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||
return (throwable, method, objects) -> {
|
||||
throwable.printStackTrace();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Exception message - ").append(throwable.getMessage())
|
||||
.append(", Method name - ").append(method.getName());
|
||||
if (ArrayUtil.isNotEmpty(objects)) {
|
||||
sb.append(", Parameter value - ").append(Arrays.toString(objects));
|
||||
}
|
||||
throw new ServiceException(sb.toString());
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,11 +7,9 @@ import org.dromara.common.core.config.properties.ThreadPoolProperties;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.Threads;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.task.VirtualThreadTaskExecutor;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
@@ -34,18 +32,6 @@ public class ThreadPoolConfig {
|
||||
|
||||
private ScheduledExecutorService scheduledExecutorService;
|
||||
|
||||
@Bean(name = "threadPoolTaskExecutor")
|
||||
@ConditionalOnProperty(prefix = "thread-pool", name = "enabled", havingValue = "true")
|
||||
public ThreadPoolTaskExecutor threadPoolTaskExecutor(ThreadPoolProperties threadPoolProperties) {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(core);
|
||||
executor.setMaxPoolSize(core * 2);
|
||||
executor.setQueueCapacity(threadPoolProperties.getQueueCapacity());
|
||||
executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行周期性或定时任务
|
||||
*/
|
||||
|
||||
@@ -3,13 +3,14 @@ package org.dromara.common.core.constant;
|
||||
/**
|
||||
* 缓存组名称常量
|
||||
* <p>
|
||||
* key 格式为 cacheNames#ttl#maxIdleTime#maxSize
|
||||
* key 格式为 cacheNames#ttl#maxIdleTime#maxSize#local
|
||||
* <p>
|
||||
* ttl 过期时间 如果设置为0则不过期 默认为0
|
||||
* maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
|
||||
* maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
|
||||
* local 默认开启本地缓存为1 关闭本地缓存为0
|
||||
* <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
|
||||
*/
|
||||
|
||||
@@ -77,4 +77,9 @@ public interface SystemConstants {
|
||||
*/
|
||||
String ROOT_DEPT_ANCESTORS = "0";
|
||||
|
||||
/**
|
||||
* 默认部门 ID
|
||||
*/
|
||||
Long DEFAULT_DEPT_ID = 100L;
|
||||
|
||||
}
|
||||
|
||||
@@ -52,17 +52,17 @@ public class TaskAssigneeDTO implements Serializable {
|
||||
*/
|
||||
public static <T> List<TaskHandler> convertToHandlerList(
|
||||
List<T> sourceList,
|
||||
Function<T, Long> storageId,
|
||||
Function<T, String> storageId,
|
||||
Function<T, String> handlerCode,
|
||||
Function<T, String> handlerName,
|
||||
Function<T, Long> groupName,
|
||||
Function<T, String> groupName,
|
||||
Function<T, Date> createTimeMapper) {
|
||||
return sourceList.stream()
|
||||
.map(item -> new TaskHandler(
|
||||
String.valueOf(storageId.apply(item)),
|
||||
storageId.apply(item),
|
||||
handlerCode.apply(item),
|
||||
handlerName.apply(item),
|
||||
groupName != null ? String.valueOf(groupName.apply(item)) : null,
|
||||
groupName.apply(item),
|
||||
createTimeMapper.apply(item)
|
||||
)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package org.dromara.common.core.domain.event;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 流程创建任务监听
|
||||
*
|
||||
* @author may
|
||||
*/
|
||||
@Data
|
||||
public class ProcessCreateTaskEvent implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 流程定义编码
|
||||
*/
|
||||
private String flowCode;
|
||||
|
||||
/**
|
||||
* 审批节点编码
|
||||
*/
|
||||
private String nodeCode;
|
||||
|
||||
/**
|
||||
* 任务id
|
||||
*/
|
||||
private Long taskId;
|
||||
|
||||
/**
|
||||
* 业务id
|
||||
*/
|
||||
private String businessId;
|
||||
|
||||
}
|
||||
@@ -27,13 +27,33 @@ public class ProcessEvent implements Serializable {
|
||||
*/
|
||||
private String flowCode;
|
||||
|
||||
/**
|
||||
* 实例id
|
||||
*/
|
||||
private Long instanceId;
|
||||
|
||||
/**
|
||||
* 业务id
|
||||
*/
|
||||
private String businessId;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
* 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
|
||||
*/
|
||||
private Integer nodeType;
|
||||
|
||||
/**
|
||||
* 流程节点编码
|
||||
*/
|
||||
private String nodeCode;
|
||||
|
||||
/**
|
||||
* 流程节点名称
|
||||
*/
|
||||
private String nodeName;
|
||||
|
||||
/**
|
||||
* 流程状态
|
||||
*/
|
||||
private String status;
|
||||
|
||||
@@ -45,6 +65,6 @@ public class ProcessEvent implements Serializable {
|
||||
/**
|
||||
* 当为true时为申请人节点办理
|
||||
*/
|
||||
private boolean submit;
|
||||
private Boolean submit;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.dromara.common.core.domain.event;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 流程任务监听
|
||||
*
|
||||
* @author may
|
||||
*/
|
||||
@Data
|
||||
public class ProcessTaskEvent implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 流程定义编码
|
||||
*/
|
||||
private String flowCode;
|
||||
|
||||
/**
|
||||
* 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
|
||||
*/
|
||||
private Integer nodeType;
|
||||
|
||||
/**
|
||||
* 流程节点编码
|
||||
*/
|
||||
private String nodeCode;
|
||||
|
||||
/**
|
||||
* 流程节点名称
|
||||
*/
|
||||
private String nodeName;
|
||||
|
||||
/**
|
||||
* 任务id
|
||||
*/
|
||||
private Long taskId;
|
||||
|
||||
/**
|
||||
* 实例id
|
||||
*/
|
||||
private Long instanceId;
|
||||
|
||||
/**
|
||||
* 业务id
|
||||
*/
|
||||
private String businessId;
|
||||
|
||||
/**
|
||||
* 流程状态
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 办理参数
|
||||
*/
|
||||
private Map<String, Object> params;
|
||||
|
||||
}
|
||||
@@ -26,6 +26,7 @@ public class PasswordLoginBody extends LoginBody {
|
||||
*/
|
||||
@NotBlank(message = "{user.password.not.blank}")
|
||||
@Length(min = 5, max = 30, message = "{user.password.length.valid}")
|
||||
// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}")
|
||||
private String password;
|
||||
|
||||
}
|
||||
|
||||
@@ -18,16 +18,20 @@ public class RegisterBody extends LoginBody {
|
||||
* 用户名
|
||||
*/
|
||||
@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;
|
||||
|
||||
/**
|
||||
* 用户密码
|
||||
*/
|
||||
@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}")
|
||||
// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}")
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 用户类型
|
||||
*/
|
||||
private String userType;
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 设备类型
|
||||
* 针对一套 用户体系
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@@ -29,9 +28,12 @@ public enum DeviceType {
|
||||
XCX("xcx"),
|
||||
|
||||
/**
|
||||
* social第三方端
|
||||
* 第三方社交登录平台
|
||||
*/
|
||||
SOCIAL("social");
|
||||
|
||||
/**
|
||||
* 设备标识
|
||||
*/
|
||||
private final String device;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package org.dromara.common.core.enums;
|
||||
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* 设备类型
|
||||
* 针对多套 用户体系
|
||||
* 用户类型
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@@ -15,15 +14,18 @@ import lombok.Getter;
|
||||
public enum UserType {
|
||||
|
||||
/**
|
||||
* pc端
|
||||
* 后台系统用户
|
||||
*/
|
||||
SYS_USER("sys_user"),
|
||||
|
||||
/**
|
||||
* app端
|
||||
* 移动客户端用户
|
||||
*/
|
||||
APP_USER("app_user");
|
||||
|
||||
/**
|
||||
* 用户类型标识(用于 token、权限识别等)
|
||||
*/
|
||||
private final String userType;
|
||||
|
||||
public static UserType getUserType(String str) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.dromara.common.core.service;
|
||||
import org.dromara.common.core.domain.dto.DeptDTO;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 通用 部门服务
|
||||
@@ -34,4 +35,12 @@ public interface DeptService {
|
||||
*/
|
||||
List<DeptDTO> selectDeptsByList();
|
||||
|
||||
/**
|
||||
* 根据部门 ID 列表查询部门名称映射关系
|
||||
*
|
||||
* @param deptIds 部门 ID 列表
|
||||
* @return Map,其中 key 为部门 ID,value 为对应的部门名称
|
||||
*/
|
||||
Map<Long, String> selectDeptNamesByIds(List<Long> deptIds);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
package org.dromara.common.core.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 通用 岗位服务
|
||||
*
|
||||
@@ -7,4 +10,12 @@ package org.dromara.common.core.service;
|
||||
*/
|
||||
public interface PostService {
|
||||
|
||||
/**
|
||||
* 根据岗位 ID 列表查询岗位名称映射关系
|
||||
*
|
||||
* @param postIds 岗位 ID 列表
|
||||
* @return Map,其中 key 为岗位 ID,value 为对应的岗位名称
|
||||
*/
|
||||
Map<Long, String> selectPostNamesByIds(List<Long> postIds);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package org.dromara.common.core.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 通用 角色服务
|
||||
*
|
||||
@@ -7,4 +10,12 @@ package org.dromara.common.core.service;
|
||||
*/
|
||||
public interface RoleService {
|
||||
|
||||
/**
|
||||
* 根据角色 ID 列表查询角色名称映射关系
|
||||
*
|
||||
* @param roleIds 角色 ID 列表
|
||||
* @return Map,其中 key 为角色 ID,value 为对应的角色名称
|
||||
*/
|
||||
Map<Long, String> selectRoleNamesByIds(List<Long> roleIds);
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.dromara.common.core.service;
|
||||
import org.dromara.common.core.domain.dto.UserDTO;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 通用 用户服务
|
||||
@@ -91,4 +92,12 @@ public interface UserService {
|
||||
*/
|
||||
List<UserDTO> selectUsersByPostIds(List<Long> postIds);
|
||||
|
||||
/**
|
||||
* 根据用户 ID 列表查询用户名称映射关系
|
||||
*
|
||||
* @param userIds 用户 ID 列表
|
||||
* @return Map,其中 key 为用户 ID,value 为对应的用户名称
|
||||
*/
|
||||
Map<Long, String> selectUserNamesByIds(List<Long> userIds);
|
||||
|
||||
}
|
||||
|
||||
@@ -78,9 +78,28 @@ public interface WorkflowService {
|
||||
|
||||
/**
|
||||
* 办理任务
|
||||
* 系统后台发起审批 无用户信息 需要忽略权限
|
||||
* completeTask.getVariables().put("ignore", true);
|
||||
*
|
||||
* @param completeTask 参数
|
||||
* @return 结果
|
||||
*/
|
||||
boolean completeTask(CompleteTaskDTO completeTask);
|
||||
|
||||
/**
|
||||
* 办理任务
|
||||
*
|
||||
* @param taskId 任务ID
|
||||
* @param message 办理意见
|
||||
* @return 结果
|
||||
*/
|
||||
boolean completeTask(Long taskId, String message);
|
||||
|
||||
/**
|
||||
* 启动流程并办理第一个任务
|
||||
*
|
||||
* @param startProcess 参数
|
||||
* @return 结果
|
||||
*/
|
||||
boolean startCompleteTask(StartProcessDTO startProcess);
|
||||
}
|
||||
|
||||
@@ -175,14 +175,27 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两个日期之间的天数差(以毫秒为单位)
|
||||
* 计算两个时间之间的时间差,并以指定单位返回(绝对值)
|
||||
*
|
||||
* @param date1 第一个日期
|
||||
* @param date2 第二个日期
|
||||
* @return 两个日期之间的天数差的绝对值
|
||||
* @param start 起始时间
|
||||
* @param end 结束时间
|
||||
* @param unit 所需返回的时间单位(DAYS、HOURS、MINUTES、SECONDS、MILLISECONDS、MICROSECONDS、NANOSECONDS)
|
||||
* @return 时间差的绝对值,以指定单位表示
|
||||
*/
|
||||
public static int differentDaysByMillisecond(Date date1, Date date2) {
|
||||
return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
|
||||
public static long difference(Date start, Date end, TimeUnit unit) {
|
||||
// 计算时间差,单位为毫秒,取绝对值避免负数
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -6,6 +6,7 @@ import cn.hutool.core.lang.Validator;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -259,13 +260,13 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
|
||||
if (s != null) {
|
||||
final int len = s.length();
|
||||
if (s.length() <= size) {
|
||||
sb.append(String.valueOf(c).repeat(size - len));
|
||||
sb.append(Convert.toStr(c).repeat(size - len));
|
||||
sb.append(s);
|
||||
} else {
|
||||
return s.substring(len - size, len);
|
||||
}
|
||||
} else {
|
||||
sb.append(String.valueOf(c).repeat(Math.max(0, size)));
|
||||
sb.append(Convert.toStr(c).repeat(Math.max(0, size)));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -339,4 +340,26 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import lombok.NoArgsConstructor;
|
||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -60,6 +62,31 @@ public class TreeBuildUtils extends TreeUtil {
|
||||
return TreeUtil.build(list, parentId, DEFAULT_CONFIG, nodeParser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建多根节点的树结构(支持多个顶级节点)
|
||||
*
|
||||
* @param list 原始数据列表
|
||||
* @param getId 获取节点 ID 的方法引用,例如:node -> node.getId()
|
||||
* @param getParentId 获取节点父级 ID 的方法引用,例如:node -> node.getParentId()
|
||||
* @param parser 树节点属性映射器,用于将原始节点 T 转为 Tree 节点
|
||||
* @param <T> 原始数据类型(如实体类、DTO 等)
|
||||
* @param <K> 节点 ID 类型(如 Long、String)
|
||||
* @return 构建完成的树形结构(可能包含多个顶级根节点)
|
||||
*/
|
||||
public static <T, K> List<Tree<K>> buildMultiRoot(List<T> list, Function<T, K> getId, Function<T, K> getParentId, NodeParser<T, K> parser) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return CollUtil.newArrayList();
|
||||
}
|
||||
|
||||
Set<K> rootParentIds = StreamUtils.toSet(list, getParentId);
|
||||
rootParentIds.removeAll(StreamUtils.toSet(list, getId));
|
||||
|
||||
// 构建每一个根 parentId 下的树,并合并成最终结果列表
|
||||
return rootParentIds.stream()
|
||||
.flatMap(rootParentId -> TreeUtil.build(list, rootParentId, parser).stream())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取节点列表中所有节点的叶子节点
|
||||
*
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.dromara.common.core.utils.ip;
|
||||
|
||||
import cn.hutool.core.net.NetUtil;
|
||||
import cn.hutool.http.HtmlUtil;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.utils.NetUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
|
||||
/**
|
||||
* 获取地址类
|
||||
@@ -16,18 +16,55 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class AddressUtils {
|
||||
|
||||
// 未知IP
|
||||
public static final String UNKNOWN_IP = "XX XX";
|
||||
// 内网地址
|
||||
public static final String LOCAL_ADDRESS = "内网IP";
|
||||
// 未知地址
|
||||
public static final String UNKNOWN = "XX XX";
|
||||
public static final String UNKNOWN_ADDRESS = "未知";
|
||||
|
||||
public static String getRealAddressByIP(String ip) {
|
||||
if (StringUtils.isBlank(ip)) {
|
||||
return UNKNOWN;
|
||||
// 处理空串并过滤HTML标签
|
||||
ip = HtmlUtil.cleanHtmlTag(StringUtils.blankToDefault(ip,""));
|
||||
// 判断是否为IPv4
|
||||
if (NetUtils.isIPv4(ip)) {
|
||||
return resolverIPv4Region(ip);
|
||||
}
|
||||
// 判断是否为IPv6
|
||||
if (NetUtils.isIPv6(ip)) {
|
||||
return resolverIPv6Region(ip);
|
||||
}
|
||||
// 如果不是IPv4或IPv6,则返回未知IP
|
||||
return UNKNOWN_IP;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据IPv4地址查询IP归属行政区域
|
||||
* @param ip ipv4地址
|
||||
* @return 归属行政区域
|
||||
*/
|
||||
private static String resolverIPv4Region(String ip){
|
||||
// 内网不查询
|
||||
ip = StringUtils.contains(ip, "0:0:0:0:0:0:0:1") ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
|
||||
if (NetUtil.isInnerIP(ip)) {
|
||||
return "内网IP";
|
||||
if (NetUtils.isInnerIP(ip)) {
|
||||
return LOCAL_ADDRESS;
|
||||
}
|
||||
return RegionUtils.getCityInfo(ip);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据IPv6地址查询IP归属行政区域
|
||||
* @param ip ipv6地址
|
||||
* @return 归属行政区域
|
||||
*/
|
||||
private static String resolverIPv6Region(String ip){
|
||||
// 内网不查询
|
||||
if (NetUtils.isInnerIPv6(ip)) {
|
||||
return LOCAL_ADDRESS;
|
||||
}
|
||||
log.warn("ip2region不支持IPV6地址解析:{}", ip);
|
||||
// 不支持IPv6,不再进行没有必要的IP地址信息的解析,直接返回
|
||||
// 如有需要,可自行实现IPv6地址信息解析逻辑,并在这里返回
|
||||
return UNKNOWN_ADDRESS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
package org.dromara.common.core.utils.ip;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.resource.ClassPathResource;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.file.FileUtils;
|
||||
import cn.hutool.core.io.resource.NoResourceException;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
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 java.io.File;
|
||||
|
||||
/**
|
||||
* 根据ip地址定位工具类,离线方式
|
||||
* 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a>
|
||||
@@ -19,31 +16,19 @@ import java.io.File;
|
||||
@Slf4j
|
||||
public class RegionUtils {
|
||||
|
||||
// IP地址库文件名称
|
||||
public static final String IP_XDB_FILENAME = "ip2region.xdb";
|
||||
|
||||
private static final Searcher SEARCHER;
|
||||
|
||||
static {
|
||||
String fileName = "/ip2region.xdb";
|
||||
File existFile = FileUtils.file(FileUtil.getTmpDir() + FileUtil.FILE_SEPARATOR + fileName);
|
||||
if (!FileUtils.exist(existFile)) {
|
||||
ClassPathResource fileStream = new ClassPathResource(fileName);
|
||||
if (ObjectUtil.isEmpty(fileStream.getStream())) {
|
||||
throw new ServiceException("RegionUtils初始化失败,原因:IP地址库数据不存在!");
|
||||
}
|
||||
FileUtils.writeFromStream(fileStream.getStream(), existFile);
|
||||
}
|
||||
|
||||
String dbPath = existFile.getPath();
|
||||
|
||||
// 1、从 dbPath 加载整个 xdb 到内存。
|
||||
byte[] cBuff;
|
||||
try {
|
||||
cBuff = Searcher.loadContentFromFile(dbPath);
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException("RegionUtils初始化失败,原因:从ip2region.xdb文件加载内容失败!" + e.getMessage());
|
||||
}
|
||||
// 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
|
||||
try {
|
||||
SEARCHER = Searcher.newWithBuffer(cBuff);
|
||||
// 1、将 ip2region 数据库文件 xdb 从 ClassPath 加载到内存。
|
||||
// 2、基于加载到内存的 xdb 数据创建一个 Searcher 查询对象。
|
||||
SEARCHER = Searcher.newWithBuffer(ResourceUtil.readBytes(IP_XDB_FILENAME));
|
||||
log.info("RegionUtils初始化成功,加载IP地址库数据成功!");
|
||||
} catch (NoResourceException e) {
|
||||
throw new ServiceException("RegionUtils初始化失败,原因:IP地址库数据不存在!");
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException("RegionUtils初始化失败,原因:" + e.getMessage());
|
||||
}
|
||||
@@ -54,9 +39,8 @@ public class RegionUtils {
|
||||
*/
|
||||
public static String getCityInfo(String ip) {
|
||||
try {
|
||||
ip = ip.trim();
|
||||
// 3、执行查询
|
||||
String region = SEARCHER.search(ip);
|
||||
String region = SEARCHER.search(StringUtils.trim(ip));
|
||||
return region.replace("0|", "").replace("|0", "");
|
||||
} catch (Exception e) {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import org.dromara.common.core.utils.reflect.ReflectUtils;
|
||||
*/
|
||||
public class EnumPatternValidator implements ConstraintValidator<EnumPattern, String> {
|
||||
|
||||
private EnumPattern annotation;;
|
||||
private EnumPattern annotation;
|
||||
|
||||
@Override
|
||||
public void initialize(EnumPattern annotation) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
org.dromara.common.core.config.ApplicationConfig
|
||||
org.dromara.common.core.config.AsyncConfig
|
||||
org.dromara.common.core.config.ThreadPoolConfig
|
||||
org.dromara.common.core.config.ValidatorConfig
|
||||
org.dromara.common.core.utils.SpringUtils
|
||||
|
||||
@@ -30,7 +30,7 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Swagger 文档配置
|
||||
* 接口文档配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@@ -54,14 +54,15 @@ public class SpringDocConfig {
|
||||
openApi.externalDocs(properties.getExternalDocs());
|
||||
openApi.tags(properties.getTags());
|
||||
openApi.paths(properties.getPaths());
|
||||
openApi.components(properties.getComponents());
|
||||
Set<String> keySet = properties.getComponents().getSecuritySchemes().keySet();
|
||||
List<SecurityRequirement> list = new ArrayList<>();
|
||||
SecurityRequirement securityRequirement = new SecurityRequirement();
|
||||
keySet.forEach(securityRequirement::addList);
|
||||
list.add(securityRequirement);
|
||||
openApi.security(list);
|
||||
|
||||
if (properties.getComponents() != null) {
|
||||
openApi.components(properties.getComponents());
|
||||
Set<String> keySet = properties.getComponents().getSecuritySchemes().keySet();
|
||||
List<SecurityRequirement> list = new ArrayList<>();
|
||||
SecurityRequirement securityRequirement = new SecurityRequirement();
|
||||
keySet.forEach(securityRequirement::addList);
|
||||
list.add(securityRequirement);
|
||||
openApi.security(list);
|
||||
}
|
||||
return openApi;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.dromara.common.encrypt.properties.ApiDecryptProperties;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.servlet.FilterRegistration;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@@ -20,13 +21,14 @@ import org.springframework.context.annotation.Bean;
|
||||
public class ApiDecryptAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean<CryptoFilter> cryptoFilterRegistration(ApiDecryptProperties properties) {
|
||||
FilterRegistrationBean<CryptoFilter> registration = new FilterRegistrationBean<>();
|
||||
registration.setDispatcherTypes(DispatcherType.REQUEST);
|
||||
registration.setFilter(new CryptoFilter(properties));
|
||||
registration.addUrlPatterns("/*");
|
||||
registration.setName("cryptoFilter");
|
||||
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
|
||||
return registration;
|
||||
@FilterRegistration(
|
||||
name = "cryptoFilter",
|
||||
urlPatterns = "/*",
|
||||
order = FilterRegistrationBean.HIGHEST_PRECEDENCE,
|
||||
dispatcherTypes = DispatcherType.REQUEST
|
||||
)
|
||||
public CryptoFilter cryptoFilter(ApiDecryptProperties properties) {
|
||||
return new CryptoFilter(properties);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.executor.parameter.ParameterHandler;
|
||||
import org.apache.ibatis.executor.resultset.ResultSetHandler;
|
||||
import org.apache.ibatis.plugin.*;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
@@ -39,12 +40,23 @@ public class MybatisDecryptInterceptor implements Interceptor {
|
||||
|
||||
@Override
|
||||
public Object intercept(Invocation invocation) throws Throwable {
|
||||
// 开始进行参数解密
|
||||
ResultSetHandler resultSetHandler = (ResultSetHandler) invocation.getTarget();
|
||||
Field parameterHandlerField = resultSetHandler.getClass().getDeclaredField("parameterHandler");
|
||||
parameterHandlerField.setAccessible(true);
|
||||
Object target = parameterHandlerField.get(resultSetHandler);
|
||||
if (target instanceof ParameterHandler parameterHandler) {
|
||||
Object parameterObject = parameterHandler.getParameterObject();
|
||||
if (ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)) {
|
||||
this.decryptHandler(parameterObject);
|
||||
}
|
||||
}
|
||||
// 获取执行mysql执行结果
|
||||
Object result = invocation.proceed();
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
decryptHandler(result);
|
||||
this.decryptHandler(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ public class EncryptUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* sm4加密
|
||||
* SM4加密(Base64编码)
|
||||
*
|
||||
* @param data 待加密数据
|
||||
* @param password 秘钥字符串
|
||||
@@ -127,11 +127,11 @@ public class EncryptUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* sm4加密
|
||||
* SM4加密(Hex编码)
|
||||
*
|
||||
* @param data 待加密数据
|
||||
* @param password 秘钥字符串
|
||||
* @return 加密后字符串, 采用Base64编码
|
||||
* @return 加密后字符串, 采用Hex编码
|
||||
*/
|
||||
public static String encryptBySm4Hex(String data, String password) {
|
||||
if (StrUtil.isBlank(password)) {
|
||||
@@ -148,7 +148,7 @@ public class EncryptUtils {
|
||||
/**
|
||||
* sm4解密
|
||||
*
|
||||
* @param data 待解密数据
|
||||
* @param data 待解密数据(可以是Base64或Hex编码)
|
||||
* @param password 秘钥字符串
|
||||
* @return 解密后字符串
|
||||
*/
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>easyexcel</artifactId>
|
||||
<groupId>cn.idev.excel</groupId>
|
||||
<artifactId>fastexcel</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -6,17 +6,13 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 批注
|
||||
* 批注 此注解仅用于单表头 不支持多层级表头
|
||||
* @author guzhouyanyu
|
||||
*/
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ExcelNotation {
|
||||
|
||||
/**
|
||||
* col index
|
||||
*/
|
||||
int index() default -1;
|
||||
/**
|
||||
* 批注内容
|
||||
*/
|
||||
|
||||
@@ -8,17 +8,13 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 是否必填
|
||||
* 是否必填 此注解仅用于单表头 不支持多层级表头
|
||||
* @author guzhouyanyu
|
||||
*/
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ExcelRequired {
|
||||
|
||||
/**
|
||||
* col index
|
||||
*/
|
||||
int index() default -1;
|
||||
/**
|
||||
* 字体颜色
|
||||
*/
|
||||
|
||||
@@ -2,12 +2,12 @@ package org.dromara.common.excel.convert;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.alibaba.excel.converters.Converter;
|
||||
import com.alibaba.excel.enums.CellDataTypeEnum;
|
||||
import com.alibaba.excel.metadata.GlobalConfiguration;
|
||||
import com.alibaba.excel.metadata.data.ReadCellData;
|
||||
import com.alibaba.excel.metadata.data.WriteCellData;
|
||||
import com.alibaba.excel.metadata.property.ExcelContentProperty;
|
||||
import cn.idev.excel.converters.Converter;
|
||||
import cn.idev.excel.enums.CellDataTypeEnum;
|
||||
import cn.idev.excel.metadata.GlobalConfiguration;
|
||||
import cn.idev.excel.metadata.data.ReadCellData;
|
||||
import cn.idev.excel.metadata.data.WriteCellData;
|
||||
import cn.idev.excel.metadata.property.ExcelContentProperty;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@@ -3,12 +3,12 @@ package org.dromara.common.excel.convert;
|
||||
import cn.hutool.core.annotation.AnnotationUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.alibaba.excel.converters.Converter;
|
||||
import com.alibaba.excel.enums.CellDataTypeEnum;
|
||||
import com.alibaba.excel.metadata.GlobalConfiguration;
|
||||
import com.alibaba.excel.metadata.data.ReadCellData;
|
||||
import com.alibaba.excel.metadata.data.WriteCellData;
|
||||
import com.alibaba.excel.metadata.property.ExcelContentProperty;
|
||||
import cn.idev.excel.converters.Converter;
|
||||
import cn.idev.excel.enums.CellDataTypeEnum;
|
||||
import cn.idev.excel.metadata.GlobalConfiguration;
|
||||
import cn.idev.excel.metadata.data.ReadCellData;
|
||||
import cn.idev.excel.metadata.data.WriteCellData;
|
||||
import cn.idev.excel.metadata.property.ExcelContentProperty;
|
||||
import org.dromara.common.excel.annotation.ExcelDictFormat;
|
||||
import org.dromara.common.core.service.DictService;
|
||||
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.convert.Convert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.alibaba.excel.converters.Converter;
|
||||
import com.alibaba.excel.enums.CellDataTypeEnum;
|
||||
import com.alibaba.excel.metadata.GlobalConfiguration;
|
||||
import com.alibaba.excel.metadata.data.ReadCellData;
|
||||
import com.alibaba.excel.metadata.data.WriteCellData;
|
||||
import com.alibaba.excel.metadata.property.ExcelContentProperty;
|
||||
import cn.idev.excel.converters.Converter;
|
||||
import cn.idev.excel.enums.CellDataTypeEnum;
|
||||
import cn.idev.excel.metadata.GlobalConfiguration;
|
||||
import cn.idev.excel.metadata.data.ReadCellData;
|
||||
import cn.idev.excel.metadata.data.WriteCellData;
|
||||
import cn.idev.excel.metadata.property.ExcelContentProperty;
|
||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
||||
import org.dromara.common.excel.annotation.ExcelEnumFormat;
|
||||
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.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.alibaba.excel.metadata.Head;
|
||||
import com.alibaba.excel.write.handler.WorkbookWriteHandler;
|
||||
import com.alibaba.excel.write.handler.context.WorkbookWriteHandlerContext;
|
||||
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import cn.idev.excel.metadata.Head;
|
||||
import cn.idev.excel.write.handler.WorkbookWriteHandler;
|
||||
import cn.idev.excel.write.handler.context.WorkbookWriteHandlerContext;
|
||||
import cn.idev.excel.write.merge.AbstractMergeStrategy;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.SneakyThrows;
|
||||
@@ -112,7 +112,13 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements Workbook
|
||||
}
|
||||
map.put(field, new RepeatCell(val, i));
|
||||
} 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));
|
||||
}
|
||||
} else if (!isMerge(list, i, field)) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.dromara.common.excel.core;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.excel.context.AnalysisContext;
|
||||
import com.alibaba.excel.event.AnalysisEventListener;
|
||||
import com.alibaba.excel.exception.ExcelAnalysisException;
|
||||
import com.alibaba.excel.exception.ExcelDataConvertException;
|
||||
import cn.idev.excel.context.AnalysisContext;
|
||||
import cn.idev.excel.event.AnalysisEventListener;
|
||||
import cn.idev.excel.exception.ExcelAnalysisException;
|
||||
import cn.idev.excel.exception.ExcelDataConvertException;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
import org.dromara.common.core.utils.ValidatorUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.dromara.common.excel.core;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
@@ -65,7 +66,7 @@ public class DropDownOptions {
|
||||
StringBuilder stringBuffer = new StringBuilder();
|
||||
String regex = "^[\\S\\d\\u4e00-\\u9fa5]+$";
|
||||
for (int i = 0; i < vars.length; i++) {
|
||||
String var = StrUtil.trimToEmpty(String.valueOf(vars[i]));
|
||||
String var = StrUtil.trimToEmpty(Convert.toStr(vars[i]));
|
||||
if (!var.matches(regex)) {
|
||||
throw new ServiceException("选项数据不符合规则,仅允许使用中英文字符以及数字");
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
package org.dromara.common.excel.core;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.EnumUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.excel.metadata.FieldCache;
|
||||
import com.alibaba.excel.metadata.FieldWrapper;
|
||||
import com.alibaba.excel.util.ClassUtils;
|
||||
import com.alibaba.excel.write.handler.SheetWriteHandler;
|
||||
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
|
||||
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
|
||||
import cn.idev.excel.metadata.FieldCache;
|
||||
import cn.idev.excel.metadata.FieldWrapper;
|
||||
import cn.idev.excel.util.ClassUtils;
|
||||
import cn.idev.excel.write.handler.SheetWriteHandler;
|
||||
import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
|
||||
import cn.idev.excel.write.metadata.holder.WriteWorkbookHolder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.ss.util.CellRangeAddressList;
|
||||
@@ -115,7 +116,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
||||
// 否则如果指定了@ExcelEnumFormat,则使用枚举的逻辑
|
||||
ExcelEnumFormat format = field.getDeclaredAnnotation(ExcelEnumFormat.class);
|
||||
List<Object> values = EnumUtil.getFieldValues(format.enumClass(), format.textField());
|
||||
options = StreamUtils.toList(values, String::valueOf);
|
||||
options = StreamUtils.toList(values, Convert::toStr);
|
||||
}
|
||||
if (ObjectUtil.isNotEmpty(options)) {
|
||||
// 仅当下拉可选项不为空时执行
|
||||
@@ -175,7 +176,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
||||
List<String> firstOptions = options.getOptions();
|
||||
Map<String, List<String>> secoundOptionsMap = options.getNextOptions();
|
||||
|
||||
// 采用按行填充数据的方式,避免EasyExcel出现数据无法写入的问题
|
||||
// 采用按行填充数据的方式,避免出现数据无法写入的问题
|
||||
// Attempting to write a row in the range that is already written to disk
|
||||
|
||||
// 使用ArrayList记载数据,防止乱序
|
||||
@@ -291,9 +292,11 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
||||
* @param 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)))
|
||||
.orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)));
|
||||
Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(tmpOptionsSheetName)))
|
||||
.orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(tmpOptionsSheetName)));
|
||||
// 将下拉表隐藏
|
||||
workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), true);
|
||||
// 完善纵向的一级选项数据表
|
||||
@@ -302,9 +305,9 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
||||
// 获取每一选项行,如果没有则创建
|
||||
Row row = Optional.ofNullable(simpleDataSheet.getRow(i))
|
||||
.orElseGet(() -> simpleDataSheet.createRow(finalI));
|
||||
// 获取本级选项对应的选项列,如果没有则创建
|
||||
Cell cell = Optional.ofNullable(row.getCell(currentOptionsColumnIndex))
|
||||
.orElseGet(() -> row.createCell(currentOptionsColumnIndex));
|
||||
// 获取本级选项对应的选项列,如果没有则创建。上述采用多个sheet,默认索引为1列
|
||||
Cell cell = Optional.ofNullable(row.getCell(0))
|
||||
.orElseGet(() -> row.createCell(0));
|
||||
// 设置值
|
||||
cell.setCellValue(value.get(i));
|
||||
}
|
||||
@@ -312,13 +315,13 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
||||
// 创建名称管理器
|
||||
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);
|
||||
// 以纵向第一列创建一级下拉拼接引用位置
|
||||
String function = String.format("%s!$%s$1:$%s$%d",
|
||||
OPTIONS_SHEET_NAME,
|
||||
getExcelColumnName(currentOptionsColumnIndex),
|
||||
getExcelColumnName(currentOptionsColumnIndex),
|
||||
tmpOptionsSheetName,
|
||||
getExcelColumnName(0),
|
||||
getExcelColumnName(0),
|
||||
value.size());
|
||||
// 设置名称管理器的引用位置
|
||||
name.setRefersToFormula(function);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.dromara.common.excel.core;
|
||||
|
||||
import com.alibaba.excel.read.listener.ReadListener;
|
||||
import cn.idev.excel.read.listener.ReadListener;
|
||||
|
||||
/**
|
||||
* Excel 导入监听
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package org.dromara.common.excel.handler;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.alibaba.excel.metadata.data.DataFormatData;
|
||||
import com.alibaba.excel.metadata.data.WriteCellData;
|
||||
import com.alibaba.excel.util.StyleUtil;
|
||||
import com.alibaba.excel.write.handler.CellWriteHandler;
|
||||
import com.alibaba.excel.write.handler.SheetWriteHandler;
|
||||
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
|
||||
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
|
||||
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
|
||||
import com.alibaba.excel.write.metadata.style.WriteFont;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import cn.idev.excel.metadata.data.DataFormatData;
|
||||
import cn.idev.excel.metadata.data.WriteCellData;
|
||||
import cn.idev.excel.util.StyleUtil;
|
||||
import cn.idev.excel.write.handler.CellWriteHandler;
|
||||
import cn.idev.excel.write.handler.SheetWriteHandler;
|
||||
import cn.idev.excel.write.handler.context.CellWriteHandlerContext;
|
||||
import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
|
||||
import cn.idev.excel.write.metadata.style.WriteCellStyle;
|
||||
import cn.idev.excel.write.metadata.style.WriteFont;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
|
||||
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
|
||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
||||
import org.dromara.common.excel.annotation.ExcelNotation;
|
||||
import org.dromara.common.excel.annotation.ExcelRequired;
|
||||
|
||||
@@ -31,12 +31,12 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
|
||||
/**
|
||||
* 批注
|
||||
*/
|
||||
private final Map<Integer, String> notationMap;
|
||||
private final Map<String, String> notationMap;
|
||||
|
||||
/**
|
||||
* 头列字体颜色
|
||||
*/
|
||||
private final Map<Integer, Short> headColumnMap;
|
||||
private final Map<String, Short> headColumnMap;
|
||||
|
||||
|
||||
public DataWriteHandler(Class<?> clazz) {
|
||||
@@ -49,15 +49,16 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
|
||||
if (CollUtil.isEmpty(notationMap) && CollUtil.isEmpty(headColumnMap)) {
|
||||
return;
|
||||
}
|
||||
// 第一行
|
||||
WriteCellData<?> cellData = context.getFirstCellData();
|
||||
// 第一个格子
|
||||
WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();
|
||||
|
||||
DataFormatData dataFormatData = new DataFormatData();
|
||||
// 单元格设置为文本格式
|
||||
dataFormatData.setIndex((short) 49);
|
||||
writeCellStyle.setDataFormatData(dataFormatData);
|
||||
|
||||
if (context.getHead()) {
|
||||
DataFormatData dataFormatData = new DataFormatData();
|
||||
// 单元格设置为文本格式
|
||||
dataFormatData.setIndex((short) 49);
|
||||
writeCellStyle.setDataFormatData(dataFormatData);
|
||||
Cell cell = context.getCell();
|
||||
WriteSheetHolder writeSheetHolder = context.getWriteSheetHolder();
|
||||
Sheet sheet = writeSheetHolder.getSheet();
|
||||
@@ -67,17 +68,17 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
|
||||
WriteFont headWriteFont = new WriteFont();
|
||||
// 加粗
|
||||
headWriteFont.setBold(true);
|
||||
if (CollUtil.isNotEmpty(headColumnMap) && headColumnMap.containsKey(cell.getColumnIndex())) {
|
||||
if (CollUtil.isNotEmpty(headColumnMap) && headColumnMap.containsKey(cell.getStringCellValue())) {
|
||||
// 设置字体颜色
|
||||
headWriteFont.setColor(headColumnMap.get(cell.getColumnIndex()));
|
||||
headWriteFont.setColor(headColumnMap.get(cell.getStringCellValue()));
|
||||
}
|
||||
writeCellStyle.setWriteFont(headWriteFont);
|
||||
CellStyle cellStyle = StyleUtil.buildCellStyle(workbook, null, writeCellStyle);
|
||||
cell.setCellStyle(cellStyle);
|
||||
|
||||
if (CollUtil.isNotEmpty(notationMap) && notationMap.containsKey(cell.getColumnIndex())) {
|
||||
if (CollUtil.isNotEmpty(notationMap) && notationMap.containsKey(cell.getStringCellValue())) {
|
||||
// 批注内容
|
||||
String notationContext = notationMap.get(cell.getColumnIndex());
|
||||
String notationContext = notationMap.get(cell.getStringCellValue());
|
||||
// 创建绘图对象
|
||||
Comment comment = drawing.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), 0, (short) 5, 5));
|
||||
comment.setString(new XSSFRichTextString(notationContext));
|
||||
@@ -89,23 +90,16 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
|
||||
/**
|
||||
* 获取必填列
|
||||
*/
|
||||
private static Map<Integer, Short> getRequiredMap(Class<?> clazz) {
|
||||
Map<Integer, Short> requiredMap = new HashMap<>();
|
||||
private static Map<String, Short> getRequiredMap(Class<?> clazz) {
|
||||
Map<String, Short> requiredMap = new HashMap<>();
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
// 检查 fields 数组是否为空
|
||||
if (fields.length == 0) {
|
||||
return requiredMap;
|
||||
}
|
||||
Field[] filteredFields = ReflectUtils.getFields(clazz, field -> !"serialVersionUID".equals(field.getName()));
|
||||
|
||||
for (int i = 0; i < filteredFields.length; i++) {
|
||||
Field field = filteredFields[i];
|
||||
for (Field field : fields) {
|
||||
if (!field.isAnnotationPresent(ExcelRequired.class)) {
|
||||
continue;
|
||||
}
|
||||
ExcelRequired excelRequired = field.getAnnotation(ExcelRequired.class);
|
||||
int columnIndex = excelRequired.index() == -1 ? i : excelRequired.index();
|
||||
requiredMap.put(columnIndex, excelRequired.fontColor().getIndex());
|
||||
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
|
||||
requiredMap.put(excelProperty.value()[0], excelRequired.fontColor().getIndex());
|
||||
}
|
||||
return requiredMap;
|
||||
}
|
||||
@@ -113,22 +107,16 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
|
||||
/**
|
||||
* 获取批注
|
||||
*/
|
||||
private static Map<Integer, String> getNotationMap(Class<?> clazz) {
|
||||
Map<Integer, String> notationMap = new HashMap<>();
|
||||
private static Map<String, String> getNotationMap(Class<?> clazz) {
|
||||
Map<String, String> notationMap = new HashMap<>();
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
// 检查 fields 数组是否为空
|
||||
if (fields.length == 0) {
|
||||
return notationMap;
|
||||
}
|
||||
Field[] filteredFields = ReflectUtils.getFields(clazz, field -> !"serialVersionUID".equals(field.getName()));
|
||||
for (int i = 0; i < filteredFields.length; i++) {
|
||||
Field field = filteredFields[i];
|
||||
for (Field field : fields) {
|
||||
if (!field.isAnnotationPresent(ExcelNotation.class)) {
|
||||
continue;
|
||||
}
|
||||
ExcelNotation excelNotation = field.getAnnotation(ExcelNotation.class);
|
||||
int columnIndex = excelNotation.index() == -1 ? i : excelNotation.index();
|
||||
notationMap.put(columnIndex, excelNotation.value());
|
||||
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
|
||||
notationMap.put(excelProperty.value()[0], excelNotation.value());
|
||||
}
|
||||
return notationMap;
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@ package org.dromara.common.excel.utils;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.resource.ClassPathResource;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.excel.ExcelWriter;
|
||||
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
|
||||
import com.alibaba.excel.write.metadata.WriteSheet;
|
||||
import com.alibaba.excel.write.metadata.fill.FillConfig;
|
||||
import com.alibaba.excel.write.metadata.fill.FillWrapper;
|
||||
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
||||
import cn.idev.excel.FastExcel;
|
||||
import cn.idev.excel.ExcelWriter;
|
||||
import cn.idev.excel.write.builder.ExcelWriterSheetBuilder;
|
||||
import cn.idev.excel.write.metadata.WriteSheet;
|
||||
import cn.idev.excel.write.metadata.fill.FillConfig;
|
||||
import cn.idev.excel.write.metadata.fill.FillWrapper;
|
||||
import cn.idev.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.AccessLevel;
|
||||
@@ -43,7 +43,7 @@ public class ExcelUtil {
|
||||
* @return 转换后集合
|
||||
*/
|
||||
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) {
|
||||
DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
|
||||
EasyExcel.read(is, clazz, listener).sheet().doRead();
|
||||
FastExcel.read(is, clazz, listener).sheet().doRead();
|
||||
return listener.getExcelResult();
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public class ExcelUtil {
|
||||
* @return 转换后集合
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ public class ExcelUtil {
|
||||
*/
|
||||
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge,
|
||||
OutputStream os, List<DropDownOptions> options) {
|
||||
ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
|
||||
ExcelWriterSheetBuilder builder = FastExcel.write(os, clazz)
|
||||
.autoCloseStream(false)
|
||||
// 自动适配
|
||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
||||
@@ -215,6 +215,9 @@ public class ExcelUtil {
|
||||
*/
|
||||
public static <T> void exportTemplate(List<T> data, String filename, String templatePath, HttpServletResponse response) {
|
||||
try {
|
||||
if (CollUtil.isEmpty(data)) {
|
||||
throw new IllegalArgumentException("数据为空");
|
||||
}
|
||||
resetResponse(filename, response);
|
||||
ServletOutputStream os = response.getOutputStream();
|
||||
exportTemplate(data, templatePath, os);
|
||||
@@ -233,18 +236,15 @@ public class ExcelUtil {
|
||||
* @param 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);
|
||||
ExcelWriter excelWriter = EasyExcel.write(os)
|
||||
ExcelWriter excelWriter = FastExcel.write(os)
|
||||
.withTemplate(templateResource.getStream())
|
||||
.autoCloseStream(false)
|
||||
// 大数值自动转换 防止失真
|
||||
.registerConverter(new ExcelBigNumberConvert())
|
||||
.registerWriteHandler(new DataWriteHandler(data.get(0).getClass()))
|
||||
.build();
|
||||
WriteSheet writeSheet = EasyExcel.writerSheet().build();
|
||||
WriteSheet writeSheet = FastExcel.writerSheet().build();
|
||||
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
|
||||
// 单表多数据导出 模板格式为 {.属性}
|
||||
for (T d : data) {
|
||||
@@ -265,6 +265,9 @@ public class ExcelUtil {
|
||||
*/
|
||||
public static void exportTemplateMultiList(Map<String, Object> data, String filename, String templatePath, HttpServletResponse response) {
|
||||
try {
|
||||
if (CollUtil.isEmpty(data)) {
|
||||
throw new IllegalArgumentException("数据为空");
|
||||
}
|
||||
resetResponse(filename, response);
|
||||
ServletOutputStream os = response.getOutputStream();
|
||||
exportTemplateMultiList(data, templatePath, os);
|
||||
@@ -285,6 +288,9 @@ public class ExcelUtil {
|
||||
*/
|
||||
public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String filename, String templatePath, HttpServletResponse response) {
|
||||
try {
|
||||
if (CollUtil.isEmpty(data)) {
|
||||
throw new IllegalArgumentException("数据为空");
|
||||
}
|
||||
resetResponse(filename, response);
|
||||
ServletOutputStream os = response.getOutputStream();
|
||||
exportTemplateMultiSheet(data, templatePath, os);
|
||||
@@ -303,17 +309,14 @@ public class ExcelUtil {
|
||||
* @param 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);
|
||||
ExcelWriter excelWriter = EasyExcel.write(os)
|
||||
ExcelWriter excelWriter = FastExcel.write(os)
|
||||
.withTemplate(templateResource.getStream())
|
||||
.autoCloseStream(false)
|
||||
// 大数值自动转换 防止失真
|
||||
.registerConverter(new ExcelBigNumberConvert())
|
||||
.build();
|
||||
WriteSheet writeSheet = EasyExcel.writerSheet().build();
|
||||
WriteSheet writeSheet = FastExcel.writerSheet().build();
|
||||
for (Map.Entry<String, Object> map : data.entrySet()) {
|
||||
// 设置列表后续还有数据
|
||||
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
|
||||
@@ -337,18 +340,15 @@ public class ExcelUtil {
|
||||
* @param 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);
|
||||
ExcelWriter excelWriter = EasyExcel.write(os)
|
||||
ExcelWriter excelWriter = FastExcel.write(os)
|
||||
.withTemplate(templateResource.getStream())
|
||||
.autoCloseStream(false)
|
||||
// 大数值自动转换 防止失真
|
||||
.registerConverter(new ExcelBigNumberConvert())
|
||||
.build();
|
||||
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()) {
|
||||
// 设置列表后续还有数据
|
||||
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.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import org.dromara.common.json.handler.BigNumberSerializer;
|
||||
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.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
@@ -15,6 +16,7 @@ import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
@@ -38,6 +40,7 @@ public class JacksonConfig {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
|
||||
javaTimeModule.addDeserializer(Date.class, new CustomDateDeserializer());
|
||||
builder.modules(javaTimeModule);
|
||||
builder.timeZone(TimeZone.getDefault());
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,9 +27,7 @@ import org.springframework.http.HttpMethod;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 操作日志记录处理
|
||||
@@ -176,14 +174,28 @@ public class LogAspect {
|
||||
if (ArrayUtil.isEmpty(paramsArray)) {
|
||||
return params.toString();
|
||||
}
|
||||
String[] exclude = ArrayUtil.addAll(excludeParamNames, EXCLUDE_PROPERTIES);
|
||||
for (Object o : paramsArray) {
|
||||
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
|
||||
String str = JsonUtils.toJsonString(o);
|
||||
Dict dict = JsonUtils.parseMap(str);
|
||||
if (MapUtil.isNotEmpty(dict)) {
|
||||
MapUtil.removeAny(dict, EXCLUDE_PROPERTIES);
|
||||
MapUtil.removeAny(dict, excludeParamNames);
|
||||
str = JsonUtils.toJsonString(dict);
|
||||
String str = "";
|
||||
if (o instanceof List<?> list) {
|
||||
List<Dict> list1 = new ArrayList<>();
|
||||
for (Object obj : list) {
|
||||
String str1 = JsonUtils.toJsonString(obj);
|
||||
Dict dict = JsonUtils.parseMap(str1);
|
||||
if (MapUtil.isNotEmpty(dict)) {
|
||||
MapUtil.removeAny(dict, exclude);
|
||||
list1.add(dict);
|
||||
}
|
||||
}
|
||||
str = JsonUtils.toJsonString(list1);
|
||||
} else {
|
||||
str = JsonUtils.toJsonString(o);
|
||||
Dict dict = JsonUtils.parseMap(str);
|
||||
if (MapUtil.isNotEmpty(dict)) {
|
||||
MapUtil.removeAny(dict, exclude);
|
||||
str = JsonUtils.toJsonString(dict);
|
||||
}
|
||||
}
|
||||
params.add(str);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.dromara.common.mybatis.aspect;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.dromara.common.mybatis.annotation.DataPermission;
|
||||
import org.dromara.common.mybatis.helper.DataPermissionHelper;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
/**
|
||||
* 数据权限注解Advice
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
@Slf4j
|
||||
public class DataPermissionAdvice implements MethodInterceptor {
|
||||
|
||||
@Override
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
Object target = invocation.getThis();
|
||||
Method method = invocation.getMethod();
|
||||
Object[] args = invocation.getArguments();
|
||||
// 设置权限注解
|
||||
DataPermissionHelper.setPermission(getDataPermissionAnnotation(target, method, args));
|
||||
try {
|
||||
// 执行代理方法
|
||||
return invocation.proceed();
|
||||
} finally {
|
||||
// 清除权限注解
|
||||
DataPermissionHelper.removePermission();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据权限注解
|
||||
*/
|
||||
private DataPermission getDataPermissionAnnotation(Object target, Method method,Object[] args){
|
||||
DataPermission dataPermission = method.getAnnotation(DataPermission.class);
|
||||
// 优先获取方法上的注解
|
||||
if (dataPermission != null) {
|
||||
return dataPermission;
|
||||
}
|
||||
// 方法上没有注解,则获取类上的注解
|
||||
Class<?> targetClass = target.getClass();
|
||||
// 如果是 JDK 动态代理,则获取真实的Class实例
|
||||
if (Proxy.isProxyClass(targetClass)) {
|
||||
targetClass = targetClass.getInterfaces()[0];
|
||||
}
|
||||
dataPermission = targetClass.getAnnotation(DataPermission.class);
|
||||
return dataPermission;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package org.dromara.common.mybatis.aspect;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.dromara.common.mybatis.annotation.DataPermission;
|
||||
import org.dromara.common.mybatis.helper.DataPermissionHelper;
|
||||
|
||||
/**
|
||||
* 数据权限处理
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
public class DataPermissionAspect {
|
||||
|
||||
/**
|
||||
* 处理请求前执行
|
||||
*/
|
||||
@Before(value = "@annotation(dataPermission)")
|
||||
public void doBefore(JoinPoint joinPoint, DataPermission dataPermission) {
|
||||
DataPermissionHelper.setPermission(dataPermission);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理完请求后执行
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
*/
|
||||
@AfterReturning(pointcut = "@annotation(dataPermission)")
|
||||
public void doAfterReturning(JoinPoint joinPoint, DataPermission dataPermission) {
|
||||
DataPermissionHelper.removePermission();
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截异常操作
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
* @param e 异常
|
||||
*/
|
||||
@AfterThrowing(value = "@annotation(dataPermission)", throwing = "e")
|
||||
public void doAfterThrowing(JoinPoint joinPoint, DataPermission dataPermission, Exception e) {
|
||||
DataPermissionHelper.removePermission();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.dromara.common.mybatis.aspect;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.mybatis.annotation.DataPermission;
|
||||
import org.springframework.aop.support.StaticMethodMatcherPointcut;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
/**
|
||||
* 数据权限匹配切点
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
@Slf4j
|
||||
@SuppressWarnings("all")
|
||||
public class DataPermissionPointcut extends StaticMethodMatcherPointcut {
|
||||
|
||||
@Override
|
||||
public boolean matches(Method method, Class<?> targetClass) {
|
||||
// 优先匹配方法
|
||||
// 数据权限注解不对继承生效,所以检查当前方法是否有注解即可,不再往上匹配父类或接口
|
||||
if (method.isAnnotationPresent(DataPermission.class)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// MyBatis 的 Mapper 就是通过 JDK 动态代理实现的,所以这里需要检查是否匹配 JDK 的动态代理
|
||||
Class<?> targetClassRef = targetClass;
|
||||
if (Proxy.isProxyClass(targetClassRef)) {
|
||||
// 数据权限注解不对继承生效,但由于 SpringIOC 容器拿到的实际上是 MyBatis 代理过后的 Mapper,而 targetClass.isAnnotationPresent 实际匹配的是 Proxy 类的注解,不会查找代理类。
|
||||
// 所以这里不能用 targetClass.isAnnotationPresent,只能用 AnnotatedElementUtils.hasAnnotation 或 targetClass.getInterfaces()[0].isAnnotationPresent 去做匹配,以检查被代理的 MapperClass 是否具有注解
|
||||
// 原理:JDK 动态代理本质上就是对接口进行实现然后对具体的接口实现做代理,所以直接通过接口可以拿到实际的 MapperClass
|
||||
targetClassRef = targetClass.getInterfaces()[0];
|
||||
|
||||
}
|
||||
return targetClassRef.isAnnotationPresent(DataPermission.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.dromara.common.mybatis.aspect;
|
||||
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.support.AbstractPointcutAdvisor;
|
||||
|
||||
/**
|
||||
* 数据权限注解切面定义
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
@SuppressWarnings("all")
|
||||
public class DataPermissionPointcutAdvisor extends AbstractPointcutAdvisor {
|
||||
|
||||
private final Advice advice;
|
||||
private final Pointcut pointcut;
|
||||
|
||||
public DataPermissionPointcutAdvisor() {
|
||||
this.advice = new DataPermissionAdvice();
|
||||
this.pointcut = new DataPermissionPointcut();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pointcut getPointcut() {
|
||||
return this.pointcut;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Advice getAdvice() {
|
||||
return this.advice;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,15 +11,17 @@ import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerIntercept
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
||||
import org.dromara.common.core.factory.YmlPropertySourceFactory;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.mybatis.aspect.DataPermissionAspect;
|
||||
import org.dromara.common.mybatis.aspect.DataPermissionPointcutAdvisor;
|
||||
import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler;
|
||||
import org.dromara.common.mybatis.handler.MybatisExceptionHandler;
|
||||
import org.dromara.common.mybatis.handler.PlusPostInitTableInfoHandler;
|
||||
import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.context.annotation.Role;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/**
|
||||
@@ -27,6 +29,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
@EnableTransactionManagement(proxyTargetClass = true)
|
||||
@MapperScan("${mybatis-plus.mapperPackage}")
|
||||
@PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class)
|
||||
@@ -54,15 +57,16 @@ public class MybatisPlusConfig {
|
||||
* 数据权限拦截器
|
||||
*/
|
||||
public PlusDataPermissionInterceptor dataPermissionInterceptor() {
|
||||
return new PlusDataPermissionInterceptor(SpringUtils.getProperty("mybatis-plus.mapperPackage"));
|
||||
return new PlusDataPermissionInterceptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据权限切面处理器
|
||||
*/
|
||||
@Bean
|
||||
public DataPermissionAspect dataPermissionAspect() {
|
||||
return new DataPermissionAspect();
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public DataPermissionPointcutAdvisor dataPermissionPointcutAdvisor() {
|
||||
return new DataPermissionPointcutAdvisor();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,9 +6,11 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||
import com.baomidou.mybatisplus.core.toolkit.reflect.GenericTypeUtils;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.Db;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.logging.Log;
|
||||
import org.apache.ibatis.logging.LogFactory;
|
||||
import org.dromara.common.core.utils.MapstructUtils;
|
||||
@@ -130,7 +132,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
|
||||
* @return 查询到的单个VO对象
|
||||
*/
|
||||
default V selectVoById(Serializable id) {
|
||||
return selectVoById(id, this.currentVoClass());
|
||||
return this.selectVoById(id, this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,7 +158,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
|
||||
* @return 查询到的VO对象列表
|
||||
*/
|
||||
default List<V> selectVoByIds(Collection<? extends Serializable> idList) {
|
||||
return selectVoByIds(idList, this.currentVoClass());
|
||||
return this.selectVoByIds(idList, this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,7 +184,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
|
||||
* @return 查询到的VO对象列表
|
||||
*/
|
||||
default List<V> selectVoByMap(Map<String, Object> map) {
|
||||
return selectVoByMap(map, this.currentVoClass());
|
||||
return this.selectVoByMap(map, this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,7 +210,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
|
||||
* @return 查询到的单个VO对象
|
||||
*/
|
||||
default V selectVoOne(Wrapper<T> wrapper) {
|
||||
return selectVoOne(wrapper, this.currentVoClass());
|
||||
return this.selectVoOne(wrapper, this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -219,11 +221,12 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
|
||||
* @return 查询到的单个VO对象
|
||||
*/
|
||||
default V selectVoOne(Wrapper<T> wrapper, boolean throwEx) {
|
||||
return selectVoOne(wrapper, this.currentVoClass(), throwEx);
|
||||
return this.selectVoOne(wrapper, this.currentVoClass(), throwEx);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件查询单个VO对象,并指定返回的VO对象的类型
|
||||
* 根据条件查询单个VO对象,并指定返回的VO对象的类型(自动拼接 limit 1)
|
||||
* 注意不要再自己添加 limit 1 做限制了
|
||||
*
|
||||
* @param wrapper 查询条件Wrapper
|
||||
* @param voClass 返回的VO对象的Class对象
|
||||
@@ -231,11 +234,12 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
|
||||
* @return 查询到的单个VO对象,经过类型转换为指定的VO类后返回
|
||||
*/
|
||||
default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
|
||||
return selectVoOne(wrapper, voClass, true);
|
||||
return this.selectVoOne(wrapper, voClass, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件查询单个实体对象,并将其转换为指定的VO对象
|
||||
* 根据条件查询单个实体对象,并将其转换为指定的VO对象(自动拼接 limit 1)
|
||||
* 注意不要再自己添加 limit 1 做限制了
|
||||
*
|
||||
* @param wrapper 查询条件Wrapper
|
||||
* @param voClass 要转换的VO类的Class对象
|
||||
@@ -251,13 +255,33 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
|
||||
return MapstructUtils.convert(obj, voClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件查询单条记录(自动拼接 limit 1 限制返回 1 条数据,不依赖 {@code throwEx} 参数)
|
||||
* 注意不要再自己添加 limit 1 做限制了
|
||||
* <p>
|
||||
* <strong>注意:</strong>
|
||||
* 1. 使用 {@code Page<>(1, 1)} 强制分页查询,确保 SQL 自动添加 {@code LIMIT 1},因此 {@code throwEx} 参数不再生效
|
||||
* 2. 原方法的 {@code throwEx} 逻辑(多条数据抛异常)已被优化掉,因为分页查询不会返回多条记录
|
||||
* </p>
|
||||
*
|
||||
* @param queryWrapper 查询条件(可为 null)
|
||||
* @param throwEx <del>是否抛出异常(已弃用,此参数不再生效)</del>
|
||||
* @return 单条记录或无数据时返回 null
|
||||
*/
|
||||
@Override
|
||||
default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, boolean throwEx) {
|
||||
// 强制分页查询(LIMIT 1),确保最多返回 1 条记录
|
||||
List<T> list = this.selectList(new Page<>(1, 1), queryWrapper);
|
||||
return CollUtil.isEmpty(list) ? null : list.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有VO对象列表
|
||||
*
|
||||
* @return 查询到的VO对象列表
|
||||
*/
|
||||
default List<V> selectVoList() {
|
||||
return selectVoList(new QueryWrapper<>(), this.currentVoClass());
|
||||
return this.selectVoList(new QueryWrapper<>(), this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,7 +318,7 @@ public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
|
||||
* @return 查询到的VO对象分页列表
|
||||
*/
|
||||
default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) {
|
||||
return selectVoPage(page, wrapper, this.currentVoClass());
|
||||
return this.selectVoPage(page, wrapper, this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.dromara.common.mybatis.core.page;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.http.HttpStatus;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import lombok.Data;
|
||||
@@ -88,4 +89,19 @@ public class TableDataInfo<T> implements Serializable {
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,11 @@ import java.util.Date;
|
||||
@Slf4j
|
||||
public class InjectionMetaObjectHandler implements MetaObjectHandler {
|
||||
|
||||
/**
|
||||
* 如果用户不存在默认注入-1代表无用户
|
||||
*/
|
||||
private static final Long DEFAULT_USER_ID = -1L;
|
||||
|
||||
/**
|
||||
* 插入填充方法,用于在插入数据时自动填充实体对象中的创建时间、更新时间、创建人、更新人等信息
|
||||
*
|
||||
@@ -45,6 +50,11 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
|
||||
baseEntity.setCreateBy(userId);
|
||||
baseEntity.setUpdateBy(userId);
|
||||
baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), loginUser.getDeptId()));
|
||||
} else {
|
||||
// 填充创建人、更新人和创建部门信息
|
||||
baseEntity.setCreateBy(DEFAULT_USER_ID);
|
||||
baseEntity.setUpdateBy(DEFAULT_USER_ID);
|
||||
baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), DEFAULT_USER_ID));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -74,6 +84,8 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
|
||||
Long userId = LoginHelper.getUserId();
|
||||
if (ObjectUtil.isNotNull(userId)) {
|
||||
baseEntity.setUpdateBy(userId);
|
||||
} else {
|
||||
baseEntity.setUpdateBy(DEFAULT_USER_ID);
|
||||
}
|
||||
} else {
|
||||
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
|
||||
@@ -93,7 +105,6 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
|
||||
try {
|
||||
loginUser = LoginHelper.getLoginUser();
|
||||
} catch (Exception e) {
|
||||
log.warn("自动注入警告 => 用户未登录");
|
||||
return null;
|
||||
}
|
||||
return loginUser;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.dromara.common.mybatis.handler;
|
||||
|
||||
import cn.hutool.http.HttpStatus;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.domain.R;
|
||||
@@ -25,7 +26,7 @@ public class MybatisExceptionHandler {
|
||||
public R<Void> handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
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) {
|
||||
String requestURI = request.getRequestURI();
|
||||
String message = e.getMessage();
|
||||
if (StringUtils.contains("CannotFindDataSourceException", message)) {
|
||||
if (StringUtils.contains(message, "CannotFindDataSourceException")) {
|
||||
log.error("请求地址'{}', 未找到数据源", requestURI);
|
||||
return R.fail("未找到数据源,请联系管理员确认");
|
||||
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "未找到数据源,请联系管理员确认");
|
||||
}
|
||||
log.error("请求地址'{}', Mybatis系统异常", requestURI, e);
|
||||
return R.fail(message);
|
||||
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -49,11 +49,6 @@ import java.util.function.Function;
|
||||
@Slf4j
|
||||
public class PlusDataPermissionHandler {
|
||||
|
||||
/**
|
||||
* 类名称与注解的映射关系缓存(由于aop无法拦截mybatis接口类上的注解 只能通过启动预扫描的方式进行)
|
||||
*/
|
||||
private final Map<String, DataPermission> dataPermissionCacheMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* spel 解析器
|
||||
*/
|
||||
@@ -64,27 +59,17 @@ public class PlusDataPermissionHandler {
|
||||
*/
|
||||
private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory());
|
||||
|
||||
/**
|
||||
* 构造方法,扫描指定包下的 Mapper 类并初始化缓存
|
||||
*
|
||||
* @param mapperPackage Mapper 类所在的包路径
|
||||
*/
|
||||
public PlusDataPermissionHandler(String mapperPackage) {
|
||||
scanMapperClasses(mapperPackage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据过滤条件的 SQL 片段
|
||||
*
|
||||
* @param where 原始的查询条件表达式
|
||||
* @param mappedStatementId Mapper 方法的 ID
|
||||
* @param isSelect 是否为查询语句
|
||||
* @return 数据过滤条件的 SQL 片段
|
||||
*/
|
||||
public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) {
|
||||
public Expression getSqlSegment(Expression where, boolean isSelect) {
|
||||
try {
|
||||
// 获取数据权限配置
|
||||
DataPermission dataPermission = getDataPermission(mappedStatementId);
|
||||
DataPermission dataPermission = getDataPermission();
|
||||
// 获取当前登录用户信息
|
||||
LoginUser currentUser = DataPermissionHelper.getVariable("user");
|
||||
if (ObjectUtil.isNull(currentUser)) {
|
||||
@@ -206,92 +191,22 @@ public class PlusDataPermissionHandler {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描指定包下的 Mapper 类,并查找其中带有特定注解的方法或类
|
||||
*
|
||||
* @param mapperPackage Mapper 类所在的包路径
|
||||
*/
|
||||
private void scanMapperClasses(String mapperPackage) {
|
||||
// 创建资源解析器和元数据读取工厂
|
||||
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
|
||||
// 将 Mapper 包路径按分隔符拆分为数组
|
||||
String[] packagePatternArray = StringUtils.splitPreserveAllTokens(mapperPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
|
||||
String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;
|
||||
try {
|
||||
for (String packagePattern : packagePatternArray) {
|
||||
// 将包路径转换为资源路径
|
||||
String path = ClassUtils.convertClassNameToResourcePath(packagePattern);
|
||||
// 获取指定路径下的所有 .class 文件资源
|
||||
Resource[] resources = resolver.getResources(classpath + path + "/*.class");
|
||||
for (Resource resource : resources) {
|
||||
// 获取资源的类元数据
|
||||
ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata();
|
||||
// 获取资源对应的类对象
|
||||
Class<?> clazz = Resources.classForName(classMetadata.getClassName());
|
||||
// 查找类中的特定注解
|
||||
findAnnotation(clazz);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("初始化数据安全缓存时出错:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指定的类中查找特定的注解 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 注解对象
|
||||
*
|
||||
* @param mapperId 映射语句 ID
|
||||
* @return DataPermission 注解对象,如果不存在则返回 null
|
||||
*/
|
||||
public DataPermission getDataPermission(String mapperId) {
|
||||
// 检查上下文中是否包含映射语句 ID 对应的 DataPermission 注解对象
|
||||
if (DataPermissionHelper.getPermission() != null) {
|
||||
return DataPermissionHelper.getPermission();
|
||||
}
|
||||
// 检查缓存中是否包含映射语句 ID 对应的 DataPermission 注解对象
|
||||
if (dataPermissionCacheMap.containsKey(mapperId)) {
|
||||
return dataPermissionCacheMap.get(mapperId);
|
||||
}
|
||||
// 如果缓存中不包含映射语句 ID 对应的 DataPermission 注解对象,则尝试使用类名作为键查找
|
||||
String clazzName = mapperId.substring(0, mapperId.lastIndexOf("."));
|
||||
if (dataPermissionCacheMap.containsKey(clazzName)) {
|
||||
return dataPermissionCacheMap.get(clazzName);
|
||||
}
|
||||
return null;
|
||||
public DataPermission getDataPermission() {
|
||||
return DataPermissionHelper.getPermission();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查给定的映射语句 ID 是否有效,即是否能够找到对应的 DataPermission 注解对象
|
||||
*
|
||||
* @param mapperId 映射语句 ID
|
||||
* @return 如果找到对应的 DataPermission 注解对象,则返回 false;否则返回 true
|
||||
*/
|
||||
public boolean invalid(String mapperId) {
|
||||
return getDataPermission(mapperId) == null;
|
||||
public boolean invalid() {
|
||||
return getDataPermission() == null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,16 +35,7 @@ import java.util.List;
|
||||
@Slf4j
|
||||
public class PlusDataPermissionInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor {
|
||||
|
||||
private final PlusDataPermissionHandler dataPermissionHandler;
|
||||
|
||||
/**
|
||||
* 构造函数,初始化 PlusDataPermissionHandler 实例
|
||||
*
|
||||
* @param mapperPackage 扫描的映射器包
|
||||
*/
|
||||
public PlusDataPermissionInterceptor(String mapperPackage) {
|
||||
this.dataPermissionHandler = new PlusDataPermissionHandler(mapperPackage);
|
||||
}
|
||||
private final PlusDataPermissionHandler dataPermissionHandler = new PlusDataPermissionHandler();
|
||||
|
||||
/**
|
||||
* 在执行查询之前,检查并处理数据权限相关逻辑
|
||||
@@ -64,7 +55,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
|
||||
return;
|
||||
}
|
||||
// 检查是否缺少有效的数据权限注解
|
||||
if (dataPermissionHandler.invalid(ms.getId())) {
|
||||
if (dataPermissionHandler.invalid()) {
|
||||
return;
|
||||
}
|
||||
// 解析 sql 分配对应方法
|
||||
@@ -92,7 +83,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
|
||||
return;
|
||||
}
|
||||
// 检查是否缺少有效的数据权限注解
|
||||
if (dataPermissionHandler.invalid(ms.getId())) {
|
||||
if (dataPermissionHandler.invalid()) {
|
||||
return;
|
||||
}
|
||||
PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
|
||||
@@ -128,7 +119,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
|
||||
*/
|
||||
@Override
|
||||
protected void processUpdate(Update update, int index, String sql, Object obj) {
|
||||
Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), (String) obj, false);
|
||||
Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), false);
|
||||
if (null != sqlSegment) {
|
||||
update.setWhere(sqlSegment);
|
||||
}
|
||||
@@ -144,7 +135,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
|
||||
*/
|
||||
@Override
|
||||
protected void processDelete(Delete delete, int index, String sql, Object obj) {
|
||||
Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), (String) obj, false);
|
||||
Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), false);
|
||||
if (null != sqlSegment) {
|
||||
delete.setWhere(sqlSegment);
|
||||
}
|
||||
@@ -157,7 +148,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
|
||||
* @param mappedStatementId 映射语句的 ID
|
||||
*/
|
||||
protected void setWhere(PlainSelect plainSelect, String mappedStatementId) {
|
||||
Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true);
|
||||
Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), true);
|
||||
if (null != sqlSegment) {
|
||||
plainSelect.setWhere(sqlSegment);
|
||||
}
|
||||
|
||||
@@ -177,12 +177,12 @@ public class OssClient {
|
||||
// 创建异步请求体(length如果为空会报错)
|
||||
BlockingInputStreamAsyncRequestBody body = BlockingInputStreamAsyncRequestBody.builder()
|
||||
.contentLength(length)
|
||||
.subscribeTimeout(Duration.ofSeconds(30))
|
||||
.subscribeTimeout(Duration.ofSeconds(120))
|
||||
.build();
|
||||
|
||||
// 使用 transferManager 进行上传
|
||||
Upload upload = transferManager.upload(
|
||||
x -> x.requestBody(body)
|
||||
x -> x.requestBody(body).addTransferListener(LoggingTransferListener.create())
|
||||
.putObjectRequest(
|
||||
y -> y.bucket(properties.getBucketName())
|
||||
.key(key)
|
||||
|
||||
@@ -145,18 +145,25 @@ public class PlusSpringCacheManager implements CacheManager {
|
||||
if (array.length > 3) {
|
||||
config.setMaxSize(Integer.parseInt(array[3]));
|
||||
}
|
||||
|
||||
if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
|
||||
return createMap(name, config);
|
||||
int local = 1;
|
||||
if (array.length > 4) {
|
||||
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);
|
||||
|
||||
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) {
|
||||
cache = new TransactionAwareCacheDecorator(cache);
|
||||
}
|
||||
@@ -167,10 +174,13 @@ public class PlusSpringCacheManager implements CacheManager {
|
||||
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);
|
||||
|
||||
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) {
|
||||
cache = new TransactionAwareCacheDecorator(cache);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ import java.util.function.Function;
|
||||
*
|
||||
* @author Lion Li
|
||||
* @version 3.6.0 新增
|
||||
* @deprecated redisson 新版本已经将队列功能标记删除 一些技术问题无法解决 建议搭建MQ使用
|
||||
*/
|
||||
@Deprecated
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class QueueUtils {
|
||||
|
||||
|
||||
@@ -129,9 +129,9 @@ public class RedisUtils {
|
||||
} catch (Exception e) {
|
||||
long timeToLive = bucket.remainTimeToLive();
|
||||
if (timeToLive == -1) {
|
||||
setCacheObject(key, value);
|
||||
bucket.set(value);
|
||||
} else {
|
||||
setCacheObject(key, value, Duration.ofMillis(timeToLive));
|
||||
bucket.set(value, Duration.ofMillis(timeToLive));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -147,11 +147,8 @@ public class RedisUtils {
|
||||
* @param duration 时间
|
||||
*/
|
||||
public static <T> void setCacheObject(final String key, final T value, final Duration duration) {
|
||||
RBatch batch = CLIENT.createBatch();
|
||||
RBucketAsync<T> bucket = batch.getBucket(key);
|
||||
bucket.setAsync(value);
|
||||
bucket.expireAsync(duration);
|
||||
batch.execute();
|
||||
RBucket<T> bucket = CLIENT.getBucket(key);
|
||||
bucket.set(value, duration);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
package org.dromara.common.redis.utils;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.redisson.api.RIdGenerator;
|
||||
import org.redisson.api.RedissonClient;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
|
||||
/**
|
||||
* 发号器工具类
|
||||
@@ -23,20 +27,28 @@ 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_MINUTE = Duration.ofMinutes(1);
|
||||
|
||||
/**
|
||||
* 默认最小ID容量位数 - 6位数(即至少可以生成的ID为999999个)
|
||||
*/
|
||||
public static final int DEFAULT_MIN_ID_CAPACITY_BITS = 6;
|
||||
|
||||
/**
|
||||
* 获取Redisson客户端实例
|
||||
*/
|
||||
@@ -51,14 +63,11 @@ public class SequenceUtils {
|
||||
* @param stepValue ID步长
|
||||
* @return ID生成器
|
||||
*/
|
||||
private static RIdGenerator getIdGenerator(String key, Duration expireTime, Long initValue, Long stepValue) {
|
||||
if (initValue == null || initValue <= 0) {
|
||||
initValue = DEFAULT_INIT_VALUE;
|
||||
}
|
||||
if (stepValue == null || stepValue <= 0) {
|
||||
stepValue = DEFAULT_STEP_VALUE;
|
||||
}
|
||||
public static RIdGenerator getIdGenerator(String key, Duration expireTime, long initValue, long stepValue) {
|
||||
RIdGenerator idGenerator = REDISSON_CLIENT.getIdGenerator(key);
|
||||
// 初始值和步长不能小于等于0
|
||||
initValue = initValue <= 0 ? DEFAULT_INIT_VALUE : initValue;
|
||||
stepValue = stepValue <= 0 ? DEFAULT_STEP_VALUE : stepValue;
|
||||
// 设置初始值和步长
|
||||
idGenerator.tryInit(initValue, stepValue);
|
||||
// 设置过期时间
|
||||
@@ -66,6 +75,17 @@ public class SequenceUtils {
|
||||
return idGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取ID生成器
|
||||
*
|
||||
* @param key 业务key
|
||||
* @param expireTime 过期时间
|
||||
* @return ID生成器
|
||||
*/
|
||||
public static RIdGenerator getIdGenerator(String key, Duration expireTime) {
|
||||
return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定业务key的唯一id
|
||||
*
|
||||
@@ -75,10 +95,21 @@ public class SequenceUtils {
|
||||
* @param stepValue ID步长
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static long nextId(String key, Duration expireTime, Long initValue, Long stepValue) {
|
||||
public static long getNextId(String key, Duration expireTime, long initValue, long stepValue) {
|
||||
return getIdGenerator(key, expireTime, initValue, stepValue).nextId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定业务key的唯一id (ID初始值=1,ID步长=1)
|
||||
*
|
||||
* @param key 业务key
|
||||
* @param expireTime 过期时间
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static long getNextId(String key, Duration expireTime) {
|
||||
return getIdGenerator(key, expireTime).nextId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定业务key的唯一id字符串
|
||||
*
|
||||
@@ -88,19 +119,8 @@ public class SequenceUtils {
|
||||
* @param stepValue ID步长
|
||||
* @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 过期时间
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static long nextId(String key, Duration expireTime) {
|
||||
return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
|
||||
public static String getNextIdString(String key, Duration expireTime, long initValue, long stepValue) {
|
||||
return Convert.toStr(getNextId(key, expireTime, initValue, stepValue));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,56 +130,222 @@ public class SequenceUtils {
|
||||
* @param expireTime 过期时间
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static String nextIdStr(String key, Duration expireTime) {
|
||||
return String.valueOf(nextId(key, expireTime));
|
||||
public static String getNextIdString(String key, Duration expireTime) {
|
||||
return Convert.toStr(getNextId(key, expireTime));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 yyyyMMdd 开头的唯一id
|
||||
* 获取指定业务key的唯一id字符串 (ID初始值=1,ID步长=1),不足位数自动补零
|
||||
*
|
||||
* @param key 业务key
|
||||
* @param expireTime 过期时间
|
||||
* @param width 位数,不足左补0
|
||||
* @return 补零后的唯一id字符串
|
||||
*/
|
||||
public static String getPaddedNextIdString(String key, Duration expireTime, Integer width) {
|
||||
return StringUtils.leftPad(getNextIdString(key, expireTime), width, '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 yyyyMMdd 格式的唯一id
|
||||
*
|
||||
* @return 唯一id
|
||||
* @deprecated 请使用 {@link #getDateId(String)} 或 {@link #getDateId(String, boolean)}、{@link #getDateId(String, boolean, int)},确保不同业务的ID连续性
|
||||
*/
|
||||
public static String nextIdDate() {
|
||||
return nextIdDate("");
|
||||
@Deprecated
|
||||
public static String getDateId() {
|
||||
return getDateId("");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 prefix + yyyyMMdd 开头的唯一id
|
||||
* 获取 prefix + yyyyMMdd 格式的唯一id
|
||||
*
|
||||
* @param prefix 业务前缀
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static String nextIdDate(String prefix) {
|
||||
// 前缀+日期 构建 prefixKey
|
||||
String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_FORMATTER));
|
||||
// 获取下一个id
|
||||
long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_DAY, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
|
||||
// 返回完整id
|
||||
return StringUtils.format("{}{}", prefixKey, nextId);
|
||||
public static String getDateId(String prefix) {
|
||||
return getDateId(prefix, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 yyyyMMddHHmmss 开头的唯一id
|
||||
* 获取 prefix + yyyyMMdd 格式的唯一id
|
||||
*
|
||||
* @param prefix 业务前缀
|
||||
* @param isWithPrefix id是否携带业务前缀
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static String nextIdDateTime() {
|
||||
return nextIdDateTime("");
|
||||
public static String getDateId(String prefix, boolean isWithPrefix) {
|
||||
return getDateId(prefix, isWithPrefix, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 prefix + yyyyMMddHHmmss 开头的唯一id
|
||||
* 获取 prefix + yyyyMMdd 格式的唯一id (启用ID补位,补位长度 = {@link #DEFAULT_MIN_ID_CAPACITY_BITS})})
|
||||
*
|
||||
* @param prefix 业务前缀
|
||||
* @param isWithPrefix id是否携带业务前缀
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static String getPaddedDateId(String prefix, boolean isWithPrefix) {
|
||||
return getDateId(prefix, isWithPrefix, DEFAULT_MIN_ID_CAPACITY_BITS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 prefix + yyyyMMdd 格式的唯一id
|
||||
*
|
||||
* @param prefix 业务前缀
|
||||
* @param isWithPrefix id是否携带业务前缀
|
||||
* @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位)
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static String getDateId(String prefix, boolean isWithPrefix, int minIdCapacityBits) {
|
||||
return getDateId(prefix, isWithPrefix, minIdCapacityBits, LocalDate.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 prefix + yyyyMMdd 格式的唯一id
|
||||
*
|
||||
* @param prefix 业务前缀
|
||||
* @param isWithPrefix id是否携带业务前缀
|
||||
* @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位)
|
||||
* @param time 时间
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static String getDateId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDate time) {
|
||||
return getDateId(prefix, isWithPrefix, minIdCapacityBits, time, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 prefix + yyyyMMdd 格式的唯一id
|
||||
*
|
||||
* @param prefix 业务前缀
|
||||
* @param isWithPrefix id是否携带业务前缀
|
||||
* @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位)
|
||||
* @param time 时间
|
||||
* @param initValue ID初始值
|
||||
* @param stepValue ID步长
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static String getDateId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDate time, long initValue, long stepValue) {
|
||||
return getDatePatternId(prefix, isWithPrefix, minIdCapacityBits, time, DatePattern.PURE_DATE_FORMATTER, DEFAULT_EXPIRE_TIME_DAY, initValue, stepValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 yyyyMMddHHmmss 格式的唯一id
|
||||
*
|
||||
* @return 唯一id
|
||||
* @deprecated 请使用 {@link #getDateTimeId(String)} 或 {@link #getDateTimeId(String, boolean)}、{@link #getDateTimeId(String, boolean, int)},确保不同业务的ID连续性
|
||||
*/
|
||||
@Deprecated
|
||||
public static String getDateTimeId() {
|
||||
return getDateTimeId("", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 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);
|
||||
public static String getDateTimeId(String prefix) {
|
||||
return getDateTimeId(prefix, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 prefix + yyyyMMddHHmmss 格式的唯一id
|
||||
*
|
||||
* @param prefix 业务前缀
|
||||
* @param isWithPrefix id是否携带业务前缀
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static String getDateTimeId(String prefix, boolean isWithPrefix) {
|
||||
return getDateTimeId(prefix, isWithPrefix, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 prefix + yyyyMMddHHmmss 格式的唯一id (启用ID补位,补位长度 = {@link #DEFAULT_MIN_ID_CAPACITY_BITS})})
|
||||
*
|
||||
* @param prefix 业务前缀
|
||||
* @param isWithPrefix id是否携带业务前缀
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static String getPaddedDateTimeId(String prefix, boolean isWithPrefix) {
|
||||
return getDateTimeId(prefix, isWithPrefix, DEFAULT_MIN_ID_CAPACITY_BITS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 prefix + yyyyMMddHHmmss 格式的唯一id
|
||||
*
|
||||
* @param prefix 业务前缀
|
||||
* @param isWithPrefix id是否携带业务前缀
|
||||
* @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位)
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static String getDateTimeId(String prefix, boolean isWithPrefix, int minIdCapacityBits) {
|
||||
return getDateTimeId(prefix, isWithPrefix, minIdCapacityBits, LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 prefix + yyyyMMddHHmmss 格式的唯一id
|
||||
*
|
||||
* @param prefix 业务前缀
|
||||
* @param isWithPrefix id是否携带业务前缀
|
||||
* @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位)
|
||||
* @param time 时间
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static String getDateTimeId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDateTime time) {
|
||||
return getDateTimeId(prefix, isWithPrefix, minIdCapacityBits, time, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 prefix + yyyyMMddHHmmss 格式的唯一id
|
||||
*
|
||||
* @param prefix 业务前缀
|
||||
* @param isWithPrefix id是否携带业务前缀
|
||||
* @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位)
|
||||
* @param initValue ID初始值
|
||||
* @param stepValue ID步长
|
||||
* @return 唯一id
|
||||
*/
|
||||
public static String getDateTimeId(String prefix, boolean isWithPrefix, int minIdCapacityBits, LocalDateTime time, long initValue, long stepValue) {
|
||||
return getDatePatternId(prefix, isWithPrefix, minIdCapacityBits, time, DatePattern.PURE_DATETIME_FORMATTER, DEFAULT_EXPIRE_TIME_MINUTE, initValue, stepValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定业务key的指定时间格式的ID
|
||||
*
|
||||
* @param prefix 业务前缀
|
||||
* @param isWithPrefix id是否携带业务前缀
|
||||
* @param minIdCapacityBits 最小ID容量位数,小于该位数的ID,左补0(小于等于0表示不启用补位)
|
||||
* @param temporalAccessor 时间访问器
|
||||
* @param timeFormatter 时间格式
|
||||
* @param expireTime 过期时间
|
||||
* @param initValue ID初始值
|
||||
* @param stepValue ID步长
|
||||
* @return 唯一id
|
||||
*/
|
||||
private static String getDatePatternId(String prefix, boolean isWithPrefix, int minIdCapacityBits, TemporalAccessor temporalAccessor, DateTimeFormatter timeFormatter, Duration expireTime, long initValue, long stepValue) {
|
||||
// 时间前缀
|
||||
String timePrefix = timeFormatter.format(temporalAccessor);
|
||||
// 业务前缀 + 时间前缀 构建 prefixKey
|
||||
String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), timePrefix);
|
||||
|
||||
// 获取id,例 -> 1
|
||||
String nextId = getNextIdString(prefixKey, expireTime, initValue, stepValue);
|
||||
|
||||
// minIdCapacityBits 大于0,且 nextId 的长度小于 minIdCapacityBits,则左补0
|
||||
if (minIdCapacityBits > 0 && nextId.length() < minIdCapacityBits) {
|
||||
nextId = StringUtils.leftPad(nextId, minIdCapacityBits, '0');
|
||||
}
|
||||
|
||||
// 是否携带业务前缀
|
||||
if (isWithPrefix) {
|
||||
// 例 -> P202507031
|
||||
// 其中 P 为业务前缀,202507031 为 yyyyMMdd 格式时间, 1 为nextId
|
||||
return StringUtils.format("{}{}", prefixKey, nextId);
|
||||
}
|
||||
// 例 -> 202507031
|
||||
// 其中 202507031 为 yyyyMMdd 格式时间, 1 为nextId
|
||||
return StringUtils.format("{}{}", timePrefix, nextId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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 com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
@@ -16,10 +16,12 @@ import java.util.concurrent.TimeUnit;
|
||||
* Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一)
|
||||
* <p>
|
||||
* 采用 caffeine + redis 多级缓存 优化并发查询效率
|
||||
* <p>
|
||||
* SaTokenDaoBySessionFollowObject 是 SaTokenDao 子集简化了session方法处理
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public class PlusSaTokenDao implements SaTokenDao {
|
||||
public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
||||
|
||||
private static final Cache<String, Object> CAFFEINE = Caffeine.newBuilder()
|
||||
// 设置最后一次写入或访问后经过固定时间过期
|
||||
@@ -51,11 +53,7 @@ public class PlusSaTokenDao implements SaTokenDao {
|
||||
if (timeout == NEVER_EXPIRE) {
|
||||
RedisUtils.setCacheObject(key, value);
|
||||
} else {
|
||||
if (RedisUtils.hasKey(key)) {
|
||||
RedisUtils.setCacheObject(key, value, true);
|
||||
} else {
|
||||
RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
|
||||
}
|
||||
RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
|
||||
}
|
||||
CAFFEINE.invalidate(key);
|
||||
}
|
||||
@@ -76,7 +74,9 @@ public class PlusSaTokenDao implements SaTokenDao {
|
||||
*/
|
||||
@Override
|
||||
public void delete(String key) {
|
||||
RedisUtils.deleteObject(key);
|
||||
if (RedisUtils.deleteObject(key)) {
|
||||
CAFFEINE.invalidate(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,7 +85,8 @@ public class PlusSaTokenDao implements SaTokenDao {
|
||||
@Override
|
||||
public long getTimeout(String 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 +107,19 @@ public class PlusSaTokenDao implements SaTokenDao {
|
||||
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,并设定存活时间 (单位: 秒)
|
||||
*/
|
||||
@@ -118,11 +132,7 @@ public class PlusSaTokenDao implements SaTokenDao {
|
||||
if (timeout == NEVER_EXPIRE) {
|
||||
RedisUtils.setCacheObject(key, object);
|
||||
} else {
|
||||
if (RedisUtils.hasKey(key)) {
|
||||
RedisUtils.setCacheObject(key, object, true);
|
||||
} else {
|
||||
RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout));
|
||||
}
|
||||
RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout));
|
||||
}
|
||||
CAFFEINE.invalidate(key);
|
||||
}
|
||||
@@ -143,7 +153,9 @@ public class PlusSaTokenDao implements SaTokenDao {
|
||||
*/
|
||||
@Override
|
||||
public void deleteObject(String key) {
|
||||
RedisUtils.deleteObject(key);
|
||||
if (RedisUtils.deleteObject(key)) {
|
||||
CAFFEINE.invalidate(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,7 +164,8 @@ public class PlusSaTokenDao implements SaTokenDao {
|
||||
@Override
|
||||
public long getObjectTimeout(String 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 +176,6 @@ public class PlusSaTokenDao implements SaTokenDao {
|
||||
RedisUtils.expire(key, Duration.ofSeconds(timeout));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 搜索数据
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
package org.dromara.common.satoken.core.service;
|
||||
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import org.dromara.common.core.domain.model.LoginUser;
|
||||
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 java.util.ArrayList;
|
||||
@@ -21,13 +27,25 @@ public class SaPermissionImpl implements StpInterface {
|
||||
@Override
|
||||
public List<String> getPermissionList(Object loginId, String loginType) {
|
||||
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());
|
||||
if (userType == UserType.SYS_USER) {
|
||||
return new ArrayList<>(loginUser.getMenuPermission());
|
||||
} else if (userType == UserType.APP_USER) {
|
||||
if (userType == UserType.APP_USER) {
|
||||
// 其他端 自行根据业务编写
|
||||
}
|
||||
return new ArrayList<>();
|
||||
if (CollUtil.isNotEmpty(loginUser.getMenuPermission())) {
|
||||
// SYS_USER 默认返回权限
|
||||
return new ArrayList<>(loginUser.getMenuPermission());
|
||||
} else {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,12 +54,33 @@ public class SaPermissionImpl implements StpInterface {
|
||||
@Override
|
||||
public List<String> getRoleList(Object loginId, String loginType) {
|
||||
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());
|
||||
if (userType == UserType.SYS_USER) {
|
||||
return new ArrayList<>(loginUser.getRolePermission());
|
||||
} else if (userType == UserType.APP_USER) {
|
||||
if (userType == UserType.APP_USER) {
|
||||
// 其他端 自行根据业务编写
|
||||
}
|
||||
return new ArrayList<>();
|
||||
if (CollUtil.isNotEmpty(loginUser.getRolePermission())) {
|
||||
// SYS_USER 默认返回权限
|
||||
return new ArrayList<>(loginUser.getRolePermission());
|
||||
} else {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
private PermissionService getPermissionService() {
|
||||
try {
|
||||
return SpringUtils.getBean(PermissionService.class);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.dromara.common.satoken.utils;
|
||||
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
@@ -47,8 +47,8 @@ public class LoginHelper {
|
||||
* @param loginUser 登录用户信息
|
||||
* @param model 配置参数
|
||||
*/
|
||||
public static void login(LoginUser loginUser, SaLoginModel model) {
|
||||
model = ObjectUtil.defaultIfNull(model, new SaLoginModel());
|
||||
public static void login(LoginUser loginUser, SaLoginParameter model) {
|
||||
model = ObjectUtil.defaultIfNull(model, new SaLoginParameter());
|
||||
StpUtil.login(loginUser.getLoginId(),
|
||||
model.setExtra(TENANT_KEY, loginUser.getTenantId())
|
||||
.setExtra(USER_KEY, loginUser.getUserId())
|
||||
@@ -207,7 +207,8 @@ public class LoginHelper {
|
||||
*/
|
||||
public static boolean isLogin() {
|
||||
try {
|
||||
return getLoginUser() != null;
|
||||
StpUtil.checkLogin();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.SpringUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.security.config.properties.SecurityProperties;
|
||||
import org.dromara.common.security.handler.AllUrlHandler;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -37,6 +37,8 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
public class SecurityConfig implements WebMvcConfigurer {
|
||||
|
||||
private final SecurityProperties securityProperties;
|
||||
@Value("${sse.path}")
|
||||
private String ssePath;
|
||||
|
||||
/**
|
||||
* 注册sa-token的拦截器
|
||||
@@ -54,15 +56,7 @@ public class SecurityConfig implements WebMvcConfigurer {
|
||||
.check(() -> {
|
||||
HttpServletRequest request = ServletUtils.getRequest();
|
||||
// 检查是否登录 是否有token
|
||||
try {
|
||||
StpUtil.checkLogin();
|
||||
} catch (NotLoginException e) {
|
||||
if (request.getRequestURI().contains("sse")) {
|
||||
throw new SseException(e.getMessage(), e.getCode());
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
StpUtil.checkLogin();
|
||||
|
||||
// 检查 header 与 param 里的 clientid 与 token 里的是否一致
|
||||
String headerCid = request.getHeader(LoginHelper.CLIENT_KEY);
|
||||
@@ -84,7 +78,8 @@ public class SecurityConfig implements WebMvcConfigurer {
|
||||
});
|
||||
})).addPathPatterns("/**")
|
||||
// 排除不需要拦截的路径
|
||||
.excludePathPatterns(securityProperties.getExcludes());
|
||||
.excludePathPatterns(securityProperties.getExcludes())
|
||||
.excludePathPatterns(ssePath);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.dromara.common.sensitive.core;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.DesensitizedUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@@ -52,7 +53,7 @@ public enum SensitiveStrategy {
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
USER_ID(s -> String.valueOf(DesensitizedUtil.userId())),
|
||||
USER_ID(s -> Convert.toStr(DesensitizedUtil.userId())),
|
||||
|
||||
/**
|
||||
* 密码
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
package me.zhyd.oauth.request;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import me.zhyd.oauth.cache.AuthStateCache;
|
||||
import me.zhyd.oauth.config.AuthConfig;
|
||||
import me.zhyd.oauth.config.AuthSource;
|
||||
import me.zhyd.oauth.enums.AuthResponseStatus;
|
||||
import me.zhyd.oauth.enums.AuthUserGender;
|
||||
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.utils.HttpUtils;
|
||||
import me.zhyd.oauth.utils.StringUtils;
|
||||
import me.zhyd.oauth.utils.UrlBuilder;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 企业微信登录父类
|
||||
* </p>
|
||||
*
|
||||
* @author liguanhua (347826496(a)qq.com)
|
||||
* @since 1.15.9
|
||||
*/
|
||||
public abstract class AbstractAuthWeChatEnterpriseRequest extends AuthDefaultRequest {
|
||||
|
||||
public AbstractAuthWeChatEnterpriseRequest(AuthConfig config, AuthSource source) {
|
||||
super(config,source);
|
||||
}
|
||||
|
||||
|
||||
public AbstractAuthWeChatEnterpriseRequest(AuthConfig config, AuthSource source, AuthStateCache authStateCache) {
|
||||
super(config, source, authStateCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthToken getAccessToken(AuthCallback authCallback) {
|
||||
String response = doGetAuthorizationCode(accessTokenUrl(null));
|
||||
|
||||
JSONObject object = this.checkResponse(response);
|
||||
|
||||
return AuthToken.builder()
|
||||
.accessToken(object.getString("access_token"))
|
||||
.expireIn(object.getIntValue("expires_in"))
|
||||
.code(authCallback.getCode())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthUser getUserInfo(AuthToken authToken) {
|
||||
String response = doGetUserInfo(authToken);
|
||||
JSONObject object = this.checkResponse(response);
|
||||
|
||||
// 返回 OpenId 或其他,均代表非当前企业用户,不支持
|
||||
// https://github.com/justauth/JustAuth/issues/227 修复bug
|
||||
if (!object.containsKey("userid")) {
|
||||
throw new AuthException(AuthResponseStatus.UNIDENTIFIED_PLATFORM, source);
|
||||
}
|
||||
String userId = object.getString("userid");
|
||||
String userTicket = object.getString("user_ticket");
|
||||
JSONObject userDetail = getUserDetail(authToken.getAccessToken(), userId, userTicket);
|
||||
|
||||
return AuthUser.builder()
|
||||
.rawUserInfo(userDetail)
|
||||
.username(userDetail.getString("name"))
|
||||
.nickname(userDetail.getString("alias"))
|
||||
.avatar(userDetail.getString("avatar"))
|
||||
.location(userDetail.getString("address"))
|
||||
.email(userDetail.getString("email"))
|
||||
.uuid(userId)
|
||||
.gender(AuthUserGender.getWechatRealGender(userDetail.getString("gender")))
|
||||
.token(authToken)
|
||||
.source(source.toString())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验请求结果
|
||||
*
|
||||
* @param response 请求结果
|
||||
* @return 如果请求结果正常,则返回JSONObject
|
||||
*/
|
||||
private JSONObject checkResponse(String response) {
|
||||
JSONObject object = JSONObject.parseObject(response);
|
||||
|
||||
if (object.containsKey("errcode") && object.getIntValue("errcode") != 0) {
|
||||
throw new AuthException(object.getString("errmsg"), source);
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回获取accessToken的url
|
||||
*
|
||||
* @param code 授权码
|
||||
* @return 返回获取accessToken的url
|
||||
*/
|
||||
@Override
|
||||
protected String accessTokenUrl(String code) {
|
||||
return UrlBuilder.fromBaseUrl(source.accessToken())
|
||||
.queryParam("corpid", config.getClientId())
|
||||
.queryParam("corpsecret", config.getClientSecret())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回获取userInfo的url
|
||||
*
|
||||
* @param authToken 用户授权后的token
|
||||
* @return 返回获取userInfo的url
|
||||
*/
|
||||
@Override
|
||||
protected String userInfoUrl(AuthToken authToken) {
|
||||
return UrlBuilder.fromBaseUrl(source.userInfo())
|
||||
.queryParam("access_token", authToken.getAccessToken())
|
||||
.queryParam("code", authToken.getCode())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户详情
|
||||
*
|
||||
* @param accessToken accessToken
|
||||
* @param userId 企业内用户id
|
||||
* @param userTicket 成员票据,用于获取用户信息或敏感信息
|
||||
* @return 用户详情
|
||||
*/
|
||||
private JSONObject getUserDetail(String accessToken, String userId, String userTicket) {
|
||||
// 用户基础信息
|
||||
String userInfoUrl = UrlBuilder.fromBaseUrl("https://qyapi.weixin.qq.com/cgi-bin/user/get")
|
||||
.queryParam("access_token", accessToken)
|
||||
.queryParam("userid", userId)
|
||||
.build();
|
||||
String userInfoResponse = new HttpUtils(config.getHttpConfig()).get(userInfoUrl).getBody();
|
||||
JSONObject userInfo = checkResponse(userInfoResponse);
|
||||
|
||||
// 用户敏感信息
|
||||
if (StringUtils.isNotEmpty(userTicket)) {
|
||||
String userDetailUrl = UrlBuilder.fromBaseUrl("https://qyapi.weixin.qq.com/cgi-bin/auth/getuserdetail")
|
||||
.queryParam("access_token", accessToken)
|
||||
.build();
|
||||
JSONObject param = new JSONObject();
|
||||
param.put("user_ticket", userTicket);
|
||||
String userDetailResponse = new HttpUtils(config.getHttpConfig()).post(userDetailUrl, param.toJSONString()).getBody();
|
||||
JSONObject userDetail = checkResponse(userDetailResponse);
|
||||
|
||||
userInfo.putAll(userDetail);
|
||||
}
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.social.config.properties.SocialLoginConfigProperties;
|
||||
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.topiam.AuthTopIamRequest;
|
||||
|
||||
@@ -42,7 +43,7 @@ public class SocialUtils {
|
||||
.redirectUri(obj.getRedirectUri())
|
||||
.scopes(obj.getScopes());
|
||||
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 "github" -> new AuthGithubRequest(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 "stack_overflow" -> new AuthStackOverflowRequest(builder.stackOverflowKey(obj.getStackOverflowKey()).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 "wechat_mp" -> new AuthWeChatMpRequest(builder.build(), STATE_CACHE);
|
||||
case "aliyun" -> new AuthAliyunRequest(builder.build(), STATE_CACHE);
|
||||
case "maxkey" -> new AuthMaxKeyRequest(builder.build(), STATE_CACHE);
|
||||
case "topiam" -> new AuthTopIamRequest(builder.build(), STATE_CACHE);
|
||||
case "gitea" -> new AuthGiteaRequest(builder.build(), STATE_CACHE);
|
||||
default -> throw new AuthException("未获取到有效的Auth配置");
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.sse.core.SseEmitterManager;
|
||||
import org.dromara.common.sse.dto.SseMessageDto;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -14,8 +13,6 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* SSE 控制器
|
||||
*
|
||||
@@ -33,6 +30,9 @@ public class SseController implements DisposableBean {
|
||||
*/
|
||||
@GetMapping(value = "${sse.path}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public SseEmitter connect() {
|
||||
if (!StpUtil.isLogin()) {
|
||||
return null;
|
||||
}
|
||||
String tokenValue = StpUtil.getTokenValue();
|
||||
Long userId = LoginHelper.getUserId();
|
||||
return sseEmitterManager.connect(userId, tokenValue);
|
||||
@@ -50,31 +50,32 @@ public class SseController implements DisposableBean {
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 向特定用户发送消息
|
||||
*
|
||||
* @param userId 目标用户的 ID
|
||||
* @param msg 要发送的消息内容
|
||||
*/
|
||||
@GetMapping(value = "${sse.path}/send")
|
||||
public R<Void> send(Long userId, String msg) {
|
||||
SseMessageDto dto = new SseMessageDto();
|
||||
dto.setUserIds(List.of(userId));
|
||||
dto.setMessage(msg);
|
||||
sseEmitterManager.publishMessage(dto);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 向所有用户发送消息
|
||||
*
|
||||
* @param msg 要发送的消息内容
|
||||
*/
|
||||
@GetMapping(value = "${sse.path}/sendAll")
|
||||
public R<Void> send(String msg) {
|
||||
sseEmitterManager.publishAll(msg);
|
||||
return R.ok();
|
||||
}
|
||||
// 以下为demo仅供参考 禁止使用 请在业务逻辑中使用工具发送而不是用接口发送
|
||||
// /**
|
||||
// * 向特定用户发送消息
|
||||
// *
|
||||
// * @param userId 目标用户的 ID
|
||||
// * @param msg 要发送的消息内容
|
||||
// */
|
||||
// @GetMapping(value = "${sse.path}/send")
|
||||
// public R<Void> send(Long userId, String msg) {
|
||||
// SseMessageDto dto = new SseMessageDto();
|
||||
// dto.setUserIds(List.of(userId));
|
||||
// dto.setMessage(msg);
|
||||
// sseEmitterManager.publishMessage(dto);
|
||||
// return R.ok();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 向所有用户发送消息
|
||||
// *
|
||||
// * @param msg 要发送的消息内容
|
||||
// */
|
||||
// @GetMapping(value = "${sse.path}/sendAll")
|
||||
// public R<Void> send(String msg) {
|
||||
// sseEmitterManager.publishAll(msg);
|
||||
// return R.ok();
|
||||
// }
|
||||
|
||||
/**
|
||||
* 清理资源。此方法目前不执行任何操作,但避免因未实现而导致错误
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user