mirror of
https://github.com/dromara/RuoYi-Vue-Plus.git
synced 2026-03-02 10:14:27 +08:00
Compare commits
420 Commits
v5.4.0
...
futuer/boo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39781fbe7f | ||
|
|
819234610c | ||
|
|
391b674945 | ||
|
|
ea32d9c4c8 | ||
|
|
4fb9858190 | ||
|
|
3b5d7eba37 | ||
|
|
529f614dae | ||
|
|
ac6fe13bcf | ||
|
|
4468656a97 | ||
|
|
310bffef54 | ||
|
|
240a581311 | ||
|
|
6c1eef7ff4 | ||
|
|
efae1c914b | ||
|
|
7c3a5c4a1d | ||
|
|
ed9de38c6f | ||
|
|
28d4c88387 | ||
|
|
c656f3340d | ||
|
|
cd0ee3f016 | ||
|
|
48ea66cb1a | ||
|
|
34c3b81190 | ||
|
|
3797d9b8ed | ||
|
|
59e0e6ab95 | ||
|
|
c2da068482 | ||
|
|
1bb597b855 | ||
|
|
348d7fc534 | ||
|
|
8d19744cbc | ||
|
|
32dba43ec4 | ||
|
|
8666815963 | ||
|
|
eec3c44866 | ||
|
|
fb631283ca | ||
|
|
9c0636978f | ||
|
|
fc00210a39 | ||
|
|
ff299cea86 | ||
|
|
7ffd918338 | ||
|
|
19ef199da7 | ||
|
|
b8571e9ca1 | ||
|
|
660757cb71 | ||
|
|
4072b080fe | ||
|
|
76218091ad | ||
|
|
95c9e37797 | ||
|
|
aa277b373b | ||
|
|
79d9f47053 | ||
|
|
f984f08a14 | ||
|
|
161b52d8d7 | ||
|
|
befabc61de | ||
|
|
5b82c12e17 | ||
|
|
6f94095bb0 | ||
|
|
2d4685ac5f | ||
|
|
7f9e4e14f0 | ||
|
|
145b903185 | ||
|
|
c5777c01c1 | ||
|
|
55098339d4 | ||
|
|
469274d9b1 | ||
|
|
596e83701a | ||
|
|
8a87c7aa4e | ||
|
|
b4467aa8e9 | ||
|
|
5de3114f05 | ||
|
|
8d4fdd9fc8 | ||
|
|
459e9caf14 | ||
|
|
0940ba6762 | ||
|
|
d8ed23f227 | ||
|
|
948eba6566 | ||
|
|
1a14bdf256 | ||
|
|
bbc684b335 | ||
|
|
2b8f4e1d2c | ||
|
|
2f4e89ee42 | ||
|
|
d634c2a292 | ||
|
|
8b97e7bc53 | ||
|
|
874ad7c9b7 | ||
|
|
89d6f6f247 | ||
|
|
1324a1cb16 | ||
|
|
961bca462e | ||
|
|
496df8494e | ||
|
|
2f1f9689e0 | ||
|
|
8110413fdf | ||
|
|
c1f64d3450 | ||
|
|
cb00f4c9c1 | ||
|
|
79512c69b2 | ||
|
|
a5fb128f11 | ||
|
|
8a04e3c88f | ||
|
|
dac447b76f | ||
|
|
35a9e4c8e8 | ||
|
|
0d87c12d3c | ||
|
|
f20a0c4342 | ||
|
|
6c8d637bd2 | ||
|
|
20e9957db2 | ||
|
|
9baded9326 | ||
|
|
b5902debb6 | ||
|
|
bcd5bb0f86 | ||
|
|
1a461f7d3d | ||
|
|
e23d99d85b | ||
|
|
f07c20afab | ||
|
|
420553eaa6 | ||
|
|
1d8d93eaa3 | ||
|
|
5f0d09fd45 | ||
|
|
1c2b7d7017 | ||
|
|
5fb2890167 | ||
|
|
1165c8dc06 | ||
|
|
ee09377997 | ||
|
|
1921b22a57 | ||
|
|
8718989c52 | ||
|
|
36069cd0e4 | ||
|
|
39b19ac361 | ||
|
|
279488e7ed | ||
|
|
e28e15d943 | ||
|
|
b44b5551e3 | ||
|
|
0cb4b35f53 | ||
|
|
9571e71707 | ||
|
|
dfa7d88255 | ||
|
|
8d29091afa | ||
|
|
116fa0053d | ||
|
|
0c08455b32 | ||
|
|
581203ba15 | ||
|
|
50fa220471 | ||
|
|
287effdc6d | ||
|
|
1d4fcf737a | ||
|
|
e672a3bc6c | ||
|
|
ec703ceeb8 | ||
|
|
6aa4e83413 | ||
|
|
65d677ac90 | ||
|
|
aca2b6d498 | ||
|
|
dd5f72cc99 | ||
|
|
b1d3d87360 | ||
|
|
e67fc5ebd4 | ||
|
|
6a2c74537e | ||
|
|
041e226059 | ||
|
|
0418b6c6ff | ||
|
|
c9272acce2 | ||
|
|
8d51adee10 | ||
|
|
6d4cc28dcd | ||
|
|
fc35a1469f | ||
|
|
f70a37c050 | ||
|
|
181f461984 | ||
|
|
75618347fa | ||
|
|
5a57e6b835 | ||
|
|
d1d47d2599 | ||
|
|
f35938a068 | ||
|
|
888c14615d | ||
|
|
fa6c9696f0 | ||
|
|
37038449ab | ||
|
|
9bff358afd | ||
|
|
d2a45156a2 | ||
|
|
9df0a8de1c | ||
|
|
5ea8d8c950 | ||
|
|
3318109044 | ||
|
|
aa1f89e253 | ||
|
|
35c77403d6 | ||
|
|
603fb7b92d | ||
|
|
6cf0c79433 | ||
|
|
3934e119d6 | ||
|
|
33a6a21fdf | ||
|
|
7800b1259f | ||
|
|
3623fc33d9 | ||
|
|
f8612eb52e | ||
|
|
8d32b0311a | ||
|
|
60bcd2d6e9 | ||
|
|
5ccb511064 | ||
|
|
78baf6497a | ||
|
|
0719e53f01 | ||
|
|
5f2c4205a5 | ||
|
|
2fe4c96706 | ||
|
|
5c634940c2 | ||
|
|
6036f8750b | ||
|
|
dbcd8f58eb | ||
|
|
8905e232e5 | ||
|
|
4f15158486 | ||
|
|
d2413abd5c | ||
|
|
f7ffadeaff | ||
|
|
f9eec856e7 | ||
|
|
62562650fe | ||
|
|
df171097c3 | ||
|
|
1977aabc9a | ||
|
|
483c4e6d0a | ||
|
|
f616c6931c | ||
|
|
26e10293f5 | ||
|
|
60e578f763 | ||
|
|
5cd4d8ca11 | ||
|
|
41a6230b6e | ||
|
|
effda4f6e8 | ||
|
|
af4c38e439 | ||
|
|
fafa8cd573 | ||
|
|
8909b8a7d4 | ||
|
|
8ae9bde731 | ||
|
|
a703cb2ad1 | ||
|
|
a918b880d6 | ||
|
|
e795e315eb | ||
|
|
81869cfeb3 | ||
|
|
fc6f61bc95 | ||
|
|
d44e45ad3b | ||
|
|
00ed9ddd10 | ||
|
|
341fc144a1 | ||
|
|
b6b1b2de18 | ||
|
|
c19f2b9e4e | ||
|
|
3a11f18656 | ||
|
|
5a43212ccc | ||
|
|
f4cfd1c913 | ||
|
|
26ce8f30c9 | ||
|
|
2258962770 | ||
|
|
655e84012c | ||
|
|
f683ef00b8 | ||
|
|
424b2ea164 | ||
|
|
7bb4838132 | ||
|
|
20516758ea | ||
|
|
2d5f84ebc2 | ||
|
|
6bc28e41de | ||
|
|
a4fb3fadaf | ||
|
|
cfa67fcd8c | ||
|
|
e5e8d305d2 | ||
|
|
9d0084409e | ||
|
|
ee02f46dfd | ||
|
|
25de0b3530 | ||
|
|
aa76859a05 | ||
|
|
71b70a59fe | ||
|
|
05c9528549 | ||
|
|
1feb2a3861 | ||
|
|
237e78e80c | ||
|
|
ffc3dcaec9 | ||
|
|
a94e474069 | ||
|
|
40a0e57870 | ||
|
|
c01ed34602 | ||
|
|
26a99003d2 | ||
|
|
93c886d3ed | ||
|
|
9e1027690b | ||
|
|
cc120c06fd | ||
|
|
3827da078a | ||
|
|
70d3505b94 | ||
|
|
a39a69cac5 | ||
|
|
1dbce3ab7c | ||
|
|
9742b1b596 | ||
|
|
d98d11ae2d | ||
|
|
6742dcb33e | ||
|
|
09a51478a5 | ||
|
|
f02601ab2c | ||
|
|
ac56ca0e81 | ||
|
|
0fcf77e2ed | ||
|
|
0f0a3a181e | ||
|
|
e24e2c51e4 | ||
|
|
2b0dd82d3d | ||
|
|
b97f711eb4 | ||
|
|
0250ca4eb8 | ||
|
|
23338995d7 | ||
|
|
84fd02e7d8 | ||
|
|
ae5bec994d | ||
|
|
8f3a1b589e | ||
|
|
ad6b3d4b3f | ||
|
|
e2801037cf | ||
|
|
65061f17fe | ||
|
|
d0f4d93615 | ||
|
|
5d69832423 | ||
|
|
0c1e39ea14 | ||
|
|
a39bc870d1 | ||
|
|
7357912681 | ||
|
|
901992674e | ||
|
|
7ceb85ffa0 | ||
|
|
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 |
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/mvnw text eol=lf
|
||||
*.cmd text eol=crlf
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -38,6 +38,7 @@ nbdist/
|
||||
######################################################################
|
||||
# Others
|
||||
*.log
|
||||
*.log.gz
|
||||
*.xml.versionsBackup
|
||||
*.swp
|
||||
|
||||
|
||||
8
.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
8
.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
wrapperVersion=3.3.4
|
||||
distributionType=only-script
|
||||
# Maven 3.9.X
|
||||
distributionUrl=https://mirrors.huaweicloud.com/apache/maven/maven-3/3.9.12/binaries/apache-maven-3.9.12-bin.zip
|
||||
# Maven 4.0
|
||||
#distributionUrl=https://mirrors.huaweicloud.com/apache/maven/maven-4/4.0.0-rc-5/binaries/apache-maven-4.0.0-rc-5-bin.zip
|
||||
# 分发类型为 only-script 或 bin 可以不需要该项(因为用不到),具体参考:https://maven.apache.org/tools/wrapper/maven-wrapper-distribution/index.html
|
||||
#wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar
|
||||
@@ -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.4.0" />
|
||||
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.5.3" />
|
||||
<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.4.0" />
|
||||
<option name="imageTag" value="ruoyi/ruoyi-server:5.5.3" />
|
||||
<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.4.0" />
|
||||
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.5.3" />
|
||||
<option name="buildOnly" value="true" />
|
||||
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
|
||||
</settings>
|
||||
|
||||
16
README.md
16
README.md
@@ -8,10 +8,9 @@
|
||||
[](https://github.com/dromara/RuoYi-Vue-Plus)
|
||||
[](https://gitcode.com/dromara/RuoYi-Vue-Plus)
|
||||
[](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)
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
|
||||
@@ -27,17 +26,20 @@
|
||||
> 成员前端项目地址: 基于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) 文档在华为云上如果打不开大概率是DNS问题 可以尝试切换网络等方式(或者科学上网)
|
||||
> 文档地址: [plus-doc](https://plus-doc.dromara.org) 国内加速: [plus-doc.top](https://plus-doc.top)
|
||||
|
||||
## 赞助商
|
||||
|
||||
MaxKey 业界领先单点登录产品 - https://gitee.com/dromara/MaxKey <br>
|
||||
CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
|
||||
数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br>
|
||||
数舵科技 软件定制开发APP小程序等 - https://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>
|
||||
[如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group)
|
||||
aizuda flowlong 工作流 - https://gitee.com/aizuda/flowlong <br>
|
||||
Ruoyi-Plus-Uniapp - https://ruoyi.plus <br>
|
||||
Topiam IAM/IDaaS身份管理平台 - https://www.topiam.cn/ <br>
|
||||
|
||||
[如何成为赞助商 加群联系作者详谈 每日PV2500-3000 IP1700-2500](https://plus-doc.dromara.org/#/common/add_group)
|
||||
|
||||
# 本框架与RuoYi的功能差异
|
||||
|
||||
|
||||
295
mvnw
vendored
Normal file
295
mvnw
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
#!/bin/sh
|
||||
# ----------------------------------------------------------------------------
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Apache Maven Wrapper startup batch script, version 3.3.4
|
||||
#
|
||||
# Optional ENV vars
|
||||
# -----------------
|
||||
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
|
||||
# MVNW_REPOURL - repo url base for downloading maven distribution
|
||||
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
set -euf
|
||||
[ "${MVNW_VERBOSE-}" != debug ] || set -x
|
||||
|
||||
# OS specific support.
|
||||
native_path() { printf %s\\n "$1"; }
|
||||
case "$(uname)" in
|
||||
CYGWIN* | MINGW*)
|
||||
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
|
||||
native_path() { cygpath --path --windows "$1"; }
|
||||
;;
|
||||
esac
|
||||
|
||||
# set JAVACMD and JAVACCMD
|
||||
set_java_home() {
|
||||
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
|
||||
if [ -n "${JAVA_HOME-}" ]; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACCMD="$JAVA_HOME/jre/sh/javac"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACCMD="$JAVA_HOME/bin/javac"
|
||||
|
||||
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
|
||||
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
|
||||
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
JAVACMD="$(
|
||||
'set' +e
|
||||
'unset' -f command 2>/dev/null
|
||||
'command' -v java
|
||||
)" || :
|
||||
JAVACCMD="$(
|
||||
'set' +e
|
||||
'unset' -f command 2>/dev/null
|
||||
'command' -v javac
|
||||
)" || :
|
||||
|
||||
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
|
||||
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# hash string like Java String::hashCode
|
||||
hash_string() {
|
||||
str="${1:-}" h=0
|
||||
while [ -n "$str" ]; do
|
||||
char="${str%"${str#?}"}"
|
||||
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
|
||||
str="${str#?}"
|
||||
done
|
||||
printf %x\\n $h
|
||||
}
|
||||
|
||||
verbose() { :; }
|
||||
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
|
||||
|
||||
die() {
|
||||
printf %s\\n "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
trim() {
|
||||
# MWRAPPER-139:
|
||||
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
|
||||
# Needed for removing poorly interpreted newline sequences when running in more
|
||||
# exotic environments such as mingw bash on Windows.
|
||||
printf "%s" "${1}" | tr -d '[:space:]'
|
||||
}
|
||||
|
||||
scriptDir="$(dirname "$0")"
|
||||
scriptName="$(basename "$0")"
|
||||
|
||||
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
|
||||
while IFS="=" read -r key value; do
|
||||
case "${key-}" in
|
||||
distributionUrl) distributionUrl=$(trim "${value-}") ;;
|
||||
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
|
||||
esac
|
||||
done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||
|
||||
case "${distributionUrl##*/}" in
|
||||
maven-mvnd-*bin.*)
|
||||
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
|
||||
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
|
||||
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
|
||||
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
|
||||
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
|
||||
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
|
||||
*)
|
||||
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
|
||||
distributionPlatform=linux-amd64
|
||||
;;
|
||||
esac
|
||||
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
|
||||
;;
|
||||
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
|
||||
*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
|
||||
esac
|
||||
|
||||
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
|
||||
distributionUrlName="${distributionUrl##*/}"
|
||||
distributionUrlNameMain="${distributionUrlName%.*}"
|
||||
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
|
||||
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
|
||||
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
|
||||
|
||||
exec_maven() {
|
||||
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
|
||||
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
|
||||
}
|
||||
|
||||
if [ -d "$MAVEN_HOME" ]; then
|
||||
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||
exec_maven "$@"
|
||||
fi
|
||||
|
||||
case "${distributionUrl-}" in
|
||||
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
|
||||
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
|
||||
esac
|
||||
|
||||
# prepare tmp dir
|
||||
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
|
||||
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
|
||||
trap clean HUP INT TERM EXIT
|
||||
else
|
||||
die "cannot create temp dir"
|
||||
fi
|
||||
|
||||
mkdir -p -- "${MAVEN_HOME%/*}"
|
||||
|
||||
# Download and Install Apache Maven
|
||||
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||
verbose "Downloading from: $distributionUrl"
|
||||
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||
|
||||
# select .zip or .tar.gz
|
||||
if ! command -v unzip >/dev/null; then
|
||||
distributionUrl="${distributionUrl%.zip}.tar.gz"
|
||||
distributionUrlName="${distributionUrl##*/}"
|
||||
fi
|
||||
|
||||
# verbose opt
|
||||
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
|
||||
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
|
||||
|
||||
# normalize http auth
|
||||
case "${MVNW_PASSWORD:+has-password}" in
|
||||
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||
esac
|
||||
|
||||
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
|
||||
verbose "Found wget ... using wget"
|
||||
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
|
||||
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
|
||||
verbose "Found curl ... using curl"
|
||||
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
|
||||
elif set_java_home; then
|
||||
verbose "Falling back to use Java to download"
|
||||
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
|
||||
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||
cat >"$javaSource" <<-END
|
||||
public class Downloader extends java.net.Authenticator
|
||||
{
|
||||
protected java.net.PasswordAuthentication getPasswordAuthentication()
|
||||
{
|
||||
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
|
||||
}
|
||||
public static void main( String[] args ) throws Exception
|
||||
{
|
||||
setDefault( new Downloader() );
|
||||
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
|
||||
}
|
||||
}
|
||||
END
|
||||
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
|
||||
verbose " - Compiling Downloader.java ..."
|
||||
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
|
||||
verbose " - Running Downloader.java ..."
|
||||
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
|
||||
fi
|
||||
|
||||
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||
if [ -n "${distributionSha256Sum-}" ]; then
|
||||
distributionSha256Result=false
|
||||
if [ "$MVN_CMD" = mvnd.sh ]; then
|
||||
echo "Checksum validation is not supported for maven-mvnd." >&2
|
||||
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||
exit 1
|
||||
elif command -v sha256sum >/dev/null; then
|
||||
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
|
||||
distributionSha256Result=true
|
||||
fi
|
||||
elif command -v shasum >/dev/null; then
|
||||
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
|
||||
distributionSha256Result=true
|
||||
fi
|
||||
else
|
||||
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
|
||||
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ $distributionSha256Result = false ]; then
|
||||
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
|
||||
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# unzip and move
|
||||
if command -v unzip >/dev/null; then
|
||||
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
|
||||
else
|
||||
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
|
||||
fi
|
||||
|
||||
# Find the actual extracted directory name (handles snapshots where filename != directory name)
|
||||
actualDistributionDir=""
|
||||
|
||||
# First try the expected directory name (for regular distributions)
|
||||
if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
|
||||
if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
|
||||
actualDistributionDir="$distributionUrlNameMain"
|
||||
fi
|
||||
fi
|
||||
|
||||
# If not found, search for any directory with the Maven executable (for snapshots)
|
||||
if [ -z "$actualDistributionDir" ]; then
|
||||
# enable globbing to iterate over items
|
||||
set +f
|
||||
for dir in "$TMP_DOWNLOAD_DIR"/*; do
|
||||
if [ -d "$dir" ]; then
|
||||
if [ -f "$dir/bin/$MVN_CMD" ]; then
|
||||
actualDistributionDir="$(basename "$dir")"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
set -f
|
||||
fi
|
||||
|
||||
if [ -z "$actualDistributionDir" ]; then
|
||||
verbose "Contents of $TMP_DOWNLOAD_DIR:"
|
||||
verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
|
||||
die "Could not find Maven distribution directory in extracted archive"
|
||||
fi
|
||||
|
||||
verbose "Found extracted Maven distribution directory: $actualDistributionDir"
|
||||
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
|
||||
mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
|
||||
|
||||
clean || :
|
||||
exec_maven "$@"
|
||||
189
mvnw.cmd
vendored
Normal file
189
mvnw.cmd
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
<# : batch portion
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. You may obtain a copy of the License at
|
||||
@REM
|
||||
@REM http://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM
|
||||
@REM Unless required by applicable law or agreed to in writing,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Apache Maven Wrapper startup batch script, version 3.3.4
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM MVNW_REPOURL - repo url base for downloading maven distribution
|
||||
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
|
||||
@SET __MVNW_CMD__=
|
||||
@SET __MVNW_ERROR__=
|
||||
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
|
||||
@SET PSModulePath=
|
||||
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
|
||||
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
|
||||
)
|
||||
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
|
||||
@SET __MVNW_PSMODULEP_SAVE=
|
||||
@SET __MVNW_ARG0_NAME__=
|
||||
@SET MVNW_USERNAME=
|
||||
@SET MVNW_PASSWORD=
|
||||
@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
|
||||
@echo Cannot start maven from wrapper >&2 && exit /b 1
|
||||
@GOTO :EOF
|
||||
: end batch / begin powershell #>
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
if ($env:MVNW_VERBOSE -eq "true") {
|
||||
$VerbosePreference = "Continue"
|
||||
}
|
||||
|
||||
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
|
||||
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
|
||||
if (!$distributionUrl) {
|
||||
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||
}
|
||||
|
||||
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
|
||||
"maven-mvnd-*" {
|
||||
$USE_MVND = $true
|
||||
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
|
||||
$MVN_CMD = "mvnd.cmd"
|
||||
break
|
||||
}
|
||||
default {
|
||||
$USE_MVND = $false
|
||||
$MVN_CMD = $script -replace '^mvnw','mvn'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||
if ($env:MVNW_REPOURL) {
|
||||
$MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
|
||||
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
|
||||
}
|
||||
$distributionUrlName = $distributionUrl -replace '^.*/',''
|
||||
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
|
||||
|
||||
$MAVEN_M2_PATH = "$HOME/.m2"
|
||||
if ($env:MAVEN_USER_HOME) {
|
||||
$MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
|
||||
New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
|
||||
}
|
||||
|
||||
$MAVEN_WRAPPER_DISTS = $null
|
||||
if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
|
||||
$MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
|
||||
} else {
|
||||
$MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
|
||||
}
|
||||
|
||||
$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
|
||||
$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
|
||||
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
|
||||
|
||||
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
|
||||
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
||||
exit $?
|
||||
}
|
||||
|
||||
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
|
||||
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
|
||||
}
|
||||
|
||||
# prepare tmp dir
|
||||
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
|
||||
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
|
||||
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
|
||||
trap {
|
||||
if ($TMP_DOWNLOAD_DIR.Exists) {
|
||||
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||
}
|
||||
}
|
||||
|
||||
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
|
||||
|
||||
# Download and Install Apache Maven
|
||||
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||
Write-Verbose "Downloading from: $distributionUrl"
|
||||
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||
|
||||
$webclient = New-Object System.Net.WebClient
|
||||
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
|
||||
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
|
||||
}
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
|
||||
|
||||
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
|
||||
if ($distributionSha256Sum) {
|
||||
if ($USE_MVND) {
|
||||
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
|
||||
}
|
||||
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
|
||||
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
|
||||
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
|
||||
}
|
||||
}
|
||||
|
||||
# unzip and move
|
||||
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
|
||||
|
||||
# Find the actual extracted directory name (handles snapshots where filename != directory name)
|
||||
$actualDistributionDir = ""
|
||||
|
||||
# First try the expected directory name (for regular distributions)
|
||||
$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
|
||||
$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
|
||||
if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
|
||||
$actualDistributionDir = $distributionUrlNameMain
|
||||
}
|
||||
|
||||
# If not found, search for any directory with the Maven executable (for snapshots)
|
||||
if (!$actualDistributionDir) {
|
||||
Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
|
||||
$testPath = Join-Path $_.FullName "bin/$MVN_CMD"
|
||||
if (Test-Path -Path $testPath -PathType Leaf) {
|
||||
$actualDistributionDir = $_.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$actualDistributionDir) {
|
||||
Write-Error "Could not find Maven distribution directory in extracted archive"
|
||||
}
|
||||
|
||||
Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
|
||||
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
|
||||
try {
|
||||
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
|
||||
} catch {
|
||||
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
|
||||
Write-Error "fail to move MAVEN_HOME"
|
||||
}
|
||||
} finally {
|
||||
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||
}
|
||||
|
||||
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
||||
118
pom.xml
118
pom.xml
@@ -13,50 +13,52 @@
|
||||
<description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
|
||||
|
||||
<properties>
|
||||
<revision>5.4.0</revision>
|
||||
<spring-boot.version>3.4.6</spring-boot.version>
|
||||
<revision>5.5.3</revision>
|
||||
<spring-boot.version>4.0.3</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.8</springdoc.version>
|
||||
<java.version>21</java.version>
|
||||
<mybatis.version>3.5.19</mybatis.version>
|
||||
<springdoc.version>3.0.1</springdoc.version>
|
||||
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
|
||||
<fastexcel.version>1.2.0</fastexcel.version>
|
||||
<fesod.version>2.0.1-incubating</fesod.version>
|
||||
<velocity.version>2.3</velocity.version>
|
||||
<satoken.version>1.42.0</satoken.version>
|
||||
<mybatis-plus.version>3.5.12</mybatis-plus.version>
|
||||
<satoken.version>1.44.0</satoken.version>
|
||||
<mybatis-plus.version>3.5.16</mybatis-plus.version>
|
||||
<p6spy.version>3.9.1</p6spy.version>
|
||||
<hutool.version>5.8.35</hutool.version>
|
||||
<spring-boot-admin.version>3.4.7</spring-boot-admin.version>
|
||||
<redisson.version>3.45.1</redisson.version>
|
||||
<hutool.version>5.8.43</hutool.version>
|
||||
<spring-boot-admin.version>4.0.1</spring-boot-admin.version>
|
||||
<redisson.version>4.2.0</redisson.version>
|
||||
<lock4j.version>2.2.7</lock4j.version>
|
||||
<dynamic-ds.version>4.3.1</dynamic-ds.version>
|
||||
<snailjob.version>1.5.0</snailjob.version>
|
||||
<mapstruct-plus.version>1.4.8</mapstruct-plus.version>
|
||||
<dynamic-ds.version>4.5.0</dynamic-ds.version>
|
||||
<snailjob.version>1.9.0</snailjob.version>
|
||||
<mapstruct-plus.version>1.5.0</mapstruct-plus.version>
|
||||
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
|
||||
<lombok.version>1.18.36</lombok.version>
|
||||
<bouncycastle.version>1.80</bouncycastle.version>
|
||||
<lombok.version>1.18.42</lombok.version>
|
||||
<bouncycastle.version>1.83</bouncycastle.version>
|
||||
<justauth.version>1.16.7</justauth.version>
|
||||
<!-- 离线IP地址定位库 -->
|
||||
<ip2region.version>2.7.0</ip2region.version>
|
||||
|
||||
<ip2region.version>3.3.4</ip2region.version>
|
||||
<!-- OSS 配置 -->
|
||||
<aws.sdk.version>2.28.22</aws.sdk.version>
|
||||
<aws.sdk.version>2.41.34</aws.sdk.version>
|
||||
<!-- SMS 配置 -->
|
||||
<sms4j.version>3.3.4</sms4j.version>
|
||||
<sms4j.version>3.3.5</sms4j.version>
|
||||
<!-- 限制框架中的fastjson版本 -->
|
||||
<fastjson.version>1.2.83</fastjson.version>
|
||||
<!-- 面向运行时的D-ORM依赖 -->
|
||||
<anyline.version>8.7.2-20250101</anyline.version>
|
||||
<anyline.version>8.7.3-20260202</anyline.version>
|
||||
<!-- 工作流配置 -->
|
||||
<warm-flow.version>1.7.3</warm-flow.version>
|
||||
<warm-flow.version>1.8.4</warm-flow.version>
|
||||
<!-- mqtt客户端 -->
|
||||
<mica-mqtt.version>2.5.12</mica-mqtt.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>
|
||||
<flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
|
||||
<maven-jar-plugin.version>3.5.0</maven-jar-plugin.version>
|
||||
<maven-war-plugin.version>3.5.1</maven-war-plugin.version>
|
||||
<maven-compiler-plugin.version>3.15.0</maven-compiler-plugin.version>
|
||||
<maven-surefire-plugin.version>3.5.4</maven-surefire-plugin.version>
|
||||
<!-- 统一版本号管理:Maven3.X需要,Maven4.0之后已原生支持 -->
|
||||
<flatten-maven-plugin.version>1.7.3</flatten-maven-plugin.version>
|
||||
<!-- 打包默认跳过测试 -->
|
||||
<skipTests>true</skipTests>
|
||||
</properties>
|
||||
@@ -119,25 +121,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>
|
||||
@@ -165,10 +148,11 @@
|
||||
<version>${lombok.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- fesod (EasyExcel/FastExcel的前身) 的依赖 -->
|
||||
<dependency>
|
||||
<groupId>cn.idev.excel</groupId>
|
||||
<artifactId>fastexcel</artifactId>
|
||||
<version>${fastexcel.version}</version>
|
||||
<groupId>org.apache.fesod</groupId>
|
||||
<artifactId>fesod-sheet</artifactId>
|
||||
<version>${fesod.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- velocity代码生成使用模板 -->
|
||||
@@ -205,7 +189,7 @@
|
||||
<!-- dynamic-datasource 多数据源-->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
|
||||
<artifactId>dynamic-datasource-spring-boot4-starter</artifactId>
|
||||
<version>${dynamic-ds.version}</version>
|
||||
</dependency>
|
||||
|
||||
@@ -217,7 +201,7 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
|
||||
@@ -246,13 +230,13 @@
|
||||
<artifactId>s3</artifactId>
|
||||
<version>${aws.sdk.version}</version>
|
||||
</dependency>
|
||||
<!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
|
||||
<!-- 客户端的性能增强传输管理器 -->
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3-transfer-manager</artifactId>
|
||||
<version>${aws.sdk.version}</version>
|
||||
</dependency>
|
||||
<!-- 将基于 Netty 的 HTTP 客户端从类路径中移除 -->
|
||||
<!-- 适用于 Netty 的客户端 -->
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>netty-nio-client</artifactId>
|
||||
@@ -314,6 +298,31 @@
|
||||
<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>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara.mica-mqtt</groupId>
|
||||
<artifactId>mica-mqtt-client-spring-boot-starter</artifactId>
|
||||
<version>${mica-mqtt.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>
|
||||
@@ -376,8 +385,7 @@
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven-compiler-plugin.version}</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
<release>${java.version}</release>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -55,11 +55,6 @@
|
||||
<artifactId>ruoyi-common-social</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-ratelimiter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-mail</artifactId>
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package org.dromara.web.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -18,37 +15,34 @@ import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.domain.model.LoginBody;
|
||||
import org.dromara.common.core.domain.model.RegisterBody;
|
||||
import org.dromara.common.core.domain.model.SocialLoginBody;
|
||||
import org.dromara.common.core.utils.*;
|
||||
import org.dromara.common.core.utils.DateUtils;
|
||||
import org.dromara.common.core.utils.MessageUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.core.utils.ValidatorUtils;
|
||||
import org.dromara.common.encrypt.annotation.ApiEncrypt;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.common.redis.annotation.RateLimiter;
|
||||
import org.dromara.common.redis.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;
|
||||
import org.dromara.common.social.utils.SocialUtils;
|
||||
import org.dromara.common.sse.dto.SseMessageDto;
|
||||
import org.dromara.common.sse.utils.SseMessageUtils;
|
||||
import org.dromara.common.tenant.helper.TenantHelper;
|
||||
import org.dromara.system.domain.bo.SysTenantBo;
|
||||
import org.dromara.system.domain.vo.SysClientVo;
|
||||
import org.dromara.system.domain.vo.SysTenantVo;
|
||||
import org.dromara.system.service.ISysClientService;
|
||||
import org.dromara.system.service.ISysConfigService;
|
||||
import org.dromara.system.service.ISysSocialService;
|
||||
import org.dromara.system.service.ISysTenantService;
|
||||
import org.dromara.web.domain.vo.LoginTenantVo;
|
||||
import org.dromara.web.domain.vo.LoginVo;
|
||||
import org.dromara.web.domain.vo.TenantListVo;
|
||||
import org.dromara.web.service.IAuthStrategy;
|
||||
import org.dromara.web.service.SysLoginService;
|
||||
import org.dromara.web.service.SysRegisterService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -68,7 +62,6 @@ public class AuthController {
|
||||
private final SysLoginService loginService;
|
||||
private final SysRegisterService registerService;
|
||||
private final ISysConfigService configService;
|
||||
private final ISysTenantService tenantService;
|
||||
private final ISysSocialService socialUserService;
|
||||
private final ISysClientService clientService;
|
||||
private final ScheduledExecutorService scheduledExecutorService;
|
||||
@@ -96,15 +89,13 @@ public class AuthController {
|
||||
} else if (!SystemConstants.NORMAL.equals(client.getStatus())) {
|
||||
return R.fail(MessageUtils.message("auth.grant.type.blocked"));
|
||||
}
|
||||
// 校验租户
|
||||
loginService.checkTenant(loginBody.getTenantId());
|
||||
// 登录
|
||||
LoginVo loginVo = IAuthStrategy.login(body, client, grantType);
|
||||
|
||||
Long userId = LoginHelper.getUserId();
|
||||
scheduledExecutorService.schedule(() -> {
|
||||
SseMessageDto dto = new SseMessageDto();
|
||||
dto.setMessage("欢迎登录RuoYi-Vue-Plus后台管理系统");
|
||||
dto.setMessage(DateUtils.getTodayHour(new Date()) + "好,欢迎登录 RuoYi-Vue-Plus 后台管理系统");
|
||||
dto.setUserIds(List.of(userId));
|
||||
SseMessageUtils.publishMessage(dto);
|
||||
}, 5, TimeUnit.SECONDS);
|
||||
@@ -118,18 +109,13 @@ public class AuthController {
|
||||
* @return 结果
|
||||
*/
|
||||
@GetMapping("/binding/{source}")
|
||||
public R<String> authBinding(@PathVariable("source") String source,
|
||||
@RequestParam String tenantId, @RequestParam String domain) {
|
||||
public R<String> authBinding(@PathVariable("source") String source) {
|
||||
SocialLoginConfigProperties obj = socialProperties.getType().get(source);
|
||||
if (ObjectUtil.isNull(obj)) {
|
||||
return R.fail(source + "平台账号暂不支持");
|
||||
}
|
||||
AuthRequest authRequest = SocialUtils.getAuthRequest(source, socialProperties);
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("tenantId", tenantId);
|
||||
map.put("domain", domain);
|
||||
map.put("state", AuthStateUtils.createState());
|
||||
String authorizeUrl = authRequest.authorize(Base64.encode(JsonUtils.toJsonString(map), StandardCharsets.UTF_8));
|
||||
String authorizeUrl = authRequest.authorize(AuthStateUtils.createState());
|
||||
return R.ok("操作成功", authorizeUrl);
|
||||
}
|
||||
|
||||
@@ -145,8 +131,8 @@ public class AuthController {
|
||||
StpUtil.checkLogin();
|
||||
// 获取第三方登录信息
|
||||
AuthResponse<AuthUser> response = SocialUtils.loginAuth(
|
||||
loginBody.getSource(), loginBody.getSocialCode(),
|
||||
loginBody.getSocialState(), socialProperties);
|
||||
loginBody.getSource(), loginBody.getSocialCode(),
|
||||
loginBody.getSocialState(), socialProperties);
|
||||
AuthUser authUserData = response.getData();
|
||||
// 判断授权响应是否成功
|
||||
if (!response.ok()) {
|
||||
@@ -186,7 +172,7 @@ public class AuthController {
|
||||
@ApiEncrypt
|
||||
@PostMapping("/register")
|
||||
public R<Void> register(@Validated @RequestBody RegisterBody user) {
|
||||
if (!configService.selectRegisterEnabled(user.getTenantId())) {
|
||||
if (!configService.selectRegisterEnabled()) {
|
||||
return R.fail("当前系统没有开启注册功能!");
|
||||
}
|
||||
registerService.register(user);
|
||||
@@ -198,41 +184,12 @@ public class AuthController {
|
||||
*
|
||||
* @return 租户列表
|
||||
*/
|
||||
@RateLimiter(time = 60, count = 20, limitType = LimitType.IP)
|
||||
@GetMapping("/tenant/list")
|
||||
public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
|
||||
// 返回对象
|
||||
// 暂时预留给前端使用 后续删除
|
||||
LoginTenantVo result = new LoginTenantVo();
|
||||
boolean enable = TenantHelper.isEnable();
|
||||
result.setTenantEnabled(enable);
|
||||
// 如果未开启租户这直接返回
|
||||
if (!enable) {
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
List<SysTenantVo> tenantList = tenantService.queryList(new SysTenantBo());
|
||||
List<TenantListVo> voList = MapstructUtils.convert(tenantList, TenantListVo.class);
|
||||
try {
|
||||
// 如果只超管返回所有租户
|
||||
if (LoginHelper.isSuperAdmin()) {
|
||||
result.setVoList(voList);
|
||||
return R.ok(result);
|
||||
}
|
||||
} catch (NotLoginException ignored) {
|
||||
}
|
||||
|
||||
// 获取域名
|
||||
String host;
|
||||
String referer = request.getHeader("referer");
|
||||
if (StringUtils.isNotBlank(referer)) {
|
||||
// 这里从referer中取值是为了本地使用hosts添加虚拟域名,方便本地环境调试
|
||||
host = referer.split("//")[1].split("/")[0];
|
||||
} else {
|
||||
host = new URL(request.getRequestURL().toString()).getHost();
|
||||
}
|
||||
// 根据域名进行筛选
|
||||
List<TenantListVo> list = StreamUtils.filter(voList, vo ->
|
||||
StringUtils.equalsIgnoreCase(vo.getDomain(), host));
|
||||
result.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
|
||||
result.setTenantEnabled(false);
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package org.dromara.web.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import cn.hutool.captcha.AbstractCaptcha;
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.captcha.generator.MathGenerator;
|
||||
import cn.hutool.captcha.generator.RandomGenerator;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
@@ -14,14 +15,13 @@ import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
||||
import org.dromara.common.mail.config.properties.MailProperties;
|
||||
import org.dromara.common.mail.utils.MailUtils;
|
||||
import org.dromara.common.ratelimiter.annotation.RateLimiter;
|
||||
import org.dromara.common.ratelimiter.enums.LimitType;
|
||||
import org.dromara.common.redis.annotation.RateLimiter;
|
||||
import org.dromara.common.redis.enums.LimitType;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.dromara.common.web.core.WaveAndCircleCaptcha;
|
||||
import org.dromara.common.web.config.properties.CaptchaProperties;
|
||||
import org.dromara.common.web.enums.CaptchaType;
|
||||
import org.dromara.sms4j.api.SmsBlend;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.core.factory.SmsFactory;
|
||||
@@ -33,6 +33,7 @@ import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.awt.*;
|
||||
import java.time.Duration;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
@@ -130,16 +131,21 @@ public class CaptchaController {
|
||||
String uuid = IdUtil.simpleUUID();
|
||||
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
|
||||
// 生成验证码
|
||||
CaptchaType captchaType = captchaProperties.getType();
|
||||
boolean isMath = CaptchaType.MATH == captchaType;
|
||||
Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength();
|
||||
CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length);
|
||||
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
|
||||
String captchaType = captchaProperties.getType();
|
||||
CodeGenerator codeGenerator;
|
||||
if ("math".equals(captchaType)) {
|
||||
codeGenerator = new MathGenerator(captchaProperties.getNumberLength(), false);
|
||||
} else {
|
||||
codeGenerator = new RandomGenerator(captchaProperties.getCharLength());
|
||||
}
|
||||
WaveAndCircleCaptcha captcha = new WaveAndCircleCaptcha(160, 60);
|
||||
// captcha.setBackground(Color.WHITE); // 不设置就是透明底
|
||||
captcha.setFont(new Font("Arial", Font.BOLD, 45));
|
||||
captcha.setGenerator(codeGenerator);
|
||||
captcha.createCode();
|
||||
// 如果是数学验证码,使用SpEL表达式处理验证码结果
|
||||
String code = captcha.getCode();
|
||||
if (isMath) {
|
||||
if ("math".equals(captchaType)) {
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
|
||||
code = exp.getValue(String.class);
|
||||
|
||||
@@ -2,8 +2,6 @@ package org.dromara.web.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 登录租户对象
|
||||
*
|
||||
@@ -17,9 +15,4 @@ public class LoginTenantVo {
|
||||
*/
|
||||
private Boolean tenantEnabled;
|
||||
|
||||
/**
|
||||
* 租户对象列表
|
||||
*/
|
||||
private List<TenantListVo> voList;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package org.dromara.web.domain.vo;
|
||||
|
||||
import org.dromara.system.domain.vo.SysTenantVo;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 租户列表
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@AutoMapper(target = SysTenantVo.class)
|
||||
public class TenantListVo {
|
||||
|
||||
/**
|
||||
* 租户编号
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 企业名称
|
||||
*/
|
||||
private String companyName;
|
||||
|
||||
/**
|
||||
* 域名
|
||||
*/
|
||||
private String domain;
|
||||
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
package org.dromara.web.listener;
|
||||
|
||||
import cn.dev33.satoken.listener.SaTokenListener;
|
||||
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;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -18,7 +16,6 @@ import org.dromara.common.core.utils.ip.AddressUtils;
|
||||
import org.dromara.common.log.event.LogininforEvent;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.tenant.helper.TenantHelper;
|
||||
import org.dromara.web.service.SysLoginService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -51,21 +48,18 @@ public class UserActionListener implements SaTokenListener {
|
||||
dto.setLoginTime(System.currentTimeMillis());
|
||||
dto.setTokenId(tokenValue);
|
||||
String username = (String) loginParameter.getExtra(LoginHelper.USER_NAME_KEY);
|
||||
String tenantId = (String) loginParameter.getExtra(LoginHelper.TENANT_KEY);
|
||||
dto.setUserName(username);
|
||||
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(loginParameter.getTimeout() == -1) {
|
||||
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
|
||||
} else {
|
||||
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(loginParameter.getTimeout()));
|
||||
}
|
||||
});
|
||||
if (loginParameter.getTimeout() == -1) {
|
||||
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
|
||||
} else {
|
||||
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(loginParameter.getTimeout()));
|
||||
}
|
||||
|
||||
// 记录登录日志
|
||||
LogininforEvent logininforEvent = new LogininforEvent();
|
||||
logininforEvent.setTenantId(tenantId);
|
||||
logininforEvent.setUsername(username);
|
||||
logininforEvent.setStatus(Constants.LOGIN_SUCCESS);
|
||||
logininforEvent.setMessage(MessageUtils.message("user.login.success"));
|
||||
@@ -81,10 +75,7 @@ public class UserActionListener implements SaTokenListener {
|
||||
*/
|
||||
@Override
|
||||
public void doLogout(String loginType, Object loginId, String tokenValue) {
|
||||
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
|
||||
TenantHelper.dynamic(tenantId, () -> {
|
||||
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
|
||||
});
|
||||
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
|
||||
log.info("user doLogout, userId:{}, token:{}", loginId, tokenValue);
|
||||
}
|
||||
|
||||
@@ -93,10 +84,7 @@ public class UserActionListener implements SaTokenListener {
|
||||
*/
|
||||
@Override
|
||||
public void doKickout(String loginType, Object loginId, String tokenValue) {
|
||||
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
|
||||
TenantHelper.dynamic(tenantId, () -> {
|
||||
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
|
||||
});
|
||||
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
|
||||
log.info("user doKickout, userId:{}, token:{}", loginId, tokenValue);
|
||||
}
|
||||
|
||||
@@ -105,10 +93,7 @@ public class UserActionListener implements SaTokenListener {
|
||||
*/
|
||||
@Override
|
||||
public void doReplaced(String loginType, Object loginId, String tokenValue) {
|
||||
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginHelper.TENANT_KEY));
|
||||
TenantHelper.dynamic(tenantId, () -> {
|
||||
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
|
||||
});
|
||||
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
|
||||
log.info("user doReplaced, userId:{}, token:{}", loginId, tokenValue);
|
||||
}
|
||||
|
||||
@@ -158,6 +143,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) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import me.zhyd.oauth.model.AuthUser;
|
||||
import org.dromara.common.core.constant.CacheConstants;
|
||||
import org.dromara.common.core.constant.Constants;
|
||||
import org.dromara.common.core.constant.SystemConstants;
|
||||
import org.dromara.common.core.constant.TenantConstants;
|
||||
import org.dromara.common.core.domain.dto.PostDTO;
|
||||
import org.dromara.common.core.domain.dto.RoleDTO;
|
||||
import org.dromara.common.core.domain.model.LoginUser;
|
||||
@@ -25,8 +23,6 @@ import org.dromara.common.log.event.LogininforEvent;
|
||||
import org.dromara.common.mybatis.helper.DataPermissionHelper;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.tenant.exception.TenantException;
|
||||
import org.dromara.common.tenant.helper.TenantHelper;
|
||||
import org.dromara.system.domain.SysUser;
|
||||
import org.dromara.system.domain.bo.SysSocialBo;
|
||||
import org.dromara.system.domain.vo.*;
|
||||
@@ -36,8 +32,9 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
@@ -56,7 +53,6 @@ public class SysLoginService {
|
||||
@Value("${user.password.lockTime}")
|
||||
private Integer lockTime;
|
||||
|
||||
private final ISysTenantService tenantService;
|
||||
private final ISysPermissionService permissionService;
|
||||
private final ISysSocialService sysSocialService;
|
||||
private final ISysRoleService roleService;
|
||||
@@ -113,11 +109,7 @@ public class SysLoginService {
|
||||
if (ObjectUtil.isNull(loginUser)) {
|
||||
return;
|
||||
}
|
||||
if (TenantHelper.isEnable() && LoginHelper.isSuperAdmin()) {
|
||||
// 超级管理员 登出清除动态租户
|
||||
TenantHelper.clearDynamic();
|
||||
}
|
||||
recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
|
||||
recordLogininfor(loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
|
||||
} catch (NotLoginException ignored) {
|
||||
} finally {
|
||||
try {
|
||||
@@ -130,14 +122,12 @@ public class SysLoginService {
|
||||
/**
|
||||
* 记录登录信息
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @param username 用户名
|
||||
* @param status 状态
|
||||
* @param message 消息内容
|
||||
*/
|
||||
public void recordLogininfor(String tenantId, String username, String status, String message) {
|
||||
public void recordLogininfor(String username, String status, String message) {
|
||||
LogininforEvent logininforEvent = new LogininforEvent();
|
||||
logininforEvent.setTenantId(tenantId);
|
||||
logininforEvent.setUsername(username);
|
||||
logininforEvent.setStatus(status);
|
||||
logininforEvent.setMessage(message);
|
||||
@@ -151,23 +141,27 @@ public class SysLoginService {
|
||||
public LoginUser buildLoginUser(SysUserVo user) {
|
||||
LoginUser loginUser = new LoginUser();
|
||||
Long userId = user.getUserId();
|
||||
loginUser.setTenantId(user.getTenantId());
|
||||
loginUser.setUserId(userId);
|
||||
loginUser.setDeptId(user.getDeptId());
|
||||
loginUser.setUsername(user.getUserName());
|
||||
loginUser.setNickname(user.getNickName());
|
||||
loginUser.setUserType(user.getUserType());
|
||||
loginUser.setMenuPermission(permissionService.getMenuPermission(userId));
|
||||
loginUser.setRolePermission(permissionService.getRolePermission(userId));
|
||||
if (ObjectUtil.isNotNull(user.getDeptId())) {
|
||||
Opt<SysDeptVo> deptOpt = Opt.of(user.getDeptId()).map(deptService::selectDeptById);
|
||||
loginUser.setDeptName(deptOpt.map(SysDeptVo::getDeptName).orElse(StringUtils.EMPTY));
|
||||
loginUser.setDeptCategory(deptOpt.map(SysDeptVo::getDeptCategory).orElse(StringUtils.EMPTY));
|
||||
}
|
||||
List<SysRoleVo> roles = roleService.selectRolesByUserId(userId);
|
||||
List<SysPostVo> posts = postService.selectPostsByUserId(userId);
|
||||
loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
|
||||
loginUser.setPosts(BeanUtil.copyToList(posts, PostDTO.class));
|
||||
ThreadUtils.virtualSubmit(() -> {
|
||||
loginUser.setMenuPermission(permissionService.getMenuPermission(userId));
|
||||
}, () -> {
|
||||
loginUser.setRolePermission(permissionService.getRolePermission(userId));
|
||||
}, () -> {
|
||||
List<SysRoleVo> roles = roleService.selectRolesByUserId(userId);
|
||||
loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
|
||||
}, () -> {
|
||||
List<SysPostVo> posts = postService.selectPostsByUserId(userId);
|
||||
loginUser.setPosts(BeanUtil.copyToList(posts, PostDTO.class));
|
||||
});
|
||||
return loginUser;
|
||||
}
|
||||
|
||||
@@ -188,7 +182,7 @@ public class SysLoginService {
|
||||
/**
|
||||
* 登录校验
|
||||
*/
|
||||
public void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) {
|
||||
public void checkLogin(LoginType loginType, String username, Supplier<Boolean> supplier) {
|
||||
String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
|
||||
String loginFail = Constants.LOGIN_FAIL;
|
||||
|
||||
@@ -196,7 +190,7 @@ public class SysLoginService {
|
||||
int errorNumber = ObjectUtil.defaultIfNull(RedisUtils.getCacheObject(errorKey), 0);
|
||||
// 锁定时间内登录 则踢出
|
||||
if (errorNumber >= maxRetryCount) {
|
||||
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
|
||||
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
|
||||
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
|
||||
}
|
||||
|
||||
@@ -206,11 +200,11 @@ public class SysLoginService {
|
||||
RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
|
||||
// 达到规定错误次数 则锁定登录
|
||||
if (errorNumber >= maxRetryCount) {
|
||||
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
|
||||
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
|
||||
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
|
||||
} else {
|
||||
// 未达到规定错误次数
|
||||
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
|
||||
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
|
||||
throw new UserException(loginType.getRetryLimitCount(), errorNumber);
|
||||
}
|
||||
}
|
||||
@@ -219,33 +213,4 @@ public class SysLoginService {
|
||||
RedisUtils.deleteObject(errorKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验租户
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
public void checkTenant(String tenantId) {
|
||||
if (!TenantHelper.isEnable()) {
|
||||
return;
|
||||
}
|
||||
if (StringUtils.isBlank(tenantId)) {
|
||||
throw new TenantException("tenant.number.not.blank");
|
||||
}
|
||||
if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
|
||||
return;
|
||||
}
|
||||
SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
|
||||
if (ObjectUtil.isNull(tenant)) {
|
||||
log.info("登录租户:{} 不存在.", tenantId);
|
||||
throw new TenantException("tenant.not.exists");
|
||||
} else if (SystemConstants.DISABLE.equals(tenant.getStatus())) {
|
||||
log.info("登录租户:{} 已被停用.", tenantId);
|
||||
throw new TenantException("tenant.blocked");
|
||||
} else if (ObjectUtil.isNotNull(tenant.getExpireTime())
|
||||
&& new Date().after(tenant.getExpireTime())) {
|
||||
log.info("登录租户:{} 已超过有效期.", tenantId);
|
||||
throw new TenantException("tenant.expired");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.log.event.LogininforEvent;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.dromara.common.tenant.helper.TenantHelper;
|
||||
import org.dromara.common.web.config.properties.CaptchaProperties;
|
||||
import org.dromara.system.domain.SysUser;
|
||||
import org.dromara.system.domain.bo.SysUserBo;
|
||||
@@ -41,7 +40,6 @@ public class SysRegisterService {
|
||||
* 注册
|
||||
*/
|
||||
public void register(RegisterBody registerBody) {
|
||||
String tenantId = registerBody.getTenantId();
|
||||
String username = registerBody.getUsername();
|
||||
String password = registerBody.getPassword();
|
||||
// 校验用户类型是否存在
|
||||
@@ -50,7 +48,7 @@ public class SysRegisterService {
|
||||
boolean captchaEnabled = captchaProperties.getEnable();
|
||||
// 验证码开关
|
||||
if (captchaEnabled) {
|
||||
validateCaptcha(tenantId, username, registerBody.getCode(), registerBody.getUuid());
|
||||
validateCaptcha(username, registerBody.getCode(), registerBody.getUuid());
|
||||
}
|
||||
SysUserBo sysUser = new SysUserBo();
|
||||
sysUser.setUserName(username);
|
||||
@@ -58,18 +56,16 @@ public class SysRegisterService {
|
||||
sysUser.setPassword(BCrypt.hashpw(password));
|
||||
sysUser.setUserType(userType);
|
||||
|
||||
boolean exist = TenantHelper.dynamic(tenantId, () -> {
|
||||
return userMapper.exists(new LambdaQueryWrapper<SysUser>()
|
||||
.eq(SysUser::getUserName, sysUser.getUserName()));
|
||||
});
|
||||
boolean exist = userMapper.exists(new LambdaQueryWrapper<SysUser>()
|
||||
.eq(SysUser::getUserName, sysUser.getUserName()));
|
||||
if (exist) {
|
||||
throw new UserException("user.register.save.error", username);
|
||||
}
|
||||
boolean regFlag = userService.registerUser(sysUser, tenantId);
|
||||
boolean regFlag = userService.registerUser(sysUser);
|
||||
if (!regFlag) {
|
||||
throw new UserException("user.register.error");
|
||||
}
|
||||
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
|
||||
recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.register.success"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,16 +75,16 @@ public class SysRegisterService {
|
||||
* @param code 验证码
|
||||
* @param uuid 唯一标识
|
||||
*/
|
||||
public void validateCaptcha(String tenantId, String username, String code, String uuid) {
|
||||
public void validateCaptcha(String username, String code, String uuid) {
|
||||
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
|
||||
String captcha = RedisUtils.getCacheObject(verifyKey);
|
||||
RedisUtils.deleteObject(verifyKey);
|
||||
if (captcha == null) {
|
||||
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
|
||||
recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
|
||||
throw new CaptchaExpireException();
|
||||
}
|
||||
if (!code.equalsIgnoreCase(captcha)) {
|
||||
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
|
||||
if (!StringUtils.equalsIgnoreCase(code, captcha)) {
|
||||
recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
|
||||
throw new CaptchaException();
|
||||
}
|
||||
}
|
||||
@@ -96,15 +92,13 @@ public class SysRegisterService {
|
||||
/**
|
||||
* 记录登录信息
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @param username 用户名
|
||||
* @param status 状态
|
||||
* @param message 消息内容
|
||||
* @return
|
||||
*/
|
||||
private void recordLogininfor(String tenantId, String username, String status, String message) {
|
||||
private void recordLogininfor(String username, String status, String message) {
|
||||
LogininforEvent logininforEvent = new LogininforEvent();
|
||||
logininforEvent.setTenantId(tenantId);
|
||||
logininforEvent.setUsername(username);
|
||||
logininforEvent.setStatus(status);
|
||||
logininforEvent.setMessage(message);
|
||||
|
||||
@@ -20,7 +20,6 @@ import org.dromara.common.core.utils.ValidatorUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.tenant.helper.TenantHelper;
|
||||
import org.dromara.system.domain.SysUser;
|
||||
import org.dromara.system.domain.vo.SysClientVo;
|
||||
import org.dromara.system.domain.vo.SysUserVo;
|
||||
@@ -47,15 +46,12 @@ public class EmailAuthStrategy implements IAuthStrategy {
|
||||
public LoginVo login(String body, SysClientVo client) {
|
||||
EmailLoginBody loginBody = JsonUtils.parseObject(body, EmailLoginBody.class);
|
||||
ValidatorUtils.validate(loginBody);
|
||||
String tenantId = loginBody.getTenantId();
|
||||
String email = loginBody.getEmail();
|
||||
String emailCode = loginBody.getEmailCode();
|
||||
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
|
||||
SysUserVo user = loadUserByEmail(email);
|
||||
loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
|
||||
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
|
||||
return loginService.buildLoginUser(user);
|
||||
});
|
||||
SysUserVo user = loadUserByEmail(email);
|
||||
loginService.checkLogin(LoginType.EMAIL, user.getUserName(), () -> !validateEmailCode(email, emailCode));
|
||||
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
|
||||
LoginUser loginUser = loginService.buildLoginUser(user);
|
||||
loginUser.setClientKey(client.getClientKey());
|
||||
loginUser.setDeviceType(client.getDeviceType());
|
||||
SaLoginParameter model = new SaLoginParameter();
|
||||
@@ -78,10 +74,10 @@ public class EmailAuthStrategy implements IAuthStrategy {
|
||||
/**
|
||||
* 校验邮箱验证码
|
||||
*/
|
||||
private boolean validateEmailCode(String tenantId, String email, String emailCode) {
|
||||
private boolean validateEmailCode(String email, String emailCode) {
|
||||
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + email);
|
||||
if (StringUtils.isBlank(code)) {
|
||||
loginService.recordLogininfor(tenantId, email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
|
||||
loginService.recordLogininfor(email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
|
||||
throw new CaptchaExpireException();
|
||||
}
|
||||
return code.equals(emailCode);
|
||||
|
||||
@@ -22,7 +22,6 @@ import org.dromara.common.core.utils.ValidatorUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.tenant.helper.TenantHelper;
|
||||
import org.dromara.common.web.config.properties.CaptchaProperties;
|
||||
import org.dromara.system.domain.SysUser;
|
||||
import org.dromara.system.domain.vo.SysClientVo;
|
||||
@@ -51,7 +50,6 @@ public class PasswordAuthStrategy implements IAuthStrategy {
|
||||
public LoginVo login(String body, SysClientVo client) {
|
||||
PasswordLoginBody loginBody = JsonUtils.parseObject(body, PasswordLoginBody.class);
|
||||
ValidatorUtils.validate(loginBody);
|
||||
String tenantId = loginBody.getTenantId();
|
||||
String username = loginBody.getUsername();
|
||||
String password = loginBody.getPassword();
|
||||
String code = loginBody.getCode();
|
||||
@@ -60,14 +58,12 @@ public class PasswordAuthStrategy implements IAuthStrategy {
|
||||
boolean captchaEnabled = captchaProperties.getEnable();
|
||||
// 验证码开关
|
||||
if (captchaEnabled) {
|
||||
validateCaptcha(tenantId, username, code, uuid);
|
||||
validateCaptcha(username, code, uuid);
|
||||
}
|
||||
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
|
||||
SysUserVo user = loadUserByUsername(username);
|
||||
loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
|
||||
// 此处可根据登录用户的数据不同 自行创建 loginUser
|
||||
return loginService.buildLoginUser(user);
|
||||
});
|
||||
SysUserVo user = loadUserByUsername(username);
|
||||
loginService.checkLogin(LoginType.PASSWORD, username, () -> !BCrypt.checkpw(password, user.getPassword()));
|
||||
// 此处可根据登录用户的数据不同 自行创建 loginUser
|
||||
LoginUser loginUser = loginService.buildLoginUser(user);
|
||||
loginUser.setClientKey(client.getClientKey());
|
||||
loginUser.setDeviceType(client.getDeviceType());
|
||||
SaLoginParameter model = new SaLoginParameter();
|
||||
@@ -94,16 +90,16 @@ public class PasswordAuthStrategy implements IAuthStrategy {
|
||||
* @param code 验证码
|
||||
* @param uuid 唯一标识
|
||||
*/
|
||||
private void validateCaptcha(String tenantId, String username, String code, String uuid) {
|
||||
private void validateCaptcha(String username, String code, String uuid) {
|
||||
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
|
||||
String captcha = RedisUtils.getCacheObject(verifyKey);
|
||||
RedisUtils.deleteObject(verifyKey);
|
||||
if (captcha == null) {
|
||||
loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
|
||||
loginService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
|
||||
throw new CaptchaExpireException();
|
||||
}
|
||||
if (!code.equalsIgnoreCase(captcha)) {
|
||||
loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
|
||||
if (!StringUtils.equalsIgnoreCase(code, captcha)) {
|
||||
loginService.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
|
||||
throw new CaptchaException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import org.dromara.common.core.utils.ValidatorUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.tenant.helper.TenantHelper;
|
||||
import org.dromara.system.domain.SysUser;
|
||||
import org.dromara.system.domain.vo.SysClientVo;
|
||||
import org.dromara.system.domain.vo.SysUserVo;
|
||||
@@ -47,15 +46,12 @@ public class SmsAuthStrategy implements IAuthStrategy {
|
||||
public LoginVo login(String body, SysClientVo client) {
|
||||
SmsLoginBody loginBody = JsonUtils.parseObject(body, SmsLoginBody.class);
|
||||
ValidatorUtils.validate(loginBody);
|
||||
String tenantId = loginBody.getTenantId();
|
||||
String phonenumber = loginBody.getPhonenumber();
|
||||
String smsCode = loginBody.getSmsCode();
|
||||
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
|
||||
SysUserVo user = loadUserByPhonenumber(phonenumber);
|
||||
loginService.checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
|
||||
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
|
||||
return loginService.buildLoginUser(user);
|
||||
});
|
||||
SysUserVo user = loadUserByPhonenumber(phonenumber);
|
||||
loginService.checkLogin(LoginType.SMS, user.getUserName(), () -> !validateSmsCode(phonenumber, smsCode));
|
||||
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
|
||||
LoginUser loginUser = loginService.buildLoginUser(user);
|
||||
loginUser.setClientKey(client.getClientKey());
|
||||
loginUser.setDeviceType(client.getDeviceType());
|
||||
SaLoginParameter model = new SaLoginParameter();
|
||||
@@ -78,10 +74,10 @@ public class SmsAuthStrategy implements IAuthStrategy {
|
||||
/**
|
||||
* 校验短信验证码
|
||||
*/
|
||||
private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) {
|
||||
private boolean validateSmsCode(String phonenumber, String smsCode) {
|
||||
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber);
|
||||
if (StringUtils.isBlank(code)) {
|
||||
loginService.recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
|
||||
loginService.recordLogininfor(phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
|
||||
throw new CaptchaExpireException();
|
||||
}
|
||||
return code.equals(smsCode);
|
||||
|
||||
@@ -3,10 +3,7 @@ package org.dromara.web.service.impl;
|
||||
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;
|
||||
@@ -16,13 +13,11 @@ import org.dromara.common.core.domain.model.LoginUser;
|
||||
import org.dromara.common.core.domain.model.SocialLoginBody;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.exception.user.UserException;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
import org.dromara.common.core.utils.ValidatorUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.social.config.properties.SocialProperties;
|
||||
import org.dromara.common.social.utils.SocialUtils;
|
||||
import org.dromara.common.tenant.helper.TenantHelper;
|
||||
import org.dromara.system.domain.vo.SysClientVo;
|
||||
import org.dromara.system.domain.vo.SysSocialVo;
|
||||
import org.dromara.system.domain.vo.SysUserVo;
|
||||
@@ -34,7 +29,6 @@ import org.dromara.web.service.SysLoginService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 第三方授权策略
|
||||
@@ -68,35 +62,14 @@ 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)) {
|
||||
throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!");
|
||||
}
|
||||
SysSocialVo social;
|
||||
if (TenantHelper.isEnable()) {
|
||||
Optional<SysSocialVo> opt = StreamUtils.findAny(list, x -> x.getTenantId().equals(loginBody.getTenantId()));
|
||||
if (opt.isEmpty()) {
|
||||
throw new ServiceException("对不起,你没有权限登录当前租户!");
|
||||
}
|
||||
social = opt.get();
|
||||
} else {
|
||||
social = list.get(0);
|
||||
}
|
||||
LoginUser loginUser = TenantHelper.dynamic(social.getTenantId(), () -> {
|
||||
SysUserVo user = loadUser(social.getUserId());
|
||||
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
|
||||
return loginService.buildLoginUser(user);
|
||||
});
|
||||
SysUserVo user = loadUser(list.get(0).getUserId());
|
||||
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
|
||||
LoginUser loginUser = loginService.buildLoginUser(user);
|
||||
loginUser.setClientKey(client.getClientKey());
|
||||
loginUser.setDeviceType(client.getDeviceType());
|
||||
SaLoginParameter model = new SaLoginParameter();
|
||||
|
||||
@@ -67,7 +67,6 @@ public class XcxAuthStrategy implements IAuthStrategy {
|
||||
SysUserVo user = loadUserByOpenid(openid);
|
||||
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
|
||||
XcxLoginUser loginUser = new XcxLoginUser();
|
||||
loginUser.setTenantId(user.getTenantId());
|
||||
loginUser.setUserId(user.getUserId());
|
||||
loginUser.setUsername(user.getUserName());
|
||||
loginUser.setNickname(user.getNickName());
|
||||
|
||||
@@ -27,8 +27,6 @@ snail-job:
|
||||
port: 2${server.port}
|
||||
# 客户端ip指定
|
||||
host:
|
||||
# RPC类型: netty, grpc
|
||||
rpc-type: grpc
|
||||
|
||||
--- # 数据源配置
|
||||
spring:
|
||||
|
||||
@@ -30,8 +30,6 @@ snail-job:
|
||||
port: 2${server.port}
|
||||
# 客户端ip指定
|
||||
host:
|
||||
# RPC类型: netty, grpc
|
||||
rpc-type: grpc
|
||||
|
||||
--- # 数据源配置
|
||||
spring:
|
||||
|
||||
@@ -5,28 +5,21 @@ server:
|
||||
servlet:
|
||||
# 应用的访问路径
|
||||
context-path: /
|
||||
# undertow 配置
|
||||
undertow:
|
||||
# jetty 配置
|
||||
jetty:
|
||||
# HTTP post内容的最大大小。当值为-1时,默认值为大小是无限的
|
||||
max-http-post-size: -1
|
||||
# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
|
||||
# 每块buffer的空间大小,越小的空间被利用越充分
|
||||
buffer-size: 512
|
||||
# 是否分配的直接内存
|
||||
direct-buffers: true
|
||||
max-http-form-post-size: -1
|
||||
threads:
|
||||
# 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程
|
||||
io: 8
|
||||
# 阻塞任务线程池, 当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
|
||||
worker: 256
|
||||
# 最小线程数
|
||||
min: 8
|
||||
# 最大线程数
|
||||
max: 256
|
||||
|
||||
captcha:
|
||||
# 是否启用验证码校验
|
||||
enable: true
|
||||
# 验证码类型 math 数组计算 char 字符验证
|
||||
type: MATH
|
||||
# line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
|
||||
category: CIRCLE
|
||||
type: math
|
||||
# 数字验证码位数
|
||||
numberLength: 1
|
||||
# 字符验证码长度
|
||||
@@ -57,6 +50,13 @@ spring:
|
||||
# 开启虚拟线程 仅jdk21可用
|
||||
virtual:
|
||||
enabled: false
|
||||
task:
|
||||
execution:
|
||||
# 从 springboot 3.5 开始 spring自带线程池
|
||||
# 不再需要 AsyncConfig与ThreadPoolConfig 可直接注入线程池使用
|
||||
thread-name-prefix: async-
|
||||
# 由spring自己初始化线程池
|
||||
mode: force
|
||||
# 资源信息
|
||||
messages:
|
||||
# 国际化资源文件路径
|
||||
@@ -112,22 +112,6 @@ security:
|
||||
- /*/api-docs/**
|
||||
- /warm-flow-ui/config
|
||||
|
||||
# 多租户配置
|
||||
tenant:
|
||||
# 是否开启
|
||||
enable: true
|
||||
# 排除表
|
||||
excludes:
|
||||
- sys_menu
|
||||
- sys_tenant
|
||||
- sys_tenant_package
|
||||
- sys_role_dept
|
||||
- sys_role_menu
|
||||
- sys_user_post
|
||||
- sys_user_role
|
||||
- sys_client
|
||||
- sys_oss_config
|
||||
|
||||
# MyBatisPlus配置
|
||||
# https://baomidou.com/config/
|
||||
mybatis-plus:
|
||||
@@ -183,19 +167,12 @@ springdoc:
|
||||
# 描述
|
||||
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.演示模块
|
||||
@@ -213,20 +190,10 @@ springdoc:
|
||||
xss:
|
||||
# 过滤开关
|
||||
enabled: true
|
||||
# 排除链接(多个用逗号分隔)
|
||||
# 排除链接
|
||||
excludeUrls:
|
||||
- /system/notice
|
||||
|
||||
# 全局线程池相关配置
|
||||
# 如使用JDK21请直接使用虚拟线程 不要开启此配置
|
||||
thread-pool:
|
||||
# 是否开启线程池
|
||||
enabled: false
|
||||
# 队列最大长度
|
||||
queueCapacity: 128
|
||||
# 线程池维护线程所允许的空闲时间
|
||||
keepAliveSeconds: 300
|
||||
|
||||
--- # 分布式锁 lock4j 全局配置
|
||||
lock4j:
|
||||
# 获取分布式锁超时时间,默认为 3000 毫秒
|
||||
@@ -266,13 +233,49 @@ warm-flow:
|
||||
enabled: true
|
||||
# 是否开启设计器ui
|
||||
ui: true
|
||||
# 是否显示流程图顶部文字
|
||||
top-text-show: true
|
||||
# 是否渲染节点悬浮提示,默认true
|
||||
node-tooltip: true
|
||||
# 默认Authorization,如果有多个token,用逗号分隔
|
||||
token-name: ${sa-token.token-name},clientid
|
||||
# 流程状态对应的三元色
|
||||
chart-status-color:
|
||||
## 未办理
|
||||
- 62,62,62
|
||||
## 待办理
|
||||
- 255,205,23
|
||||
## 已办理
|
||||
- 157,255,0
|
||||
|
||||
--- # mqtt 配置
|
||||
# 具体配置还需查看文档
|
||||
# https://mica-mqtt.dreamlu.net/guide/spring/client.html
|
||||
mqtt.client:
|
||||
# 是否开启客户端,默认:true
|
||||
enabled: false
|
||||
# 连接的服务端 ip ,默认:127.0.0.1
|
||||
ip: 127.0.0.1
|
||||
# 端口:默认:1883
|
||||
port: 1883
|
||||
# 客户端名称
|
||||
name: Mqtt-Client
|
||||
# 客户端Id(非常重要,一般为设备 sn,不可重复)
|
||||
client-id: 000001
|
||||
username: ruoyi
|
||||
password: 123456
|
||||
# 超时时间,单位:秒,默认:5秒
|
||||
timeout: 5
|
||||
# 重连时间,默认 5000 毫秒
|
||||
re-interval: 5000
|
||||
# mqtt 协议版本,可选 MQTT_3_1、mqtt_3_1_1、mqtt_5,默认:mqtt_3_1_1
|
||||
version: mqtt_3_1_1
|
||||
# 接收数据的 buffer size,默认:8k
|
||||
read-buffer-size: 8KB
|
||||
# 消息解析最大 bytes 长度,默认:10M
|
||||
max-bytes-in-message: 10MB
|
||||
# keep-alive 时间,单位:秒
|
||||
keep-alive-secs: 60
|
||||
# 开启保留 session 时,session 的有效期
|
||||
session-expiry-interval-secs: 0
|
||||
# 工作线程数,如果消息量比较大,例如做 emqx 的转发消息处理,可以调大此参数
|
||||
mqtt-executor-size: 2
|
||||
# 是否开启 ssl
|
||||
ssl:
|
||||
enabled: false
|
||||
keystore-path:
|
||||
keystore-pass:
|
||||
truststore-path:
|
||||
truststore-pass:
|
||||
|
||||
@@ -5,7 +5,7 @@ user.jcaptcha.expire=验证码已失效
|
||||
user.not.exists=对不起, 您的账号:{0} 不存在.
|
||||
user.password.not.match=用户不存在/密码错误
|
||||
user.password.retry.limit.count=密码输入错误{0}次
|
||||
user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
|
||||
user.password.retry.limit.exceed=密码输入错误{0}次,账户锁定{1}分钟
|
||||
user.password.delete=对不起,您的账号:{0} 已被删除
|
||||
user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
|
||||
role.blocked=角色已封禁,请联系管理员
|
||||
@@ -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=用户手机号不能为空
|
||||
@@ -46,16 +47,12 @@ repeat.submit.message=不允许重复提交,请稍候再试
|
||||
rate.limiter.message=访问过于频繁,请稍候再试
|
||||
sms.code.not.blank=短信验证码不能为空
|
||||
sms.code.retry.limit.count=短信验证码输入错误{0}次
|
||||
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
|
||||
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,账户锁定{1}分钟
|
||||
email.code.not.blank=邮箱验证码不能为空
|
||||
email.code.retry.limit.count=邮箱验证码输入错误{0}次
|
||||
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
|
||||
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,账户锁定{1}分钟
|
||||
xcx.code.not.blank=小程序[code]不能为空
|
||||
social.source.not.blank=第三方登录平台[source]不能为空
|
||||
social.code.not.blank=第三方登录平台[code]不能为空
|
||||
social.state.not.blank=第三方登录平台[state]不能为空
|
||||
##租户
|
||||
tenant.number.not.blank=租户编号不能为空
|
||||
tenant.not.exists=对不起, 您的租户不存在,请联系管理员
|
||||
tenant.blocked=对不起,您的租户已禁用,请联系管理员
|
||||
tenant.expired=对不起,您的租户已过期,请联系管理员
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -54,8 +55,4 @@ xcx.code.not.blank=Mini program [code] cannot be blank
|
||||
social.source.not.blank=Social login platform [source] cannot be blank
|
||||
social.code.not.blank=Social login platform [code] cannot be blank
|
||||
social.state.not.blank=Social login platform [state] cannot be blank
|
||||
##租户
|
||||
tenant.number.not.blank=Tenant number cannot be blank
|
||||
tenant.not.exists=Sorry, your tenant does not exist. Please contact the administrator
|
||||
tenant.blocked=Sorry, your tenant is disabled. Please contact the administrator
|
||||
tenant.expired=Sorry, your tenant has expired. Please contact the administrator.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ user.jcaptcha.expire=验证码已失效
|
||||
user.not.exists=对不起, 您的账号:{0} 不存在.
|
||||
user.password.not.match=用户不存在/密码错误
|
||||
user.password.retry.limit.count=密码输入错误{0}次
|
||||
user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟
|
||||
user.password.retry.limit.exceed=密码输入错误{0}次,账户锁定{1}分钟
|
||||
user.password.delete=对不起,您的账号:{0} 已被删除
|
||||
user.blocked=对不起,您的账号:{0} 已禁用,请联系管理员
|
||||
role.blocked=角色已封禁,请联系管理员
|
||||
@@ -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=用户手机号不能为空
|
||||
@@ -46,16 +47,12 @@ repeat.submit.message=不允许重复提交,请稍候再试
|
||||
rate.limiter.message=访问过于频繁,请稍候再试
|
||||
sms.code.not.blank=短信验证码不能为空
|
||||
sms.code.retry.limit.count=短信验证码输入错误{0}次
|
||||
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
|
||||
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,账户锁定{1}分钟
|
||||
email.code.not.blank=邮箱验证码不能为空
|
||||
email.code.retry.limit.count=邮箱验证码输入错误{0}次
|
||||
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
|
||||
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,账户锁定{1}分钟
|
||||
xcx.code.not.blank=小程序[code]不能为空
|
||||
social.source.not.blank=第三方登录平台[source]不能为空
|
||||
social.code.not.blank=第三方登录平台[code]不能为空
|
||||
social.state.not.blank=第三方登录平台[state]不能为空
|
||||
##租户
|
||||
tenant.number.not.blank=租户编号不能为空
|
||||
tenant.not.exists=对不起, 您的租户不存在,请联系管理员
|
||||
tenant.blocked=对不起,您的租户已禁用,请联系管理员
|
||||
tenant.expired=对不起,您的租户已过期,请联系管理员
|
||||
|
||||
|
||||
Binary file not shown.
@@ -38,7 +38,7 @@
|
||||
<!-- 循环政策:基于时间创建日志文件 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志文件名格式 -->
|
||||
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>60</maxHistory>
|
||||
</rollingPolicy>
|
||||
@@ -60,7 +60,7 @@
|
||||
<!-- 循环政策:基于时间创建日志文件 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 日志文件名格式 -->
|
||||
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
|
||||
<!-- 日志最大的历史 60天 -->
|
||||
<maxHistory>60</maxHistory>
|
||||
</rollingPolicy>
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>ruoyi-common</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<description>
|
||||
common 通用模块
|
||||
</description>
|
||||
|
||||
<modules>
|
||||
<module>ruoyi-common-bom</module>
|
||||
@@ -15,13 +21,11 @@
|
||||
<module>ruoyi-common-core</module>
|
||||
<module>ruoyi-common-doc</module>
|
||||
<module>ruoyi-common-excel</module>
|
||||
<module>ruoyi-common-idempotent</module>
|
||||
<module>ruoyi-common-job</module>
|
||||
<module>ruoyi-common-log</module>
|
||||
<module>ruoyi-common-mail</module>
|
||||
<module>ruoyi-common-mybatis</module>
|
||||
<module>ruoyi-common-oss</module>
|
||||
<module>ruoyi-common-ratelimiter</module>
|
||||
<module>ruoyi-common-redis</module>
|
||||
<module>ruoyi-common-satoken</module>
|
||||
<module>ruoyi-common-security</module>
|
||||
@@ -31,16 +35,9 @@
|
||||
<module>ruoyi-common-sensitive</module>
|
||||
<module>ruoyi-common-json</module>
|
||||
<module>ruoyi-common-encrypt</module>
|
||||
<module>ruoyi-common-tenant</module>
|
||||
<module>ruoyi-common-websocket</module>
|
||||
<module>ruoyi-common-sse</module>
|
||||
<module>ruoyi-common-mqtt</module>
|
||||
</modules>
|
||||
|
||||
<artifactId>ruoyi-common</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<description>
|
||||
common 通用模块
|
||||
</description>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</description>
|
||||
|
||||
<properties>
|
||||
<revision>5.4.0</revision>
|
||||
<revision>5.5.3</revision>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -40,13 +40,6 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 幂等 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-idempotent</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 调度模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
@@ -82,13 +75,6 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 限流 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-ratelimiter</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 缓存服务 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
@@ -158,13 +144,6 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 租户模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-tenant</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- WebSocket模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
@@ -179,6 +158,12 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-mqtt</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
<artifactId>spring-boot-starter-aspectj</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--常用工具类 -->
|
||||
|
||||
@@ -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());
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,19 +3,12 @@ package org.dromara.common.core.config;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
||||
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;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* 线程池配置
|
||||
@@ -24,7 +17,6 @@ import java.util.concurrent.ThreadPoolExecutor;
|
||||
**/
|
||||
@Slf4j
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(ThreadPoolProperties.class)
|
||||
public class ThreadPoolConfig {
|
||||
|
||||
/**
|
||||
@@ -34,18 +26,6 @@ public class ThreadPoolConfig {
|
||||
|
||||
private ScheduledExecutorService scheduledExecutorService;
|
||||
|
||||
@Bean(name = "threadPoolTaskExecutor")
|
||||
@ConditionalOnProperty(prefix = "thread-pool", name = "enabled", havingValue = "true")
|
||||
public ThreadPoolTaskExecutor threadPoolTaskExecutor(ThreadPoolProperties threadPoolProperties) {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(core);
|
||||
executor.setMaxPoolSize(core * 2);
|
||||
executor.setQueueCapacity(threadPoolProperties.getQueueCapacity());
|
||||
executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行周期性或定时任务
|
||||
*/
|
||||
@@ -64,7 +44,7 @@ public class ThreadPoolConfig {
|
||||
@Override
|
||||
protected void afterExecute(Runnable r, Throwable t) {
|
||||
super.afterExecute(r, t);
|
||||
Threads.printException(r, t);
|
||||
printException(r, t);
|
||||
}
|
||||
};
|
||||
this.scheduledExecutorService = scheduledThreadPoolExecutor;
|
||||
@@ -73,15 +53,57 @@ public class ThreadPoolConfig {
|
||||
|
||||
/**
|
||||
* 销毁事件
|
||||
* 停止线程池
|
||||
* 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
|
||||
* 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
|
||||
* 如果仍然超時,則強制退出.
|
||||
* 另对在shutdown时线程本身被调用中断做了处理.
|
||||
*/
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
try {
|
||||
log.info("====关闭后台任务任务线程池====");
|
||||
Threads.shutdownAndAwaitTermination(scheduledExecutorService);
|
||||
ScheduledExecutorService pool = scheduledExecutorService;
|
||||
if (pool != null && !pool.isShutdown()) {
|
||||
pool.shutdown();
|
||||
try {
|
||||
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
|
||||
pool.shutdownNow();
|
||||
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
|
||||
log.info("Pool did not terminate");
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
pool.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印线程异常信息
|
||||
*/
|
||||
public static void printException(Runnable r, Throwable t) {
|
||||
if (t == null && r instanceof Future<?>) {
|
||||
try {
|
||||
Future<?> future = (Future<?>) r;
|
||||
if (future.isDone()) {
|
||||
future.get();
|
||||
}
|
||||
} catch (CancellationException ce) {
|
||||
t = ce;
|
||||
} catch (ExecutionException ee) {
|
||||
t = ee.getCause();
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
if (t != null) {
|
||||
log.error(t.getMessage(), t);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.dromara.common.core.config;
|
||||
import jakarta.validation.Validator;
|
||||
import org.hibernate.validator.HibernateValidator;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
|
||||
import org.springframework.boot.validation.autoconfigure.ValidationAutoConfiguration;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package org.dromara.common.core.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* 线程池 配置属性
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "thread-pool")
|
||||
public class ThreadPoolProperties {
|
||||
|
||||
/**
|
||||
* 是否开启线程池
|
||||
*/
|
||||
private boolean enabled;
|
||||
|
||||
/**
|
||||
* 队列最大长度
|
||||
*/
|
||||
private int queueCapacity;
|
||||
|
||||
/**
|
||||
* 线程池维护线程所允许的空闲时间
|
||||
*/
|
||||
private int keepAliveSeconds;
|
||||
|
||||
}
|
||||
@@ -36,11 +36,6 @@ public interface CacheNames {
|
||||
*/
|
||||
String SYS_DICT_TYPE = "sys_dict_type";
|
||||
|
||||
/**
|
||||
* 租户
|
||||
*/
|
||||
String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";
|
||||
|
||||
/**
|
||||
* 客户端
|
||||
*/
|
||||
|
||||
@@ -72,5 +72,10 @@ public interface Constants {
|
||||
*/
|
||||
Long TOP_PARENT_ID = 0L;
|
||||
|
||||
/**
|
||||
* 加密头
|
||||
*/
|
||||
String ENCRYPT_HEADER = "ENC_";
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -72,9 +72,25 @@ public interface SystemConstants {
|
||||
*/
|
||||
Long SUPER_ADMIN_ID = 1L;
|
||||
|
||||
/**
|
||||
* 超级管理员角色 roleKey
|
||||
*/
|
||||
String SUPER_ADMIN_ROLE_KEY = "superadmin";
|
||||
|
||||
/**
|
||||
* 根部门祖级列表
|
||||
*/
|
||||
String ROOT_DEPT_ANCESTORS = "0";
|
||||
|
||||
/**
|
||||
* 默认部门 ID
|
||||
*/
|
||||
Long DEFAULT_DEPT_ID = 100L;
|
||||
|
||||
/**
|
||||
* 排除敏感属性字段
|
||||
*/
|
||||
String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package org.dromara.common.core.constant;
|
||||
|
||||
/**
|
||||
* 租户常量信息
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public interface TenantConstants {
|
||||
|
||||
/**
|
||||
* 超级管理员ID
|
||||
*/
|
||||
Long SUPER_ADMIN_ID = 1L;
|
||||
|
||||
/**
|
||||
* 超级管理员角色 roleKey
|
||||
*/
|
||||
String SUPER_ADMIN_ROLE_KEY = "superadmin";
|
||||
|
||||
/**
|
||||
* 租户管理员角色 roleKey
|
||||
*/
|
||||
String TENANT_ADMIN_ROLE_KEY = "admin";
|
||||
|
||||
/**
|
||||
* 租户管理员角色名称
|
||||
*/
|
||||
String TENANT_ADMIN_ROLE_NAME = "管理员";
|
||||
|
||||
/**
|
||||
* 默认租户ID
|
||||
*/
|
||||
String DEFAULT_TENANT_ID = "000000";
|
||||
|
||||
}
|
||||
@@ -50,6 +50,11 @@ public class CompleteTaskDTO implements Serializable {
|
||||
*/
|
||||
private String notice;
|
||||
|
||||
/**
|
||||
* 办理人(可不填 用于覆盖当前节点办理人)
|
||||
*/
|
||||
private String handler;
|
||||
|
||||
/**
|
||||
* 流程变量
|
||||
*/
|
||||
@@ -62,7 +67,8 @@ public class CompleteTaskDTO implements Serializable {
|
||||
|
||||
public Map<String, Object> getVariables() {
|
||||
if (variables == null) {
|
||||
return new HashMap<>(16);
|
||||
variables = new HashMap<>(16);
|
||||
return variables;
|
||||
}
|
||||
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
|
||||
return variables;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.dromara.common.core.domain.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 流程实例业务扩展对象
|
||||
*
|
||||
* @author may
|
||||
* @date 2025-08-05
|
||||
*/
|
||||
@Data
|
||||
public class FlowInstanceBizExtDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 流程实例ID
|
||||
*/
|
||||
private Long instanceId;
|
||||
|
||||
/**
|
||||
* 业务ID
|
||||
*/
|
||||
private String businessId;
|
||||
|
||||
/**
|
||||
* 业务编码
|
||||
*/
|
||||
private String businessCode;
|
||||
|
||||
/**
|
||||
* 业务标题
|
||||
*/
|
||||
private String businessTitle;
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.dromara.common.core.domain.dto;
|
||||
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
@@ -30,11 +31,21 @@ public class StartProcessDTO implements Serializable {
|
||||
*/
|
||||
private String flowCode;
|
||||
|
||||
/**
|
||||
* 办理人(可不填 用于覆盖当前节点办理人)
|
||||
*/
|
||||
private String handler;
|
||||
|
||||
/**
|
||||
* 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}}
|
||||
*/
|
||||
private Map<String, Object> variables;
|
||||
|
||||
/**
|
||||
* 流程业务扩展信息
|
||||
*/
|
||||
private FlowInstanceBizExtDTO bizExt;
|
||||
|
||||
public Map<String, Object> getVariables() {
|
||||
if (variables == null) {
|
||||
return new HashMap<>(16);
|
||||
@@ -42,4 +53,11 @@ public class StartProcessDTO implements Serializable {
|
||||
variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
|
||||
return variables;
|
||||
}
|
||||
|
||||
public FlowInstanceBizExtDTO getBizExt() {
|
||||
if (ObjectUtil.isNull(bizExt)) {
|
||||
bizExt = new FlowInstanceBizExtDTO();
|
||||
}
|
||||
return bizExt;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ public class UserDTO implements Serializable {
|
||||
private String sex;
|
||||
|
||||
/**
|
||||
* 帐号状态(0正常 1停用)
|
||||
* 账号状态(0正常 1停用)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
|
||||
@@ -16,11 +16,6 @@ public class ProcessDeleteEvent implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 流程定义编码
|
||||
*/
|
||||
|
||||
@@ -17,16 +17,16 @@ public class ProcessEvent implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 流程定义编码
|
||||
*/
|
||||
private String flowCode;
|
||||
|
||||
/**
|
||||
* 实例id
|
||||
*/
|
||||
private Long instanceId;
|
||||
|
||||
/**
|
||||
* 业务id
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,7 @@ import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 流程任务监听
|
||||
@@ -16,11 +17,6 @@ public class ProcessTaskEvent implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 流程定义编码
|
||||
*/
|
||||
@@ -46,6 +42,11 @@ public class ProcessTaskEvent implements Serializable {
|
||||
*/
|
||||
private Long taskId;
|
||||
|
||||
/**
|
||||
* 实例id
|
||||
*/
|
||||
private Long instanceId;
|
||||
|
||||
/**
|
||||
* 业务id
|
||||
*/
|
||||
@@ -56,4 +57,9 @@ public class ProcessTaskEvent implements Serializable {
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 办理参数
|
||||
*/
|
||||
private Map<String, Object> params;
|
||||
|
||||
}
|
||||
|
||||
@@ -30,11 +30,6 @@ public class LoginBody implements Serializable {
|
||||
@NotBlank(message = "{auth.grant.type.not.blank}")
|
||||
private String grantType;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
*/
|
||||
|
||||
@@ -22,11 +22,6 @@ public class LoginUser implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -26,8 +26,12 @@ public class RegisterBody 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;
|
||||
|
||||
/**
|
||||
* 用户类型
|
||||
*/
|
||||
private String userType;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package org.dromara.common.core.exception;
|
||||
|
||||
import lombok.*;
|
||||
import cn.hutool.core.text.StrFormatter;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 业务异常
|
||||
* 业务异常(支持占位符 {} )
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@@ -42,6 +46,10 @@ public final class ServiceException extends RuntimeException {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public ServiceException(String message, Object... args) {
|
||||
this.message = StrFormatter.format(message, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
package org.dromara.common.core.service;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.lang.Dict;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 通用 参数配置服务
|
||||
*
|
||||
@@ -15,4 +21,80 @@ public interface ConfigService {
|
||||
*/
|
||||
String getConfigValue(String configKey);
|
||||
|
||||
/**
|
||||
* 根据参数 key 获取布尔值
|
||||
*
|
||||
* @param configKey 参数 key
|
||||
* @return Boolean 值
|
||||
*/
|
||||
default Boolean getConfigBool(String configKey) {
|
||||
return Convert.toBool(getConfigValue(configKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据参数 key 获取整数值
|
||||
*
|
||||
* @param configKey 参数 key
|
||||
* @return Integer 值
|
||||
*/
|
||||
default Integer getConfigInt(String configKey) {
|
||||
return Convert.toInt(getConfigValue(configKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据参数 key 获取长整型值
|
||||
*
|
||||
* @param configKey 参数 key
|
||||
* @return Long 值
|
||||
*/
|
||||
default Long getConfigLong(String configKey) {
|
||||
return Convert.toLong(getConfigValue(configKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据参数 key 获取 BigDecimal 值
|
||||
*
|
||||
* @param configKey 参数 key
|
||||
* @return BigDecimal 值
|
||||
*/
|
||||
default BigDecimal getConfigDecimal(String configKey) {
|
||||
return Convert.toBigDecimal(getConfigValue(configKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据参数 key 获取 Map 类型的配置
|
||||
*
|
||||
* @param configKey 参数 key
|
||||
* @return Dict 对象,如果配置为空或无法解析,返回空 Dict
|
||||
*/
|
||||
Dict getConfigMap(String configKey);
|
||||
|
||||
/**
|
||||
* 根据参数 key 获取 Map 类型的配置列表
|
||||
*
|
||||
* @param configKey 参数 key
|
||||
* @return Dict 列表,如果配置为空或无法解析,返回空列表
|
||||
*/
|
||||
List<Dict> getConfigArrayMap(String configKey);
|
||||
|
||||
/**
|
||||
* 根据参数 key 获取指定类型的配置对象
|
||||
*
|
||||
* @param configKey 参数 key
|
||||
* @param clazz 目标对象类型
|
||||
* @param <T> 目标对象泛型
|
||||
* @return 对象实例,如果配置为空或无法解析,返回 null
|
||||
*/
|
||||
<T> T getConfigObject(String configKey, Class<T> clazz);
|
||||
|
||||
/**
|
||||
* 根据参数 key 获取指定类型的配置列表
|
||||
*
|
||||
* @param configKey 参数 key
|
||||
* @param clazz 目标元素类型
|
||||
* @param <T> 元素类型泛型
|
||||
* @return 指定类型列表,如果配置为空或无法解析,返回空列表
|
||||
*/
|
||||
<T> List<T> getConfigArray(String configKey, Class<T> clazz);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -100,28 +100,4 @@ public interface UserService {
|
||||
*/
|
||||
Map<Long, String> selectUserNamesByIds(List<Long> userIds);
|
||||
|
||||
/**
|
||||
* 根据角色 ID 列表查询角色名称映射关系
|
||||
*
|
||||
* @param roleIds 角色 ID 列表
|
||||
* @return Map,其中 key 为角色 ID,value 为对应的角色名称
|
||||
*/
|
||||
Map<Long, String> selectRoleNamesByIds(List<Long> roleIds);
|
||||
|
||||
/**
|
||||
* 根据部门 ID 列表查询部门名称映射关系
|
||||
*
|
||||
* @param deptIds 部门 ID 列表
|
||||
* @return Map,其中 key 为部门 ID,value 为对应的部门名称
|
||||
*/
|
||||
Map<Long, String> selectDeptNamesByIds(List<Long> deptIds);
|
||||
|
||||
/**
|
||||
* 根据岗位 ID 列表查询岗位名称映射关系
|
||||
*
|
||||
* @param postIds 岗位 ID 列表
|
||||
* @return Map,其中 key 为岗位 ID,value 为对应的岗位名称
|
||||
*/
|
||||
Map<Long, String> selectPostNamesByIds(List<Long> postIds);
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public interface WorkflowService {
|
||||
* @param businessIds 业务id
|
||||
* @return 结果
|
||||
*/
|
||||
boolean deleteInstance(List<Long> businessIds);
|
||||
boolean deleteInstance(List<String> businessIds);
|
||||
|
||||
/**
|
||||
* 获取当前流程状态
|
||||
@@ -61,13 +61,6 @@ public interface WorkflowService {
|
||||
*/
|
||||
Long getInstanceIdByBusinessId(String businessId);
|
||||
|
||||
/**
|
||||
* 新增租户流程定义
|
||||
*
|
||||
* @param tenantId 租户id
|
||||
*/
|
||||
void syncDef(String tenantId);
|
||||
|
||||
/**
|
||||
* 启动流程
|
||||
*
|
||||
@@ -82,6 +75,7 @@ public interface WorkflowService {
|
||||
* completeTask.getVariables().put("ignore", true);
|
||||
*
|
||||
* @param completeTask 参数
|
||||
* @return 结果
|
||||
*/
|
||||
boolean completeTask(CompleteTaskDTO completeTask);
|
||||
|
||||
@@ -90,6 +84,15 @@ public interface WorkflowService {
|
||||
*
|
||||
* @param taskId 任务ID
|
||||
* @param message 办理意见
|
||||
* @return 结果
|
||||
*/
|
||||
boolean completeTask(Long taskId, String message);
|
||||
|
||||
/**
|
||||
* 启动流程并办理第一个任务
|
||||
*
|
||||
* @param startProcess 参数
|
||||
* @return 结果
|
||||
*/
|
||||
boolean startCompleteTask(StartProcessDTO startProcess);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.dromara.common.core.utils;
|
||||
|
||||
import cn.hutool.core.date.DateUnit;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
import org.dromara.common.core.enums.FormatsType;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
@@ -293,8 +295,84 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
|
||||
|
||||
// 校验时间跨度不超过最大限制
|
||||
if (diff > maxValue) {
|
||||
throw new ServiceException("最大时间跨度为 " + maxValue + " " + unit.toString().toLowerCase());
|
||||
throw new ServiceException("最大时间跨度为 {} {}", maxValue, unit.toString().toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定日期时间获取时间段(凌晨 / 上午 / 中午 / 下午 / 晚上)
|
||||
*
|
||||
* @param date 日期时间
|
||||
* @return 时间段描述
|
||||
*/
|
||||
public static String getTodayHour(Date date) {
|
||||
int hour = DateUtil.hour(date, true);
|
||||
if (hour <= 6) {
|
||||
return "凌晨";
|
||||
} else if (hour < 12) {
|
||||
return "上午";
|
||||
} else if (hour == 12) {
|
||||
return "中午";
|
||||
} else if (hour <= 18) {
|
||||
return "下午";
|
||||
} else {
|
||||
return "晚上";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将日期格式化为仿微信的友好时间
|
||||
* <p>
|
||||
* 规则说明:
|
||||
* 1. 未来时间:yyyy-MM-dd HH:mm
|
||||
* 2. 今天:
|
||||
* - 1 分钟内:刚刚
|
||||
* - 1 小时内:X 分钟前
|
||||
* - 超过 1 小时:凌晨/上午/中午/下午/晚上 HH:mm
|
||||
* 3. 昨天:昨天 HH:mm
|
||||
* 4. 本周:周X HH:mm
|
||||
* 5. 今年内:MM-dd HH:mm
|
||||
* 6. 非今年:yyyy-MM-dd HH:mm
|
||||
*
|
||||
* @param date 日期时间
|
||||
* @return 格式化后的时间描述
|
||||
*/
|
||||
public static String formatFriendlyTime(Date date) {
|
||||
if (date == null) {
|
||||
return "";
|
||||
}
|
||||
Date now = DateUtil.date();
|
||||
|
||||
// 未来时间或非今年
|
||||
if (date.after(now) || DateUtil.year(date) != DateUtil.year(now)) {
|
||||
return parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM, date);
|
||||
}
|
||||
|
||||
// 今天
|
||||
if (DateUtil.isSameDay(date, now)) {
|
||||
long minutes = DateUtil.between(date, now, DateUnit.MINUTE);
|
||||
if (minutes < 1) {
|
||||
return "刚刚";
|
||||
}
|
||||
if (minutes < 60) {
|
||||
return minutes + "分钟前";
|
||||
}
|
||||
return getTodayHour(date) + " " + DateUtil.format(date, "HH:mm");
|
||||
}
|
||||
|
||||
// 昨天
|
||||
if (DateUtil.isSameDay(date, DateUtil.yesterday())) {
|
||||
return "昨天 " + DateUtil.format(date, "HH:mm");
|
||||
}
|
||||
|
||||
// 本周
|
||||
if (DateUtil.isSameWeek(date, now, true)) {
|
||||
return DateUtil.dayOfWeekEnum(date).toChinese("周")
|
||||
+ " " + DateUtil.format(date, "HH:mm");
|
||||
}
|
||||
|
||||
// 今年内其它时间
|
||||
return DateUtil.format(date, "MM-dd HH:mm");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package org.dromara.common.core.utils;
|
||||
|
||||
import cn.hutool.core.util.DesensitizedUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 脱敏工具类
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class DesensitizedUtils extends DesensitizedUtil {
|
||||
|
||||
/**
|
||||
* 灵活脱敏方法
|
||||
*
|
||||
* @param value 原始字符串
|
||||
* @param prefixVisible 前面可见长度
|
||||
* @param suffixVisible 后面可见长度
|
||||
* @param maskLength 中间掩码长度(固定显示多少 *,如果总长度不足则自动缩减)
|
||||
* @return 脱敏后字符串
|
||||
*/
|
||||
public static String mask(String value, int prefixVisible, int suffixVisible, int maskLength) {
|
||||
if (StrUtil.isBlank(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
int len = value.length();
|
||||
int prefixMaskLimit = prefixVisible + maskLength;
|
||||
int fullLimit = prefixMaskLimit + suffixVisible;
|
||||
|
||||
// 规则 1:长度 <= 中间掩码长度 → 全掩码
|
||||
if (len <= maskLength) {
|
||||
return StrUtil.repeat('*', len);
|
||||
}
|
||||
String mask = StrUtil.repeat('*', maskLength);
|
||||
|
||||
// 规则 2:长度 <= 前缀 + 中间掩码
|
||||
if (len <= prefixMaskLimit) {
|
||||
return value.substring(0, len - maskLength) + mask;
|
||||
}
|
||||
|
||||
String prefix = value.substring(0, prefixVisible);
|
||||
|
||||
// 规则 3:长度 <= 前缀 + 中间掩码 + 后缀
|
||||
if (len <= fullLimit) {
|
||||
int suffixLen = len - prefixMaskLimit;
|
||||
return prefix + mask + value.substring(len - suffixLen);
|
||||
}
|
||||
|
||||
// 规则 4:标准形态
|
||||
return prefix + mask + value.substring(len - suffixVisible);
|
||||
}
|
||||
|
||||
/**
|
||||
* 高安全级别脱敏方法(Token / 私钥)
|
||||
*
|
||||
* @param value 原始字符串
|
||||
* @param prefixVisible 前面可见长度(推荐0~4)
|
||||
* @param suffixVisible 后面可见长度(推荐0~4)
|
||||
* @return 脱敏后字符串
|
||||
*/
|
||||
public static String maskHighSecurity(String value, int prefixVisible, int suffixVisible) {
|
||||
if (StrUtil.isBlank(value)) {
|
||||
return value;
|
||||
}
|
||||
int len = value.length();
|
||||
|
||||
// 规则1:长度 <= 前缀可见长度 → 全部掩码
|
||||
if (len <= prefixVisible) {
|
||||
return StrUtil.repeat('*', len);
|
||||
}
|
||||
|
||||
// 规则2:长度 <= 前缀 + 后缀可见长度 → 优先掩码后面
|
||||
if (len <= prefixVisible + suffixVisible) {
|
||||
return value.substring(0, len - prefixVisible) + StrUtil.repeat('*', prefixVisible);
|
||||
}
|
||||
|
||||
// 规则3:标准形态 → 前后可见,中间全部掩码
|
||||
return value.substring(0, prefixVisible)
|
||||
+ StrUtil.repeat('*', len - prefixVisible - suffixVisible)
|
||||
+ value.substring(len - suffixVisible);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -115,7 +115,7 @@ public class ServletUtils extends JakartaServletUtil {
|
||||
public static Map<String, String> getParamMap(ServletRequest request) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
for (Map.Entry<String, String[]> entry : getParams(request).entrySet()) {
|
||||
params.put(entry.getKey(), StringUtils.join(entry.getValue(), StringUtils.SEPARATOR));
|
||||
params.put(entry.getKey(), StringUtils.joinComma(entry.getValue()));
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.dromara.common.core.utils;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.boot.autoconfigure.thread.Threading;
|
||||
import org.springframework.boot.thread.Threading;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -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;
|
||||
@@ -31,8 +30,10 @@ public class StreamUtils {
|
||||
if (CollUtil.isEmpty(collection)) {
|
||||
return CollUtil.newArrayList();
|
||||
}
|
||||
// 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
|
||||
return collection.stream().filter(function).collect(Collectors.toList());
|
||||
return collection.stream()
|
||||
.filter(function)
|
||||
// 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,13 +41,26 @@ public class StreamUtils {
|
||||
*
|
||||
* @param collection 需要查询的集合
|
||||
* @param function 过滤方法
|
||||
* @return 找到符合条件的第一个元素,没有则返回null
|
||||
* @return 找到符合条件的第一个元素,没有则返回 Optional.empty()
|
||||
*/
|
||||
public static <E> E findFirst(Collection<E> collection, Predicate<E> function) {
|
||||
public static <E> Optional<E> findFirst(Collection<E> collection, Predicate<E> function) {
|
||||
if (CollUtil.isEmpty(collection)) {
|
||||
return null;
|
||||
return Optional.empty();
|
||||
}
|
||||
return collection.stream().filter(function).findFirst().orElse(null);
|
||||
return collection.stream()
|
||||
.filter(function)
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* 找到流中满足条件的第一个元素值
|
||||
*
|
||||
* @param collection 需要查询的集合
|
||||
* @param function 过滤方法
|
||||
* @return 找到符合条件的第一个元素,没有则返回 null
|
||||
*/
|
||||
public static <E> E findFirstValue(Collection<E> collection, Predicate<E> function) {
|
||||
return findFirst(collection,function).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,13 +68,26 @@ public class StreamUtils {
|
||||
*
|
||||
* @param collection 需要查询的集合
|
||||
* @param function 过滤方法
|
||||
* @return 找到符合条件的任意一个元素,没有则返回null
|
||||
* @return 找到符合条件的任意一个元素,没有则返回 Optional.empty()
|
||||
*/
|
||||
public static <E> Optional<E> findAny(Collection<E> collection, Predicate<E> function) {
|
||||
if (CollUtil.isEmpty(collection)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return collection.stream().filter(function).findAny();
|
||||
return collection.stream()
|
||||
.filter(function)
|
||||
.findAny();
|
||||
}
|
||||
|
||||
/**
|
||||
* 找到流中任意一个满足条件的元素值
|
||||
*
|
||||
* @param collection 需要查询的集合
|
||||
* @param function 过滤方法
|
||||
* @return 找到符合条件的任意一个元素,没有则返回null
|
||||
*/
|
||||
public static <E> E findAnyValue(Collection<E> collection, Predicate<E> function) {
|
||||
return findAny(collection,function).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,7 +113,10 @@ public class StreamUtils {
|
||||
if (CollUtil.isEmpty(collection)) {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter));
|
||||
return collection.stream()
|
||||
.map(function)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.joining(delimiter));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,8 +130,11 @@ public class StreamUtils {
|
||||
if (CollUtil.isEmpty(collection)) {
|
||||
return CollUtil.newArrayList();
|
||||
}
|
||||
// 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
|
||||
return collection.stream().filter(Objects::nonNull).sorted(comparing).collect(Collectors.toList());
|
||||
return collection.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.sorted(comparing)
|
||||
// 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,7 +151,9 @@ public class StreamUtils {
|
||||
if (CollUtil.isEmpty(collection)) {
|
||||
return MapUtil.newHashMap();
|
||||
}
|
||||
return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, Function.identity(), (l, r) -> l));
|
||||
return collection.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(key, Function.identity(), (l, r) -> l));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,7 +172,25 @@ public class StreamUtils {
|
||||
if (CollUtil.isEmpty(collection)) {
|
||||
return MapUtil.newHashMap();
|
||||
}
|
||||
return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, value, (l, r) -> l));
|
||||
return collection.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(key, value, (l, r) -> l));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 map 中的数据作为新 Map 的 value ,key 不变
|
||||
* @param map 需要处理的map
|
||||
* @param take 取值函数
|
||||
* @param <K> map中的key类型
|
||||
* @param <E> map中的value类型
|
||||
* @param <V> 新map中的value类型
|
||||
* @return 新的map
|
||||
*/
|
||||
public static <K, E, V> Map<K, V> toMap(Map<K, E> map, BiFunction<K, E, V> take) {
|
||||
if (CollUtil.isEmpty(map)) {
|
||||
return MapUtil.newHashMap();
|
||||
}
|
||||
return toMap(map.entrySet(), Map.Entry::getKey, entry -> take.apply(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,8 +207,8 @@ public class StreamUtils {
|
||||
if (CollUtil.isEmpty(collection)) {
|
||||
return MapUtil.newHashMap();
|
||||
}
|
||||
return collection
|
||||
.stream().filter(Objects::nonNull)
|
||||
return collection.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList()));
|
||||
}
|
||||
|
||||
@@ -175,8 +228,8 @@ public class StreamUtils {
|
||||
if (CollUtil.isEmpty(collection)) {
|
||||
return MapUtil.newHashMap();
|
||||
}
|
||||
return collection
|
||||
.stream().filter(Objects::nonNull)
|
||||
return collection.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.groupingBy(key2, LinkedHashMap::new, Collectors.toList())));
|
||||
}
|
||||
|
||||
@@ -193,11 +246,11 @@ public class StreamUtils {
|
||||
* @return 分类后的map
|
||||
*/
|
||||
public static <E, T, U> Map<T, Map<U, E>> group2Map(Collection<E> collection, Function<E, T> key1, Function<E, U> key2) {
|
||||
if (CollUtil.isEmpty(collection) || key1 == null || key2 == null) {
|
||||
if (CollUtil.isEmpty(collection)) {
|
||||
return MapUtil.newHashMap();
|
||||
}
|
||||
return collection
|
||||
.stream().filter(Objects::nonNull)
|
||||
return collection.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.toMap(key2, Function.identity(), (l, r) -> l)));
|
||||
}
|
||||
|
||||
@@ -215,8 +268,7 @@ public class StreamUtils {
|
||||
if (CollUtil.isEmpty(collection)) {
|
||||
return CollUtil.newArrayList();
|
||||
}
|
||||
return collection
|
||||
.stream()
|
||||
return collection.stream()
|
||||
.map(function)
|
||||
.filter(Objects::nonNull)
|
||||
// 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题
|
||||
@@ -234,11 +286,10 @@ public class StreamUtils {
|
||||
* @return 转化后的Set
|
||||
*/
|
||||
public static <E, T> Set<T> toSet(Collection<E> collection, Function<E, T> function) {
|
||||
if (CollUtil.isEmpty(collection) || function == null) {
|
||||
if (CollUtil.isEmpty(collection)) {
|
||||
return CollUtil.newHashSet();
|
||||
}
|
||||
return collection
|
||||
.stream()
|
||||
return collection.stream()
|
||||
.map(function)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
@@ -258,26 +309,20 @@ public class StreamUtils {
|
||||
* @return 合并后的map
|
||||
*/
|
||||
public static <K, X, Y, V> Map<K, V> merge(Map<K, X> map1, Map<K, Y> map2, BiFunction<X, Y, V> merge) {
|
||||
if (MapUtil.isEmpty(map1) && MapUtil.isEmpty(map2)) {
|
||||
if (CollUtil.isEmpty(map1) && CollUtil.isEmpty(map2)) {
|
||||
// 如果两个 map 都为空,则直接返回空的 map
|
||||
return MapUtil.newHashMap();
|
||||
} else if (MapUtil.isEmpty(map1)) {
|
||||
map1 = MapUtil.newHashMap();
|
||||
} else if (MapUtil.isEmpty(map2)) {
|
||||
map2 = MapUtil.newHashMap();
|
||||
} else if (CollUtil.isEmpty(map1)) {
|
||||
// 如果 map1 为空,则直接处理返回 map2
|
||||
return toMap(map2.entrySet(), Map.Entry::getKey, entry -> merge.apply(null, entry.getValue()));
|
||||
} else if (CollUtil.isEmpty(map2)) {
|
||||
// 如果 map2 为空,则直接处理返回 map1
|
||||
return toMap(map1.entrySet(), Map.Entry::getKey, entry -> merge.apply(entry.getValue(), null));
|
||||
}
|
||||
Set<K> key = new HashSet<>();
|
||||
key.addAll(map1.keySet());
|
||||
key.addAll(map2.keySet());
|
||||
Map<K, V> map = new HashMap<>();
|
||||
for (K t : key) {
|
||||
X x = map1.get(t);
|
||||
Y y = map2.get(t);
|
||||
V z = merge.apply(x, y);
|
||||
if (z != null) {
|
||||
map.put(t, z);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
Set<K> keySet = new HashSet<>();
|
||||
keySet.addAll(map1.keySet());
|
||||
keySet.addAll(map2.keySet());
|
||||
return toMap(keySet, key -> key, key -> merge.apply(map1.get(key), map2.get(key)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.apache.commons.lang3.Strings;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
@@ -260,13 +261,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();
|
||||
}
|
||||
@@ -333,7 +334,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
|
||||
public static boolean startWithAnyIgnoreCase(CharSequence str, CharSequence... prefixs) {
|
||||
// 判断是否是以指定字符串开头
|
||||
for (CharSequence prefix : prefixs) {
|
||||
if (StringUtils.startsWithIgnoreCase(str, prefix)) {
|
||||
if (Strings.CI.startsWith(str, prefix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -361,5 +362,91 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
|
||||
return input;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 将可迭代对象中的元素使用逗号拼接成字符串
|
||||
*
|
||||
* @param iterable 可迭代对象,如 List、Set 等
|
||||
* @return 拼接后的字符串
|
||||
*/
|
||||
public static String joinComma(Iterable<?> iterable) {
|
||||
return StringUtils.join(iterable, SEPARATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数组中的元素使用逗号拼接成字符串
|
||||
*
|
||||
* @param array 任意类型的数组
|
||||
* @return 拼接后的字符串
|
||||
*/
|
||||
public static String joinComma(Object[] array) {
|
||||
return StringUtils.join(array, SEPARATOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个字符串是否相等
|
||||
*
|
||||
* @param cs1 字符串1
|
||||
* @param cs2 字符串2
|
||||
* @return 是否相等
|
||||
*/
|
||||
public static boolean equals(final CharSequence cs1, final CharSequence cs2) {
|
||||
return Strings.CS.equals(cs1, cs2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否在指定的字符串列表中
|
||||
*
|
||||
* @param string 字符串
|
||||
* @param searchStrings 字符串列表
|
||||
* @return 是否在列表中
|
||||
*/
|
||||
public static boolean equalsAny(final CharSequence string, final CharSequence... searchStrings) {
|
||||
return Strings.CS.equalsAny(string, searchStrings);
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略大小写判断字符串是否在指定的字符串列表中
|
||||
*
|
||||
* @param string 字符串
|
||||
* @param searchStrings 字符串列表
|
||||
* @return 是否在列表中
|
||||
*/
|
||||
public static boolean equalsAnyIgnoreCase(final CharSequence string, final CharSequence... searchStrings) {
|
||||
return Strings.CI.equalsAny(string, searchStrings);
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略大小写判断两个字符串是否相等
|
||||
*
|
||||
* @param cs1 字符串1
|
||||
* @param cs2 字符串2
|
||||
* @return 是否相等
|
||||
*/
|
||||
public static boolean equalsIgnoreCase(final CharSequence cs1, final CharSequence cs2) {
|
||||
return Strings.CI.equals(cs1, cs2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查指定的字符序列中是否包含另一个字符序列。
|
||||
*
|
||||
* @param seq 要检查的字符序列,不能为null
|
||||
* @param searchSeq 要搜索的字符序列,不能为null
|
||||
* @return 如果seq中包含searchSeq,则返回true;否则返回false
|
||||
*/
|
||||
public static boolean contains(final CharSequence seq, final CharSequence searchSeq) {
|
||||
return Strings.CS.contains(seq, searchSeq);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除字符串中的指定字符序列。
|
||||
*
|
||||
* @param str 要处理的字符串,不能为null
|
||||
* @param remove 要移除的字符序列,不能为null
|
||||
* @return 处理后的字符串
|
||||
*/
|
||||
public static String remove(final String str, final String remove) {
|
||||
return Strings.CS.remove(str, remove);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.dromara.common.core.utils;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* 线程工具
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class ThreadUtils {
|
||||
|
||||
/**
|
||||
* 批量执行任务
|
||||
*/
|
||||
public static void virtualSubmit(Runnable ...runnableList) {
|
||||
List<Future<?>> callableList = new ArrayList<>();
|
||||
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
|
||||
for (Runnable runnable : runnableList) {
|
||||
callableList.add(executor.submit(runnable));
|
||||
}
|
||||
for (Future<?> future : callableList) {
|
||||
future.get();
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package org.dromara.common.core.utils;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* 线程相关工具类.
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Slf4j
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class Threads {
|
||||
/**
|
||||
* 停止线程池
|
||||
* 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
|
||||
* 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
|
||||
* 如果仍然超時,則強制退出.
|
||||
* 另对在shutdown时线程本身被调用中断做了处理.
|
||||
*/
|
||||
public static void shutdownAndAwaitTermination(ExecutorService pool) {
|
||||
if (pool != null && !pool.isShutdown()) {
|
||||
pool.shutdown();
|
||||
try {
|
||||
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
|
||||
pool.shutdownNow();
|
||||
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
|
||||
log.info("Pool did not terminate");
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
pool.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印线程异常信息
|
||||
*/
|
||||
public static void printException(Runnable r, Throwable t) {
|
||||
if (t == null && r instanceof Future<?>) {
|
||||
try {
|
||||
Future<?> future = (Future<?>) r;
|
||||
if (future.isDone()) {
|
||||
future.get();
|
||||
}
|
||||
} catch (CancellationException ce) {
|
||||
t = ce;
|
||||
} catch (ExecutionException ee) {
|
||||
t = ee.getCause();
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
if (t != null) {
|
||||
log.error(t.getMessage(), t);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取节点列表中所有节点的叶子节点
|
||||
*
|
||||
|
||||
@@ -16,28 +16,28 @@ import org.dromara.common.core.utils.StringUtils;
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class AddressUtils {
|
||||
|
||||
// 未知地址
|
||||
public static final String UNKNOWN = "XX XX";
|
||||
// 未知IP
|
||||
public static final String UNKNOWN_IP = "XX XX";
|
||||
// 内网地址
|
||||
public static final String LOCAL_ADDRESS = "内网IP";
|
||||
|
||||
public static String getRealAddressByIP(String ip) {
|
||||
// 处理空串并过滤HTML标签
|
||||
ip = HtmlUtil.cleanHtmlTag(StringUtils.blankToDefault(ip,""));
|
||||
// 判断是否为IPv4
|
||||
boolean isIPv4 = NetUtils.isIPv4(ip);
|
||||
// 判断是否为IPv6
|
||||
boolean isIPv6 = NetUtils.isIPv6(ip);
|
||||
// 判断是否为IPv4或IPv6,如果不是则返回未知地址
|
||||
if (!NetUtils.isIPv4(ip) && !isIPv6) {
|
||||
return UNKNOWN;
|
||||
// 如果不是IPv4或IPv6,则返回未知IP
|
||||
if (!isIPv4 && !isIPv6) {
|
||||
return UNKNOWN_IP;
|
||||
}
|
||||
// 内网不查询
|
||||
if (NetUtils.isInnerIPv6(ip) || NetUtils.isInnerIP(ip)) {
|
||||
return "内网IP";
|
||||
if ((isIPv4 && NetUtils.isInnerIP(ip)) || (isIPv6 && NetUtils.isInnerIPv6(ip))) {
|
||||
return LOCAL_ADDRESS;
|
||||
}
|
||||
// 不支持IPv6,不再进行没有必要的IP地址信息的解析,直接返回
|
||||
if (isIPv6) {
|
||||
log.warn("ip2region不支持IPV6地址解析:{}", ip);
|
||||
// 如有需要,可自行实现IPv6地址信息解析逻辑,并在这里返回
|
||||
return "未知";
|
||||
}
|
||||
return RegionUtils.getCityInfo(ip);
|
||||
// Tips:Ip2Region 提供了精简的IPv6地址库,精简的IPv6地址库并不能完全支持IPv6地址的查询,且准确度上可能会存在问题,如需要准确的IPv6地址查询,建议自行实现
|
||||
return RegionUtils.getRegion(ip);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,50 +1,154 @@
|
||||
package org.dromara.common.core.utils.ip;
|
||||
|
||||
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 org.lionsoul.ip2region.service.Config;
|
||||
import org.lionsoul.ip2region.service.Ip2Region;
|
||||
import org.lionsoul.ip2region.xdb.Util;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* 根据ip地址定位工具类,离线方式
|
||||
* 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a>
|
||||
* IP地址行政区域工具类
|
||||
* 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">ip2region xdb java 查询客户端实现</a>
|
||||
* xdb数据库文件下载:<a href="https://gitee.com/lionsoul/ip2region/tree/master/data">ip2region data</a>
|
||||
*
|
||||
* @author lishuyan
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
@Slf4j
|
||||
public class RegionUtils {
|
||||
|
||||
// IP地址库文件名称
|
||||
public static final String IP_XDB_FILENAME = "ip2region.xdb";
|
||||
// 默认IPv4地址库文件路径
|
||||
// 下载地址:https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region_v4.xdb
|
||||
public static final String DEFAULT_IPV4_XDB_PATH = "ip2region_v4.xdb";
|
||||
|
||||
private static final Searcher SEARCHER;
|
||||
// 默认IPv6地址库文件路径
|
||||
// 下载地址:https://gitee.com/lionsoul/ip2region/blob/master/data/ip2region_v6.xdb
|
||||
public static final String DEFAULT_IPV6_XDB_PATH = "ip2region_v6.xdb";
|
||||
|
||||
// 默认缓存切片大小为15MB(仅针对BufferCache全量读取有效,如果你的xdb数据库很大,合理设置该值可以有效提升BufferCache模式下的查询效率,具体可以查看Ip2Region的README)
|
||||
// 注意:设置过大的值可能会申请内存时,因内存不足而导致OOM,请合理设置该值。
|
||||
// README:https://gitee.com/lionsoul/ip2region/tree/master/binding/java
|
||||
public static final int DEFAULT_CACHE_SLICE_BYTES = 1024 * 1024 * 15;
|
||||
|
||||
// 未知地址
|
||||
public static final String UNKNOWN_ADDRESS = "未知";
|
||||
|
||||
// Ip2Region服务实例
|
||||
private static Ip2Region ip2Region;
|
||||
|
||||
// 初始化Ip2Region服务实例
|
||||
static {
|
||||
try {
|
||||
// 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地址库数据不存在!");
|
||||
// 注意:Ip2Region 的xdb文件加载策略 CachePolicy 有三种,分别是:BufferCache(全量读取xdb到内存中)、VIndexCache(默认策略,按需读取并缓存)、NoCache(实时读取)
|
||||
// 本项目工具使用的 CachePolicy 为 BufferCache,BufferCache会加载整个xdb文件到内存中,setXdbInputStream 仅支持 BufferCache 策略。
|
||||
// 因为加载整个xdb文件会耗费非常大的内存,如果你不希望加载整个xdb到内存中,更推荐使用 VIndexCache 或 NoCache(即实时读取文件)策略和 setXdbPath/setXdbFile 加载方法(需要注意的一点,setXdbPath 和 setXdbFile 不支持读取ClassPath(即源码和resource目录)中的文件)。
|
||||
// 一般而言,更建议把xdb数据库放到一个指定的文件目录中(即不打包进jar包中),然后使用 VIndexCache + 配合SearcherPool的并发池读取数据,更方便随时更新xdb数据库
|
||||
|
||||
InputStream v4InputStream = ResourceUtil.getStream(DEFAULT_IPV4_XDB_PATH);
|
||||
|
||||
// IPv4配置
|
||||
Config v4Config = Config.custom()
|
||||
.setCachePolicy(Config.BufferCache)
|
||||
//.setXdbFile(v4TempXdb)
|
||||
.setXdbInputStream(v4InputStream)
|
||||
//
|
||||
.setCacheSliceBytes(DEFAULT_CACHE_SLICE_BYTES)
|
||||
.asV4();
|
||||
|
||||
// IPv6配置
|
||||
Config v6Config = null;
|
||||
InputStream v6XdbInputStream = ResourceUtil.getStreamSafe(DEFAULT_IPV6_XDB_PATH);
|
||||
if (v6XdbInputStream == null) {
|
||||
log.warn("未加载 IPv6 地址库:未在类路径下找到文件 {}。当前仅启用 IPv4 查询。如需启用 IPv6,请将 ip2region_v6.xdb 放置到 resources 目录", DEFAULT_IPV6_XDB_PATH);
|
||||
} else {
|
||||
v6Config = Config.custom()
|
||||
.setCachePolicy(Config.BufferCache)
|
||||
//.setXdbFile(v6TempXdb)
|
||||
.setXdbInputStream(v6XdbInputStream)
|
||||
.setCacheSliceBytes(DEFAULT_CACHE_SLICE_BYTES)
|
||||
.asV6();
|
||||
}
|
||||
|
||||
// 初始化Ip2Region实例
|
||||
RegionUtils.ip2Region = Ip2Region.create(v4Config, v6Config);
|
||||
log.debug("IP工具初始化成功,加载IP地址库数据成功!");
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException("RegionUtils初始化失败,原因:" + e.getMessage());
|
||||
throw new ServiceException("RegionUtils初始化失败,原因:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据IP地址离线获取城市
|
||||
*
|
||||
* @param ipString ip地址字符串
|
||||
*/
|
||||
public static String getCityInfo(String ip) {
|
||||
public static String getRegion(String ipString) {
|
||||
try {
|
||||
// 3、执行查询
|
||||
String region = SEARCHER.search(StringUtils.trim(ip));
|
||||
return region.replace("0|", "").replace("|0", "");
|
||||
String region = ip2Region.search(ipString);
|
||||
if (StringUtils.isBlank(region)) {
|
||||
return UNKNOWN_ADDRESS;
|
||||
}
|
||||
return StringUtils.replace(region, "0", UNKNOWN_ADDRESS);
|
||||
} catch (Exception e) {
|
||||
log.error("IP地址离线获取城市异常 {}", ip);
|
||||
return "未知";
|
||||
log.error("IP地址离线获取城市异常 {}", ipString);
|
||||
return UNKNOWN_ADDRESS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据IP地址离线获取城市
|
||||
*
|
||||
* @param ipBytes ip地址字节数组
|
||||
*/
|
||||
public static String getRegion(byte[] ipBytes) {
|
||||
try {
|
||||
String region = ip2Region.search(ipBytes);
|
||||
if (StringUtils.isBlank(region)) {
|
||||
return UNKNOWN_ADDRESS;
|
||||
}
|
||||
return StringUtils.replace(region, "0", UNKNOWN_ADDRESS);
|
||||
} catch (Exception e) {
|
||||
log.error("IP地址离线获取城市异常 {}", Util.ipToString(ipBytes));
|
||||
return UNKNOWN_ADDRESS;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭Ip2Region服务
|
||||
*/
|
||||
public static void close() {
|
||||
if (ip2Region == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ip2Region.close(10000);
|
||||
} catch (Exception e) {
|
||||
log.error("Ip2Region服务关闭异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭Ip2Region服务
|
||||
*
|
||||
* @param timeout 关闭超时时间
|
||||
*/
|
||||
public static void close(final Duration timeout) {
|
||||
if (ip2Region == null) {
|
||||
return;
|
||||
}
|
||||
if (timeout == null) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ip2Region.close(timeout.toMillis());
|
||||
} catch (Exception e) {
|
||||
log.error("Ip2Region服务关闭异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -21,6 +21,11 @@
|
||||
<artifactId>ruoyi-common-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-web-server</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
|
||||
|
||||
@@ -20,8 +20,8 @@ import org.springdoc.core.utils.PropertyResolverUtils;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.server.autoconfigure.ServerProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
|
||||
@@ -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.util.ReflectUtil;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.io.Resources;
|
||||
import org.dromara.common.core.constant.Constants;
|
||||
import org.dromara.common.core.utils.ObjectUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.encrypt.annotation.EncryptField;
|
||||
@@ -92,8 +93,12 @@ public class EncryptorManager {
|
||||
* @param encryptContext 加密相关的配置信息
|
||||
*/
|
||||
public String encrypt(String value, EncryptContext encryptContext) {
|
||||
if (StringUtils.startsWith(value, Constants.ENCRYPT_HEADER)) {
|
||||
return value;
|
||||
}
|
||||
IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);
|
||||
return encryptor.encrypt(value, encryptContext.getEncode());
|
||||
String encrypt = encryptor.encrypt(value, encryptContext.getEncode());
|
||||
return Constants.ENCRYPT_HEADER + encrypt;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,8 +108,12 @@ public class EncryptorManager {
|
||||
* @param encryptContext 加密相关的配置信息
|
||||
*/
|
||||
public String decrypt(String value, EncryptContext encryptContext) {
|
||||
if (!StringUtils.startsWith(value, Constants.ENCRYPT_HEADER)) {
|
||||
return value;
|
||||
}
|
||||
IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);
|
||||
return encryptor.decrypt(value);
|
||||
String str = StringUtils.removeStart(value, Constants.ENCRYPT_HEADER);
|
||||
return encryptor.decrypt(str);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.idev.excel</groupId>
|
||||
<artifactId>fastexcel</artifactId>
|
||||
<groupId>org.apache.fesod</groupId>
|
||||
<artifactId>fesod-sheet</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.dromara.common.excel.annotation;
|
||||
|
||||
import org.dromara.common.excel.core.ExcelOptionsProvider;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Excel动态下拉选项注解
|
||||
*
|
||||
* @author Angus
|
||||
*/
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface ExcelDynamicOptions {
|
||||
|
||||
/**
|
||||
* 提供者类全限定名
|
||||
* <p>
|
||||
* {@link org.dromara.common.excel.core.ExcelOptionsProvider} 接口实现类 class
|
||||
*/
|
||||
Class<? extends ExcelOptionsProvider> providerClass();
|
||||
}
|
||||
@@ -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,13 +2,13 @@ package org.dromara.common.excel.convert;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
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 org.apache.fesod.sheet.converters.Converter;
|
||||
import org.apache.fesod.sheet.enums.CellDataTypeEnum;
|
||||
import org.apache.fesod.sheet.metadata.GlobalConfiguration;
|
||||
import org.apache.fesod.sheet.metadata.data.ReadCellData;
|
||||
import org.apache.fesod.sheet.metadata.data.WriteCellData;
|
||||
import org.apache.fesod.sheet.metadata.property.ExcelContentProperty;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@@ -28,7 +28,7 @@ public class ExcelBigNumberConvert implements Converter<Long> {
|
||||
|
||||
@Override
|
||||
public CellDataTypeEnum supportExcelTypeKey() {
|
||||
return CellDataTypeEnum.STRING;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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 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.apache.fesod.sheet.converters.Converter;
|
||||
import org.apache.fesod.sheet.enums.CellDataTypeEnum;
|
||||
import org.apache.fesod.sheet.metadata.GlobalConfiguration;
|
||||
import org.apache.fesod.sheet.metadata.data.ReadCellData;
|
||||
import org.apache.fesod.sheet.metadata.data.WriteCellData;
|
||||
import org.apache.fesod.sheet.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 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.apache.fesod.sheet.converters.Converter;
|
||||
import org.apache.fesod.sheet.enums.CellDataTypeEnum;
|
||||
import org.apache.fesod.sheet.metadata.GlobalConfiguration;
|
||||
import org.apache.fesod.sheet.metadata.data.ReadCellData;
|
||||
import org.apache.fesod.sheet.metadata.data.WriteCellData;
|
||||
import org.apache.fesod.sheet.metadata.property.ExcelContentProperty;
|
||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
||||
import org.dromara.common.excel.annotation.ExcelEnumFormat;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
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 org.apache.fesod.sheet.annotation.ExcelIgnore;
|
||||
import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
|
||||
import org.apache.fesod.sheet.annotation.ExcelProperty;
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
||||
import org.dromara.common.excel.annotation.CellMerge;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 单元格合并处理器
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public class CellMergeHandler {
|
||||
|
||||
private final boolean hasTitle;
|
||||
private int rowIndex;
|
||||
|
||||
private CellMergeHandler(final boolean hasTitle) {
|
||||
this.hasTitle = hasTitle;
|
||||
// 行合并开始下标
|
||||
this.rowIndex = hasTitle ? 1 : 0;
|
||||
}
|
||||
private CellMergeHandler(final boolean hasTitle, final int rowIndex) {
|
||||
this.hasTitle = hasTitle;
|
||||
this.rowIndex = hasTitle ? rowIndex : 0;
|
||||
}
|
||||
@SneakyThrows
|
||||
public List<CellRangeAddress> handle(List<?> rows) {
|
||||
// 如果入参为空集合则返回空集
|
||||
if (CollUtil.isEmpty(rows)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// 获取有合并注解的字段
|
||||
Map<Field, FieldColumnIndex> mergeFields = getFieldColumnIndexMap(rows.get(0).getClass());
|
||||
// 如果没有需要合并的字段则返回空集
|
||||
if (CollUtil.isEmpty(mergeFields)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// 结果集
|
||||
List<CellRangeAddress> result = new ArrayList<>();
|
||||
|
||||
// 生成两两合并单元格
|
||||
Map<Field, RepeatCell> rowRepeatCellMap = new HashMap<>();
|
||||
for (Map.Entry<Field, FieldColumnIndex> item : mergeFields.entrySet()) {
|
||||
Field field = item.getKey();
|
||||
FieldColumnIndex itemValue = item.getValue();
|
||||
int colNum = itemValue.colIndex();
|
||||
CellMerge cellMerge = itemValue.cellMerge();
|
||||
|
||||
for (int i = 0; i < rows.size(); i++) {
|
||||
// 当前行数据
|
||||
Object currentRowObj = rows.get(i);
|
||||
// 当前行数据字段值
|
||||
Object currentRowObjFieldVal = ReflectUtils.invokeGetter(currentRowObj, field.getName());
|
||||
|
||||
// 空值跳过不处理
|
||||
if (currentRowObjFieldVal == null || "".equals(currentRowObjFieldVal)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 单元格合并Map是否存在数据,如果不存在则添加当前行的字段值
|
||||
if (!rowRepeatCellMap.containsKey(field)) {
|
||||
rowRepeatCellMap.put(field, RepeatCell.of(currentRowObjFieldVal, i));
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取 单元格合并Map 中字段值
|
||||
RepeatCell repeatCell = rowRepeatCellMap.get(field);
|
||||
Object cellValue = repeatCell.value();
|
||||
int current = repeatCell.current();
|
||||
|
||||
// 检查是否满足合并条件
|
||||
// currentRowObj 当前行数据
|
||||
// rows.get(i - 1) 上一行数据 注:由于 if (!rowRepeatCellMap.containsKey(field)) 条件的存在,所以该 i 必不可能小于1
|
||||
// cellMerge 当前行字段合并注解
|
||||
boolean merge = isMerge(currentRowObj, rows.get(i - 1), cellMerge);
|
||||
|
||||
// 是否添加到结果集
|
||||
boolean isAddResult = false;
|
||||
// 最新行
|
||||
int lastRow = i + rowIndex - 1;
|
||||
|
||||
// 如果当前行字段值和缓存中的字段值不相等,或不满足合并条件,则替换
|
||||
if (!currentRowObjFieldVal.equals(cellValue) || !merge) {
|
||||
rowRepeatCellMap.put(field, RepeatCell.of(currentRowObjFieldVal, i));
|
||||
isAddResult = true;
|
||||
}
|
||||
|
||||
// 如果最后一行不能合并,检查之前的数据是否需要合并;如果最后一行可以合并,则直接合并到最后
|
||||
if (i == rows.size() - 1) {
|
||||
isAddResult = true;
|
||||
if (i > current) {
|
||||
lastRow = i + rowIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (isAddResult && i > current) {
|
||||
//如果是同一行,则跳过合并
|
||||
if (current + rowIndex == lastRow) {
|
||||
continue;
|
||||
}
|
||||
result.add(new CellRangeAddress(current + rowIndex, lastRow, colNum, colNum));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取带有合并注解的字段列索引和合并注解信息Map集
|
||||
*/
|
||||
private Map<Field, FieldColumnIndex> getFieldColumnIndexMap(Class<?> clazz) {
|
||||
boolean annotationPresent = clazz.isAnnotationPresent(ExcelIgnoreUnannotated.class);
|
||||
Field[] fields = ReflectUtils.getFields(clazz, field -> {
|
||||
if ("serialVersionUID".equals(field.getName())) {
|
||||
return false;
|
||||
}
|
||||
if (field.isAnnotationPresent(ExcelIgnore.class)) {
|
||||
return false;
|
||||
}
|
||||
return !annotationPresent || field.isAnnotationPresent(ExcelProperty.class);
|
||||
});
|
||||
|
||||
// 有注解的字段
|
||||
Map<Field, FieldColumnIndex> mergeFields = new HashMap<>();
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
Field field = fields[i];
|
||||
if (!field.isAnnotationPresent(CellMerge.class)) {
|
||||
continue;
|
||||
}
|
||||
CellMerge cm = field.getAnnotation(CellMerge.class);
|
||||
int index = cm.index() == -1 ? i : cm.index();
|
||||
mergeFields.put(field, FieldColumnIndex.of(index, cm));
|
||||
|
||||
if (hasTitle) {
|
||||
ExcelProperty property = field.getAnnotation(ExcelProperty.class);
|
||||
rowIndex = Math.max(rowIndex, property.value().length);
|
||||
}
|
||||
}
|
||||
return mergeFields;
|
||||
}
|
||||
|
||||
private boolean isMerge(Object currentRow, Object preRow, CellMerge cellMerge) {
|
||||
final String[] mergeBy = cellMerge.mergeBy();
|
||||
if (StrUtil.isAllNotBlank(mergeBy)) {
|
||||
// 比对当前行和上一行的各个属性值一一比对 如果全为真 则为真
|
||||
for (String fieldName : mergeBy) {
|
||||
final Object valCurrent = ReflectUtil.getFieldValue(currentRow, fieldName);
|
||||
final Object valPre = ReflectUtil.getFieldValue(preRow, fieldName);
|
||||
if (!Objects.equals(valPre, valCurrent)) {
|
||||
// 依赖字段如有任一不等值,则标记为不可合并
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 单元格合并
|
||||
*/
|
||||
record RepeatCell(Object value, int current) {
|
||||
static RepeatCell of(Object value, int current) {
|
||||
return new RepeatCell(value, current);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段列索引和合并注解信息
|
||||
*/
|
||||
record FieldColumnIndex(int colIndex, CellMerge cellMerge) {
|
||||
static FieldColumnIndex of(int colIndex, CellMerge cellMerge) {
|
||||
return new FieldColumnIndex(colIndex, cellMerge);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 创建一个单元格合并处理器实例
|
||||
*
|
||||
* @param hasTitle 是否合并标题
|
||||
* @param rowIndex 行索引
|
||||
* @return 单元格合并处理器
|
||||
*/
|
||||
public static CellMergeHandler of(final boolean hasTitle, final int rowIndex) {
|
||||
return new CellMergeHandler(hasTitle, rowIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个单元格合并处理器实例
|
||||
*
|
||||
* @param hasTitle 是否合并标题
|
||||
* @return 单元格合并处理器
|
||||
*/
|
||||
public static CellMergeHandler of(final boolean hasTitle) {
|
||||
return new CellMergeHandler(hasTitle);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个单元格合并处理器实例(默认不合并标题)
|
||||
*
|
||||
* @return 单元格合并处理器
|
||||
*/
|
||||
public static CellMergeHandler of() {
|
||||
return new CellMergeHandler(false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +1,17 @@
|
||||
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 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;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fesod.sheet.metadata.Head;
|
||||
import org.apache.fesod.sheet.write.handler.SheetWriteHandler;
|
||||
import org.apache.fesod.sheet.write.merge.AbstractMergeStrategy;
|
||||
import org.apache.fesod.sheet.write.metadata.holder.WriteSheetHolder;
|
||||
import org.apache.fesod.sheet.write.metadata.holder.WriteWorkbookHolder;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
||||
import org.dromara.common.excel.annotation.CellMerge;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 列值重复合并策略
|
||||
@@ -27,137 +19,47 @@ import java.util.*;
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
public class CellMergeStrategy extends AbstractMergeStrategy implements WorkbookWriteHandler {
|
||||
public class CellMergeStrategy extends AbstractMergeStrategy implements SheetWriteHandler {
|
||||
|
||||
private final List<CellRangeAddress> cellList;
|
||||
private final boolean hasTitle;
|
||||
private int rowIndex;
|
||||
|
||||
public CellMergeStrategy(List<CellRangeAddress> cellList) {
|
||||
this.cellList = cellList;
|
||||
}
|
||||
|
||||
public CellMergeStrategy(List<?> list, boolean hasTitle) {
|
||||
this.hasTitle = hasTitle;
|
||||
// 行合并开始下标
|
||||
this.rowIndex = hasTitle ? 1 : 0;
|
||||
this.cellList = handle(list, hasTitle);
|
||||
this.cellList = CellMergeHandler.of(hasTitle).handle(list);
|
||||
}
|
||||
|
||||
public CellMergeStrategy(List<?> list, boolean hasTitle, int rowIndex) {
|
||||
this.cellList = CellMergeHandler.of(hasTitle, rowIndex).handle(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
|
||||
//单元格写入了,遍历合并区域,如果该Cell在区域内,但非首行,则清空
|
||||
if (CollUtil.isEmpty(cellList)) {
|
||||
return;
|
||||
}
|
||||
// 单元格写入了,遍历合并区域,如果该Cell在区域内,但非首行,则清空
|
||||
final int rowIndex = cell.getRowIndex();
|
||||
if (CollUtil.isNotEmpty(cellList)){
|
||||
for (CellRangeAddress cellAddresses : cellList) {
|
||||
final int firstRow = cellAddresses.getFirstRow();
|
||||
if (cellAddresses.isInRange(cell) && rowIndex != firstRow){
|
||||
cell.setBlank();
|
||||
}
|
||||
for (CellRangeAddress cellAddresses : cellList) {
|
||||
final int firstRow = cellAddresses.getFirstRow();
|
||||
if (cellAddresses.isInRange(cell) && rowIndex != firstRow) {
|
||||
cell.setBlank();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterWorkbookDispose(final WorkbookWriteHandlerContext context) {
|
||||
//当前表格写完后,统一写入
|
||||
if (CollUtil.isNotEmpty(cellList)){
|
||||
for (CellRangeAddress item : cellList) {
|
||||
context.getWriteContext().writeSheetHolder().getSheet().addMergedRegion(item);
|
||||
}
|
||||
public void afterSheetCreate(final WriteWorkbookHolder writeWorkbookHolder, final WriteSheetHolder writeSheetHolder) {
|
||||
if (CollUtil.isEmpty(cellList)) {
|
||||
return;
|
||||
}
|
||||
// 在 Sheet 创建时提前写入合并区域;后续写入只会影响首格,不会移除合并
|
||||
final Sheet sheet = writeSheetHolder.getSheet();
|
||||
for (CellRangeAddress item : cellList) {
|
||||
sheet.addMergedRegion(item);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private List<CellRangeAddress> handle(List<?> list, boolean hasTitle) {
|
||||
List<CellRangeAddress> cellList = new ArrayList<>();
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return cellList;
|
||||
}
|
||||
Field[] fields = ReflectUtils.getFields(list.get(0).getClass(), field -> !"serialVersionUID".equals(field.getName()));
|
||||
|
||||
// 有注解的字段
|
||||
List<Field> mergeFields = new ArrayList<>();
|
||||
List<Integer> mergeFieldsIndex = new ArrayList<>();
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
Field field = fields[i];
|
||||
if (field.isAnnotationPresent(CellMerge.class)) {
|
||||
CellMerge cm = field.getAnnotation(CellMerge.class);
|
||||
mergeFields.add(field);
|
||||
mergeFieldsIndex.add(cm.index() == -1 ? i : cm.index());
|
||||
if (hasTitle) {
|
||||
ExcelProperty property = field.getAnnotation(ExcelProperty.class);
|
||||
rowIndex = Math.max(rowIndex, property.value().length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Map<Field, RepeatCell> map = new HashMap<>();
|
||||
// 生成两两合并单元格
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
for (int j = 0; j < mergeFields.size(); j++) {
|
||||
Field field = mergeFields.get(j);
|
||||
Object val = ReflectUtils.invokeGetter(list.get(i), field.getName());
|
||||
|
||||
int colNum = mergeFieldsIndex.get(j);
|
||||
if (!map.containsKey(field)) {
|
||||
map.put(field, new RepeatCell(val, i));
|
||||
} else {
|
||||
RepeatCell repeatCell = map.get(field);
|
||||
Object cellValue = repeatCell.getValue();
|
||||
if (cellValue == null || "".equals(cellValue)) {
|
||||
// 空值跳过不合并
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!cellValue.equals(val)) {
|
||||
if ((i - repeatCell.getCurrent() > 1)) {
|
||||
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
|
||||
}
|
||||
map.put(field, new RepeatCell(val, i));
|
||||
} else if (i == list.size() - 1) {
|
||||
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)) {
|
||||
if ((i - repeatCell.getCurrent() > 1)) {
|
||||
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
|
||||
}
|
||||
map.put(field, new RepeatCell(val, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return cellList;
|
||||
}
|
||||
|
||||
private boolean isMerge(List<?> list, int i, Field field) {
|
||||
boolean isMerge = true;
|
||||
CellMerge cm = field.getAnnotation(CellMerge.class);
|
||||
final String[] mergeBy = cm.mergeBy();
|
||||
if (StrUtil.isAllNotBlank(mergeBy)) {
|
||||
//比对当前list(i)和list(i - 1)的各个属性值一一比对 如果全为真 则为真
|
||||
for (String fieldName : mergeBy) {
|
||||
final Object valCurrent = ReflectUtil.getFieldValue(list.get(i), fieldName);
|
||||
final Object valPre = ReflectUtil.getFieldValue(list.get(i - 1), fieldName);
|
||||
if (!Objects.equals(valPre, valCurrent)) {
|
||||
//依赖字段如有任一不等值,则标记为不可合并
|
||||
isMerge = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return isMerge;
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
static class RepeatCell {
|
||||
|
||||
private Object value;
|
||||
|
||||
private int current;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.dromara.common.excel.core;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
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.apache.fesod.sheet.context.AnalysisContext;
|
||||
import org.apache.fesod.sheet.event.AnalysisEventListener;
|
||||
import org.apache.fesod.sheet.exception.ExcelAnalysisException;
|
||||
import org.apache.fesod.sheet.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 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 org.apache.fesod.sheet.metadata.FieldCache;
|
||||
import org.apache.fesod.sheet.metadata.FieldWrapper;
|
||||
import org.apache.fesod.sheet.util.ClassUtils;
|
||||
import org.apache.fesod.sheet.write.handler.SheetWriteHandler;
|
||||
import org.apache.fesod.sheet.write.metadata.holder.WriteSheetHolder;
|
||||
import org.apache.fesod.sheet.write.metadata.holder.WriteWorkbookHolder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.ss.util.CellRangeAddressList;
|
||||
@@ -22,6 +23,7 @@ import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.excel.annotation.ExcelDictFormat;
|
||||
import org.dromara.common.excel.annotation.ExcelDynamicOptions;
|
||||
import org.dromara.common.excel.annotation.ExcelEnumFormat;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -103,7 +105,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
||||
if (StringUtils.isNotBlank(dictType)) {
|
||||
// 如果传递了字典名,则依据字典建立下拉
|
||||
Collection<String> values = Optional.ofNullable(dictService.getAllDictByDictType(dictType))
|
||||
.orElseThrow(() -> new ServiceException(String.format("字典 %s 不存在", dictType)))
|
||||
.orElseThrow(() -> new ServiceException("字典 {} 不存在", dictType))
|
||||
.values();
|
||||
options = new ArrayList<>(values);
|
||||
} else if (StringUtils.isNotBlank(converterExp)) {
|
||||
@@ -115,7 +117,16 @@ 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);
|
||||
} else if (field.isAnnotationPresent(ExcelDynamicOptions.class)) {
|
||||
// 处理动态下拉选项
|
||||
ExcelDynamicOptions dynamicOptions = field.getDeclaredAnnotation(ExcelDynamicOptions.class);
|
||||
// 获取提供者实例
|
||||
ExcelOptionsProvider provider = SpringUtils.getBean(dynamicOptions.providerClass());
|
||||
Set<String> providerOptions = provider.getOptions();
|
||||
if (CollUtil.isNotEmpty(providerOptions)) {
|
||||
options = new ArrayList<>(providerOptions);
|
||||
}
|
||||
}
|
||||
if (ObjectUtil.isNotEmpty(options)) {
|
||||
// 仅当下拉可选项不为空时执行
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.dromara.common.excel.core;
|
||||
|
||||
import cn.idev.excel.read.listener.ReadListener;
|
||||
import org.apache.fesod.sheet.read.listener.ReadListener;
|
||||
|
||||
/**
|
||||
* Excel 导入监听
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.dromara.common.excel.core;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Excel下拉选项数据提供接口
|
||||
*
|
||||
* @author Angus
|
||||
*/
|
||||
public interface ExcelOptionsProvider {
|
||||
|
||||
/**
|
||||
* 获取下拉选项数据
|
||||
*
|
||||
* @return 下拉选项列表
|
||||
*/
|
||||
Set<String> getOptions();
|
||||
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
package org.dromara.common.excel.handler;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
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.fesod.sheet.annotation.ExcelProperty;
|
||||
import org.apache.fesod.sheet.metadata.data.DataFormatData;
|
||||
import org.apache.fesod.sheet.metadata.data.WriteCellData;
|
||||
import org.apache.fesod.sheet.util.StyleUtil;
|
||||
import org.apache.fesod.sheet.write.handler.CellWriteHandler;
|
||||
import org.apache.fesod.sheet.write.handler.SheetWriteHandler;
|
||||
import org.apache.fesod.sheet.write.handler.context.CellWriteHandlerContext;
|
||||
import org.apache.fesod.sheet.write.metadata.holder.WriteSheetHolder;
|
||||
import org.apache.fesod.sheet.write.metadata.style.WriteCellStyle;
|
||||
import org.apache.fesod.sheet.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 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 org.apache.fesod.sheet.FesodSheet;
|
||||
import org.apache.fesod.sheet.ExcelWriter;
|
||||
import org.apache.fesod.sheet.write.builder.ExcelWriterSheetBuilder;
|
||||
import org.apache.fesod.sheet.write.metadata.WriteSheet;
|
||||
import org.apache.fesod.sheet.write.metadata.fill.FillConfig;
|
||||
import org.apache.fesod.sheet.write.metadata.fill.FillWrapper;
|
||||
import org.apache.fesod.sheet.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
||||
import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.AccessLevel;
|
||||
@@ -27,6 +27,7 @@ import java.io.UnsupportedEncodingException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Excel相关处理
|
||||
@@ -43,7 +44,7 @@ public class ExcelUtil {
|
||||
* @return 转换后集合
|
||||
*/
|
||||
public static <T> List<T> importExcel(InputStream is, Class<T> clazz) {
|
||||
return FastExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
|
||||
return FesodSheet.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +58,7 @@ public class ExcelUtil {
|
||||
*/
|
||||
public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate) {
|
||||
DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
|
||||
FastExcel.read(is, clazz, listener).sheet().doRead();
|
||||
FesodSheet.read(is, clazz, listener).sheet().doRead();
|
||||
return listener.getExcelResult();
|
||||
}
|
||||
|
||||
@@ -70,7 +71,7 @@ public class ExcelUtil {
|
||||
* @return 转换后集合
|
||||
*/
|
||||
public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener) {
|
||||
FastExcel.read(is, clazz, listener).sheet().doRead();
|
||||
FesodSheet.read(is, clazz, listener).sheet().doRead();
|
||||
return listener.getExcelResult();
|
||||
}
|
||||
|
||||
@@ -186,7 +187,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 = FastExcel.write(os, clazz)
|
||||
ExcelWriterSheetBuilder builder = FesodSheet.write(os, clazz)
|
||||
.autoCloseStream(false)
|
||||
// 自动适配
|
||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
||||
@@ -203,6 +204,44 @@ public class ExcelUtil {
|
||||
builder.doWrite(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出excel
|
||||
*
|
||||
* @param headType 带Excel注解的类型
|
||||
* @param os 输出流
|
||||
* @param options Excel下拉可选项
|
||||
* @param consumer 导出助手消费函数
|
||||
*/
|
||||
public static <T> void exportExcel(Class<T> headType, OutputStream os, List<DropDownOptions> options, Consumer<ExcelWriterWrapper<T>> consumer) {
|
||||
try (ExcelWriter writer = FesodSheet.write(os, headType)
|
||||
.autoCloseStream(false)
|
||||
// 自动适配
|
||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
||||
// 大数值自动转换 防止失真
|
||||
.registerConverter(new ExcelBigNumberConvert())
|
||||
// 批注必填项处理
|
||||
.registerWriteHandler(new DataWriteHandler(headType))
|
||||
// 添加下拉框操作
|
||||
.registerWriteHandler(new ExcelDownHandler(options))
|
||||
.build()) {
|
||||
// 执行消费函数
|
||||
consumer.accept(ExcelWriterWrapper.of(writer));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出excel
|
||||
*
|
||||
* @param headType 带Excel注解的类型
|
||||
* @param os 输出流
|
||||
* @param consumer 导出助手消费函数
|
||||
*/
|
||||
public static <T> void exportExcel(Class<T> headType, OutputStream os, Consumer<ExcelWriterWrapper<T>> consumer) {
|
||||
exportExcel(headType, os, null, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 单表多数据模板导出 模板格式为 {.属性}
|
||||
*
|
||||
@@ -237,14 +276,14 @@ public class ExcelUtil {
|
||||
*/
|
||||
public static <T> void exportTemplate(List<T> data, String templatePath, OutputStream os) {
|
||||
ClassPathResource templateResource = new ClassPathResource(templatePath);
|
||||
ExcelWriter excelWriter = FastExcel.write(os)
|
||||
ExcelWriter excelWriter = FesodSheet.write(os)
|
||||
.withTemplate(templateResource.getStream())
|
||||
.autoCloseStream(false)
|
||||
// 大数值自动转换 防止失真
|
||||
.registerConverter(new ExcelBigNumberConvert())
|
||||
.registerWriteHandler(new DataWriteHandler(data.get(0).getClass()))
|
||||
.registerWriteHandler(new DataWriteHandler(data.getFirst().getClass()))
|
||||
.build();
|
||||
WriteSheet writeSheet = FastExcel.writerSheet().build();
|
||||
WriteSheet writeSheet = FesodSheet.writerSheet().build();
|
||||
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
|
||||
// 单表多数据导出 模板格式为 {.属性}
|
||||
for (T d : data) {
|
||||
@@ -310,13 +349,13 @@ public class ExcelUtil {
|
||||
*/
|
||||
public static void exportTemplateMultiList(Map<String, Object> data, String templatePath, OutputStream os) {
|
||||
ClassPathResource templateResource = new ClassPathResource(templatePath);
|
||||
ExcelWriter excelWriter = FastExcel.write(os)
|
||||
ExcelWriter excelWriter = FesodSheet.write(os)
|
||||
.withTemplate(templateResource.getStream())
|
||||
.autoCloseStream(false)
|
||||
// 大数值自动转换 防止失真
|
||||
.registerConverter(new ExcelBigNumberConvert())
|
||||
.build();
|
||||
WriteSheet writeSheet = FastExcel.writerSheet().build();
|
||||
WriteSheet writeSheet = FesodSheet.writerSheet().build();
|
||||
for (Map.Entry<String, Object> map : data.entrySet()) {
|
||||
// 设置列表后续还有数据
|
||||
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
|
||||
@@ -341,14 +380,14 @@ public class ExcelUtil {
|
||||
*/
|
||||
public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String templatePath, OutputStream os) {
|
||||
ClassPathResource templateResource = new ClassPathResource(templatePath);
|
||||
ExcelWriter excelWriter = FastExcel.write(os)
|
||||
ExcelWriter excelWriter = FesodSheet.write(os)
|
||||
.withTemplate(templateResource.getStream())
|
||||
.autoCloseStream(false)
|
||||
// 大数值自动转换 防止失真
|
||||
.registerConverter(new ExcelBigNumberConvert())
|
||||
.build();
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
WriteSheet writeSheet = FastExcel.writerSheet(i).build();
|
||||
WriteSheet writeSheet = FesodSheet.writerSheet(i).build();
|
||||
for (Map.Entry<String, Object> map : data.get(i).entrySet()) {
|
||||
// 设置列表后续还有数据
|
||||
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
|
||||
@@ -388,7 +427,7 @@ public class ExcelUtil {
|
||||
if (StringUtils.containsAny(propertyValue, separator)) {
|
||||
for (String value : propertyValue.split(separator)) {
|
||||
if (itemArray[0].equals(value)) {
|
||||
propertyString.append(itemArray[1] + separator);
|
||||
propertyString.append(itemArray[1]).append(separator);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -417,7 +456,7 @@ public class ExcelUtil {
|
||||
if (StringUtils.containsAny(propertyValue, separator)) {
|
||||
for (String value : propertyValue.split(separator)) {
|
||||
if (itemArray[1].equals(value)) {
|
||||
propertyString.append(itemArray[0] + separator);
|
||||
propertyString.append(itemArray[0]).append(separator);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package org.dromara.common.excel.utils;
|
||||
|
||||
import org.apache.fesod.sheet.ExcelWriter;
|
||||
import org.apache.fesod.sheet.FesodSheet;
|
||||
import org.apache.fesod.sheet.context.WriteContext;
|
||||
import org.apache.fesod.sheet.write.builder.ExcelWriterSheetBuilder;
|
||||
import org.apache.fesod.sheet.write.builder.ExcelWriterTableBuilder;
|
||||
import org.apache.fesod.sheet.write.metadata.WriteSheet;
|
||||
import org.apache.fesod.sheet.write.metadata.WriteTable;
|
||||
import org.apache.fesod.sheet.write.metadata.fill.FillConfig;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* ExcelWriterWrapper Excel写出包装器
|
||||
* <br>
|
||||
* 提供了一组与 ExcelWriter 一一对应的写出方法,避免直接提供 ExcelWriter 而导致的一些不可控问题(比如提前关闭了IO流等)
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
* @see ExcelWriter
|
||||
*/
|
||||
public record ExcelWriterWrapper<T>(ExcelWriter excelWriter) {
|
||||
|
||||
public void write(Collection<T> data, WriteSheet writeSheet) {
|
||||
excelWriter.write(data, writeSheet);
|
||||
}
|
||||
|
||||
public void write(Supplier<Collection<T>> supplier, WriteSheet writeSheet) {
|
||||
excelWriter.write(supplier.get(), writeSheet);
|
||||
}
|
||||
|
||||
public void write(Collection<T> data, WriteSheet writeSheet, WriteTable writeTable) {
|
||||
excelWriter.write(data, writeSheet, writeTable);
|
||||
}
|
||||
|
||||
public void write(Supplier<Collection<T>> supplier, WriteSheet writeSheet, WriteTable writeTable) {
|
||||
excelWriter.write(supplier.get(), writeSheet, writeTable);
|
||||
}
|
||||
|
||||
public void fill(Object data, WriteSheet writeSheet) {
|
||||
excelWriter.fill(data, writeSheet);
|
||||
}
|
||||
|
||||
public void fill(Object data, FillConfig fillConfig, WriteSheet writeSheet) {
|
||||
excelWriter.fill(data, fillConfig, writeSheet);
|
||||
}
|
||||
|
||||
public void fill(Supplier<Object> supplier, WriteSheet writeSheet) {
|
||||
excelWriter.fill(supplier, writeSheet);
|
||||
}
|
||||
|
||||
public void fill(Supplier<Object> supplier, FillConfig fillConfig, WriteSheet writeSheet) {
|
||||
excelWriter.fill(supplier, fillConfig, writeSheet);
|
||||
}
|
||||
|
||||
public WriteContext writeContext() {
|
||||
return excelWriter.writeContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个 ExcelWriterWrapper
|
||||
*
|
||||
* @param excelWriter ExcelWriter
|
||||
* @return ExcelWriterWrapper
|
||||
*/
|
||||
public static <T> ExcelWriterWrapper<T> of(ExcelWriter excelWriter) {
|
||||
return new ExcelWriterWrapper<>(excelWriter);
|
||||
}
|
||||
|
||||
// -------------------------------- sheet start
|
||||
|
||||
public static WriteSheet buildSheet(Integer sheetNo, String sheetName) {
|
||||
return sheetBuilder(sheetNo, sheetName).build();
|
||||
}
|
||||
|
||||
public static WriteSheet buildSheet(Integer sheetNo) {
|
||||
return sheetBuilder(sheetNo).build();
|
||||
}
|
||||
|
||||
public static WriteSheet buildSheet(String sheetName) {
|
||||
return sheetBuilder(sheetName).build();
|
||||
}
|
||||
|
||||
public static WriteSheet buildSheet() {
|
||||
return sheetBuilder().build();
|
||||
}
|
||||
|
||||
public static ExcelWriterSheetBuilder sheetBuilder(Integer sheetNo, String sheetName) {
|
||||
return FesodSheet.writerSheet(sheetNo, sheetName);
|
||||
}
|
||||
|
||||
public static ExcelWriterSheetBuilder sheetBuilder(Integer sheetNo) {
|
||||
return FesodSheet.writerSheet(sheetNo);
|
||||
}
|
||||
|
||||
public static ExcelWriterSheetBuilder sheetBuilder(String sheetName) {
|
||||
return FesodSheet.writerSheet(sheetName);
|
||||
}
|
||||
|
||||
public static ExcelWriterSheetBuilder sheetBuilder() {
|
||||
return FesodSheet.writerSheet();
|
||||
}
|
||||
|
||||
// -------------------------------- sheet end
|
||||
|
||||
// -------------------------------- table start
|
||||
|
||||
public static WriteTable buildTable(Integer tableNo) {
|
||||
return tableBuilder(tableNo).build();
|
||||
}
|
||||
|
||||
public static WriteTable buildTable() {
|
||||
return tableBuilder().build();
|
||||
}
|
||||
|
||||
public static ExcelWriterTableBuilder tableBuilder(Integer tableNo) {
|
||||
return FesodSheet.writerTable(tableNo);
|
||||
}
|
||||
|
||||
public static ExcelWriterTableBuilder tableBuilder() {
|
||||
return FesodSheet.writerTable();
|
||||
}
|
||||
|
||||
// -------------------------------- table end
|
||||
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>ruoyi-common-idempotent</artifactId>
|
||||
|
||||
<description>
|
||||
ruoyi-common-idempotent 幂等功能
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-json</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -1 +0,0 @@
|
||||
org.dromara.common.idempotent.config.IdempotentConfig
|
||||
@@ -21,15 +21,9 @@
|
||||
<artifactId>ruoyi-common-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JSON工具类 -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jackson</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user