mirror of
https://github.com/dromara/RuoYi-Vue-Plus.git
synced 2025-09-24 07:19:46 +08:00
Compare commits
136 Commits
Author | SHA1 | Date | |
---|---|---|---|
ded2a32fdf | |||
80644cbba3 | |||
ed43f0a889 | |||
a7daed30d8 | |||
f2598e7961 | |||
4c848cf4cb | |||
4d1bf7c25c | |||
88bd127b67 | |||
1186b86f73 | |||
eb946e029d | |||
9b68b27168 | |||
5dbb255323 | |||
cc326e36cc | |||
bacdd8d685 | |||
8ce96690fc | |||
9d0d52a48c | |||
8d0c06b5bf | |||
245ade0d79 | |||
8de02e285e | |||
0005710f45 | |||
88e09f2189 | |||
c7e79d2681 | |||
86223d5a6b | |||
ce04052da3 | |||
f91f225ba6 | |||
2cfdb9bcda | |||
a06f3255d0 | |||
2affe91837 | |||
7544ae318c | |||
e59f04bfee | |||
e35df6d1d2 | |||
81dcd8b512 | |||
8ca601dc66 | |||
5ff7203929 | |||
a6ef8bb938 | |||
79fd63599a | |||
3c89a83d6b | |||
d45ae7ffc5 | |||
34babcbfa5 | |||
e5661a2cf4 | |||
cc40f9c4ac | |||
6e38a9e412 | |||
481170922e | |||
f46b8d0758 | |||
1d43448423 | |||
2de97fe45c | |||
c41688831c | |||
2a36ce4302 | |||
f2200df15a | |||
3b498c4939 | |||
2cead2c5fd | |||
23bf840684 | |||
f8d551e226 | |||
2e3b068341 | |||
0d9b1c2e82 | |||
148c7c588e | |||
3a82fb82e8 | |||
c319579c0b | |||
acfa39369e | |||
f71b8110a9 | |||
8311454342 | |||
ed4df16201 | |||
2f3dbbfe32 | |||
dc752ade25 | |||
ebef89be62 | |||
7d209aeb41 | |||
e82a031dd0 | |||
fea9fdaa5f | |||
952cd0b4d3 | |||
ec6704181f | |||
3389a11933 | |||
db0c882f1b | |||
13d468d8cc | |||
b6daa886e0 | |||
1d10611172 | |||
8a5cc7f76c | |||
d4ab8a8e22 | |||
8a86c5bdc5 | |||
e9cc7c3058 | |||
f7dd7d1e42 | |||
71317201b8 | |||
19ba2b4207 | |||
cb54a9576e | |||
7f73bfc478 | |||
ed8d6b651a | |||
fd2a1cb9b9 | |||
775bd5925a | |||
fc0a33e512 | |||
e093b7b2ec | |||
350e62c6cb | |||
2c3d2e4ea3 | |||
57bddf259a | |||
c40b45e7bb | |||
7b924e2482 | |||
9699d1fedb | |||
2004a0cc64 | |||
11485c2538 | |||
36b5051cbd | |||
8b3310cd00 | |||
788e370cc0 | |||
c230eedbc0 | |||
39d4efee6a | |||
1bd9688533 | |||
2e50b52ae8 | |||
73e14d33fe | |||
9dab37060b | |||
1e69726d77 | |||
45eec24b7f | |||
618ba481b8 | |||
81f488e24a | |||
7cdeb3d3d7 | |||
0ff533e6b5 | |||
d785a01973 | |||
bbbfeb59c8 | |||
361e210583 | |||
c7f14a8b21 | |||
040ce14acd | |||
cf66216f3e | |||
adc286d25a | |||
ad7731a0e9 | |||
bbda70c2a0 | |||
cbd0aca8eb | |||
d2e18fd571 | |||
df544f32c9 | |||
cad98d8384 | |||
88377e2e05 | |||
c6324e80af | |||
04eb841cde | |||
2ad0299493 | |||
0f5603aed4 | |||
72882374be | |||
21570cbd33 | |||
049da896f8 | |||
0affb8ab54 | |||
a412b20118 | |||
2dcfb8e3ce |
50
.gitee/ISSUE_TEMPLATE/bug.yml
Normal file
50
.gitee/ISSUE_TEMPLATE/bug.yml
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
name: Bug 反馈
|
||||||
|
description: 当你中发现了一个 Bug,导致应用崩溃或抛出异常,或者有一个组件存在问题,或者某些地方看起来不对劲。
|
||||||
|
title: "[Bug]: "
|
||||||
|
labels: ["bug"]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: 版本
|
||||||
|
description: 你当前正在使用我们软件的哪个版本(pom文件内的版本号)?
|
||||||
|
value: |
|
||||||
|
jdk版本(带上尾号): 例如 1.8.0
|
||||||
|
框架版本(项目启动时输出的版本号): 例如 4.4.0
|
||||||
|
其他依赖版本(你觉得有必要的):
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: 功能不好用不会用是否已经看过项目文档?
|
||||||
|
options:
|
||||||
|
- label: https://plus-doc.dromara.org
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: 这个问题是否已经存在?
|
||||||
|
options:
|
||||||
|
- label: 我已经搜索过现有的问题 (https://gitee.com/dromara/RuoYi-Vue-Plus/issues)
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 希望结果
|
||||||
|
description: 想知道你觉得怎么样是正常或者合理的。
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
label: 如何复现
|
||||||
|
description: 请详细告诉我们如何复现你遇到的问题
|
||||||
|
value: |
|
||||||
|
如涉及代码 可提供一个最小代码示例 并使用```附上它 或者截图均可 越详细越好<br>
|
||||||
|
大多数问题都是 代码编写错误问题 逻辑问题 或者用法错误等问题
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: 相关代码与报错信息(请勿发混乱格式)
|
||||||
|
description: 如果可以的话,上传任何关于 bug 的截图。
|
||||||
|
value: |
|
||||||
|
[在这里上传图片]
|
||||||
|
|
5
.gitee/ISSUE_TEMPLATE/config.yml
Normal file
5
.gitee/ISSUE_TEMPLATE/config.yml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: RuoYi-Vue-Plus 文档中心
|
||||||
|
url: https://plus-doc.dromara.org
|
||||||
|
about: 提供 RuoYi-Vue-Plus 搭建使用指南、平台基本开发使用方式、介绍、基础知识和常见问题解答
|
43
.gitee/ISSUE_TEMPLATE/feature.yml
Normal file
43
.gitee/ISSUE_TEMPLATE/feature.yml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
name: 功能建议
|
||||||
|
description: 对本项目提出一个功能建议
|
||||||
|
title: "[功能建议]: "
|
||||||
|
labels: ["enhancement"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
感谢提出功能建议,我们将仔细考虑!请持续关注该issues,在加入计划后我们会有贡献者设置为负责人,同时状态成为进行中。
|
||||||
|
- type: textarea
|
||||||
|
id: related-problem
|
||||||
|
attributes:
|
||||||
|
label: 你的功能建议是否和某个问题相关?
|
||||||
|
description: 清晰并简洁地描述问题是什么,例如,当我...时,我总是感到困扰。
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: desired-solution
|
||||||
|
attributes:
|
||||||
|
label: 你希望看到什么解决方案?
|
||||||
|
description: 清晰并简洁地描述你希望发生的事情。
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: 你考虑过哪些替代方案?
|
||||||
|
description: 清晰并简洁地描述你考虑过的任何替代解决方案或功能。
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: additional-context
|
||||||
|
attributes:
|
||||||
|
label: 你有其他上下文或截图吗?
|
||||||
|
description: 在此处添加有关功能请求的任何其他上下文或截图。
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: 意向参与贡献
|
||||||
|
options:
|
||||||
|
- label: 我有意向参与具体功能的开发实现并将代码贡献回到上游社区
|
||||||
|
required: false
|
@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
<configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
||||||
<deployment type="dockerfile">
|
<deployment type="dockerfile">
|
||||||
<settings>
|
<settings>
|
||||||
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:4.7.0" />
|
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:4.8.2" />
|
||||||
<option name="buildOnly" value="true" />
|
<option name="buildOnly" value="true" />
|
||||||
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
|
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
|
||||||
</settings>
|
</settings>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
<configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
||||||
<deployment type="dockerfile">
|
<deployment type="dockerfile">
|
||||||
<settings>
|
<settings>
|
||||||
<option name="imageTag" value="ruoyi/ruoyi-server:4.7.0" />
|
<option name="imageTag" value="ruoyi/ruoyi-server:4.8.2" />
|
||||||
<option name="buildOnly" value="true" />
|
<option name="buildOnly" value="true" />
|
||||||
<option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
|
<option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
|
||||||
</settings>
|
</settings>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="ruoyi-xxl-job-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
<configuration default="false" name="ruoyi-xxl-job-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
|
||||||
<deployment type="dockerfile">
|
<deployment type="dockerfile">
|
||||||
<settings>
|
<settings>
|
||||||
<option name="imageTag" value="ruoyi/ruoyi-xxl-job-admin:4.7.0" />
|
<option name="imageTag" value="ruoyi/ruoyi-xxl-job-admin:4.8.2" />
|
||||||
<option name="buildOnly" value="true" />
|
<option name="buildOnly" value="true" />
|
||||||
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-xxl-job-admin/Dockerfile" />
|
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-xxl-job-admin/Dockerfile" />
|
||||||
</settings>
|
</settings>
|
||||||
|
13
README.md
13
README.md
@ -3,6 +3,14 @@
|
|||||||
|
|
||||||
- - -
|
- - -
|
||||||
|
|
||||||
|
## 版本状态说明
|
||||||
|
|
||||||
|
由于 springboot 2.X 与 vue 2.X 官方均宣布停止维护, 故而 框架 4.X 版本 进入维护状态(只处理问题不更新功能)
|
||||||
|
|
||||||
|
停止维护时间预计: 2024年6-10月具体根据使用人数动态决定, 此版本已经相当稳定 即便不更新功能也不影响使用
|
||||||
|
|
||||||
|
如果依旧选择使用 jdk8 或者 jdk11 可以放心使用此版本, 如果希望使用 jdk17 或者 jdk21 可以选择使用 5.X 分支
|
||||||
|
|
||||||
## 平台简介
|
## 平台简介
|
||||||
|
|
||||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus)
|
[](https://gitee.com/dromara/RuoYi-Vue-Plus)
|
||||||
@ -10,7 +18,7 @@
|
|||||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE)
|
[](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE)
|
||||||
[](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
|
[](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
|
||||||
<br>
|
<br>
|
||||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus)
|
[](https://gitee.com/dromara/RuoYi-Vue-Plus)
|
||||||
[]()
|
[]()
|
||||||
[]()
|
[]()
|
||||||
[]()
|
[]()
|
||||||
@ -53,7 +61,7 @@
|
|||||||
| 分布式任务调度 | 采用 Xxl-Job 天生支持分布式 统一的管理中心 | 采用 Quartz 基于数据库锁性能差 集群需要做很多配置与改造 |
|
| 分布式任务调度 | 采用 Xxl-Job 天生支持分布式 统一的管理中心 | 采用 Quartz 基于数据库锁性能差 集群需要做很多配置与改造 |
|
||||||
| 文件存储 | 采用 Minio 分布式文件存储 天生支持多机、多硬盘、多分片、多副本存储<br/>支持权限管理 安全可靠 文件可加密存储 | 采用 本机文件存储 文件裸漏 易丢失泄漏 不支持集群有单点效应 |
|
| 文件存储 | 采用 Minio 分布式文件存储 天生支持多机、多硬盘、多分片、多副本存储<br/>支持权限管理 安全可靠 文件可加密存储 | 采用 本机文件存储 文件裸漏 易丢失泄漏 不支持集群有单点效应 |
|
||||||
| 云存储 | 采用 AWS S3 协议客户端 支持 七牛、阿里、腾讯 等一切支持S3协议的厂家 | 不支持 |
|
| 云存储 | 采用 AWS S3 协议客户端 支持 七牛、阿里、腾讯 等一切支持S3协议的厂家 | 不支持 |
|
||||||
| 短信 | 支持 阿里、腾讯 只需在yml配置好厂家密钥即可使用 接口化支持扩展其他厂家 | 不支持 |
|
| 短信 | 采用 sms4j 短信融合包 支持数十种短信厂家 只需在yml配置好厂家密钥即可使用 可多厂家共用 | 不支持 |
|
||||||
| 邮件 | 采用 mail-api 通用协议支持大部分邮件厂商 | 不支持 |
|
| 邮件 | 采用 mail-api 通用协议支持大部分邮件厂商 | 不支持 |
|
||||||
| 接口文档 | 采用 SpringDoc、javadoc 无注解零入侵基于java注释<br/>只需把注释写好 无需再写一大堆的文档注解了 | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成 |
|
| 接口文档 | 采用 SpringDoc、javadoc 无注解零入侵基于java注释<br/>只需把注释写好 无需再写一大堆的文档注解了 | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成 |
|
||||||
| 校验框架 | 采用 Validation 支持注解与工具类校验 注解支持国际化 | 仅支持注解 且注解不支持国际化 |
|
| 校验框架 | 采用 Validation 支持注解与工具类校验 注解支持国际化 | 仅支持注解 且注解不支持国际化 |
|
||||||
@ -125,7 +133,6 @@
|
|||||||
* GitHub 地址 [RuoYi-Vue-Plus-github](https://github.com/dromara/RuoYi-Vue-Plus)
|
* GitHub 地址 [RuoYi-Vue-Plus-github](https://github.com/dromara/RuoYi-Vue-Plus)
|
||||||
* 单模块 分支 [RuoYi-Vue-Plus-fast](https://gitee.com/dromara/RuoYi-Vue-Plus/tree/fast/)
|
* 单模块 分支 [RuoYi-Vue-Plus-fast](https://gitee.com/dromara/RuoYi-Vue-Plus/tree/fast/)
|
||||||
* 微服务 分支 [RuoYi-Cloud-Plus](https://gitee.com/JavaLionLi/RuoYi-Cloud-Plus)
|
* 微服务 分支 [RuoYi-Cloud-Plus](https://gitee.com/JavaLionLi/RuoYi-Cloud-Plus)
|
||||||
* Vue3 分支 [RuoYi-Vue-Plus-UI](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus-UI)
|
|
||||||
* 用户扩展项目 [扩展项目列表](https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages?sort_id=4478302&doc_id=1469725)
|
* 用户扩展项目 [扩展项目列表](https://gitee.com/dromara/RuoYi-Vue-Plus/wikis/pages?sort_id=4478302&doc_id=1469725)
|
||||||
|
|
||||||
## 加群与捐献
|
## 加群与捐献
|
||||||
|
58
pom.xml
58
pom.xml
@ -6,15 +6,15 @@
|
|||||||
|
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<artifactId>ruoyi-vue-plus</artifactId>
|
<artifactId>ruoyi-vue-plus</artifactId>
|
||||||
<version>4.7.0</version>
|
<version>4.8.2</version>
|
||||||
|
|
||||||
<name>RuoYi-Vue-Plus</name>
|
<name>RuoYi-Vue-Plus</name>
|
||||||
<url>https://gitee.com/dromara/RuoYi-Vue-Plus</url>
|
<url>https://gitee.com/dromara/RuoYi-Vue-Plus</url>
|
||||||
<description>RuoYi-Vue-Plus后台管理系统</description>
|
<description>RuoYi-Vue-Plus后台管理系统</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<ruoyi-vue-plus.version>4.7.0</ruoyi-vue-plus.version>
|
<ruoyi-vue-plus.version>4.8.2</ruoyi-vue-plus.version>
|
||||||
<spring-boot.version>2.7.11</spring-boot.version>
|
<spring-boot.version>2.7.18</spring-boot.version>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
<java.version>1.8</java.version>
|
<java.version>1.8</java.version>
|
||||||
@ -22,32 +22,28 @@
|
|||||||
<spring-boot.mybatis>2.2.2</spring-boot.mybatis>
|
<spring-boot.mybatis>2.2.2</spring-boot.mybatis>
|
||||||
<springdoc.version>1.6.15</springdoc.version>
|
<springdoc.version>1.6.15</springdoc.version>
|
||||||
<poi.version>5.2.3</poi.version>
|
<poi.version>5.2.3</poi.version>
|
||||||
<easyexcel.version>3.2.1</easyexcel.version>
|
<easyexcel.version>3.3.2</easyexcel.version>
|
||||||
<velocity.version>2.3</velocity.version>
|
<velocity.version>2.3</velocity.version>
|
||||||
<satoken.version>1.34.0</satoken.version>
|
<satoken.version>1.37.0</satoken.version>
|
||||||
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
|
<mybatis-plus.version>3.5.4</mybatis-plus.version>
|
||||||
<p6spy.version>3.9.1</p6spy.version>
|
<p6spy.version>3.9.1</p6spy.version>
|
||||||
<hutool.version>5.8.18</hutool.version>
|
<hutool.version>5.8.22</hutool.version>
|
||||||
<okhttp.version>4.10.0</okhttp.version>
|
<okhttp.version>4.10.0</okhttp.version>
|
||||||
<spring-boot-admin.version>2.7.10</spring-boot-admin.version>
|
<spring-boot-admin.version>2.7.11</spring-boot-admin.version>
|
||||||
<redisson.version>3.20.1</redisson.version>
|
<redisson.version>3.20.1</redisson.version>
|
||||||
<lock4j.version>2.2.3</lock4j.version>
|
<lock4j.version>2.2.3</lock4j.version>
|
||||||
<dynamic-ds.version>3.5.2</dynamic-ds.version>
|
<dynamic-ds.version>3.5.2</dynamic-ds.version>
|
||||||
<alibaba-ttl.version>2.14.2</alibaba-ttl.version>
|
<alibaba-ttl.version>2.14.2</alibaba-ttl.version>
|
||||||
<xxl-job.version>2.4.0</xxl-job.version>
|
<xxl-job.version>2.4.0</xxl-job.version>
|
||||||
<lombok.version>1.18.26</lombok.version>
|
<lombok.version>1.18.30</lombok.version>
|
||||||
<bouncycastle.version>1.72</bouncycastle.version>
|
<bouncycastle.version>1.72</bouncycastle.version>
|
||||||
<!-- 离线IP地址定位库 -->
|
<!-- 离线IP地址定位库 -->
|
||||||
<ip2region.version>2.7.0</ip2region.version>
|
<ip2region.version>2.7.0</ip2region.version>
|
||||||
|
|
||||||
<!-- 临时修复 snakeyaml 漏洞 -->
|
|
||||||
<snakeyaml.version>1.33</snakeyaml.version>
|
|
||||||
|
|
||||||
<!-- OSS 配置 -->
|
<!-- OSS 配置 -->
|
||||||
<aws-java-sdk-s3.version>1.12.400</aws-java-sdk-s3.version>
|
<aws-java-sdk-s3.version>1.12.540</aws-java-sdk-s3.version>
|
||||||
<!-- SMS 配置 -->
|
<!-- SMS 配置 -->
|
||||||
<aliyun.sms.version>2.0.23</aliyun.sms.version>
|
<sms4j.version>2.2.0</sms4j.version>
|
||||||
<tencent.sms.version>3.1.687</tencent.sms.version>
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
@ -56,7 +52,7 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<!-- 环境标识,需要与配置文件的名称相对应 -->
|
<!-- 环境标识,需要与配置文件的名称相对应 -->
|
||||||
<profiles.active>local</profiles.active>
|
<profiles.active>local</profiles.active>
|
||||||
<logging.level>debug</logging.level>
|
<logging.level>info</logging.level>
|
||||||
</properties>
|
</properties>
|
||||||
</profile>
|
</profile>
|
||||||
<profile>
|
<profile>
|
||||||
@ -64,7 +60,7 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<!-- 环境标识,需要与配置文件的名称相对应 -->
|
<!-- 环境标识,需要与配置文件的名称相对应 -->
|
||||||
<profiles.active>dev</profiles.active>
|
<profiles.active>dev</profiles.active>
|
||||||
<logging.level>debug</logging.level>
|
<logging.level>info</logging.level>
|
||||||
</properties>
|
</properties>
|
||||||
<activation>
|
<activation>
|
||||||
<!-- 默认环境 -->
|
<!-- 默认环境 -->
|
||||||
@ -200,16 +196,11 @@
|
|||||||
<version>${aws-java-sdk-s3.version}</version>
|
<version>${aws-java-sdk-s3.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!--短信sms4j-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aliyun</groupId>
|
<groupId>org.dromara.sms4j</groupId>
|
||||||
<artifactId>dysmsapi20170525</artifactId>
|
<artifactId>sms4j-spring-boot-starter</artifactId>
|
||||||
<version>${aliyun.sms.version}</version>
|
<version>${sms4j.version}</version>
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.tencentcloudapi</groupId>
|
|
||||||
<artifactId>tencentcloud-sdk-java-sms</artifactId>
|
|
||||||
<version>${tencent.sms.version}</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -267,13 +258,6 @@
|
|||||||
<version>${ip2region.version}</version>
|
<version>${ip2region.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 临时修复 snakeyaml 漏洞 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.yaml</groupId>
|
|
||||||
<artifactId>snakeyaml</artifactId>
|
|
||||||
<version>${snakeyaml.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- 加密包引入 -->
|
<!-- 加密包引入 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.bouncycastle</groupId>
|
<groupId>org.bouncycastle</groupId>
|
||||||
@ -420,8 +404,8 @@
|
|||||||
<repositories>
|
<repositories>
|
||||||
<repository>
|
<repository>
|
||||||
<id>public</id>
|
<id>public</id>
|
||||||
<name>aliyun nexus</name>
|
<name>huawei nexus</name>
|
||||||
<url>https://maven.aliyun.com/repository/public/</url>
|
<url>https://mirrors.huaweicloud.com/repository/maven/</url>
|
||||||
<releases>
|
<releases>
|
||||||
<enabled>true</enabled>
|
<enabled>true</enabled>
|
||||||
</releases>
|
</releases>
|
||||||
@ -431,8 +415,8 @@
|
|||||||
<pluginRepositories>
|
<pluginRepositories>
|
||||||
<pluginRepository>
|
<pluginRepository>
|
||||||
<id>public</id>
|
<id>public</id>
|
||||||
<name>aliyun nexus</name>
|
<name>huawei nexus</name>
|
||||||
<url>https://maven.aliyun.com/repository/public/</url>
|
<url>https://mirrors.huaweicloud.com/repository/maven/</url>
|
||||||
<releases>
|
<releases>
|
||||||
<enabled>true</enabled>
|
<enabled>true</enabled>
|
||||||
</releases>
|
</releases>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>ruoyi-vue-plus</artifactId>
|
<artifactId>ruoyi-vue-plus</artifactId>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<version>4.7.0</version>
|
<version>4.8.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
@ -16,12 +16,13 @@ import com.ruoyi.common.utils.reflect.ReflectUtils;
|
|||||||
import com.ruoyi.common.utils.spring.SpringUtils;
|
import com.ruoyi.common.utils.spring.SpringUtils;
|
||||||
import com.ruoyi.framework.config.properties.CaptchaProperties;
|
import com.ruoyi.framework.config.properties.CaptchaProperties;
|
||||||
import com.ruoyi.framework.config.properties.MailProperties;
|
import com.ruoyi.framework.config.properties.MailProperties;
|
||||||
import com.ruoyi.sms.config.properties.SmsProperties;
|
|
||||||
import com.ruoyi.sms.core.SmsTemplate;
|
|
||||||
import com.ruoyi.sms.entity.SmsResult;
|
|
||||||
import com.ruoyi.system.service.ISysConfigService;
|
import com.ruoyi.system.service.ISysConfigService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.dromara.sms4j.api.SmsBlend;
|
||||||
|
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||||
|
import org.dromara.sms4j.core.factory.SmsFactory;
|
||||||
|
import org.dromara.sms4j.provider.enumerate.SupplierType;
|
||||||
import org.springframework.expression.Expression;
|
import org.springframework.expression.Expression;
|
||||||
import org.springframework.expression.ExpressionParser;
|
import org.springframework.expression.ExpressionParser;
|
||||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
@ -32,6 +33,7 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
import javax.validation.constraints.NotBlank;
|
import javax.validation.constraints.NotBlank;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,7 +49,6 @@ import java.util.Map;
|
|||||||
public class CaptchaController {
|
public class CaptchaController {
|
||||||
|
|
||||||
private final CaptchaProperties captchaProperties;
|
private final CaptchaProperties captchaProperties;
|
||||||
private final SmsProperties smsProperties;
|
|
||||||
private final ISysConfigService configService;
|
private final ISysConfigService configService;
|
||||||
private final MailProperties mailProperties;
|
private final MailProperties mailProperties;
|
||||||
|
|
||||||
@ -58,21 +59,18 @@ public class CaptchaController {
|
|||||||
*/
|
*/
|
||||||
@GetMapping("/captchaSms")
|
@GetMapping("/captchaSms")
|
||||||
public R<Void> smsCaptcha(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
|
public R<Void> smsCaptcha(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
|
||||||
if (!smsProperties.getEnabled()) {
|
|
||||||
return R.fail("当前系统没有开启短信功能!");
|
|
||||||
}
|
|
||||||
String key = CacheConstants.CAPTCHA_CODE_KEY + phonenumber;
|
String key = CacheConstants.CAPTCHA_CODE_KEY + phonenumber;
|
||||||
String code = RandomUtil.randomNumbers(4);
|
String code = RandomUtil.randomNumbers(4);
|
||||||
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||||
// 验证码模板id 自行处理 (查数据库或写死均可)
|
// 验证码模板id 自行处理 (查数据库或写死均可)
|
||||||
String templateId = "";
|
String templateId = "";
|
||||||
Map<String, String> map = new HashMap<>(1);
|
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
|
||||||
map.put("code", code);
|
map.put("code", code);
|
||||||
SmsTemplate smsTemplate = SpringUtils.getBean(SmsTemplate.class);
|
SmsBlend smsBlend = SmsFactory.createSmsBlend(SupplierType.ALIBABA);
|
||||||
SmsResult result = smsTemplate.send(phonenumber, templateId, map);
|
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map);
|
||||||
if (!result.isSuccess()) {
|
if (!"OK".equals(smsResponse.getCode())) {
|
||||||
log.error("验证码短信发送异常 => {}", result);
|
log.error("验证码短信发送异常 => {}", smsResponse);
|
||||||
return R.fail(result.getMessage());
|
return R.fail(smsResponse.getMessage());
|
||||||
}
|
}
|
||||||
return R.ok();
|
return R.ok();
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ public class SysUserOnlineController extends BaseController {
|
|||||||
for (String key : keys) {
|
for (String key : keys) {
|
||||||
String token = StringUtils.substringAfterLast(key, ":");
|
String token = StringUtils.substringAfterLast(key, ":");
|
||||||
// 如果已经过期则跳过
|
// 如果已经过期则跳过
|
||||||
if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < -1) {
|
if (StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) < -1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
userOnlineDTOList.add(RedisUtils.getCacheObject(CacheConstants.ONLINE_TOKEN_KEY + token));
|
userOnlineDTOList.add(RedisUtils.getCacheObject(CacheConstants.ONLINE_TOKEN_KEY + token));
|
||||||
|
@ -91,9 +91,12 @@ public class SysDeptController extends BaseController {
|
|||||||
return R.fail("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在");
|
return R.fail("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在");
|
||||||
} else if (dept.getParentId().equals(deptId)) {
|
} else if (dept.getParentId().equals(deptId)) {
|
||||||
return R.fail("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己");
|
return R.fail("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己");
|
||||||
} else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus())
|
} else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus())) {
|
||||||
&& deptService.selectNormalChildrenDeptById(deptId) > 0) {
|
if (deptService.selectNormalChildrenDeptById(deptId) > 0) {
|
||||||
return R.fail("该部门包含未停用的子部门!");
|
return R.fail("该部门包含未停用的子部门!");
|
||||||
|
} else if (deptService.checkDeptExistUser(deptId)) {
|
||||||
|
return R.fail("该部门下存在已分配用户,不能禁用!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return toAjax(deptService.updateDept(dept));
|
return toAjax(deptService.updateDept(dept));
|
||||||
}
|
}
|
||||||
|
@ -88,6 +88,9 @@ public class SysPostController extends BaseController {
|
|||||||
return R.fail("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在");
|
return R.fail("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在");
|
||||||
} else if (!postService.checkPostCodeUnique(post)) {
|
} else if (!postService.checkPostCodeUnique(post)) {
|
||||||
return R.fail("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在");
|
return R.fail("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在");
|
||||||
|
} else if (UserConstants.POST_DISABLE.equals(post.getStatus())
|
||||||
|
&& postService.countUserPostById(post.getPostId()) > 0) {
|
||||||
|
return R.fail("该岗位下存在已分配用户,不能禁用!");
|
||||||
}
|
}
|
||||||
return toAjax(postService.updatePost(post));
|
return toAjax(postService.updatePost(post));
|
||||||
}
|
}
|
||||||
@ -109,7 +112,9 @@ public class SysPostController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@GetMapping("/optionselect")
|
@GetMapping("/optionselect")
|
||||||
public R<List<SysPost>> optionselect() {
|
public R<List<SysPost>> optionselect() {
|
||||||
List<SysPost> posts = postService.selectPostAll();
|
SysPost post = new SysPost();
|
||||||
|
post.setStatus(UserConstants.POST_NORMAL);
|
||||||
|
List<SysPost> posts = postService.selectPostList(post);
|
||||||
return R.ok(posts);
|
return R.ok(posts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,8 +78,8 @@ public class SysProfileController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* 重置密码
|
* 重置密码
|
||||||
*
|
*
|
||||||
* @param newPassword 旧密码
|
* @param newPassword 新密码
|
||||||
* @param oldPassword 新密码
|
* @param oldPassword 旧密码
|
||||||
*/
|
*/
|
||||||
@Log(title = "个人信息", businessType = BusinessType.UPDATE)
|
@Log(title = "个人信息", businessType = BusinessType.UPDATE)
|
||||||
@PutMapping("/updatePwd")
|
@PutMapping("/updatePwd")
|
||||||
|
@ -80,6 +80,7 @@ public class SysRoleController extends BaseController {
|
|||||||
@Log(title = "角色管理", businessType = BusinessType.INSERT)
|
@Log(title = "角色管理", businessType = BusinessType.INSERT)
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public R<Void> add(@Validated @RequestBody SysRole role) {
|
public R<Void> add(@Validated @RequestBody SysRole role) {
|
||||||
|
roleService.checkRoleAllowed(role);
|
||||||
if (!roleService.checkRoleNameUnique(role)) {
|
if (!roleService.checkRoleNameUnique(role)) {
|
||||||
return R.fail("新增角色'" + role.getRoleName() + "'失败,角色名称已存在");
|
return R.fail("新增角色'" + role.getRoleName() + "'失败,角色名称已存在");
|
||||||
} else if (!roleService.checkRoleKeyUnique(role)) {
|
} else if (!roleService.checkRoleKeyUnique(role)) {
|
||||||
|
@ -21,6 +21,7 @@ import com.ruoyi.common.helper.LoginHelper;
|
|||||||
import com.ruoyi.common.utils.StreamUtils;
|
import com.ruoyi.common.utils.StreamUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||||
|
import com.ruoyi.system.domain.SysPost;
|
||||||
import com.ruoyi.system.domain.vo.SysUserExportVo;
|
import com.ruoyi.system.domain.vo.SysUserExportVo;
|
||||||
import com.ruoyi.system.domain.vo.SysUserImportVo;
|
import com.ruoyi.system.domain.vo.SysUserImportVo;
|
||||||
import com.ruoyi.system.listener.SysUserImportListener;
|
import com.ruoyi.system.listener.SysUserImportListener;
|
||||||
@ -117,9 +118,13 @@ public class SysUserController extends BaseController {
|
|||||||
public R<Map<String, Object>> getInfo(@PathVariable(value = "userId", required = false) Long userId) {
|
public R<Map<String, Object>> getInfo(@PathVariable(value = "userId", required = false) Long userId) {
|
||||||
userService.checkUserDataScope(userId);
|
userService.checkUserDataScope(userId);
|
||||||
Map<String, Object> ajax = new HashMap<>();
|
Map<String, Object> ajax = new HashMap<>();
|
||||||
List<SysRole> roles = roleService.selectRoleAll();
|
SysRole role = new SysRole();
|
||||||
|
role.setStatus(UserConstants.ROLE_NORMAL);
|
||||||
|
SysPost post = new SysPost();
|
||||||
|
post.setStatus(UserConstants.POST_NORMAL);
|
||||||
|
List<SysRole> roles = roleService.selectRoleList(role);
|
||||||
ajax.put("roles", LoginHelper.isAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isAdmin()));
|
ajax.put("roles", LoginHelper.isAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isAdmin()));
|
||||||
ajax.put("posts", postService.selectPostAll());
|
ajax.put("posts", postService.selectPostList(post));
|
||||||
if (ObjectUtil.isNotNull(userId)) {
|
if (ObjectUtil.isNotNull(userId)) {
|
||||||
SysUser sysUser = userService.selectUserById(userId);
|
SysUser sysUser = userService.selectUserById(userId);
|
||||||
ajax.put("user", sysUser);
|
ajax.put("user", sysUser);
|
||||||
@ -136,6 +141,7 @@ public class SysUserController extends BaseController {
|
|||||||
@Log(title = "用户管理", businessType = BusinessType.INSERT)
|
@Log(title = "用户管理", businessType = BusinessType.INSERT)
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public R<Void> add(@Validated @RequestBody SysUser user) {
|
public R<Void> add(@Validated @RequestBody SysUser user) {
|
||||||
|
deptService.checkDeptDataScope(user.getDeptId());
|
||||||
if (!userService.checkUserNameUnique(user)) {
|
if (!userService.checkUserNameUnique(user)) {
|
||||||
return R.fail("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
|
return R.fail("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
|
||||||
} else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
|
} else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
|
||||||
@ -156,6 +162,7 @@ public class SysUserController extends BaseController {
|
|||||||
public R<Void> edit(@Validated @RequestBody SysUser user) {
|
public R<Void> edit(@Validated @RequestBody SysUser user) {
|
||||||
userService.checkUserAllowed(user);
|
userService.checkUserAllowed(user);
|
||||||
userService.checkUserDataScope(user.getUserId());
|
userService.checkUserDataScope(user.getUserId());
|
||||||
|
deptService.checkDeptDataScope(user.getDeptId());
|
||||||
if (!userService.checkUserNameUnique(user)) {
|
if (!userService.checkUserNameUnique(user)) {
|
||||||
return R.fail("修改用户'" + user.getUserName() + "'失败,登录账号已存在");
|
return R.fail("修改用户'" + user.getUserName() + "'失败,登录账号已存在");
|
||||||
} else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
|
} else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) {
|
||||||
|
@ -19,8 +19,8 @@ xxl.job:
|
|||||||
executor:
|
executor:
|
||||||
# 执行器AppName:执行器心跳注册分组依据;为空则关闭自动注册
|
# 执行器AppName:执行器心跳注册分组依据;为空则关闭自动注册
|
||||||
appname: xxl-job-executor
|
appname: xxl-job-executor
|
||||||
# 执行器端口号 执行器从9101开始往后写
|
# 28080 端口 随着主应用端口飘逸 避免集群冲突
|
||||||
port: 9101
|
port: 2${server.port}
|
||||||
# 执行器注册:默认IP:PORT
|
# 执行器注册:默认IP:PORT
|
||||||
address:
|
address:
|
||||||
# 执行器IP:默认自动获取IP
|
# 执行器IP:默认自动获取IP
|
||||||
@ -66,8 +66,6 @@ spring:
|
|||||||
# url: jdbc:oracle:thin:@//localhost:1521/XE
|
# url: jdbc:oracle:thin:@//localhost:1521/XE
|
||||||
# username: ROOT
|
# username: ROOT
|
||||||
# password: root
|
# password: root
|
||||||
# hikari:
|
|
||||||
# connectionTestQuery: SELECT 1 FROM DUAL
|
|
||||||
# postgres:
|
# postgres:
|
||||||
# type: ${spring.datasource.type}
|
# type: ${spring.datasource.type}
|
||||||
# driverClassName: org.postgresql.Driver
|
# driverClassName: org.postgresql.Driver
|
||||||
@ -93,8 +91,6 @@ spring:
|
|||||||
idleTimeout: 600000
|
idleTimeout: 600000
|
||||||
# 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
|
# 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
|
||||||
maxLifetime: 1800000
|
maxLifetime: 1800000
|
||||||
# 连接测试query(配置检测连接是否有效)
|
|
||||||
connectionTestQuery: SELECT 1
|
|
||||||
# 多久检查一次连接的活性
|
# 多久检查一次连接的活性
|
||||||
keepaliveTime: 30000
|
keepaliveTime: 30000
|
||||||
|
|
||||||
@ -158,14 +154,29 @@ mail:
|
|||||||
# Socket连接超时值,单位毫秒,缺省值不超时
|
# Socket连接超时值,单位毫秒,缺省值不超时
|
||||||
connectionTimeout: 0
|
connectionTimeout: 0
|
||||||
|
|
||||||
--- # sms 短信
|
--- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
|
||||||
|
# https://wind.kim/doc/start 文档地址 各个厂商可同时使用
|
||||||
sms:
|
sms:
|
||||||
enabled: false
|
|
||||||
# 阿里云 dysmsapi.aliyuncs.com
|
# 阿里云 dysmsapi.aliyuncs.com
|
||||||
# 腾讯云 sms.tencentcloudapi.com
|
alibaba:
|
||||||
endpoint: "dysmsapi.aliyuncs.com"
|
#请求地址 默认为 dysmsapi.aliyuncs.com 如无特殊改变可以不用设置
|
||||||
accessKeyId: xxxxxxx
|
requestUrl: dysmsapi.aliyuncs.com
|
||||||
accessKeySecret: xxxxxx
|
#阿里云的accessKey
|
||||||
signName: 测试
|
accessKeyId: xxxxxxx
|
||||||
# 腾讯专用
|
#阿里云的accessKeySecret
|
||||||
sdkAppId:
|
accessKeySecret: xxxxxxx
|
||||||
|
#短信签名
|
||||||
|
signature: 测试
|
||||||
|
tencent:
|
||||||
|
#请求地址默认为 sms.tencentcloudapi.com 如无特殊改变可不用设置
|
||||||
|
requestUrl: sms.tencentcloudapi.com
|
||||||
|
#腾讯云的accessKey
|
||||||
|
accessKeyId: xxxxxxx
|
||||||
|
#腾讯云的accessKeySecret
|
||||||
|
accessKeySecret: xxxxxxx
|
||||||
|
#短信签名
|
||||||
|
signature: 测试
|
||||||
|
#短信sdkAppId
|
||||||
|
sdkAppId: appid
|
||||||
|
#地域信息默认为 ap-guangzhou 如无特殊改变可不用设置
|
||||||
|
territory: ap-guangzhou
|
||||||
|
@ -22,8 +22,8 @@ xxl.job:
|
|||||||
executor:
|
executor:
|
||||||
# 执行器AppName:执行器心跳注册分组依据;为空则关闭自动注册
|
# 执行器AppName:执行器心跳注册分组依据;为空则关闭自动注册
|
||||||
appname: xxl-job-executor
|
appname: xxl-job-executor
|
||||||
# 执行器端口号 执行器从9101开始往后写
|
# 28080 端口 随着主应用端口飘逸 避免集群冲突
|
||||||
port: 9101
|
port: 2${server.port}
|
||||||
# 执行器注册:默认IP:PORT
|
# 执行器注册:默认IP:PORT
|
||||||
address:
|
address:
|
||||||
# 执行器IP:默认自动获取IP
|
# 执行器IP:默认自动获取IP
|
||||||
@ -69,8 +69,6 @@ spring:
|
|||||||
# url: jdbc:oracle:thin:@//localhost:1521/XE
|
# url: jdbc:oracle:thin:@//localhost:1521/XE
|
||||||
# username: ROOT
|
# username: ROOT
|
||||||
# password: root
|
# password: root
|
||||||
# hikari:
|
|
||||||
# connectionTestQuery: SELECT 1 FROM DUAL
|
|
||||||
# postgres:
|
# postgres:
|
||||||
# type: ${spring.datasource.type}
|
# type: ${spring.datasource.type}
|
||||||
# driverClassName: org.postgresql.Driver
|
# driverClassName: org.postgresql.Driver
|
||||||
@ -96,8 +94,6 @@ spring:
|
|||||||
idleTimeout: 600000
|
idleTimeout: 600000
|
||||||
# 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
|
# 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
|
||||||
maxLifetime: 1800000
|
maxLifetime: 1800000
|
||||||
# 连接测试query(配置检测连接是否有效)
|
|
||||||
connectionTestQuery: SELECT 1
|
|
||||||
# 多久检查一次连接的活性
|
# 多久检查一次连接的活性
|
||||||
keepaliveTime: 30000
|
keepaliveTime: 30000
|
||||||
|
|
||||||
@ -161,14 +157,29 @@ mail:
|
|||||||
# Socket连接超时值,单位毫秒,缺省值不超时
|
# Socket连接超时值,单位毫秒,缺省值不超时
|
||||||
connectionTimeout: 0
|
connectionTimeout: 0
|
||||||
|
|
||||||
--- # sms 短信
|
--- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
|
||||||
|
# https://wind.kim/doc/start 文档地址 各个厂商可同时使用
|
||||||
sms:
|
sms:
|
||||||
enabled: false
|
|
||||||
# 阿里云 dysmsapi.aliyuncs.com
|
# 阿里云 dysmsapi.aliyuncs.com
|
||||||
# 腾讯云 sms.tencentcloudapi.com
|
alibaba:
|
||||||
endpoint: "dysmsapi.aliyuncs.com"
|
#请求地址 默认为 dysmsapi.aliyuncs.com 如无特殊改变可以不用设置
|
||||||
accessKeyId: xxxxxxx
|
requestUrl: dysmsapi.aliyuncs.com
|
||||||
accessKeySecret: xxxxxx
|
#阿里云的accessKey
|
||||||
signName: 测试
|
accessKeyId: xxxxxxx
|
||||||
# 腾讯专用
|
#阿里云的accessKeySecret
|
||||||
sdkAppId:
|
accessKeySecret: xxxxxxx
|
||||||
|
#短信签名
|
||||||
|
signature: 测试
|
||||||
|
tencent:
|
||||||
|
#请求地址默认为 sms.tencentcloudapi.com 如无特殊改变可不用设置
|
||||||
|
requestUrl: sms.tencentcloudapi.com
|
||||||
|
#腾讯云的accessKey
|
||||||
|
accessKeyId: xxxxxxx
|
||||||
|
#腾讯云的accessKeySecret
|
||||||
|
accessKeySecret: xxxxxxx
|
||||||
|
#短信签名
|
||||||
|
signature: 测试
|
||||||
|
#短信sdkAppId
|
||||||
|
sdkAppId: appid
|
||||||
|
#地域信息默认为 ap-guangzhou 如无特殊改变可不用设置
|
||||||
|
territory: ap-guangzhou
|
||||||
|
@ -5,11 +5,7 @@ ruoyi:
|
|||||||
# 版本
|
# 版本
|
||||||
version: ${ruoyi-vue-plus.version}
|
version: ${ruoyi-vue-plus.version}
|
||||||
# 版权年份
|
# 版权年份
|
||||||
copyrightYear: 2022
|
copyrightYear: 2023
|
||||||
# 实例演示开关
|
|
||||||
demoEnabled: true
|
|
||||||
# 获取ip地址开关
|
|
||||||
addressEnabled: true
|
|
||||||
# 缓存懒加载
|
# 缓存懒加载
|
||||||
cacheLazy: false
|
cacheLazy: false
|
||||||
|
|
||||||
@ -104,8 +100,11 @@ sa-token:
|
|||||||
token-name: Authorization
|
token-name: Authorization
|
||||||
# token有效期 设为一天 (必定过期) 单位: 秒
|
# token有效期 设为一天 (必定过期) 单位: 秒
|
||||||
timeout: 86400
|
timeout: 86400
|
||||||
# token临时有效期 (指定时间无操作就过期) 单位: 秒
|
# 多端不同 token 有效期 可查看 LoginHelper.loginByDevice 方法自定义
|
||||||
activity-timeout: 1800
|
# token最低活跃时间 (指定时间无操作就过期) 单位: 秒
|
||||||
|
active-timeout: 1800
|
||||||
|
# 允许动态设置 token 有效期
|
||||||
|
dynamic-active-timeout: true
|
||||||
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
|
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
|
||||||
is-concurrent: true
|
is-concurrent: true
|
||||||
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
|
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
|
||||||
@ -196,8 +195,13 @@ mybatis-encryptor:
|
|||||||
publicKey:
|
publicKey:
|
||||||
privateKey:
|
privateKey:
|
||||||
|
|
||||||
# Swagger配置
|
springdoc:
|
||||||
swagger:
|
api-docs:
|
||||||
|
# 是否开启接口文档
|
||||||
|
enabled: true
|
||||||
|
# swagger-ui:
|
||||||
|
# # 持久化认证数据
|
||||||
|
# persistAuthorization: true
|
||||||
info:
|
info:
|
||||||
# 标题
|
# 标题
|
||||||
title: '标题:${ruoyi.name}后台管理系统_接口文档'
|
title: '标题:${ruoyi.name}后台管理系统_接口文档'
|
||||||
@ -217,14 +221,6 @@ swagger:
|
|||||||
type: APIKEY
|
type: APIKEY
|
||||||
in: HEADER
|
in: HEADER
|
||||||
name: ${sa-token.token-name}
|
name: ${sa-token.token-name}
|
||||||
|
|
||||||
springdoc:
|
|
||||||
api-docs:
|
|
||||||
# 是否开启接口文档
|
|
||||||
enabled: true
|
|
||||||
swagger-ui:
|
|
||||||
# 持久化认证数据
|
|
||||||
persistAuthorization: true
|
|
||||||
#这里定义了两个分组,可定义多个,也可以不定义
|
#这里定义了两个分组,可定义多个,也可以不定义
|
||||||
group-configs:
|
group-configs:
|
||||||
- group: 1.演示模块
|
- group: 1.演示模块
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>ruoyi-vue-plus</artifactId>
|
<artifactId>ruoyi-vue-plus</artifactId>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<version>4.7.0</version>
|
<version>4.8.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ public @interface EncryptField {
|
|||||||
String publicKey() default "";
|
String publicKey() default "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 公钥。RSA、SM2需要
|
* 私钥。RSA、SM2需要
|
||||||
*/
|
*/
|
||||||
String privateKey() default "";
|
String privateKey() default "";
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package com.ruoyi.common.config;
|
package com.ruoyi.common.config;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.Getter;
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@ -31,24 +30,9 @@ public class RuoYiConfig {
|
|||||||
*/
|
*/
|
||||||
private String copyrightYear;
|
private String copyrightYear;
|
||||||
|
|
||||||
/**
|
|
||||||
* 实例演示开关
|
|
||||||
*/
|
|
||||||
private boolean demoEnabled;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 缓存懒加载
|
* 缓存懒加载
|
||||||
*/
|
*/
|
||||||
private boolean cacheLazy;
|
private boolean cacheLazy;
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取地址开关
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private static boolean addressEnabled;
|
|
||||||
|
|
||||||
public void setAddressEnabled(boolean addressEnabled) {
|
|
||||||
RuoYiConfig.addressEnabled = addressEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,16 @@ public interface UserConstants {
|
|||||||
*/
|
*/
|
||||||
String DEPT_DISABLE = "1";
|
String DEPT_DISABLE = "1";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 岗位正常状态
|
||||||
|
*/
|
||||||
|
String POST_NORMAL = "0";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 岗位停用状态
|
||||||
|
*/
|
||||||
|
String POST_DISABLE = "1";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字典正常状态
|
* 字典正常状态
|
||||||
*/
|
*/
|
||||||
@ -129,4 +139,9 @@ public interface UserConstants {
|
|||||||
*/
|
*/
|
||||||
Long ADMIN_ID = 1L;
|
Long ADMIN_ID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员角色key
|
||||||
|
*/
|
||||||
|
String ADMIN_ROLE_KEY = "admin";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -37,14 +37,36 @@ public class ExcelEnumConvert implements Converter<Object> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
|
public Object convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
|
||||||
Object codeValue = cellData.getData();
|
cellData.checkEmpty();
|
||||||
|
// Excel中填入的是枚举中指定的描述
|
||||||
|
Object textValue = null;
|
||||||
|
switch (cellData.getType()) {
|
||||||
|
case STRING:
|
||||||
|
case DIRECT_STRING:
|
||||||
|
case RICH_TEXT_STRING:
|
||||||
|
textValue = cellData.getStringValue();
|
||||||
|
break;
|
||||||
|
case NUMBER:
|
||||||
|
textValue = cellData.getNumberValue();
|
||||||
|
break;
|
||||||
|
case BOOLEAN:
|
||||||
|
textValue = cellData.getBooleanValue();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("单元格类型异常!");
|
||||||
|
}
|
||||||
// 如果是空值
|
// 如果是空值
|
||||||
if (ObjectUtil.isNull(codeValue)) {
|
if (ObjectUtil.isNull(textValue)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Map<Object, String> enumValueMap = beforeConvert(contentProperty);
|
Map<Object, String> enumCodeToTextMap = beforeConvert(contentProperty);
|
||||||
String textValue = enumValueMap.get(codeValue);
|
// 从Java输出至Excel是code转text
|
||||||
return Convert.convert(contentProperty.getField().getType(), textValue);
|
// 因此从Excel转Java应该将text与code对调
|
||||||
|
Map<Object, Object> enumTextToCodeMap = new HashMap<>();
|
||||||
|
enumCodeToTextMap.forEach((key, value) -> enumTextToCodeMap.put(value, key));
|
||||||
|
// 应该从text -> code中查找
|
||||||
|
Object codeValue = enumTextToCodeMap.get(textValue);
|
||||||
|
return Convert.convert(contentProperty.getField().getType(), codeValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -53,6 +53,7 @@ public class SysUser extends BaseEntity {
|
|||||||
* 用户昵称
|
* 用户昵称
|
||||||
*/
|
*/
|
||||||
@Xss(message = "用户昵称不能包含脚本字符")
|
@Xss(message = "用户昵称不能包含脚本字符")
|
||||||
|
@NotBlank(message = "用户昵称不能为空")
|
||||||
@Size(min = 0, max = 30, message = "用户昵称长度不能超过{max}个字符")
|
@Size(min = 0, max = 30, message = "用户昵称长度不能超过{max}个字符")
|
||||||
private String nickName;
|
private String nickName;
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import javax.validation.constraints.Email;
|
|||||||
import javax.validation.constraints.NotBlank;
|
import javax.validation.constraints.NotBlank;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 短信登录对象
|
* 邮箱登录对象
|
||||||
*
|
*
|
||||||
* @author Lion Li
|
* @author Lion Li
|
||||||
*/
|
*/
|
||||||
|
@ -180,12 +180,12 @@ public interface BaseMapperPlus<M, T, V> extends BaseMapper<T> {
|
|||||||
* 分页查询VO
|
* 分页查询VO
|
||||||
*/
|
*/
|
||||||
default <C, P extends IPage<C>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper, Class<C> voClass) {
|
default <C, P extends IPage<C>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper, Class<C> voClass) {
|
||||||
IPage<T> pageData = this.selectPage(page, wrapper);
|
List<T> list = this.selectList(page, wrapper);
|
||||||
IPage<C> voPage = new Page<>(pageData.getCurrent(), pageData.getSize(), pageData.getTotal());
|
IPage<C> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
|
||||||
if (CollUtil.isEmpty(pageData.getRecords())) {
|
if (CollUtil.isEmpty(list)) {
|
||||||
return (P) voPage;
|
return (P) voPage;
|
||||||
}
|
}
|
||||||
voPage.setRecords(BeanCopyUtils.copyList(pageData.getRecords(), voClass));
|
voPage.setRecords(BeanCopyUtils.copyList(list, voClass));
|
||||||
return (P) voPage;
|
return (P) voPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.ruoyi.common.core.service;
|
package com.ruoyi.common.core.service;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用 字典服务
|
* 通用 字典服务
|
||||||
*
|
*
|
||||||
@ -54,4 +56,11 @@ public interface DictService {
|
|||||||
*/
|
*/
|
||||||
String getDictValue(String dictType, String dictLabel, String separator);
|
String getDictValue(String dictType, String dictLabel, String separator);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典下所有的字典值与标签
|
||||||
|
*
|
||||||
|
* @param dictType 字典类型
|
||||||
|
* @return dictValue为key,dictLabel为值组成的Map
|
||||||
|
*/
|
||||||
|
Map<String, String> getAllDictByDictType(String dictType);
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
package com.ruoyi.common.encrypt.encryptor;
|
package com.ruoyi.common.encrypt.encryptor;
|
||||||
|
|
||||||
import cn.hutool.core.util.ArrayUtil;
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import cn.hutool.crypto.SecureUtil;
|
|
||||||
import cn.hutool.crypto.symmetric.AES;
|
|
||||||
import com.ruoyi.common.encrypt.EncryptContext;
|
import com.ruoyi.common.encrypt.EncryptContext;
|
||||||
import com.ruoyi.common.enums.AlgorithmType;
|
import com.ruoyi.common.enums.AlgorithmType;
|
||||||
import com.ruoyi.common.enums.EncodeType;
|
import com.ruoyi.common.enums.EncodeType;
|
||||||
|
import com.ruoyi.common.utils.EncryptUtils;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AES算法实现
|
* AES算法实现
|
||||||
@ -18,20 +13,11 @@ import java.nio.charset.StandardCharsets;
|
|||||||
*/
|
*/
|
||||||
public class AesEncryptor extends AbstractEncryptor {
|
public class AesEncryptor extends AbstractEncryptor {
|
||||||
|
|
||||||
private final AES aes;
|
private final EncryptContext context;
|
||||||
|
|
||||||
public AesEncryptor(EncryptContext context) {
|
public AesEncryptor(EncryptContext context) {
|
||||||
super(context);
|
super(context);
|
||||||
String password = context.getPassword();
|
this.context = context;
|
||||||
if (StrUtil.isBlank(password)) {
|
|
||||||
throw new IllegalArgumentException("AES没有获得秘钥信息");
|
|
||||||
}
|
|
||||||
// aes算法的秘钥要求是16位、24位、32位
|
|
||||||
int[] array = {16, 24, 32};
|
|
||||||
if (!ArrayUtil.contains(array, password.length())) {
|
|
||||||
throw new IllegalArgumentException("AES秘钥长度应该为16位、24位、32位,实际为" + password.length() + "位");
|
|
||||||
}
|
|
||||||
aes = SecureUtil.aes(context.getPassword().getBytes(StandardCharsets.UTF_8));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,9 +37,9 @@ public class AesEncryptor extends AbstractEncryptor {
|
|||||||
@Override
|
@Override
|
||||||
public String encrypt(String value, EncodeType encodeType) {
|
public String encrypt(String value, EncodeType encodeType) {
|
||||||
if (encodeType == EncodeType.HEX) {
|
if (encodeType == EncodeType.HEX) {
|
||||||
return aes.encryptHex(value);
|
return EncryptUtils.encryptByAesHex(value, context.getPassword());
|
||||||
} else {
|
} else {
|
||||||
return aes.encryptBase64(value);
|
return EncryptUtils.encryptByAes(value, context.getPassword());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +50,6 @@ public class AesEncryptor extends AbstractEncryptor {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String decrypt(String value) {
|
public String decrypt(String value) {
|
||||||
return this.aes.decryptStr(value);
|
return EncryptUtils.decryptByAes(value, context.getPassword());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package com.ruoyi.common.encrypt.encryptor;
|
package com.ruoyi.common.encrypt.encryptor;
|
||||||
|
|
||||||
import cn.hutool.core.codec.Base64;
|
|
||||||
import com.ruoyi.common.encrypt.EncryptContext;
|
import com.ruoyi.common.encrypt.EncryptContext;
|
||||||
import com.ruoyi.common.enums.AlgorithmType;
|
import com.ruoyi.common.enums.AlgorithmType;
|
||||||
import com.ruoyi.common.enums.EncodeType;
|
import com.ruoyi.common.enums.EncodeType;
|
||||||
|
import com.ruoyi.common.utils.EncryptUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base64算法实现
|
* Base64算法实现
|
||||||
@ -33,7 +33,7 @@ public class Base64Encryptor extends AbstractEncryptor {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String encrypt(String value, EncodeType encodeType) {
|
public String encrypt(String value, EncodeType encodeType) {
|
||||||
return Base64.encode(value);
|
return EncryptUtils.encryptByBase64(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,6 +43,6 @@ public class Base64Encryptor extends AbstractEncryptor {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String decrypt(String value) {
|
public String decrypt(String value) {
|
||||||
return Base64.decodeStr(value);
|
return EncryptUtils.decryptByBase64(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
package com.ruoyi.common.encrypt.encryptor;
|
package com.ruoyi.common.encrypt.encryptor;
|
||||||
|
|
||||||
import cn.hutool.core.codec.Base64;
|
|
||||||
import cn.hutool.crypto.SecureUtil;
|
|
||||||
import cn.hutool.crypto.asymmetric.KeyType;
|
|
||||||
import cn.hutool.crypto.asymmetric.RSA;
|
|
||||||
import com.ruoyi.common.encrypt.EncryptContext;
|
import com.ruoyi.common.encrypt.EncryptContext;
|
||||||
import com.ruoyi.common.enums.AlgorithmType;
|
import com.ruoyi.common.enums.AlgorithmType;
|
||||||
import com.ruoyi.common.enums.EncodeType;
|
import com.ruoyi.common.enums.EncodeType;
|
||||||
|
import com.ruoyi.common.utils.EncryptUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
|
||||||
|
|
||||||
@ -18,7 +15,7 @@ import com.ruoyi.common.utils.StringUtils;
|
|||||||
*/
|
*/
|
||||||
public class RsaEncryptor extends AbstractEncryptor {
|
public class RsaEncryptor extends AbstractEncryptor {
|
||||||
|
|
||||||
private final RSA rsa;
|
private final EncryptContext context;
|
||||||
|
|
||||||
public RsaEncryptor(EncryptContext context) {
|
public RsaEncryptor(EncryptContext context) {
|
||||||
super(context);
|
super(context);
|
||||||
@ -27,7 +24,7 @@ public class RsaEncryptor extends AbstractEncryptor {
|
|||||||
if (StringUtils.isAnyEmpty(privateKey, publicKey)) {
|
if (StringUtils.isAnyEmpty(privateKey, publicKey)) {
|
||||||
throw new IllegalArgumentException("RSA公私钥均需要提供,公钥加密,私钥解密。");
|
throw new IllegalArgumentException("RSA公私钥均需要提供,公钥加密,私钥解密。");
|
||||||
}
|
}
|
||||||
this.rsa = SecureUtil.rsa(Base64.decode(privateKey), Base64.decode(publicKey));
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,9 +44,9 @@ public class RsaEncryptor extends AbstractEncryptor {
|
|||||||
@Override
|
@Override
|
||||||
public String encrypt(String value, EncodeType encodeType) {
|
public String encrypt(String value, EncodeType encodeType) {
|
||||||
if (encodeType == EncodeType.HEX) {
|
if (encodeType == EncodeType.HEX) {
|
||||||
return rsa.encryptHex(value, KeyType.PublicKey);
|
return EncryptUtils.encryptByRsaHex(value, context.getPublicKey());
|
||||||
} else {
|
} else {
|
||||||
return rsa.encryptBase64(value, KeyType.PublicKey);
|
return EncryptUtils.encryptByRsa(value, context.getPublicKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +57,6 @@ public class RsaEncryptor extends AbstractEncryptor {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String decrypt(String value) {
|
public String decrypt(String value) {
|
||||||
return this.rsa.decryptStr(value, KeyType.PrivateKey);
|
return EncryptUtils.decryptByRsa(value, context.getPrivateKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
package com.ruoyi.common.encrypt.encryptor;
|
package com.ruoyi.common.encrypt.encryptor;
|
||||||
|
|
||||||
|
|
||||||
import cn.hutool.core.codec.Base64;
|
|
||||||
import cn.hutool.crypto.SmUtil;
|
|
||||||
import cn.hutool.crypto.asymmetric.KeyType;
|
|
||||||
import cn.hutool.crypto.asymmetric.SM2;
|
|
||||||
import com.ruoyi.common.encrypt.EncryptContext;
|
import com.ruoyi.common.encrypt.EncryptContext;
|
||||||
import com.ruoyi.common.enums.AlgorithmType;
|
import com.ruoyi.common.enums.AlgorithmType;
|
||||||
import com.ruoyi.common.enums.EncodeType;
|
import com.ruoyi.common.enums.EncodeType;
|
||||||
|
import com.ruoyi.common.utils.EncryptUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -18,7 +15,7 @@ import com.ruoyi.common.utils.StringUtils;
|
|||||||
*/
|
*/
|
||||||
public class Sm2Encryptor extends AbstractEncryptor {
|
public class Sm2Encryptor extends AbstractEncryptor {
|
||||||
|
|
||||||
private final SM2 sm2;
|
private final EncryptContext context;
|
||||||
|
|
||||||
public Sm2Encryptor(EncryptContext context) {
|
public Sm2Encryptor(EncryptContext context) {
|
||||||
super(context);
|
super(context);
|
||||||
@ -27,7 +24,7 @@ public class Sm2Encryptor extends AbstractEncryptor {
|
|||||||
if (StringUtils.isAnyEmpty(privateKey, publicKey)) {
|
if (StringUtils.isAnyEmpty(privateKey, publicKey)) {
|
||||||
throw new IllegalArgumentException("SM2公私钥均需要提供,公钥加密,私钥解密。");
|
throw new IllegalArgumentException("SM2公私钥均需要提供,公钥加密,私钥解密。");
|
||||||
}
|
}
|
||||||
this.sm2 = SmUtil.sm2(Base64.decode(privateKey), Base64.decode(publicKey));
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,9 +44,9 @@ public class Sm2Encryptor extends AbstractEncryptor {
|
|||||||
@Override
|
@Override
|
||||||
public String encrypt(String value, EncodeType encodeType) {
|
public String encrypt(String value, EncodeType encodeType) {
|
||||||
if (encodeType == EncodeType.HEX) {
|
if (encodeType == EncodeType.HEX) {
|
||||||
return sm2.encryptHex(value, KeyType.PublicKey);
|
return EncryptUtils.encryptBySm2Hex(value, context.getPublicKey());
|
||||||
} else {
|
} else {
|
||||||
return sm2.encryptBase64(value, KeyType.PublicKey);
|
return EncryptUtils.encryptBySm2(value, context.getPublicKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +57,6 @@ public class Sm2Encryptor extends AbstractEncryptor {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String decrypt(String value) {
|
public String decrypt(String value) {
|
||||||
return this.sm2.decryptStr(value, KeyType.PrivateKey);
|
return EncryptUtils.decryptBySm2(value, context.getPrivateKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,9 @@
|
|||||||
package com.ruoyi.common.encrypt.encryptor;
|
package com.ruoyi.common.encrypt.encryptor;
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import cn.hutool.crypto.SmUtil;
|
|
||||||
import cn.hutool.crypto.symmetric.SM4;
|
|
||||||
import com.ruoyi.common.encrypt.EncryptContext;
|
import com.ruoyi.common.encrypt.EncryptContext;
|
||||||
import com.ruoyi.common.enums.AlgorithmType;
|
import com.ruoyi.common.enums.AlgorithmType;
|
||||||
import com.ruoyi.common.enums.EncodeType;
|
import com.ruoyi.common.enums.EncodeType;
|
||||||
|
import com.ruoyi.common.utils.EncryptUtils;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sm4算法实现
|
* sm4算法实现
|
||||||
@ -17,19 +13,11 @@ import java.nio.charset.StandardCharsets;
|
|||||||
*/
|
*/
|
||||||
public class Sm4Encryptor extends AbstractEncryptor {
|
public class Sm4Encryptor extends AbstractEncryptor {
|
||||||
|
|
||||||
private final SM4 sm4;
|
private final EncryptContext context;
|
||||||
|
|
||||||
public Sm4Encryptor(EncryptContext context) {
|
public Sm4Encryptor(EncryptContext context) {
|
||||||
super(context);
|
super(context);
|
||||||
String password = context.getPassword();
|
this.context = context;
|
||||||
if (StrUtil.isBlank(password)) {
|
|
||||||
throw new IllegalArgumentException("SM4没有获得秘钥信息");
|
|
||||||
}
|
|
||||||
// sm4算法的秘钥要求是16位长度
|
|
||||||
if (16 != password.length()) {
|
|
||||||
throw new IllegalArgumentException("SM4秘钥长度应该为16位,实际为" + password.length() + "位");
|
|
||||||
}
|
|
||||||
this.sm4 = SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,9 +37,9 @@ public class Sm4Encryptor extends AbstractEncryptor {
|
|||||||
@Override
|
@Override
|
||||||
public String encrypt(String value, EncodeType encodeType) {
|
public String encrypt(String value, EncodeType encodeType) {
|
||||||
if (encodeType == EncodeType.HEX) {
|
if (encodeType == EncodeType.HEX) {
|
||||||
return sm4.encryptHex(value);
|
return EncryptUtils.encryptBySm4Hex(value, context.getPassword());
|
||||||
} else {
|
} else {
|
||||||
return sm4.encryptBase64(value);
|
return EncryptUtils.encryptBySm4(value, context.getPassword());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,6 +50,6 @@ public class Sm4Encryptor extends AbstractEncryptor {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String decrypt(String value) {
|
public String decrypt(String value) {
|
||||||
return this.sm4.decryptStr(value);
|
return EncryptUtils.decryptBySm4(value, context.getPassword());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,17 +29,17 @@ public enum DataScopeType {
|
|||||||
/**
|
/**
|
||||||
* 自定数据权限
|
* 自定数据权限
|
||||||
*/
|
*/
|
||||||
CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", ""),
|
CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", " 1 = 0 "),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 部门数据权限
|
* 部门数据权限
|
||||||
*/
|
*/
|
||||||
DEPT("3", " #{#deptName} = #{#user.deptId} ", ""),
|
DEPT("3", " #{#deptName} = #{#user.deptId} ", " 1 = 0 "),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 部门及以下数据权限
|
* 部门及以下数据权限
|
||||||
*/
|
*/
|
||||||
DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", ""),
|
DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", " 1 = 0 "),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 仅本人数据权限
|
* 仅本人数据权限
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
package com.ruoyi.common.excel;
|
package com.ruoyi.common.excel;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
import com.alibaba.excel.metadata.Head;
|
import com.alibaba.excel.metadata.Head;
|
||||||
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
|
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
|
||||||
import com.ruoyi.common.annotation.CellMerge;
|
import com.ruoyi.common.annotation.CellMerge;
|
||||||
|
import com.ruoyi.common.utils.reflect.ReflectUtils;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
|
||||||
import org.apache.poi.ss.usermodel.Cell;
|
import org.apache.poi.ss.usermodel.Cell;
|
||||||
import org.apache.poi.ss.usermodel.Sheet;
|
import org.apache.poi.ss.usermodel.Sheet;
|
||||||
import org.apache.poi.ss.util.CellRangeAddress;
|
import org.apache.poi.ss.util.CellRangeAddress;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -24,91 +25,97 @@ import java.util.Map;
|
|||||||
*
|
*
|
||||||
* @author Lion Li
|
* @author Lion Li
|
||||||
*/
|
*/
|
||||||
@AllArgsConstructor
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class CellMergeStrategy extends AbstractMergeStrategy {
|
public class CellMergeStrategy extends AbstractMergeStrategy {
|
||||||
|
|
||||||
private List<?> list;
|
private final List<CellRangeAddress> cellList;
|
||||||
private boolean hasTitle;
|
private final boolean hasTitle;
|
||||||
|
private int rowIndex;
|
||||||
|
|
||||||
@Override
|
public CellMergeStrategy(List<?> list, boolean hasTitle) {
|
||||||
protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
|
this.hasTitle = hasTitle;
|
||||||
List<CellRangeAddress> cellList = handle(list, hasTitle);
|
// 行合并开始下标
|
||||||
// judge the list is not null
|
this.rowIndex = hasTitle ? 1 : 0;
|
||||||
if (CollectionUtils.isNotEmpty(cellList)) {
|
this.cellList = handle(list, hasTitle);
|
||||||
// the judge is necessary
|
}
|
||||||
if (cell.getRowIndex() == 1 && cell.getColumnIndex() == 0) {
|
|
||||||
for (CellRangeAddress item : cellList) {
|
|
||||||
sheet.addMergedRegion(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
@Override
|
||||||
private static List<CellRangeAddress> handle(List<?> list, boolean hasTitle) {
|
protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
|
||||||
List<CellRangeAddress> cellList = new ArrayList<>();
|
// judge the list is not null
|
||||||
if (CollectionUtils.isEmpty(list)) {
|
if (CollUtil.isNotEmpty(cellList)) {
|
||||||
return cellList;
|
// the judge is necessary
|
||||||
}
|
if (cell.getRowIndex() == rowIndex && cell.getColumnIndex() == 0) {
|
||||||
Class<?> clazz = list.get(0).getClass();
|
for (CellRangeAddress item : cellList) {
|
||||||
Field[] fields = clazz.getDeclaredFields();
|
sheet.addMergedRegion(item);
|
||||||
// 有注解的字段
|
}
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 行合并开始下标
|
|
||||||
int rowIndex = hasTitle ? 1 : 0;
|
|
||||||
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);
|
|
||||||
String name = field.getName();
|
|
||||||
String methodName = "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
|
|
||||||
Method readMethod = clazz.getMethod(methodName);
|
|
||||||
Object val = readMethod.invoke(list.get(i));
|
|
||||||
|
|
||||||
int colNum = mergeFieldsIndex.get(j);
|
@SneakyThrows
|
||||||
if (!map.containsKey(field)) {
|
private List<CellRangeAddress> handle(List<?> list, boolean hasTitle) {
|
||||||
map.put(field, new RepeatCell(val, i));
|
List<CellRangeAddress> cellList = new ArrayList<>();
|
||||||
} else {
|
if (CollUtil.isEmpty(list)) {
|
||||||
RepeatCell repeatCell = map.get(field);
|
return cellList;
|
||||||
Object cellValue = repeatCell.getValue();
|
}
|
||||||
if (cellValue == null || "".equals(cellValue)) {
|
Field[] fields = ReflectUtils.getFields(list.get(0).getClass(), field -> !"serialVersionUID".equals(field.getName()));
|
||||||
// 空值跳过不合并
|
|
||||||
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 (i > repeatCell.getCurrent()) {
|
|
||||||
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cellList;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
// 有注解的字段
|
||||||
@AllArgsConstructor
|
List<Field> mergeFields = new ArrayList<>();
|
||||||
static class RepeatCell {
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Object value;
|
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());
|
||||||
|
|
||||||
private int current;
|
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 (i > repeatCell.getCurrent()) {
|
||||||
|
cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cellList;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
static class RepeatCell {
|
||||||
|
|
||||||
|
private Object value;
|
||||||
|
|
||||||
|
private int current;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,149 @@
|
|||||||
|
package com.ruoyi.common.excel;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>Excel下拉可选项</h1>
|
||||||
|
* 注意:为确保下拉框解析正确,传值务必使用createOptionValue()做为值的拼接
|
||||||
|
*
|
||||||
|
* @author Emil.Zhang
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class DropDownOptions {
|
||||||
|
/**
|
||||||
|
* 一级下拉所在列index,从0开始算
|
||||||
|
*/
|
||||||
|
private int index = 0;
|
||||||
|
/**
|
||||||
|
* 二级下拉所在的index,从0开始算,不能与一级相同
|
||||||
|
*/
|
||||||
|
private int nextIndex = 0;
|
||||||
|
/**
|
||||||
|
* 一级下拉所包含的数据
|
||||||
|
*/
|
||||||
|
private List<String> options = new ArrayList<>();
|
||||||
|
/**
|
||||||
|
* 二级下拉所包含的数据Map
|
||||||
|
* <p>以每一个一级选项值为Key,每个一级选项对应的二级数据为Value</p>
|
||||||
|
*/
|
||||||
|
private Map<String, List<String>> nextOptions = new HashMap<>();
|
||||||
|
/**
|
||||||
|
* 分隔符
|
||||||
|
*/
|
||||||
|
private static final String DELIMITER = "_";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建只有一级的下拉选
|
||||||
|
*/
|
||||||
|
public DropDownOptions(int index, List<String> options) {
|
||||||
|
this.index = index;
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h2>创建每个选项可选值</h2>
|
||||||
|
* <p>注意:不能以数字,特殊符号开头,选项中不可以包含任何运算符号</p>
|
||||||
|
*
|
||||||
|
* @param vars 可选值内包含的参数
|
||||||
|
* @return 合规的可选值
|
||||||
|
*/
|
||||||
|
public static String createOptionValue(Object... vars) {
|
||||||
|
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]));
|
||||||
|
if (!var.matches(regex)) {
|
||||||
|
throw new ServiceException("选项数据不符合规则,仅允许使用中英文字符以及数字");
|
||||||
|
}
|
||||||
|
stringBuffer.append(var);
|
||||||
|
if (i < vars.length - 1) {
|
||||||
|
// 直至最后一个前,都以_作为切割线
|
||||||
|
stringBuffer.append(DELIMITER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stringBuffer.toString().matches("^\\d_*$")) {
|
||||||
|
throw new ServiceException("禁止以数字开头");
|
||||||
|
}
|
||||||
|
return stringBuffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将处理后合理的可选值解析为原始的参数
|
||||||
|
*
|
||||||
|
* @param option 经过处理后的合理的可选项
|
||||||
|
* @return 原始的参数
|
||||||
|
*/
|
||||||
|
public static List<String> analyzeOptionValue(String option) {
|
||||||
|
return StrUtil.split(option, DELIMITER, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建级联下拉选项
|
||||||
|
*
|
||||||
|
* @param parentList 父实体可选项原始数据
|
||||||
|
* @param parentIndex 父下拉选位置
|
||||||
|
* @param sonList 子实体可选项原始数据
|
||||||
|
* @param sonIndex 子下拉选位置
|
||||||
|
* @param parentHowToGetIdFunction 父类如何获取唯一标识
|
||||||
|
* @param sonHowToGetParentIdFunction 子类如何获取父类的唯一标识
|
||||||
|
* @param howToBuildEveryOption 如何生成下拉选内容
|
||||||
|
* @return 级联下拉选项
|
||||||
|
*/
|
||||||
|
public static <T> DropDownOptions buildLinkedOptions(List<T> parentList,
|
||||||
|
int parentIndex,
|
||||||
|
List<T> sonList,
|
||||||
|
int sonIndex,
|
||||||
|
Function<T, Number> parentHowToGetIdFunction,
|
||||||
|
Function<T, Number> sonHowToGetParentIdFunction,
|
||||||
|
Function<T, String> howToBuildEveryOption) {
|
||||||
|
DropDownOptions parentLinkSonOptions = new DropDownOptions();
|
||||||
|
// 先创建父类的下拉
|
||||||
|
parentLinkSonOptions.setIndex(parentIndex);
|
||||||
|
parentLinkSonOptions.setOptions(
|
||||||
|
parentList.stream()
|
||||||
|
.map(howToBuildEveryOption)
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
);
|
||||||
|
// 提取父-子级联下拉
|
||||||
|
Map<String, List<String>> sonOptions = new HashMap<>();
|
||||||
|
// 父级依据自己的ID分组
|
||||||
|
Map<Number, List<T>> parentGroupByIdMap =
|
||||||
|
parentList.stream().collect(Collectors.groupingBy(parentHowToGetIdFunction));
|
||||||
|
// 遍历每个子集,提取到Map中
|
||||||
|
sonList.forEach(everySon -> {
|
||||||
|
if (parentGroupByIdMap.containsKey(sonHowToGetParentIdFunction.apply(everySon))) {
|
||||||
|
// 找到对应的上级
|
||||||
|
T parentObj = parentGroupByIdMap.get(sonHowToGetParentIdFunction.apply(everySon)).get(0);
|
||||||
|
// 提取名称和ID作为Key
|
||||||
|
String key = howToBuildEveryOption.apply(parentObj);
|
||||||
|
// Key对应的Value
|
||||||
|
List<String> thisParentSonOptionList;
|
||||||
|
if (sonOptions.containsKey(key)) {
|
||||||
|
thisParentSonOptionList = sonOptions.get(key);
|
||||||
|
} else {
|
||||||
|
thisParentSonOptionList = new ArrayList<>();
|
||||||
|
sonOptions.put(key, thisParentSonOptionList);
|
||||||
|
}
|
||||||
|
// 往Value中添加当前子集选项
|
||||||
|
thisParentSonOptionList.add(howToBuildEveryOption.apply(everySon));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
parentLinkSonOptions.setNextIndex(sonIndex);
|
||||||
|
parentLinkSonOptions.setNextOptions(sonOptions);
|
||||||
|
return parentLinkSonOptions;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,371 @@
|
|||||||
|
package com.ruoyi.common.excel;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
|
import cn.hutool.core.util.EnumUtil;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.alibaba.excel.metadata.FieldCache;
|
||||||
|
import com.alibaba.excel.metadata.FieldWrapper;
|
||||||
|
import com.alibaba.excel.util.ClassUtils;
|
||||||
|
import com.alibaba.excel.write.handler.SheetWriteHandler;
|
||||||
|
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
|
||||||
|
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
|
||||||
|
import com.ruoyi.common.annotation.ExcelDictFormat;
|
||||||
|
import com.ruoyi.common.annotation.ExcelEnumFormat;
|
||||||
|
import com.ruoyi.common.core.service.DictService;
|
||||||
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
|
import com.ruoyi.common.utils.StreamUtils;
|
||||||
|
import com.ruoyi.common.utils.spring.SpringUtils;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.poi.ss.usermodel.*;
|
||||||
|
import org.apache.poi.ss.util.CellRangeAddressList;
|
||||||
|
import org.apache.poi.ss.util.WorkbookUtil;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFDataValidation;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h1>Excel表格下拉选操作</h1>
|
||||||
|
* 考虑到下拉选过多可能导致Excel打开缓慢的问题,只校验前1000行
|
||||||
|
* <p>
|
||||||
|
* 即只有前1000行的数据可以用下拉框,超出的自行通过限制数据量的形式,第二次输出
|
||||||
|
*
|
||||||
|
* @author Emil.Zhang
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class ExcelDownHandler implements SheetWriteHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Excel表格中的列名英文
|
||||||
|
* 仅为了解析列英文,禁止修改
|
||||||
|
*/
|
||||||
|
private static final String EXCEL_COLUMN_NAME = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
/**
|
||||||
|
* 单选数据Sheet名
|
||||||
|
*/
|
||||||
|
private static final String OPTIONS_SHEET_NAME = "options";
|
||||||
|
/**
|
||||||
|
* 联动选择数据Sheet名的头
|
||||||
|
*/
|
||||||
|
private static final String LINKED_OPTIONS_SHEET_NAME = "linkedOptions";
|
||||||
|
/**
|
||||||
|
* 下拉可选项
|
||||||
|
*/
|
||||||
|
private final List<DropDownOptions> dropDownOptions;
|
||||||
|
/**
|
||||||
|
* 当前单选进度
|
||||||
|
*/
|
||||||
|
private int currentOptionsColumnIndex;
|
||||||
|
/**
|
||||||
|
* 当前联动选择进度
|
||||||
|
*/
|
||||||
|
private int currentLinkedOptionsSheetIndex;
|
||||||
|
private final DictService dictService;
|
||||||
|
|
||||||
|
public ExcelDownHandler(List<DropDownOptions> options) {
|
||||||
|
this.dropDownOptions = options;
|
||||||
|
this.currentOptionsColumnIndex = 0;
|
||||||
|
this.currentLinkedOptionsSheetIndex = 0;
|
||||||
|
this.dictService = SpringUtils.getBean(DictService.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h2>开始创建下拉数据</h2>
|
||||||
|
* 1.通过解析传入的@ExcelProperty同级是否标注有@DropDown选项
|
||||||
|
* 如果有且设置了value值,则将其直接置为下拉可选项
|
||||||
|
* <p>
|
||||||
|
* 2.或者在调用ExcelUtil时指定了可选项,将依据传入的可选项做下拉
|
||||||
|
* <p>
|
||||||
|
* 3.二者并存,注意调用方式
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
|
||||||
|
Sheet sheet = writeSheetHolder.getSheet();
|
||||||
|
// 开始设置下拉框 HSSFWorkbook
|
||||||
|
DataValidationHelper helper = sheet.getDataValidationHelper();
|
||||||
|
Workbook workbook = writeWorkbookHolder.getWorkbook();
|
||||||
|
FieldCache fieldCache = ClassUtils.declaredFields(writeWorkbookHolder.getClazz(), writeWorkbookHolder);
|
||||||
|
for (Map.Entry<Integer, FieldWrapper> entry : fieldCache.getSortedFieldMap().entrySet()) {
|
||||||
|
Integer index = entry.getKey();
|
||||||
|
FieldWrapper wrapper = entry.getValue();
|
||||||
|
Field field = wrapper.getField();
|
||||||
|
// 循环实体中的每个属性
|
||||||
|
// 可选的下拉值
|
||||||
|
List<String> options = new ArrayList<>();
|
||||||
|
if (field.isAnnotationPresent(ExcelDictFormat.class)) {
|
||||||
|
// 如果指定了@ExcelDictFormat,则使用字典的逻辑
|
||||||
|
ExcelDictFormat format = field.getDeclaredAnnotation(ExcelDictFormat.class);
|
||||||
|
String dictType = format.dictType();
|
||||||
|
String converterExp = format.readConverterExp();
|
||||||
|
if (StrUtil.isNotBlank(dictType)) {
|
||||||
|
// 如果传递了字典名,则依据字典建立下拉
|
||||||
|
Collection<String> values = Optional.ofNullable(dictService.getAllDictByDictType(dictType))
|
||||||
|
.orElseThrow(() -> new ServiceException(String.format("字典 %s 不存在", dictType)))
|
||||||
|
.values();
|
||||||
|
options = new ArrayList<>(values);
|
||||||
|
} else if (StrUtil.isNotBlank(converterExp)) {
|
||||||
|
// 如果指定了确切的值,则直接解析确切的值
|
||||||
|
options = StrUtil.split(converterExp, format.separator(), true, true);
|
||||||
|
}
|
||||||
|
} else if (field.isAnnotationPresent(ExcelEnumFormat.class)) {
|
||||||
|
// 否则如果指定了@ExcelEnumFormat,则使用枚举的逻辑
|
||||||
|
ExcelEnumFormat format = field.getDeclaredAnnotation(ExcelEnumFormat.class);
|
||||||
|
List<Object> values = EnumUtil.getFieldValues(format.enumClass(), format.textField());
|
||||||
|
options = StreamUtils.toList(values, String::valueOf);
|
||||||
|
}
|
||||||
|
if (ObjectUtil.isNotEmpty(options)) {
|
||||||
|
// 仅当下拉可选项不为空时执行
|
||||||
|
if (options.size() > 20) {
|
||||||
|
// 这里限制如果可选项大于20,则使用额外表形式
|
||||||
|
dropDownWithSheet(helper, workbook, sheet, index, options);
|
||||||
|
} else {
|
||||||
|
// 否则使用固定值形式
|
||||||
|
dropDownWithSimple(helper, sheet, index, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (CollUtil.isEmpty(dropDownOptions)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dropDownOptions.forEach(everyOptions -> {
|
||||||
|
// 如果传递了下拉框选择器参数
|
||||||
|
if (!everyOptions.getNextOptions().isEmpty()) {
|
||||||
|
// 当二级选项不为空时,使用额外关联表的形式
|
||||||
|
dropDownLinkedOptions(helper, workbook, sheet, everyOptions);
|
||||||
|
} else if (everyOptions.getOptions().size() > 10) {
|
||||||
|
// 当一级选项参数个数大于10,使用额外表的形式
|
||||||
|
dropDownWithSheet(helper, workbook, sheet, everyOptions.getIndex(), everyOptions.getOptions());
|
||||||
|
} else if (everyOptions.getOptions().size() != 0) {
|
||||||
|
// 当一级选项个数不为空,使用默认形式
|
||||||
|
dropDownWithSimple(helper, sheet, everyOptions.getIndex(), everyOptions.getOptions());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h2>简单下拉框</h2>
|
||||||
|
* 直接将可选项拼接为指定列的数据校验值
|
||||||
|
*
|
||||||
|
* @param celIndex 列index
|
||||||
|
* @param value 下拉选可选值
|
||||||
|
*/
|
||||||
|
private void dropDownWithSimple(DataValidationHelper helper, Sheet sheet, Integer celIndex, List<String> value) {
|
||||||
|
if (ObjectUtil.isEmpty(value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.markOptionsToSheet(helper, sheet, celIndex, helper.createExplicitListConstraint(ArrayUtil.toArray(value, String.class)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h2>额外表格形式的级联下拉框</h2>
|
||||||
|
*
|
||||||
|
* @param options 额外表格形式存储的下拉可选项
|
||||||
|
*/
|
||||||
|
private void dropDownLinkedOptions(DataValidationHelper helper, Workbook workbook, Sheet sheet, DropDownOptions options) {
|
||||||
|
String linkedOptionsSheetName = String.format("%s_%d", LINKED_OPTIONS_SHEET_NAME, currentLinkedOptionsSheetIndex);
|
||||||
|
// 创建联动下拉数据表
|
||||||
|
Sheet linkedOptionsDataSheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(linkedOptionsSheetName));
|
||||||
|
// 将下拉表隐藏
|
||||||
|
workbook.setSheetHidden(workbook.getSheetIndex(linkedOptionsDataSheet), true);
|
||||||
|
// 完善横向的一级选项数据表
|
||||||
|
List<String> firstOptions = options.getOptions();
|
||||||
|
Map<String, List<String>> secoundOptionsMap = options.getNextOptions();
|
||||||
|
|
||||||
|
// 创建名称管理器
|
||||||
|
Name name = workbook.createName();
|
||||||
|
// 设置名称管理器的别名
|
||||||
|
name.setNameName(linkedOptionsSheetName);
|
||||||
|
// 以横向第一行创建一级下拉拼接引用位置
|
||||||
|
String firstOptionsFunction = String.format("%s!$%s$1:$%s$1",
|
||||||
|
linkedOptionsSheetName,
|
||||||
|
getExcelColumnName(0),
|
||||||
|
getExcelColumnName(firstOptions.size())
|
||||||
|
);
|
||||||
|
// 设置名称管理器的引用位置
|
||||||
|
name.setRefersToFormula(firstOptionsFunction);
|
||||||
|
// 设置数据校验为序列模式,引用的是名称管理器中的别名
|
||||||
|
this.markOptionsToSheet(helper, sheet, options.getIndex(), helper.createFormulaListConstraint(linkedOptionsSheetName));
|
||||||
|
|
||||||
|
for (int columIndex = 0; columIndex < firstOptions.size(); columIndex++) {
|
||||||
|
// 先提取主表中一级下拉的列名
|
||||||
|
String firstOptionsColumnName = getExcelColumnName(columIndex);
|
||||||
|
// 一次循环是每一个一级选项
|
||||||
|
int finalI = columIndex;
|
||||||
|
// 本次循环的一级选项值
|
||||||
|
String thisFirstOptionsValue = firstOptions.get(columIndex);
|
||||||
|
// 创建第一行的数据
|
||||||
|
Optional.ofNullable(linkedOptionsDataSheet.getRow(0))
|
||||||
|
// 如果不存在则创建第一行
|
||||||
|
.orElseGet(() -> linkedOptionsDataSheet.createRow(finalI))
|
||||||
|
// 第一行当前列
|
||||||
|
.createCell(columIndex)
|
||||||
|
// 设置值为当前一级选项值
|
||||||
|
.setCellValue(thisFirstOptionsValue);
|
||||||
|
|
||||||
|
// 第二行开始,设置第二级别选项参数
|
||||||
|
List<String> secondOptions = secoundOptionsMap.get(thisFirstOptionsValue);
|
||||||
|
if (CollUtil.isEmpty(secondOptions)) {
|
||||||
|
// 必须保证至少有一个关联选项,否则将导致Excel解析错误
|
||||||
|
secondOptions = Collections.singletonList("暂无_0");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 以该一级选项值创建子名称管理器
|
||||||
|
Name sonName = workbook.createName();
|
||||||
|
// 设置名称管理器的别名
|
||||||
|
sonName.setNameName(thisFirstOptionsValue);
|
||||||
|
// 以第二行该列数据拼接引用位置
|
||||||
|
String sonFunction = String.format("%s!$%s$2:$%s$%d",
|
||||||
|
linkedOptionsSheetName,
|
||||||
|
firstOptionsColumnName,
|
||||||
|
firstOptionsColumnName,
|
||||||
|
secondOptions.size() + 1
|
||||||
|
);
|
||||||
|
// 设置名称管理器的引用位置
|
||||||
|
sonName.setRefersToFormula(sonFunction);
|
||||||
|
// 数据验证为序列模式,引用到每一个主表中的二级选项位置
|
||||||
|
// 创建子项的名称管理器,只是为了使得Excel可以识别到数据
|
||||||
|
String mainSheetFirstOptionsColumnName = getExcelColumnName(options.getIndex());
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
// 以一级选项对应的主体所在位置创建二级下拉
|
||||||
|
String secondOptionsFunction = String.format("=INDIRECT(%s%d)", mainSheetFirstOptionsColumnName, i + 1);
|
||||||
|
// 二级只能主表每一行的每一列添加二级校验
|
||||||
|
markLinkedOptionsToSheet(helper, sheet, i, options.getNextIndex(), helper.createFormulaListConstraint(secondOptionsFunction));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int rowIndex = 0; rowIndex < secondOptions.size(); rowIndex++) {
|
||||||
|
// 从第二行开始填充二级选项
|
||||||
|
int finalRowIndex = rowIndex + 1;
|
||||||
|
int finalColumIndex = columIndex;
|
||||||
|
|
||||||
|
Row row = Optional.ofNullable(linkedOptionsDataSheet.getRow(finalRowIndex))
|
||||||
|
// 没有则创建
|
||||||
|
.orElseGet(() -> linkedOptionsDataSheet.createRow(finalRowIndex));
|
||||||
|
Optional
|
||||||
|
// 在本级一级选项所在的列
|
||||||
|
.ofNullable(row.getCell(finalColumIndex))
|
||||||
|
// 不存在则创建
|
||||||
|
.orElseGet(() -> row.createCell(finalColumIndex))
|
||||||
|
// 设置二级选项值
|
||||||
|
.setCellValue(secondOptions.get(rowIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentLinkedOptionsSheetIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h2>额外表格形式的普通下拉框</h2>
|
||||||
|
* 由于下拉框可选值数量过多,为提升Excel打开效率,使用额外表格形式做下拉
|
||||||
|
*
|
||||||
|
* @param celIndex 下拉选
|
||||||
|
* @param value 下拉选可选值
|
||||||
|
*/
|
||||||
|
private void dropDownWithSheet(DataValidationHelper helper, Workbook workbook, Sheet sheet, Integer celIndex, List<String> value) {
|
||||||
|
// 创建下拉数据表
|
||||||
|
Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)))
|
||||||
|
.orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)));
|
||||||
|
// 将下拉表隐藏
|
||||||
|
workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), true);
|
||||||
|
// 完善纵向的一级选项数据表
|
||||||
|
for (int i = 0; i < value.size(); i++) {
|
||||||
|
int finalI = i;
|
||||||
|
// 获取每一选项行,如果没有则创建
|
||||||
|
Row row = Optional.ofNullable(simpleDataSheet.getRow(i))
|
||||||
|
.orElseGet(() -> simpleDataSheet.createRow(finalI));
|
||||||
|
// 获取本级选项对应的选项列,如果没有则创建
|
||||||
|
Cell cell = Optional.ofNullable(row.getCell(currentOptionsColumnIndex))
|
||||||
|
.orElseGet(() -> row.createCell(currentOptionsColumnIndex));
|
||||||
|
// 设置值
|
||||||
|
cell.setCellValue(value.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建名称管理器
|
||||||
|
Name name = workbook.createName();
|
||||||
|
// 设置名称管理器的别名
|
||||||
|
String nameName = String.format("%s_%d", OPTIONS_SHEET_NAME, celIndex);
|
||||||
|
name.setNameName(nameName);
|
||||||
|
// 以纵向第一列创建一级下拉拼接引用位置
|
||||||
|
String function = String.format("%s!$%s$1:$%s$%d",
|
||||||
|
OPTIONS_SHEET_NAME,
|
||||||
|
getExcelColumnName(currentOptionsColumnIndex),
|
||||||
|
getExcelColumnName(currentOptionsColumnIndex),
|
||||||
|
value.size());
|
||||||
|
// 设置名称管理器的引用位置
|
||||||
|
name.setRefersToFormula(function);
|
||||||
|
// 设置数据校验为序列模式,引用的是名称管理器中的别名
|
||||||
|
this.markOptionsToSheet(helper, sheet, celIndex, helper.createFormulaListConstraint(nameName));
|
||||||
|
currentOptionsColumnIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 挂载下拉的列,仅限一级选项
|
||||||
|
*/
|
||||||
|
private void markOptionsToSheet(DataValidationHelper helper, Sheet sheet, Integer celIndex,
|
||||||
|
DataValidationConstraint constraint) {
|
||||||
|
// 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列
|
||||||
|
CellRangeAddressList addressList = new CellRangeAddressList(1, 1000, celIndex, celIndex);
|
||||||
|
markDataValidationToSheet(helper, sheet, constraint, addressList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 挂载下拉的列,仅限二级选项
|
||||||
|
*/
|
||||||
|
private void markLinkedOptionsToSheet(DataValidationHelper helper, Sheet sheet, Integer rowIndex,
|
||||||
|
Integer celIndex, DataValidationConstraint constraint) {
|
||||||
|
// 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列
|
||||||
|
CellRangeAddressList addressList = new CellRangeAddressList(rowIndex, rowIndex, celIndex, celIndex);
|
||||||
|
markDataValidationToSheet(helper, sheet, constraint, addressList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用数据校验
|
||||||
|
*/
|
||||||
|
private void markDataValidationToSheet(DataValidationHelper helper, Sheet sheet,
|
||||||
|
DataValidationConstraint constraint, CellRangeAddressList addressList) {
|
||||||
|
// 数据有效性对象
|
||||||
|
DataValidation dataValidation = helper.createValidation(constraint, addressList);
|
||||||
|
// 处理Excel兼容性问题
|
||||||
|
if (dataValidation instanceof XSSFDataValidation) {
|
||||||
|
//数据校验
|
||||||
|
dataValidation.setSuppressDropDownArrow(true);
|
||||||
|
//错误提示
|
||||||
|
dataValidation.setErrorStyle(DataValidation.ErrorStyle.STOP);
|
||||||
|
dataValidation.createErrorBox("提示", "此值与单元格定义数据不一致");
|
||||||
|
dataValidation.setShowErrorBox(true);
|
||||||
|
//选定提示
|
||||||
|
dataValidation.createPromptBox("填写说明:", "填写内容只能为下拉中数据,其他数据将导致导入失败");
|
||||||
|
dataValidation.setShowPromptBox(true);
|
||||||
|
sheet.addValidationData(dataValidation);
|
||||||
|
} else {
|
||||||
|
dataValidation.setSuppressDropDownArrow(false);
|
||||||
|
}
|
||||||
|
sheet.addValidationData(dataValidation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <h2>依据列index获取列名英文</h2>
|
||||||
|
* 依据列index转换为Excel中的列名英文
|
||||||
|
* <p>例如第1列,index为0,解析出来为A列</p>
|
||||||
|
* 第27列,index为26,解析为AA列
|
||||||
|
* <p>第28列,index为27,解析为AB列</p>
|
||||||
|
*
|
||||||
|
* @param columnIndex 列index
|
||||||
|
* @return 列index所在得英文名
|
||||||
|
*/
|
||||||
|
private String getExcelColumnName(int columnIndex) {
|
||||||
|
// 26一循环的次数
|
||||||
|
int columnCircleCount = columnIndex / 26;
|
||||||
|
// 26一循环内的位置
|
||||||
|
int thisCircleColumnIndex = columnIndex % 26;
|
||||||
|
// 26一循环的次数大于0,则视为栏名至少两位
|
||||||
|
String columnPrefix = columnCircleCount == 0
|
||||||
|
? StrUtil.EMPTY
|
||||||
|
: StrUtil.subWithLength(EXCEL_COLUMN_NAME, columnCircleCount - 1, 1);
|
||||||
|
// 从26一循环内取对应的栏位名
|
||||||
|
String columnNext = StrUtil.subWithLength(EXCEL_COLUMN_NAME, thisCircleColumnIndex, 1);
|
||||||
|
// 将二者拼接即为最终的栏位名
|
||||||
|
return columnPrefix + columnNext;
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ package com.ruoyi.common.helper;
|
|||||||
|
|
||||||
import cn.dev33.satoken.context.SaHolder;
|
import cn.dev33.satoken.context.SaHolder;
|
||||||
import cn.dev33.satoken.context.model.SaStorage;
|
import cn.dev33.satoken.context.model.SaStorage;
|
||||||
|
import cn.dev33.satoken.session.SaSession;
|
||||||
import cn.dev33.satoken.stp.SaLoginModel;
|
import cn.dev33.satoken.stp.SaLoginModel;
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
import cn.hutool.core.convert.Convert;
|
import cn.hutool.core.convert.Convert;
|
||||||
@ -54,6 +55,14 @@ public class LoginHelper {
|
|||||||
if (ObjectUtil.isNotNull(deviceType)) {
|
if (ObjectUtil.isNotNull(deviceType)) {
|
||||||
model.setDevice(deviceType.getDevice());
|
model.setDevice(deviceType.getDevice());
|
||||||
}
|
}
|
||||||
|
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
||||||
|
// 例如: 后台用户30分钟过期 app用户1天过期
|
||||||
|
// UserType userType = UserType.getUserType(loginUser.getUserType());
|
||||||
|
// if (userType == UserType.SYS_USER) {
|
||||||
|
// model.setTimeout(86400).setActiveTimeout(1800);
|
||||||
|
// } else if (userType == UserType.APP_USER) {
|
||||||
|
// model.setTimeout(86400).setActiveTimeout(1800);
|
||||||
|
// }
|
||||||
StpUtil.login(loginUser.getLoginId(), model.setExtra(USER_KEY, loginUser.getUserId()));
|
StpUtil.login(loginUser.getLoginId(), model.setExtra(USER_KEY, loginUser.getUserId()));
|
||||||
StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
|
StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
|
||||||
}
|
}
|
||||||
@ -66,7 +75,11 @@ public class LoginHelper {
|
|||||||
if (loginUser != null) {
|
if (loginUser != null) {
|
||||||
return loginUser;
|
return loginUser;
|
||||||
}
|
}
|
||||||
loginUser = (LoginUser) StpUtil.getTokenSession().get(LOGIN_USER_KEY);
|
SaSession session = StpUtil.getTokenSession();
|
||||||
|
if (ObjectUtil.isNull(session)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
loginUser = (LoginUser) session.get(LOGIN_USER_KEY);
|
||||||
SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
|
SaHolder.getStorage().set(LOGIN_USER_KEY, loginUser);
|
||||||
return loginUser;
|
return loginUser;
|
||||||
}
|
}
|
||||||
@ -75,7 +88,11 @@ public class LoginHelper {
|
|||||||
* 获取用户基于token
|
* 获取用户基于token
|
||||||
*/
|
*/
|
||||||
public static LoginUser getLoginUser(String token) {
|
public static LoginUser getLoginUser(String token) {
|
||||||
return (LoginUser) StpUtil.getTokenSessionByToken(token).get(LOGIN_USER_KEY);
|
SaSession session = StpUtil.getTokenSessionByToken(token);
|
||||||
|
if (ObjectUtil.isNull(session)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (LoginUser) session.get(LOGIN_USER_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,8 +130,8 @@ public class LoginHelper {
|
|||||||
* 获取用户类型
|
* 获取用户类型
|
||||||
*/
|
*/
|
||||||
public static UserType getUserType() {
|
public static UserType getUserType() {
|
||||||
String loginId = StpUtil.getLoginIdAsString();
|
String loginType = StpUtil.getLoginIdAsString();
|
||||||
return UserType.getUserType(loginId);
|
return UserType.getUserType(loginType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,11 +17,10 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* bean深拷贝工具(基于 cglib 性能优异)
|
* bean拷贝工具(基于 cglib 性能优异)
|
||||||
* <p>
|
* <p>
|
||||||
* 重点 cglib 不支持 拷贝到链式对象
|
* 重点 cglib 不支持 拷贝到链式对象
|
||||||
* 例如: 源对象 拷贝到 目标(链式对象)
|
* 例如: 源对象 拷贝到 目标(链式对象)
|
||||||
* 请区分好`浅拷贝`和`深拷贝`再做使用
|
|
||||||
*
|
*
|
||||||
* @author Lion Li
|
* @author Lion Li
|
||||||
*/
|
*/
|
||||||
|
@ -67,6 +67,25 @@ public class EncryptUtils {
|
|||||||
return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8);
|
return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES加密
|
||||||
|
*
|
||||||
|
* @param data 待解密数据
|
||||||
|
* @param password 秘钥字符串
|
||||||
|
* @return 加密后字符串, 采用Hex编码
|
||||||
|
*/
|
||||||
|
public static String encryptByAesHex(String data, String password) {
|
||||||
|
if (StrUtil.isBlank(password)) {
|
||||||
|
throw new IllegalArgumentException("AES需要传入秘钥信息");
|
||||||
|
}
|
||||||
|
// aes算法的秘钥要求是16位、24位、32位
|
||||||
|
int[] array = {16, 24, 32};
|
||||||
|
if (!ArrayUtil.contains(array, password.length())) {
|
||||||
|
throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位");
|
||||||
|
}
|
||||||
|
return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).encryptHex(data, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AES解密
|
* AES解密
|
||||||
*
|
*
|
||||||
@ -105,6 +124,25 @@ public class EncryptUtils {
|
|||||||
return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8);
|
return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sm4加密
|
||||||
|
*
|
||||||
|
* @param data 待加密数据
|
||||||
|
* @param password 秘钥字符串
|
||||||
|
* @return 加密后字符串, 采用Base64编码
|
||||||
|
*/
|
||||||
|
public static String encryptBySm4Hex(String data, String password) {
|
||||||
|
if (StrUtil.isBlank(password)) {
|
||||||
|
throw new IllegalArgumentException("SM4需要传入秘钥信息");
|
||||||
|
}
|
||||||
|
// sm4算法的秘钥要求是16位长度
|
||||||
|
int sm4PasswordLength = 16;
|
||||||
|
if (sm4PasswordLength != password.length()) {
|
||||||
|
throw new IllegalArgumentException("SM4秘钥长度要求为16位");
|
||||||
|
}
|
||||||
|
return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).encryptHex(data, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sm4解密
|
* sm4解密
|
||||||
*
|
*
|
||||||
@ -152,6 +190,21 @@ public class EncryptUtils {
|
|||||||
return sm2.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
|
return sm2.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sm2公钥加密
|
||||||
|
*
|
||||||
|
* @param data 待加密数据
|
||||||
|
* @param publicKey 公钥
|
||||||
|
* @return 加密后字符串, 采用Hex编码
|
||||||
|
*/
|
||||||
|
public static String encryptBySm2Hex(String data, String publicKey) {
|
||||||
|
if (StrUtil.isBlank(publicKey)) {
|
||||||
|
throw new IllegalArgumentException("SM2需要传入公钥进行加密");
|
||||||
|
}
|
||||||
|
SM2 sm2 = SmUtil.sm2(null, publicKey);
|
||||||
|
return sm2.encryptHex(data, StandardCharsets.UTF_8, KeyType.PublicKey);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sm2私钥解密
|
* sm2私钥解密
|
||||||
*
|
*
|
||||||
@ -195,6 +248,21 @@ public class EncryptUtils {
|
|||||||
return rsa.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
|
return rsa.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* rsa公钥加密
|
||||||
|
*
|
||||||
|
* @param data 待加密数据
|
||||||
|
* @param publicKey 公钥
|
||||||
|
* @return 加密后字符串, 采用Hex编码
|
||||||
|
*/
|
||||||
|
public static String encryptByRsaHex(String data, String publicKey) {
|
||||||
|
if (StrUtil.isBlank(publicKey)) {
|
||||||
|
throw new IllegalArgumentException("RSA需要传入公钥进行加密");
|
||||||
|
}
|
||||||
|
RSA rsa = SecureUtil.rsa(null, publicKey);
|
||||||
|
return rsa.encryptHex(data, StandardCharsets.UTF_8, KeyType.PublicKey);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* rsa私钥解密
|
* rsa私钥解密
|
||||||
*
|
*
|
||||||
|
@ -70,7 +70,7 @@ public class StreamUtils {
|
|||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return CollUtil.newArrayList();
|
return CollUtil.newArrayList();
|
||||||
}
|
}
|
||||||
return collection.stream().sorted(comparing).collect(Collectors.toList());
|
return collection.stream().filter(Objects::nonNull).sorted(comparing).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -87,7 +87,7 @@ public class StreamUtils {
|
|||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return MapUtil.newHashMap();
|
return MapUtil.newHashMap();
|
||||||
}
|
}
|
||||||
return collection.stream().collect(Collectors.toMap(key, Function.identity(), (l, r) -> l));
|
return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, Function.identity(), (l, r) -> l));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -106,7 +106,7 @@ public class StreamUtils {
|
|||||||
if (CollUtil.isEmpty(collection)) {
|
if (CollUtil.isEmpty(collection)) {
|
||||||
return MapUtil.newHashMap();
|
return MapUtil.newHashMap();
|
||||||
}
|
}
|
||||||
return collection.stream().collect(Collectors.toMap(key, value, (l, r) -> l));
|
return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, value, (l, r) -> l));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -124,7 +124,7 @@ public class StreamUtils {
|
|||||||
return MapUtil.newHashMap();
|
return MapUtil.newHashMap();
|
||||||
}
|
}
|
||||||
return collection
|
return collection
|
||||||
.stream()
|
.stream().filter(Objects::nonNull)
|
||||||
.collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList()));
|
.collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +145,7 @@ public class StreamUtils {
|
|||||||
return MapUtil.newHashMap();
|
return MapUtil.newHashMap();
|
||||||
}
|
}
|
||||||
return collection
|
return collection
|
||||||
.stream()
|
.stream().filter(Objects::nonNull)
|
||||||
.collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.groupingBy(key2, LinkedHashMap::new, Collectors.toList())));
|
.collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.groupingBy(key2, LinkedHashMap::new, Collectors.toList())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +166,7 @@ public class StreamUtils {
|
|||||||
return MapUtil.newHashMap();
|
return MapUtil.newHashMap();
|
||||||
}
|
}
|
||||||
return collection
|
return collection
|
||||||
.stream()
|
.stream().filter(Objects::nonNull)
|
||||||
.collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.toMap(key2, Function.identity(), (l, r) -> l)));
|
.collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.toMap(key2, Function.identity(), (l, r) -> l)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ public class AddressUtils {
|
|||||||
return UNKNOWN;
|
return UNKNOWN;
|
||||||
}
|
}
|
||||||
// 内网不查询
|
// 内网不查询
|
||||||
ip = "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
|
ip = StringUtils.contains(ip, "0:0:0:0:0:0:0:1") ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
|
||||||
if (NetUtil.isInnerIP(ip)) {
|
if (NetUtil.isInnerIP(ip)) {
|
||||||
return "内网IP";
|
return "内网IP";
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,7 @@ import com.alibaba.excel.write.metadata.fill.FillConfig;
|
|||||||
import com.alibaba.excel.write.metadata.fill.FillWrapper;
|
import com.alibaba.excel.write.metadata.fill.FillWrapper;
|
||||||
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
||||||
import com.ruoyi.common.convert.ExcelBigNumberConvert;
|
import com.ruoyi.common.convert.ExcelBigNumberConvert;
|
||||||
import com.ruoyi.common.excel.CellMergeStrategy;
|
import com.ruoyi.common.excel.*;
|
||||||
import com.ruoyi.common.excel.DefaultExcelListener;
|
|
||||||
import com.ruoyi.common.excel.ExcelListener;
|
|
||||||
import com.ruoyi.common.excel.ExcelResult;
|
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import com.ruoyi.common.utils.file.FileUtils;
|
import com.ruoyi.common.utils.file.FileUtils;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
@ -88,7 +85,26 @@ public class ExcelUtil {
|
|||||||
try {
|
try {
|
||||||
resetResponse(sheetName, response);
|
resetResponse(sheetName, response);
|
||||||
ServletOutputStream os = response.getOutputStream();
|
ServletOutputStream os = response.getOutputStream();
|
||||||
exportExcel(list, sheetName, clazz, false, os);
|
exportExcel(list, sheetName, clazz, false, os, null);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("导出Excel异常");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出excel
|
||||||
|
*
|
||||||
|
* @param list 导出数据集合
|
||||||
|
* @param sheetName 工作表的名称
|
||||||
|
* @param clazz 实体类
|
||||||
|
* @param response 响应体
|
||||||
|
* @param options 级联下拉选
|
||||||
|
*/
|
||||||
|
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response, List<DropDownOptions> options) {
|
||||||
|
try {
|
||||||
|
resetResponse(sheetName, response);
|
||||||
|
ServletOutputStream os = response.getOutputStream();
|
||||||
|
exportExcel(list, sheetName, clazz, false, os, options);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException("导出Excel异常");
|
throw new RuntimeException("导出Excel异常");
|
||||||
}
|
}
|
||||||
@ -107,7 +123,27 @@ public class ExcelUtil {
|
|||||||
try {
|
try {
|
||||||
resetResponse(sheetName, response);
|
resetResponse(sheetName, response);
|
||||||
ServletOutputStream os = response.getOutputStream();
|
ServletOutputStream os = response.getOutputStream();
|
||||||
exportExcel(list, sheetName, clazz, merge, os);
|
exportExcel(list, sheetName, clazz, merge, os, null);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("导出Excel异常");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出excel
|
||||||
|
*
|
||||||
|
* @param list 导出数据集合
|
||||||
|
* @param sheetName 工作表的名称
|
||||||
|
* @param clazz 实体类
|
||||||
|
* @param merge 是否合并单元格
|
||||||
|
* @param response 响应体
|
||||||
|
* @param options 级联下拉选
|
||||||
|
*/
|
||||||
|
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge, HttpServletResponse response, List<DropDownOptions> options) {
|
||||||
|
try {
|
||||||
|
resetResponse(sheetName, response);
|
||||||
|
ServletOutputStream os = response.getOutputStream();
|
||||||
|
exportExcel(list, sheetName, clazz, merge, os, options);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException("导出Excel异常");
|
throw new RuntimeException("导出Excel异常");
|
||||||
}
|
}
|
||||||
@ -122,7 +158,20 @@ public class ExcelUtil {
|
|||||||
* @param os 输出流
|
* @param os 输出流
|
||||||
*/
|
*/
|
||||||
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, OutputStream os) {
|
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, OutputStream os) {
|
||||||
exportExcel(list, sheetName, clazz, false, os);
|
exportExcel(list, sheetName, clazz, false, os, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出excel
|
||||||
|
*
|
||||||
|
* @param list 导出数据集合
|
||||||
|
* @param sheetName 工作表的名称
|
||||||
|
* @param clazz 实体类
|
||||||
|
* @param os 输出流
|
||||||
|
* @param options 级联下拉选内容
|
||||||
|
*/
|
||||||
|
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, OutputStream os, List<DropDownOptions> options) {
|
||||||
|
exportExcel(list, sheetName, clazz, false, os, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -134,7 +183,8 @@ public class ExcelUtil {
|
|||||||
* @param merge 是否合并单元格
|
* @param merge 是否合并单元格
|
||||||
* @param os 输出流
|
* @param os 输出流
|
||||||
*/
|
*/
|
||||||
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge, OutputStream os) {
|
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge,
|
||||||
|
OutputStream os, List<DropDownOptions> options) {
|
||||||
ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
|
ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
|
||||||
.autoCloseStream(false)
|
.autoCloseStream(false)
|
||||||
// 自动适配
|
// 自动适配
|
||||||
@ -146,6 +196,8 @@ public class ExcelUtil {
|
|||||||
// 合并处理器
|
// 合并处理器
|
||||||
builder.registerWriteHandler(new CellMergeStrategy(list, true));
|
builder.registerWriteHandler(new CellMergeStrategy(list, true));
|
||||||
}
|
}
|
||||||
|
// 添加下拉框操作
|
||||||
|
builder.registerWriteHandler(new ExcelDownHandler(options));
|
||||||
builder.doWrite(list);
|
builder.doWrite(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,6 +131,32 @@ public class QueueUtils {
|
|||||||
return priorityBlockingQueue.offer(data);
|
return priorityBlockingQueue.offer(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先队列获取一个队列数据 没有数据返回 null(不支持延迟队列)
|
||||||
|
*
|
||||||
|
* @param queueName 队列名
|
||||||
|
*/
|
||||||
|
public static <T> T getPriorityQueueObject(String queueName) {
|
||||||
|
RPriorityBlockingQueue<T> queue = CLIENT.getPriorityBlockingQueue(queueName);
|
||||||
|
return queue.poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先队列删除队列数据(不支持延迟队列)
|
||||||
|
*/
|
||||||
|
public static <T> boolean removePriorityQueueObject(String queueName, T data) {
|
||||||
|
RPriorityBlockingQueue<T> queue = CLIENT.getPriorityBlockingQueue(queueName);
|
||||||
|
return queue.remove(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先队列销毁队列 所有阻塞监听 报错(不支持延迟队列)
|
||||||
|
*/
|
||||||
|
public static <T> boolean destroyPriorityQueue(String queueName) {
|
||||||
|
RPriorityBlockingQueue<T> queue = CLIENT.getPriorityBlockingQueue(queueName);
|
||||||
|
return queue.delete();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 尝试设置 有界队列 容量 用于限制数量
|
* 尝试设置 有界队列 容量 用于限制数量
|
||||||
*
|
*
|
||||||
@ -169,11 +195,41 @@ public class QueueUtils {
|
|||||||
return boundedBlockingQueue.offer(data);
|
return boundedBlockingQueue.offer(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有界队列获取一个队列数据 没有数据返回 null(不支持延迟队列)
|
||||||
|
*
|
||||||
|
* @param queueName 队列名
|
||||||
|
*/
|
||||||
|
public static <T> T getBoundedQueueObject(String queueName) {
|
||||||
|
RBoundedBlockingQueue<T> queue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||||
|
return queue.poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有界队列删除队列数据(不支持延迟队列)
|
||||||
|
*/
|
||||||
|
public static <T> boolean removeBoundedQueueObject(String queueName, T data) {
|
||||||
|
RBoundedBlockingQueue<T> queue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||||
|
return queue.remove(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有界队列销毁队列 所有阻塞监听 报错(不支持延迟队列)
|
||||||
|
*/
|
||||||
|
public static <T> boolean destroyBoundedQueue(String queueName) {
|
||||||
|
RBoundedBlockingQueue<T> queue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||||
|
return queue.delete();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订阅阻塞队列(可订阅所有实现类 例如: 延迟 优先 有界 等)
|
* 订阅阻塞队列(可订阅所有实现类 例如: 延迟 优先 有界 等)
|
||||||
*/
|
*/
|
||||||
public static <T> void subscribeBlockingQueue(String queueName, Consumer<T> consumer) {
|
public static <T> void subscribeBlockingQueue(String queueName, Consumer<T> consumer, boolean isDelayed) {
|
||||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||||
|
if (isDelayed) {
|
||||||
|
// 订阅延迟队列
|
||||||
|
CLIENT.getDelayedQueue(queue);
|
||||||
|
}
|
||||||
queue.subscribeOnElements(consumer);
|
queue.subscribeOnElements(consumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,6 +129,18 @@ public class RedisUtils {
|
|||||||
batch.execute();
|
batch.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果不存在则设置 并返回 true 如果存在则返回 false
|
||||||
|
*
|
||||||
|
* @param key 缓存的键值
|
||||||
|
* @param value 缓存的值
|
||||||
|
* @return set成功或失败
|
||||||
|
*/
|
||||||
|
public static <T> boolean setObjectIfAbsent(final String key, final T value, final Duration duration) {
|
||||||
|
RBucket<T> bucket = CLIENT.getBucket(key);
|
||||||
|
return bucket.setIfAbsent(value, duration);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注册对象监听器
|
* 注册对象监听器
|
||||||
* <p>
|
* <p>
|
||||||
@ -374,6 +386,21 @@ public class RedisUtils {
|
|||||||
return rMap.remove(hKey);
|
return rMap.remove(hKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除Hash中的数据
|
||||||
|
*
|
||||||
|
* @param key Redis键
|
||||||
|
* @param hKeys Hash键
|
||||||
|
*/
|
||||||
|
public static <T> void delMultiCacheMapValue(final String key, final Set<String> hKeys) {
|
||||||
|
RBatch batch = CLIENT.createBatch();
|
||||||
|
RMapAsync<String, T> rMap = batch.getMap(key);
|
||||||
|
for (String hKey : hKeys) {
|
||||||
|
rMap.removeAsync(hKey);
|
||||||
|
}
|
||||||
|
batch.execute();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取多个Hash中的数据
|
* 获取多个Hash中的数据
|
||||||
*
|
*
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>ruoyi-vue-plus</artifactId>
|
<artifactId>ruoyi-vue-plus</artifactId>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<version>4.7.0</version>
|
<version>4.8.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
@ -28,17 +28,6 @@
|
|||||||
<artifactId>ruoyi-sms</artifactId>
|
<artifactId>ruoyi-sms</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 短信 用哪个导入哪个依赖 -->
|
|
||||||
<!-- <dependency>-->
|
|
||||||
<!-- <groupId>com.aliyun</groupId>-->
|
|
||||||
<!-- <artifactId>dysmsapi20170525</artifactId>-->
|
|
||||||
<!-- </dependency>-->
|
|
||||||
|
|
||||||
<!-- <dependency>-->
|
|
||||||
<!-- <groupId>com.tencentcloudapi</groupId>-->
|
|
||||||
<!-- <artifactId>tencentcloud-sdk-java-sms</artifactId>-->
|
|
||||||
<!-- </dependency>-->
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
@ -34,7 +34,7 @@ public class RedisCacheController {
|
|||||||
* 如果没有,就调用方法,然后把结果缓存起来
|
* 如果没有,就调用方法,然后把结果缓存起来
|
||||||
* 这个注解「一般用在查询方法上」
|
* 这个注解「一般用在查询方法上」
|
||||||
* <p>
|
* <p>
|
||||||
* 重点说明: 缓存注解严谨与其他筛选数据功能一起使用
|
* 重点说明: 缓存注解严禁与其他筛选数据功能一起使用
|
||||||
* 例如: 数据权限注解 会造成 缓存击穿 与 数据不一致问题
|
* 例如: 数据权限注解 会造成 缓存击穿 与 数据不一致问题
|
||||||
* <p>
|
* <p>
|
||||||
* cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数
|
* cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
package com.ruoyi.demo.controller;
|
package com.ruoyi.demo.controller;
|
||||||
|
|
||||||
import com.ruoyi.common.core.domain.R;
|
import com.ruoyi.common.core.domain.R;
|
||||||
import com.ruoyi.common.utils.spring.SpringUtils;
|
|
||||||
import com.ruoyi.sms.config.properties.SmsProperties;
|
|
||||||
import com.ruoyi.sms.core.SmsTemplate;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.dromara.sms4j.api.SmsBlend;
|
||||||
|
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||||
|
import org.dromara.sms4j.core.factory.SmsFactory;
|
||||||
|
import org.dromara.sms4j.provider.enumerate.SupplierType;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 短信演示案例
|
* 短信演示案例
|
||||||
@ -26,10 +26,6 @@ import java.util.Map;
|
|||||||
@RequestMapping("/demo/sms")
|
@RequestMapping("/demo/sms")
|
||||||
public class SmsController {
|
public class SmsController {
|
||||||
|
|
||||||
private final SmsProperties smsProperties;
|
|
||||||
// private final SmsTemplate smsTemplate; // 可以使用spring注入
|
|
||||||
// private final AliyunSmsTemplate smsTemplate; // 也可以注入某个厂家的模板工具
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送短信Aliyun
|
* 发送短信Aliyun
|
||||||
*
|
*
|
||||||
@ -38,17 +34,11 @@ public class SmsController {
|
|||||||
*/
|
*/
|
||||||
@GetMapping("/sendAliyun")
|
@GetMapping("/sendAliyun")
|
||||||
public R<Object> sendAliyun(String phones, String templateId) {
|
public R<Object> sendAliyun(String phones, String templateId) {
|
||||||
if (!smsProperties.getEnabled()) {
|
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
|
||||||
return R.fail("当前系统没有开启短信功能!");
|
|
||||||
}
|
|
||||||
if (!SpringUtils.containsBean("aliyunSmsTemplate")) {
|
|
||||||
return R.fail("阿里云依赖未引入!");
|
|
||||||
}
|
|
||||||
SmsTemplate smsTemplate = SpringUtils.getBean(SmsTemplate.class);
|
|
||||||
Map<String, String> map = new HashMap<>(1);
|
|
||||||
map.put("code", "1234");
|
map.put("code", "1234");
|
||||||
Object send = smsTemplate.send(phones, templateId, map);
|
SmsBlend smsBlend = SmsFactory.createSmsBlend(SupplierType.ALIBABA);
|
||||||
return R.ok(send);
|
SmsResponse smsResponse = smsBlend.sendMessage(phones, templateId, map);
|
||||||
|
return R.ok(smsResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -59,18 +49,12 @@ public class SmsController {
|
|||||||
*/
|
*/
|
||||||
@GetMapping("/sendTencent")
|
@GetMapping("/sendTencent")
|
||||||
public R<Object> sendTencent(String phones, String templateId) {
|
public R<Object> sendTencent(String phones, String templateId) {
|
||||||
if (!smsProperties.getEnabled()) {
|
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
|
||||||
return R.fail("当前系统没有开启短信功能!");
|
|
||||||
}
|
|
||||||
if (!SpringUtils.containsBean("tencentSmsTemplate")) {
|
|
||||||
return R.fail("腾讯云依赖未引入!");
|
|
||||||
}
|
|
||||||
SmsTemplate smsTemplate = SpringUtils.getBean(SmsTemplate.class);
|
|
||||||
Map<String, String> map = new HashMap<>(1);
|
|
||||||
// map.put("2", "测试测试");
|
// map.put("2", "测试测试");
|
||||||
map.put("1", "1234");
|
map.put("1", "1234");
|
||||||
Object send = smsTemplate.send(phones, templateId, map);
|
SmsBlend smsBlend = SmsFactory.createSmsBlend(SupplierType.TENCENT);
|
||||||
return R.ok(send);
|
SmsResponse smsResponse = smsBlend.sendMessage(phones, templateId, map);
|
||||||
|
return R.ok(smsResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
package com.ruoyi.demo.controller;
|
package com.ruoyi.demo.controller;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import com.ruoyi.common.excel.ExcelResult;
|
||||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||||
|
import com.ruoyi.demo.domain.vo.ExportDemoVo;
|
||||||
|
import com.ruoyi.demo.listener.ExportDemoListener;
|
||||||
|
import com.ruoyi.demo.service.IExportExcelService;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -20,9 +25,12 @@ import java.util.Map;
|
|||||||
* @author Lion Li
|
* @author Lion Li
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
@RequestMapping("/demo/excel")
|
@RequestMapping("/demo/excel")
|
||||||
public class TestExcelController {
|
public class TestExcelController {
|
||||||
|
|
||||||
|
private final IExportExcelService exportExcelService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单列表多数据
|
* 单列表多数据
|
||||||
*/
|
*/
|
||||||
@ -76,6 +84,26 @@ public class TestExcelController {
|
|||||||
ExcelUtil.exportTemplateMultiList(multiListMap, "多列表.xlsx", "excel/多列表.xlsx", response);
|
ExcelUtil.exportTemplateMultiList(multiListMap, "多列表.xlsx", "excel/多列表.xlsx", response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出下拉框
|
||||||
|
*
|
||||||
|
* @param response /
|
||||||
|
*/
|
||||||
|
@GetMapping("/exportWithOptions")
|
||||||
|
public void exportWithOptions(HttpServletResponse response) {
|
||||||
|
exportExcelService.exportWithOptions(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入表格
|
||||||
|
*/
|
||||||
|
@PostMapping(value = "/importWithOptions", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||||
|
public List<ExportDemoVo> importWithOptions(@RequestPart("file") MultipartFile file) throws Exception {
|
||||||
|
// 处理解析结果
|
||||||
|
ExcelResult<ExportDemoVo> excelResult = ExcelUtil.importExcel(file.getInputStream(), ExportDemoVo.class, new ExportDemoListener());
|
||||||
|
return excelResult.getList();
|
||||||
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
static class TestObj1 {
|
static class TestObj1 {
|
||||||
|
@ -35,7 +35,7 @@ public class BoundedQueueController {
|
|||||||
@GetMapping("/add")
|
@GetMapping("/add")
|
||||||
public R<Void> add(String queueName, int capacity) {
|
public R<Void> add(String queueName, int capacity) {
|
||||||
// 用完了一定要销毁 否则会一直存在
|
// 用完了一定要销毁 否则会一直存在
|
||||||
boolean b = QueueUtils.destroyQueue(queueName);
|
boolean b = QueueUtils.destroyBoundedQueue(queueName);
|
||||||
log.info("通道: {} , 删除: {}", queueName, b);
|
log.info("通道: {} , 删除: {}", queueName, b);
|
||||||
// 初始化设置一次即可
|
// 初始化设置一次即可
|
||||||
if (QueueUtils.trySetBoundedQueueCapacity(queueName, capacity)) {
|
if (QueueUtils.trySetBoundedQueueCapacity(queueName, capacity)) {
|
||||||
@ -64,7 +64,7 @@ public class BoundedQueueController {
|
|||||||
@GetMapping("/remove")
|
@GetMapping("/remove")
|
||||||
public R<Void> remove(String queueName) {
|
public R<Void> remove(String queueName) {
|
||||||
String data = "data-" + 5;
|
String data = "data-" + 5;
|
||||||
if (QueueUtils.removeQueueObject(queueName, data)) {
|
if (QueueUtils.removeBoundedQueueObject(queueName, data)) {
|
||||||
log.info("通道: {} , 删除数据: {}", queueName, data);
|
log.info("通道: {} , 删除数据: {}", queueName, data);
|
||||||
} else {
|
} else {
|
||||||
return R.fail("操作失败");
|
return R.fail("操作失败");
|
||||||
@ -81,7 +81,7 @@ public class BoundedQueueController {
|
|||||||
public R<Void> get(String queueName) {
|
public R<Void> get(String queueName) {
|
||||||
String data;
|
String data;
|
||||||
do {
|
do {
|
||||||
data = QueueUtils.getQueueObject(queueName);
|
data = QueueUtils.getBoundedQueueObject(queueName);
|
||||||
log.info("通道: {} , 获取数据: {}", queueName, data);
|
log.info("通道: {} , 获取数据: {}", queueName, data);
|
||||||
} while (data != null);
|
} while (data != null);
|
||||||
return R.ok("操作成功");
|
return R.ok("操作成功");
|
||||||
|
@ -40,7 +40,7 @@ public class DelayedQueueController {
|
|||||||
QueueUtils.subscribeBlockingQueue(queueName, (String orderNum) -> {
|
QueueUtils.subscribeBlockingQueue(queueName, (String orderNum) -> {
|
||||||
// 观察接收时间
|
// 观察接收时间
|
||||||
log.info("通道: {}, 收到数据: {}", queueName, orderNum);
|
log.info("通道: {}, 收到数据: {}", queueName, orderNum);
|
||||||
});
|
}, true);
|
||||||
return R.ok("操作成功");
|
return R.ok("操作成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ public class PriorityQueueController {
|
|||||||
@GetMapping("/add")
|
@GetMapping("/add")
|
||||||
public R<Void> add(String queueName) {
|
public R<Void> add(String queueName) {
|
||||||
// 用完了一定要销毁 否则会一直存在
|
// 用完了一定要销毁 否则会一直存在
|
||||||
boolean b = QueueUtils.destroyQueue(queueName);
|
boolean b = QueueUtils.destroyPriorityQueue(queueName);
|
||||||
log.info("通道: {} , 删除: {}", queueName, b);
|
log.info("通道: {} , 删除: {}", queueName, b);
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
@ -63,7 +63,7 @@ public class PriorityQueueController {
|
|||||||
PriorityDemo data = new PriorityDemo();
|
PriorityDemo data = new PriorityDemo();
|
||||||
data.setName(name);
|
data.setName(name);
|
||||||
data.setOrderNum(orderNum);
|
data.setOrderNum(orderNum);
|
||||||
if (QueueUtils.removeQueueObject(queueName, data)) {
|
if (QueueUtils.removePriorityQueueObject(queueName, data)) {
|
||||||
log.info("通道: {} , 删除数据: {}", queueName, data);
|
log.info("通道: {} , 删除数据: {}", queueName, data);
|
||||||
} else {
|
} else {
|
||||||
return R.fail("操作失败");
|
return R.fail("操作失败");
|
||||||
@ -80,7 +80,7 @@ public class PriorityQueueController {
|
|||||||
public R<Void> get(String queueName) {
|
public R<Void> get(String queueName) {
|
||||||
PriorityDemo data;
|
PriorityDemo data;
|
||||||
do {
|
do {
|
||||||
data = QueueUtils.getQueueObject(queueName);
|
data = QueueUtils.getPriorityQueueObject(queueName);
|
||||||
log.info("通道: {} , 获取数据: {}", queueName, data);
|
log.info("通道: {} , 获取数据: {}", queueName, data);
|
||||||
} while (data != null);
|
} while (data != null);
|
||||||
return R.ok("操作成功");
|
return R.ok("操作成功");
|
||||||
|
@ -0,0 +1,119 @@
|
|||||||
|
package com.ruoyi.demo.domain.vo;
|
||||||
|
|
||||||
|
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
|
import com.ruoyi.common.annotation.ExcelDictFormat;
|
||||||
|
import com.ruoyi.common.annotation.ExcelEnumFormat;
|
||||||
|
import com.ruoyi.common.convert.ExcelDictConvert;
|
||||||
|
import com.ruoyi.common.convert.ExcelEnumConvert;
|
||||||
|
import com.ruoyi.common.core.validate.AddGroup;
|
||||||
|
import com.ruoyi.common.core.validate.EditGroup;
|
||||||
|
import com.ruoyi.common.enums.UserStatus;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带有下拉选的Excel导出
|
||||||
|
*
|
||||||
|
* @author Emil.Zhang
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ExcelIgnoreUnannotated
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ExportDemoVo {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户昵称
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "用户名", index = 0)
|
||||||
|
@NotEmpty(message = "用户名不能为空", groups = AddGroup.class)
|
||||||
|
private String nickName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户类型
|
||||||
|
* </p>
|
||||||
|
* 使用ExcelEnumFormat注解需要进行下拉选的部分
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "用户类型", index = 1, converter = ExcelEnumConvert.class)
|
||||||
|
@ExcelEnumFormat(enumClass = UserStatus.class, textField = "info")
|
||||||
|
@NotEmpty(message = "用户类型不能为空", groups = AddGroup.class)
|
||||||
|
private String userStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 性别
|
||||||
|
* <p>
|
||||||
|
* 使用ExcelDictFormat注解需要进行下拉选的部分
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "性别", index = 2, converter = ExcelDictConvert.class)
|
||||||
|
@ExcelDictFormat(dictType = "sys_user_sex")
|
||||||
|
@NotEmpty(message = "性别不能为空", groups = AddGroup.class)
|
||||||
|
private String gender;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手机号
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "手机号", index = 3)
|
||||||
|
@NotEmpty(message = "手机号不能为空", groups = AddGroup.class)
|
||||||
|
private String phoneNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Email
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "Email", index = 4)
|
||||||
|
@NotEmpty(message = "Email不能为空", groups = AddGroup.class)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 省
|
||||||
|
* <p>
|
||||||
|
* 级联下拉,仅判断是否选了
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "省", index = 5)
|
||||||
|
@NotNull(message = "省不能为空", groups = AddGroup.class)
|
||||||
|
private String province;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库中的省ID
|
||||||
|
* </p>
|
||||||
|
* 处理完毕后再判断是否市正确的值
|
||||||
|
*/
|
||||||
|
@NotNull(message = "请勿手动输入", groups = EditGroup.class)
|
||||||
|
private Integer provinceId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 市
|
||||||
|
* <p>
|
||||||
|
* 级联下拉
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "市", index = 6)
|
||||||
|
@NotNull(message = "市不能为空", groups = AddGroup.class)
|
||||||
|
private String city;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库中的市ID
|
||||||
|
*/
|
||||||
|
@NotNull(message = "请勿手动输入", groups = EditGroup.class)
|
||||||
|
private Integer cityId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 县
|
||||||
|
* <p>
|
||||||
|
* 级联下拉
|
||||||
|
*/
|
||||||
|
@ExcelProperty(value = "县", index = 7)
|
||||||
|
@NotNull(message = "县不能为空", groups = AddGroup.class)
|
||||||
|
private String area;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库中的县ID
|
||||||
|
*/
|
||||||
|
@NotNull(message = "请勿手动输入", groups = EditGroup.class)
|
||||||
|
private Integer areaId;
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package com.ruoyi.demo.listener;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.NumberUtil;
|
||||||
|
import com.alibaba.excel.context.AnalysisContext;
|
||||||
|
import com.ruoyi.common.core.validate.AddGroup;
|
||||||
|
import com.ruoyi.common.core.validate.EditGroup;
|
||||||
|
import com.ruoyi.common.excel.DefaultExcelListener;
|
||||||
|
import com.ruoyi.common.excel.DropDownOptions;
|
||||||
|
import com.ruoyi.common.utils.ValidatorUtils;
|
||||||
|
import com.ruoyi.demo.domain.vo.ExportDemoVo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Excel带下拉框的解析处理器
|
||||||
|
*
|
||||||
|
* @author Emil.Zhang
|
||||||
|
*/
|
||||||
|
public class ExportDemoListener extends DefaultExcelListener<ExportDemoVo> {
|
||||||
|
|
||||||
|
public ExportDemoListener() {
|
||||||
|
// 显示使用构造函数,否则将导致空指针
|
||||||
|
super(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invoke(ExportDemoVo data, AnalysisContext context) {
|
||||||
|
// 先校验必填
|
||||||
|
ValidatorUtils.validate(data, AddGroup.class);
|
||||||
|
|
||||||
|
// 处理级联下拉的部分
|
||||||
|
String province = data.getProvince();
|
||||||
|
String city = data.getCity();
|
||||||
|
String area = data.getArea();
|
||||||
|
// 本行用户选择的省
|
||||||
|
List<String> thisRowSelectedProvinceOption = DropDownOptions.analyzeOptionValue(province);
|
||||||
|
if (thisRowSelectedProvinceOption.size() == 2) {
|
||||||
|
String provinceIdStr = thisRowSelectedProvinceOption.get(1);
|
||||||
|
if (NumberUtil.isNumber(provinceIdStr)) {
|
||||||
|
// 严格要求数据的话可以在这里做与数据库相关的判断
|
||||||
|
// 例如判断省信息是否在数据库中存在等,建议结合RedisCache做缓存10s,减少数据库调用
|
||||||
|
data.setProvinceId(Integer.parseInt(provinceIdStr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 本行用户选择的市
|
||||||
|
List<String> thisRowSelectedCityOption = DropDownOptions.analyzeOptionValue(city);
|
||||||
|
if (thisRowSelectedCityOption.size() == 2) {
|
||||||
|
String cityIdStr = thisRowSelectedCityOption.get(1);
|
||||||
|
if (NumberUtil.isNumber(cityIdStr)) {
|
||||||
|
data.setCityId(Integer.parseInt(cityIdStr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 本行用户选择的县
|
||||||
|
List<String> thisRowSelectedAreaOption = DropDownOptions.analyzeOptionValue(area);
|
||||||
|
if (thisRowSelectedAreaOption.size() == 2) {
|
||||||
|
String areaIdStr = thisRowSelectedAreaOption.get(1);
|
||||||
|
if (NumberUtil.isNumber(areaIdStr)) {
|
||||||
|
data.setAreaId(Integer.parseInt(areaIdStr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理完毕以后判断是否符合规则
|
||||||
|
ValidatorUtils.validate(data, EditGroup.class);
|
||||||
|
|
||||||
|
// 添加到处理结果中
|
||||||
|
getExcelResult().getList().add(data);
|
||||||
|
}
|
||||||
|
}
|
@ -33,7 +33,7 @@ public interface TestDemoMapper extends BaseMapperPlus<TestDemoMapper, TestDemo,
|
|||||||
@DataColumn(key = "deptName", value = "dept_id"),
|
@DataColumn(key = "deptName", value = "dept_id"),
|
||||||
@DataColumn(key = "userName", value = "user_id")
|
@DataColumn(key = "userName", value = "user_id")
|
||||||
})
|
})
|
||||||
<P extends IPage<TestDemo>> P selectPage(P page, @Param(Constants.WRAPPER) Wrapper<TestDemo> queryWrapper);
|
List<TestDemo> selectList(IPage<TestDemo> page, @Param(Constants.WRAPPER) Wrapper<TestDemo> queryWrapper);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@DataPermission({
|
@DataPermission({
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.ruoyi.demo.service;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出下拉框Excel示例
|
||||||
|
*
|
||||||
|
* @author Emil.Zhang
|
||||||
|
*/
|
||||||
|
public interface IExportExcelService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出下拉框
|
||||||
|
*
|
||||||
|
* @param response /
|
||||||
|
*/
|
||||||
|
void exportWithOptions(HttpServletResponse response);
|
||||||
|
}
|
@ -0,0 +1,223 @@
|
|||||||
|
package com.ruoyi.demo.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.ruoyi.common.enums.UserStatus;
|
||||||
|
import com.ruoyi.common.excel.DropDownOptions;
|
||||||
|
import com.ruoyi.common.utils.StreamUtils;
|
||||||
|
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||||
|
import com.ruoyi.demo.domain.vo.ExportDemoVo;
|
||||||
|
import com.ruoyi.demo.service.IExportExcelService;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出下拉框Excel示例
|
||||||
|
*
|
||||||
|
* @author Emil.Zhang
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ExportExcelServiceImpl implements IExportExcelService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exportWithOptions(HttpServletResponse response) {
|
||||||
|
// 创建表格数据,业务中一般通过数据库查询
|
||||||
|
List<ExportDemoVo> excelDataList = new ArrayList<>();
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
// 模拟数据库中的一条数据
|
||||||
|
ExportDemoVo everyRowData = new ExportDemoVo();
|
||||||
|
everyRowData.setNickName("用户-" + i);
|
||||||
|
everyRowData.setUserStatus(UserStatus.OK.getCode());
|
||||||
|
everyRowData.setGender("1");
|
||||||
|
everyRowData.setPhoneNumber(String.format("175%08d", i));
|
||||||
|
everyRowData.setEmail(String.format("175%08d", i) + "@163.com");
|
||||||
|
everyRowData.setProvinceId(i);
|
||||||
|
everyRowData.setCityId(i);
|
||||||
|
everyRowData.setAreaId(i);
|
||||||
|
excelDataList.add(everyRowData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过@ExcelIgnoreUnannotated配合@ExcelProperty合理显示需要的列
|
||||||
|
// 并通过@DropDown注解指定下拉值,或者通过创建ExcelOptions来指定下拉框
|
||||||
|
// 使用ExcelOptions时建议指定列index,防止出现下拉列解析不对齐
|
||||||
|
|
||||||
|
// 首先从数据库中查询下拉框内的可选项
|
||||||
|
// 这里模拟查询结果
|
||||||
|
List<DemoCityData> provinceList = getProvinceList(),
|
||||||
|
cityList = getCityList(provinceList),
|
||||||
|
areaList = getAreaList(cityList);
|
||||||
|
int provinceIndex = 5, cityIndex = 6, areaIndex = 7;
|
||||||
|
|
||||||
|
DropDownOptions provinceToCity = DropDownOptions.buildLinkedOptions(
|
||||||
|
provinceList,
|
||||||
|
provinceIndex,
|
||||||
|
cityList,
|
||||||
|
cityIndex,
|
||||||
|
DemoCityData::getId,
|
||||||
|
DemoCityData::getPid,
|
||||||
|
everyOptions -> DropDownOptions.createOptionValue(
|
||||||
|
everyOptions.getName(),
|
||||||
|
everyOptions.getId()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
DropDownOptions cityToArea = DropDownOptions.buildLinkedOptions(
|
||||||
|
cityList,
|
||||||
|
cityIndex,
|
||||||
|
areaList,
|
||||||
|
areaIndex,
|
||||||
|
DemoCityData::getId,
|
||||||
|
DemoCityData::getPid,
|
||||||
|
everyOptions -> DropDownOptions.createOptionValue(
|
||||||
|
everyOptions.getName(),
|
||||||
|
everyOptions.getId()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 把所有的下拉框存储
|
||||||
|
List<DropDownOptions> options = new ArrayList<>();
|
||||||
|
options.add(provinceToCity);
|
||||||
|
options.add(cityToArea);
|
||||||
|
|
||||||
|
// 到此为止所有的下拉框可选项已全部配置完毕
|
||||||
|
|
||||||
|
// 接下来需要将Excel中的展示数据转换为对应的下拉选
|
||||||
|
List<ExportDemoVo> outList = StreamUtils.toList(excelDataList, everyRowData -> {
|
||||||
|
// 只需要处理没有使用@ExcelDictFormat注解的下拉框
|
||||||
|
// 一般来说,可以直接在数据库查询即查询出省市县信息,这里通过模拟操作赋值
|
||||||
|
everyRowData.setProvince(buildOptions(provinceList, everyRowData.getProvinceId()));
|
||||||
|
everyRowData.setCity(buildOptions(cityList, everyRowData.getCityId()));
|
||||||
|
everyRowData.setArea(buildOptions(areaList, everyRowData.getAreaId()));
|
||||||
|
return everyRowData;
|
||||||
|
});
|
||||||
|
|
||||||
|
ExcelUtil.exportExcel(outList, "下拉框示例", ExportDemoVo.class, response, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildOptions(List<DemoCityData> cityDataList, Integer id) {
|
||||||
|
Map<Integer, List<DemoCityData>> groupByIdMap =
|
||||||
|
cityDataList.stream().collect(Collectors.groupingBy(DemoCityData::getId));
|
||||||
|
if (groupByIdMap.containsKey(id)) {
|
||||||
|
DemoCityData demoCityData = groupByIdMap.get(id).get(0);
|
||||||
|
return DropDownOptions.createOptionValue(demoCityData.getName(), demoCityData.getId());
|
||||||
|
} else {
|
||||||
|
return StrUtil.EMPTY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模拟查询数据库操作
|
||||||
|
*
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
private List<DemoCityData> getProvinceList() {
|
||||||
|
List<DemoCityData> provinceList = new ArrayList<>();
|
||||||
|
|
||||||
|
// 实际业务中一般采用数据库读取的形式,这里直接拼接创建
|
||||||
|
provinceList.add(new DemoCityData(0, null, "安徽省"));
|
||||||
|
provinceList.add(new DemoCityData(1, null, "江苏省"));
|
||||||
|
|
||||||
|
return provinceList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模拟查找数据库操作,需要连带查询出省的数据
|
||||||
|
*
|
||||||
|
* @param provinceList 模拟的父省数据
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
private List<DemoCityData> getCityList(List<DemoCityData> provinceList) {
|
||||||
|
List<DemoCityData> cityList = new ArrayList<>();
|
||||||
|
|
||||||
|
// 实际业务中一般采用数据库读取的形式,这里直接拼接创建
|
||||||
|
cityList.add(new DemoCityData(0, 0, "合肥市"));
|
||||||
|
cityList.add(new DemoCityData(1, 0, "芜湖市"));
|
||||||
|
cityList.add(new DemoCityData(2, 1, "南京市"));
|
||||||
|
cityList.add(new DemoCityData(3, 1, "无锡市"));
|
||||||
|
cityList.add(new DemoCityData(4, 1, "徐州市"));
|
||||||
|
|
||||||
|
selectParentData(provinceList, cityList);
|
||||||
|
|
||||||
|
return cityList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模拟查找数据库操作,需要连带查询出市的数据
|
||||||
|
*
|
||||||
|
* @param cityList 模拟的父市数据
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
private List<DemoCityData> getAreaList(List<DemoCityData> cityList) {
|
||||||
|
List<DemoCityData> areaList = new ArrayList<>();
|
||||||
|
|
||||||
|
// 实际业务中一般采用数据库读取的形式,这里直接拼接创建
|
||||||
|
areaList.add(new DemoCityData(0, 0, "瑶海区"));
|
||||||
|
areaList.add(new DemoCityData(1, 0, "庐江区"));
|
||||||
|
areaList.add(new DemoCityData(2, 1, "南宁县"));
|
||||||
|
areaList.add(new DemoCityData(3, 1, "镜湖区"));
|
||||||
|
areaList.add(new DemoCityData(4, 2, "玄武区"));
|
||||||
|
areaList.add(new DemoCityData(5, 2, "秦淮区"));
|
||||||
|
areaList.add(new DemoCityData(6, 3, "宜兴市"));
|
||||||
|
areaList.add(new DemoCityData(7, 3, "新吴区"));
|
||||||
|
areaList.add(new DemoCityData(8, 4, "鼓楼区"));
|
||||||
|
areaList.add(new DemoCityData(9, 4, "丰县"));
|
||||||
|
|
||||||
|
selectParentData(cityList, areaList);
|
||||||
|
|
||||||
|
return areaList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模拟数据库的查询父数据操作
|
||||||
|
*
|
||||||
|
* @param parentList /
|
||||||
|
* @param sonList /
|
||||||
|
*/
|
||||||
|
private void selectParentData(List<DemoCityData> parentList, List<DemoCityData> sonList) {
|
||||||
|
Map<Integer, List<DemoCityData>> parentGroupByIdMap =
|
||||||
|
parentList.stream().collect(Collectors.groupingBy(DemoCityData::getId));
|
||||||
|
|
||||||
|
sonList.forEach(everySon -> {
|
||||||
|
if (parentGroupByIdMap.containsKey(everySon.getPid())) {
|
||||||
|
everySon.setPData(parentGroupByIdMap.get(everySon.getPid()).get(0));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模拟的数据库省市县
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
private static class DemoCityData {
|
||||||
|
/**
|
||||||
|
* 数据库id字段
|
||||||
|
*/
|
||||||
|
private Integer id;
|
||||||
|
/**
|
||||||
|
* 数据库pid字段
|
||||||
|
*/
|
||||||
|
private Integer pid;
|
||||||
|
/**
|
||||||
|
* 数据库name字段
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
/**
|
||||||
|
* MyBatisPlus连带查询父数据
|
||||||
|
*/
|
||||||
|
private DemoCityData pData;
|
||||||
|
|
||||||
|
public DemoCityData(Integer id, Integer pid, String name) {
|
||||||
|
this.id = id;
|
||||||
|
this.pid = pid;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>ruoyi-vue-plus</artifactId>
|
<artifactId>ruoyi-vue-plus</artifactId>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<version>4.7.0</version>
|
<version>4.8.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>ruoyi-extend</artifactId>
|
<artifactId>ruoyi-extend</artifactId>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>ruoyi-extend</artifactId>
|
<artifactId>ruoyi-extend</artifactId>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<version>4.7.0</version>
|
<version>4.8.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>ruoyi-extend</artifactId>
|
<artifactId>ruoyi-extend</artifactId>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<version>4.7.0</version>
|
<version>4.8.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>ruoyi-xxl-job-admin</artifactId>
|
<artifactId>ruoyi-xxl-job-admin</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>ruoyi-vue-plus</artifactId>
|
<artifactId>ruoyi-vue-plus</artifactId>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<version>4.7.0</version>
|
<version>4.8.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
@ -2,9 +2,11 @@ package com.ruoyi.framework.aspectj;
|
|||||||
|
|
||||||
import cn.hutool.core.lang.Dict;
|
import cn.hutool.core.lang.Dict;
|
||||||
import cn.hutool.core.map.MapUtil;
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.ruoyi.common.annotation.Log;
|
import com.ruoyi.common.annotation.Log;
|
||||||
import com.ruoyi.common.core.domain.event.OperLogEvent;
|
import com.ruoyi.common.core.domain.event.OperLogEvent;
|
||||||
|
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||||
import com.ruoyi.common.enums.BusinessStatus;
|
import com.ruoyi.common.enums.BusinessStatus;
|
||||||
import com.ruoyi.common.enums.HttpMethod;
|
import com.ruoyi.common.enums.HttpMethod;
|
||||||
import com.ruoyi.common.helper.LoginHelper;
|
import com.ruoyi.common.helper.LoginHelper;
|
||||||
@ -25,6 +27,7 @@ import javax.servlet.http.HttpServletRequest;
|
|||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.StringJoiner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 操作日志记录处理
|
* 操作日志记录处理
|
||||||
@ -72,7 +75,9 @@ public class LogAspect {
|
|||||||
String ip = ServletUtils.getClientIP();
|
String ip = ServletUtils.getClientIP();
|
||||||
operLog.setOperIp(ip);
|
operLog.setOperIp(ip);
|
||||||
operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
|
operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
|
||||||
operLog.setOperName(LoginHelper.getUsername());
|
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||||
|
operLog.setOperName(loginUser.getUsername());
|
||||||
|
operLog.setDeptName(loginUser.getDeptName());
|
||||||
|
|
||||||
if (e != null) {
|
if (e != null) {
|
||||||
operLog.setStatus(BusinessStatus.FAIL.ordinal());
|
operLog.setStatus(BusinessStatus.FAIL.ordinal());
|
||||||
@ -144,26 +149,23 @@ public class LogAspect {
|
|||||||
* 参数拼装
|
* 参数拼装
|
||||||
*/
|
*/
|
||||||
private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) {
|
private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) {
|
||||||
StringBuilder params = new StringBuilder();
|
StringJoiner params = new StringJoiner(" ");
|
||||||
if (paramsArray != null && paramsArray.length > 0) {
|
if (ArrayUtil.isEmpty(paramsArray)) {
|
||||||
for (Object o : paramsArray) {
|
return params.toString();
|
||||||
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
|
}
|
||||||
try {
|
for (Object o : paramsArray) {
|
||||||
String str = JsonUtils.toJsonString(o);
|
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
|
||||||
Dict dict = JsonUtils.parseMap(str);
|
String str = JsonUtils.toJsonString(o);
|
||||||
if (MapUtil.isNotEmpty(dict)) {
|
Dict dict = JsonUtils.parseMap(str);
|
||||||
MapUtil.removeAny(dict, EXCLUDE_PROPERTIES);
|
if (MapUtil.isNotEmpty(dict)) {
|
||||||
MapUtil.removeAny(dict, excludeParamNames);
|
MapUtil.removeAny(dict, EXCLUDE_PROPERTIES);
|
||||||
str = JsonUtils.toJsonString(dict);
|
MapUtil.removeAny(dict, excludeParamNames);
|
||||||
}
|
str = JsonUtils.toJsonString(dict);
|
||||||
params.append(str).append(" ");
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
params.add(str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return params.toString().trim();
|
return params.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -184,9 +186,8 @@ public class LogAspect {
|
|||||||
}
|
}
|
||||||
} else if (Map.class.isAssignableFrom(clazz)) {
|
} else if (Map.class.isAssignableFrom(clazz)) {
|
||||||
Map map = (Map) o;
|
Map map = (Map) o;
|
||||||
for (Object value : map.entrySet()) {
|
for (Object value : map.values()) {
|
||||||
Map.Entry entry = (Map.Entry) value;
|
return value instanceof MultipartFile;
|
||||||
return entry.getValue() instanceof MultipartFile;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.ruoyi.framework.aspectj;
|
package com.ruoyi.framework.aspectj;
|
||||||
|
|
||||||
import cn.dev33.satoken.SaManager;
|
import cn.dev33.satoken.SaManager;
|
||||||
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.crypto.SecureUtil;
|
import cn.hutool.crypto.SecureUtil;
|
||||||
import com.ruoyi.common.annotation.RepeatSubmit;
|
import com.ruoyi.common.annotation.RepeatSubmit;
|
||||||
@ -12,8 +13,6 @@ import com.ruoyi.common.utils.MessageUtils;
|
|||||||
import com.ruoyi.common.utils.ServletUtils;
|
import com.ruoyi.common.utils.ServletUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import com.ruoyi.common.utils.redis.RedisUtils;
|
import com.ruoyi.common.utils.redis.RedisUtils;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.aspectj.lang.JoinPoint;
|
import org.aspectj.lang.JoinPoint;
|
||||||
import org.aspectj.lang.annotation.AfterReturning;
|
import org.aspectj.lang.annotation.AfterReturning;
|
||||||
import org.aspectj.lang.annotation.AfterThrowing;
|
import org.aspectj.lang.annotation.AfterThrowing;
|
||||||
@ -28,14 +27,13 @@ import javax.servlet.http.HttpServletResponse;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.StringJoiner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 防止重复提交(参考美团GTIS防重系统)
|
* 防止重复提交(参考美团GTIS防重系统)
|
||||||
*
|
*
|
||||||
* @author Lion Li
|
* @author Lion Li
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Aspect
|
@Aspect
|
||||||
@Component
|
@Component
|
||||||
public class RepeatSubmitAspect {
|
public class RepeatSubmitAspect {
|
||||||
@ -45,10 +43,8 @@ public class RepeatSubmitAspect {
|
|||||||
@Before("@annotation(repeatSubmit)")
|
@Before("@annotation(repeatSubmit)")
|
||||||
public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable {
|
public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable {
|
||||||
// 如果注解不为0 则使用注解数值
|
// 如果注解不为0 则使用注解数值
|
||||||
long interval = 0;
|
long interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval());
|
||||||
if (repeatSubmit.interval() > 0) {
|
|
||||||
interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval());
|
|
||||||
}
|
|
||||||
if (interval < 1000) {
|
if (interval < 1000) {
|
||||||
throw new ServiceException("重复提交间隔时间不能小于'1'秒");
|
throw new ServiceException("重复提交间隔时间不能小于'1'秒");
|
||||||
}
|
}
|
||||||
@ -64,9 +60,7 @@ public class RepeatSubmitAspect {
|
|||||||
submitKey = SecureUtil.md5(submitKey + ":" + nowParams);
|
submitKey = SecureUtil.md5(submitKey + ":" + nowParams);
|
||||||
// 唯一标识(指定key + url + 消息头)
|
// 唯一标识(指定key + url + 消息头)
|
||||||
String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY + url + submitKey;
|
String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY + url + submitKey;
|
||||||
String key = RedisUtils.getCacheObject(cacheRepeatKey);
|
if (RedisUtils.setObjectIfAbsent(cacheRepeatKey, "", Duration.ofMillis(interval))) {
|
||||||
if (key == null) {
|
|
||||||
RedisUtils.setCacheObject(cacheRepeatKey, "", Duration.ofMillis(interval));
|
|
||||||
KEY_CACHE.set(cacheRepeatKey);
|
KEY_CACHE.set(cacheRepeatKey);
|
||||||
} else {
|
} else {
|
||||||
String message = repeatSubmit.message();
|
String message = repeatSubmit.message();
|
||||||
@ -114,19 +108,16 @@ public class RepeatSubmitAspect {
|
|||||||
* 参数拼装
|
* 参数拼装
|
||||||
*/
|
*/
|
||||||
private String argsArrayToString(Object[] paramsArray) {
|
private String argsArrayToString(Object[] paramsArray) {
|
||||||
StringBuilder params = new StringBuilder();
|
StringJoiner params = new StringJoiner(" ");
|
||||||
if (paramsArray != null && paramsArray.length > 0) {
|
if (ArrayUtil.isEmpty(paramsArray)) {
|
||||||
for (Object o : paramsArray) {
|
return params.toString();
|
||||||
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
|
}
|
||||||
try {
|
for (Object o : paramsArray) {
|
||||||
params.append(JsonUtils.toJsonString(o)).append(" ");
|
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
|
||||||
} catch (Exception e) {
|
params.add(JsonUtils.toJsonString(o));
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return params.toString().trim();
|
return params.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -147,9 +138,8 @@ public class RepeatSubmitAspect {
|
|||||||
}
|
}
|
||||||
} else if (Map.class.isAssignableFrom(clazz)) {
|
} else if (Map.class.isAssignableFrom(clazz)) {
|
||||||
Map map = (Map) o;
|
Map map = (Map) o;
|
||||||
for (Object value : map.entrySet()) {
|
for (Object value : map.values()) {
|
||||||
Map.Entry entry = (Map.Entry) value;
|
return value instanceof MultipartFile;
|
||||||
return entry.getValue() instanceof MultipartFile;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|
||||||
|
@ -50,8 +50,8 @@ public class SaTokenConfig implements WebMvcConfigurer {
|
|||||||
|
|
||||||
// 有效率影响 用于临时测试
|
// 有效率影响 用于临时测试
|
||||||
// if (log.isDebugEnabled()) {
|
// if (log.isDebugEnabled()) {
|
||||||
// log.debug("剩余有效时间: {}", StpUtil.getTokenTimeout());
|
// log.info("剩余有效时间: {}", StpUtil.getTokenTimeout());
|
||||||
// log.debug("临时有效时间: {}", StpUtil.getTokenActivityTimeout());
|
// log.info("临时有效时间: {}", StpUtil.getTokenActiveTimeout());
|
||||||
// }
|
// }
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package com.ruoyi.framework.config;
|
package com.ruoyi.framework.config;
|
||||||
|
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import com.ruoyi.framework.config.properties.SwaggerProperties;
|
import com.ruoyi.framework.config.properties.SpringDocProperties;
|
||||||
import com.ruoyi.framework.handler.OpenApiHandler;
|
import com.ruoyi.framework.handler.OpenApiHandler;
|
||||||
import io.swagger.v3.oas.models.OpenAPI;
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
import io.swagger.v3.oas.models.Paths;
|
import io.swagger.v3.oas.models.Paths;
|
||||||
@ -34,25 +34,24 @@ import java.util.Set;
|
|||||||
@Configuration
|
@Configuration
|
||||||
@AutoConfigureBefore(SpringDocConfiguration.class)
|
@AutoConfigureBefore(SpringDocConfiguration.class)
|
||||||
@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true", matchIfMissing = true)
|
@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true", matchIfMissing = true)
|
||||||
public class SwaggerConfig {
|
public class SpringDocConfig {
|
||||||
|
|
||||||
private final SwaggerProperties swaggerProperties;
|
|
||||||
private final ServerProperties serverProperties;
|
private final ServerProperties serverProperties;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean(OpenAPI.class)
|
@ConditionalOnMissingBean(OpenAPI.class)
|
||||||
public OpenAPI openApi() {
|
public OpenAPI openApi(SpringDocProperties properties) {
|
||||||
OpenAPI openApi = new OpenAPI();
|
OpenAPI openApi = new OpenAPI();
|
||||||
// 文档基本信息
|
// 文档基本信息
|
||||||
SwaggerProperties.InfoProperties infoProperties = swaggerProperties.getInfo();
|
SpringDocProperties.InfoProperties infoProperties = properties.getInfo();
|
||||||
Info info = convertInfo(infoProperties);
|
Info info = convertInfo(infoProperties);
|
||||||
openApi.info(info);
|
openApi.info(info);
|
||||||
// 扩展文档信息
|
// 扩展文档信息
|
||||||
openApi.externalDocs(swaggerProperties.getExternalDocs());
|
openApi.externalDocs(properties.getExternalDocs());
|
||||||
openApi.tags(swaggerProperties.getTags());
|
openApi.tags(properties.getTags());
|
||||||
openApi.paths(swaggerProperties.getPaths());
|
openApi.paths(properties.getPaths());
|
||||||
openApi.components(swaggerProperties.getComponents());
|
openApi.components(properties.getComponents());
|
||||||
Set<String> keySet = swaggerProperties.getComponents().getSecuritySchemes().keySet();
|
Set<String> keySet = properties.getComponents().getSecuritySchemes().keySet();
|
||||||
List<SecurityRequirement> list = new ArrayList<>();
|
List<SecurityRequirement> list = new ArrayList<>();
|
||||||
SecurityRequirement securityRequirement = new SecurityRequirement();
|
SecurityRequirement securityRequirement = new SecurityRequirement();
|
||||||
keySet.forEach(securityRequirement::addList);
|
keySet.forEach(securityRequirement::addList);
|
||||||
@ -62,7 +61,7 @@ public class SwaggerConfig {
|
|||||||
return openApi;
|
return openApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Info convertInfo(SwaggerProperties.InfoProperties infoProperties) {
|
private Info convertInfo(SpringDocProperties.InfoProperties infoProperties) {
|
||||||
Info info = new Info();
|
Info info = new Info();
|
||||||
info.setTitle(infoProperties.getTitle());
|
info.setTitle(infoProperties.getTitle());
|
||||||
info.setDescription(infoProperties.getDescription());
|
info.setDescription(infoProperties.getDescription());
|
@ -20,8 +20,8 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Component
|
@Component
|
||||||
@ConfigurationProperties(prefix = "swagger")
|
@ConfigurationProperties(prefix = "springdoc")
|
||||||
public class SwaggerProperties {
|
public class SpringDocProperties {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文档基本信息
|
* 文档基本信息
|
@ -1,6 +1,7 @@
|
|||||||
package com.ruoyi.framework.encrypt;
|
package com.ruoyi.framework.encrypt;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.convert.Convert;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.ruoyi.common.annotation.EncryptField;
|
import com.ruoyi.common.annotation.EncryptField;
|
||||||
import com.ruoyi.common.encrypt.EncryptContext;
|
import com.ruoyi.common.encrypt.EncryptContext;
|
||||||
@ -76,7 +77,7 @@ public class MybatisDecryptInterceptor implements Interceptor {
|
|||||||
Set<Field> fields = encryptorManager.getFieldCache(sourceObject.getClass());
|
Set<Field> fields = encryptorManager.getFieldCache(sourceObject.getClass());
|
||||||
try {
|
try {
|
||||||
for (Field field : fields) {
|
for (Field field : fields) {
|
||||||
field.set(sourceObject, this.decryptField(String.valueOf(field.get(sourceObject)), field));
|
field.set(sourceObject, this.decryptField(Convert.toStr(field.get(sourceObject)), field));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("处理解密字段时出错", e);
|
log.error("处理解密字段时出错", e);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.ruoyi.framework.encrypt;
|
package com.ruoyi.framework.encrypt;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.convert.Convert;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.ruoyi.common.annotation.EncryptField;
|
import com.ruoyi.common.annotation.EncryptField;
|
||||||
import com.ruoyi.common.encrypt.EncryptContext;
|
import com.ruoyi.common.encrypt.EncryptContext;
|
||||||
@ -86,7 +87,7 @@ public class MybatisEncryptInterceptor implements Interceptor {
|
|||||||
Set<Field> fields = encryptorManager.getFieldCache(sourceObject.getClass());
|
Set<Field> fields = encryptorManager.getFieldCache(sourceObject.getClass());
|
||||||
try {
|
try {
|
||||||
for (Field field : fields) {
|
for (Field field : fields) {
|
||||||
field.set(sourceObject, this.encryptField(String.valueOf(field.get(sourceObject)), field));
|
field.set(sourceObject, this.encryptField(Convert.toStr(field.get(sourceObject)), field));
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("处理加密字段时出错", e);
|
log.error("处理加密字段时出错", e);
|
||||||
|
@ -2,8 +2,6 @@ package com.ruoyi.framework.handler;
|
|||||||
|
|
||||||
import cn.hutool.core.annotation.AnnotationUtil;
|
import cn.hutool.core.annotation.AnnotationUtil;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.collection.ConcurrentHashSet;
|
|
||||||
import cn.hutool.core.util.ArrayUtil;
|
|
||||||
import cn.hutool.core.util.ClassUtil;
|
import cn.hutool.core.util.ClassUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import com.ruoyi.common.annotation.DataColumn;
|
import com.ruoyi.common.annotation.DataColumn;
|
||||||
@ -51,11 +49,6 @@ public class PlusDataPermissionHandler {
|
|||||||
*/
|
*/
|
||||||
private final Map<String, DataPermission> dataPermissionCacheMap = new ConcurrentHashMap<>();
|
private final Map<String, DataPermission> dataPermissionCacheMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/**
|
|
||||||
* 无效注解方法缓存用于快速返回
|
|
||||||
*/
|
|
||||||
private final Set<String> invalidCacheSet = new ConcurrentHashSet<>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* spel 解析器
|
* spel 解析器
|
||||||
*/
|
*/
|
||||||
@ -69,10 +62,6 @@ public class PlusDataPermissionHandler {
|
|||||||
|
|
||||||
public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) {
|
public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) {
|
||||||
DataColumn[] dataColumns = findAnnotation(mappedStatementId);
|
DataColumn[] dataColumns = findAnnotation(mappedStatementId);
|
||||||
if (ArrayUtil.isEmpty(dataColumns)) {
|
|
||||||
invalidCacheSet.add(mappedStatementId);
|
|
||||||
return where;
|
|
||||||
}
|
|
||||||
LoginUser currentUser = DataPermissionHelper.getVariable("user");
|
LoginUser currentUser = DataPermissionHelper.getVariable("user");
|
||||||
if (ObjectUtil.isNull(currentUser)) {
|
if (ObjectUtil.isNull(currentUser)) {
|
||||||
currentUser = LoginHelper.getLoginUser();
|
currentUser = LoginHelper.getLoginUser();
|
||||||
@ -156,7 +145,7 @@ public class PlusDataPermissionHandler {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private DataColumn[] findAnnotation(String mappedStatementId) {
|
public DataColumn[] findAnnotation(String mappedStatementId) {
|
||||||
StringBuilder sb = new StringBuilder(mappedStatementId);
|
StringBuilder sb = new StringBuilder(mappedStatementId);
|
||||||
int index = sb.lastIndexOf(".");
|
int index = sb.lastIndexOf(".");
|
||||||
String clazzName = sb.substring(0, index);
|
String clazzName = sb.substring(0, index);
|
||||||
@ -190,10 +179,4 @@ public class PlusDataPermissionHandler {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否为无效方法 无数据权限
|
|
||||||
*/
|
|
||||||
public boolean isInvalid(String mappedStatementId) {
|
|
||||||
return invalidCacheSet.contains(mappedStatementId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package com.ruoyi.framework.interceptor;
|
package com.ruoyi.framework.interceptor;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.ConcurrentHashSet;
|
||||||
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
|
import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
|
||||||
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
|
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
|
||||||
|
import com.ruoyi.common.annotation.DataColumn;
|
||||||
import com.ruoyi.framework.handler.PlusDataPermissionHandler;
|
import com.ruoyi.framework.handler.PlusDataPermissionHandler;
|
||||||
import net.sf.jsqlparser.expression.Expression;
|
import net.sf.jsqlparser.expression.Expression;
|
||||||
import net.sf.jsqlparser.statement.delete.Delete;
|
import net.sf.jsqlparser.statement.delete.Delete;
|
||||||
@ -23,6 +26,7 @@ import org.apache.ibatis.session.RowBounds;
|
|||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据权限拦截器
|
* 数据权限拦截器
|
||||||
@ -33,6 +37,11 @@ import java.util.List;
|
|||||||
public class PlusDataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {
|
public class PlusDataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {
|
||||||
|
|
||||||
private final PlusDataPermissionHandler dataPermissionHandler = new PlusDataPermissionHandler();
|
private final PlusDataPermissionHandler dataPermissionHandler = new PlusDataPermissionHandler();
|
||||||
|
/**
|
||||||
|
* 无效注解方法缓存用于快速返回
|
||||||
|
*/
|
||||||
|
private final Set<String> invalidCacheSet = new ConcurrentHashSet<>();
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
|
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
|
||||||
@ -41,7 +50,12 @@ public class PlusDataPermissionInterceptor extends JsqlParserSupport implements
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 检查是否无效 无数据权限注解
|
// 检查是否无效 无数据权限注解
|
||||||
if (dataPermissionHandler.isInvalid(ms.getId())) {
|
if (invalidCacheSet.contains(ms.getId())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DataColumn[] dataColumns = dataPermissionHandler.findAnnotation(ms.getId());
|
||||||
|
if (ArrayUtil.isEmpty(dataColumns)) {
|
||||||
|
invalidCacheSet.add(ms.getId());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 解析 sql 分配对应方法
|
// 解析 sql 分配对应方法
|
||||||
@ -58,6 +72,15 @@ public class PlusDataPermissionInterceptor extends JsqlParserSupport implements
|
|||||||
if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
|
if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 检查是否无效 无数据权限注解
|
||||||
|
if (invalidCacheSet.contains(ms.getId())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DataColumn[] dataColumns = dataPermissionHandler.findAnnotation(ms.getId());
|
||||||
|
if (ArrayUtil.isEmpty(dataColumns)) {
|
||||||
|
invalidCacheSet.add(ms.getId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
|
PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
|
||||||
mpBs.sql(parserMulti(mpBs.sql(), ms.getId()));
|
mpBs.sql(parserMulti(mpBs.sql(), ms.getId()));
|
||||||
}
|
}
|
||||||
|
@ -44,14 +44,14 @@ public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
|
|||||||
BufferedReader reader = request.getReader();
|
BufferedReader reader = request.getReader();
|
||||||
jsonParam = IoUtil.read(reader);
|
jsonParam = IoUtil.read(reader);
|
||||||
}
|
}
|
||||||
log.debug("[PLUS]开始请求 => URL[{}],参数类型[json],参数:[{}]", url, jsonParam);
|
log.info("[PLUS]开始请求 => URL[{}],参数类型[json],参数:[{}]", url, jsonParam);
|
||||||
} else {
|
} else {
|
||||||
Map<String, String[]> parameterMap = request.getParameterMap();
|
Map<String, String[]> parameterMap = request.getParameterMap();
|
||||||
if (MapUtil.isNotEmpty(parameterMap)) {
|
if (MapUtil.isNotEmpty(parameterMap)) {
|
||||||
String parameters = JsonUtils.toJsonString(parameterMap);
|
String parameters = JsonUtils.toJsonString(parameterMap);
|
||||||
log.debug("[PLUS]开始请求 => URL[{}],参数类型[param],参数:[{}]", url, parameters);
|
log.info("[PLUS]开始请求 => URL[{}],参数类型[param],参数:[{}]", url, parameters);
|
||||||
} else {
|
} else {
|
||||||
log.debug("[PLUS]开始请求 => URL[{}],无参数", url);
|
log.info("[PLUS]开始请求 => URL[{}],无参数", url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
|
|||||||
if (!prodProfile.equals(SpringUtils.getActiveProfile())) {
|
if (!prodProfile.equals(SpringUtils.getActiveProfile())) {
|
||||||
StopWatch stopWatch = invokeTimeTL.get();
|
StopWatch stopWatch = invokeTimeTL.get();
|
||||||
stopWatch.stop();
|
stopWatch.stop();
|
||||||
log.debug("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getTime());
|
log.info("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getTime());
|
||||||
invokeTimeTL.remove();
|
invokeTimeTL.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,6 +118,10 @@ public class PlusSpringCacheManager implements CacheManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cache getCache(String name) {
|
public Cache getCache(String name) {
|
||||||
|
// 重写 cacheName 支持多参数
|
||||||
|
String[] array = StringUtils.delimitedListToStringArray(name, "#");
|
||||||
|
name = array[0];
|
||||||
|
|
||||||
Cache cache = instanceMap.get(name);
|
Cache cache = instanceMap.get(name);
|
||||||
if (cache != null) {
|
if (cache != null) {
|
||||||
return cache;
|
return cache;
|
||||||
@ -132,9 +136,6 @@ public class PlusSpringCacheManager implements CacheManager {
|
|||||||
configMap.put(name, config);
|
configMap.put(name, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重写 cacheName 支持多参数
|
|
||||||
String[] array = StringUtils.delimitedListToStringArray(name, "#");
|
|
||||||
name = array[0];
|
|
||||||
if (array.length > 1) {
|
if (array.length > 1) {
|
||||||
config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis());
|
config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis());
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import cn.hutool.http.HttpStatus;
|
|||||||
import com.ruoyi.common.core.domain.R;
|
import com.ruoyi.common.core.domain.R;
|
||||||
import com.ruoyi.common.exception.DemoModeException;
|
import com.ruoyi.common.exception.DemoModeException;
|
||||||
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
|
import com.ruoyi.common.exception.base.BaseException;
|
||||||
import com.ruoyi.common.utils.StreamUtils;
|
import com.ruoyi.common.utils.StreamUtils;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.mybatis.spring.MyBatisSystemException;
|
import org.mybatis.spring.MyBatisSystemException;
|
||||||
@ -16,8 +17,10 @@ import org.springframework.dao.DuplicateKeyException;
|
|||||||
import org.springframework.validation.BindException;
|
import org.springframework.validation.BindException;
|
||||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.MissingPathVariableException;
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.validation.ConstraintViolation;
|
import javax.validation.ConstraintViolation;
|
||||||
@ -103,11 +106,40 @@ public class GlobalExceptionHandler {
|
|||||||
*/
|
*/
|
||||||
@ExceptionHandler(ServiceException.class)
|
@ExceptionHandler(ServiceException.class)
|
||||||
public R<Void> handleServiceException(ServiceException e, HttpServletRequest request) {
|
public R<Void> handleServiceException(ServiceException e, HttpServletRequest request) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage());
|
||||||
Integer code = e.getCode();
|
Integer code = e.getCode();
|
||||||
return ObjectUtil.isNotNull(code) ? R.fail(code, e.getMessage()) : R.fail(e.getMessage());
|
return ObjectUtil.isNotNull(code) ? R.fail(code, e.getMessage()) : R.fail(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务异常
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(BaseException.class)
|
||||||
|
public R<Void> handleBaseException(BaseException e, HttpServletRequest request) {
|
||||||
|
log.error(e.getMessage());
|
||||||
|
return R.fail(e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求路径中缺少必需的路径变量
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(MissingPathVariableException.class)
|
||||||
|
public R<Void> handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) {
|
||||||
|
String requestURI = request.getRequestURI();
|
||||||
|
log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI);
|
||||||
|
return R.fail(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求参数类型不匹配
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||||
|
public R<Void> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) {
|
||||||
|
String requestURI = request.getRequestURI();
|
||||||
|
log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI);
|
||||||
|
return R.fail(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), e.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 拦截未知的运行时异常
|
* 拦截未知的运行时异常
|
||||||
*/
|
*/
|
||||||
@ -133,7 +165,7 @@ public class GlobalExceptionHandler {
|
|||||||
*/
|
*/
|
||||||
@ExceptionHandler(BindException.class)
|
@ExceptionHandler(BindException.class)
|
||||||
public R<Void> handleBindException(BindException e) {
|
public R<Void> handleBindException(BindException e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage());
|
||||||
String message = StreamUtils.join(e.getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage, ", ");
|
String message = StreamUtils.join(e.getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage, ", ");
|
||||||
return R.fail(message);
|
return R.fail(message);
|
||||||
}
|
}
|
||||||
@ -143,7 +175,7 @@ public class GlobalExceptionHandler {
|
|||||||
*/
|
*/
|
||||||
@ExceptionHandler(ConstraintViolationException.class)
|
@ExceptionHandler(ConstraintViolationException.class)
|
||||||
public R<Void> constraintViolationException(ConstraintViolationException e) {
|
public R<Void> constraintViolationException(ConstraintViolationException e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage());
|
||||||
String message = StreamUtils.join(e.getConstraintViolations(), ConstraintViolation::getMessage, ", ");
|
String message = StreamUtils.join(e.getConstraintViolations(), ConstraintViolation::getMessage, ", ");
|
||||||
return R.fail(message);
|
return R.fail(message);
|
||||||
}
|
}
|
||||||
@ -153,7 +185,7 @@ public class GlobalExceptionHandler {
|
|||||||
*/
|
*/
|
||||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||||
public R<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
|
public R<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage());
|
||||||
String message = e.getBindingResult().getFieldError().getDefaultMessage();
|
String message = e.getBindingResult().getFieldError().getDefaultMessage();
|
||||||
return R.fail(message);
|
return R.fail(message);
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>ruoyi-vue-plus</artifactId>
|
<artifactId>ruoyi-vue-plus</artifactId>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<version>4.7.0</version>
|
<version>4.8.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ public class GenController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@SaCheckPermission("tool:gen:list")
|
@SaCheckPermission("tool:gen:list")
|
||||||
@GetMapping(value = "/column/{tableId}")
|
@GetMapping(value = "/column/{tableId}")
|
||||||
public TableDataInfo<GenTableColumn> columnList(Long tableId) {
|
public TableDataInfo<GenTableColumn> columnList(@PathVariable("tableId") Long tableId) {
|
||||||
TableDataInfo<GenTableColumn> dataInfo = new TableDataInfo<>();
|
TableDataInfo<GenTableColumn> dataInfo = new TableDataInfo<>();
|
||||||
List<GenTableColumn> list = genTableService.selectGenTableColumnListByTableId(tableId);
|
List<GenTableColumn> list = genTableService.selectGenTableColumnListByTableId(tableId);
|
||||||
dataInfo.setRows(list);
|
dataInfo.setRows(list);
|
||||||
|
@ -32,7 +32,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<select id="selectDbTableColumnsByName" parameterType="String" resultMap="GenTableColumnResult">
|
<select id="selectDbTableColumnsByName" parameterType="String" resultMap="GenTableColumnResult">
|
||||||
<if test="@com.ruoyi.common.helper.DataBaseHelper@isMySql()">
|
<if test="@com.ruoyi.common.helper.DataBaseHelper@isMySql()">
|
||||||
select column_name,
|
select column_name,
|
||||||
(case when (is_nullable = 'no' <![CDATA[ && ]]> column_key != 'PRI') then '1' else null end) as is_required,
|
(case when (is_nullable = 'no' <![CDATA[ && ]]> column_key != 'PRI') then '1' else '0' end) as is_required,
|
||||||
(case when column_key = 'PRI' then '1' else '0' end) as is_pk,
|
(case when column_key = 'PRI' then '1' else '0' end) as is_pk,
|
||||||
ordinal_position as sort,
|
ordinal_position as sort,
|
||||||
column_comment,
|
column_comment,
|
||||||
@ -43,7 +43,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</if>
|
</if>
|
||||||
<if test="@com.ruoyi.common.helper.DataBaseHelper@isOracle()">
|
<if test="@com.ruoyi.common.helper.DataBaseHelper@isOracle()">
|
||||||
select lower(temp.column_name) as column_name,
|
select lower(temp.column_name) as column_name,
|
||||||
(case when (temp.nullable = 'N' and temp.constraint_type != 'P') then '1' else null end) as is_required,
|
(case when (temp.nullable = 'N' and temp.constraint_type != 'P') then '1' else '0' end) as is_required,
|
||||||
(case when temp.constraint_type = 'P' then '1' else '0' end) as is_pk,
|
(case when temp.constraint_type = 'P' then '1' else '0' end) as is_pk,
|
||||||
temp.column_id as sort,
|
temp.column_id as sort,
|
||||||
temp.comments as column_comment,
|
temp.comments as column_comment,
|
||||||
|
@ -10,7 +10,7 @@ import com.ruoyi.common.convert.ExcelDictConvert;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ${functionName}视图对象 ${tableName}
|
* ${functionName}视图对象 ${tableName}
|
||||||
@ -20,7 +20,7 @@ import java.util.Date;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@ExcelIgnoreUnannotated
|
@ExcelIgnoreUnannotated
|
||||||
public class ${ClassName}Vo {
|
public class ${ClassName}Vo implements Serializable {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ -455,7 +455,7 @@ export default {
|
|||||||
this.reset();
|
this.reset();
|
||||||
this.getTreeselect();
|
this.getTreeselect();
|
||||||
if (row != null) {
|
if (row != null) {
|
||||||
this.form.${treeParentCode} = row.${treeCode};
|
this.form.${treeParentCode} = row.${treeParentCode};
|
||||||
}
|
}
|
||||||
get${BusinessName}(row.${pkColumn.javaField}).then(response => {
|
get${BusinessName}(row.${pkColumn.javaField}).then(response => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
@ -421,7 +421,7 @@ async function handleUpdate(row) {
|
|||||||
reset();
|
reset();
|
||||||
await getTreeselect();
|
await getTreeselect();
|
||||||
if (row != null) {
|
if (row != null) {
|
||||||
form.value.${treeParentCode} = row.${treeCode};
|
form.value.${treeParentCode} = row.${treeParentCode};
|
||||||
}
|
}
|
||||||
get${BusinessName}(row.${pkColumn.javaField}).then(response => {
|
get${BusinessName}(row.${pkColumn.javaField}).then(response => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>ruoyi-vue-plus</artifactId>
|
<artifactId>ruoyi-vue-plus</artifactId>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<version>4.7.0</version>
|
<version>4.8.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>ruoyi-vue-plus</artifactId>
|
<artifactId>ruoyi-vue-plus</artifactId>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<version>4.7.0</version>
|
<version>4.8.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ import com.ruoyi.oss.exception.OssException;
|
|||||||
import com.ruoyi.oss.properties.OssProperties;
|
import com.ruoyi.oss.properties.OssProperties;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -115,6 +116,18 @@ public class OssClient {
|
|||||||
return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build();
|
return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UploadResult upload(File file, String path) {
|
||||||
|
try {
|
||||||
|
PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, file);
|
||||||
|
// 设置上传对象的 Acl 为公共读
|
||||||
|
putObjectRequest.setCannedAcl(getAccessPolicy().getAcl());
|
||||||
|
client.putObject(putObjectRequest);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
|
||||||
|
}
|
||||||
|
return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build();
|
||||||
|
}
|
||||||
|
|
||||||
public void delete(String path) {
|
public void delete(String path) {
|
||||||
path = path.replace(getUrl() + "/", "");
|
path = path.replace(getUrl() + "/", "");
|
||||||
try {
|
try {
|
||||||
@ -132,6 +145,10 @@ public class OssClient {
|
|||||||
return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
|
return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UploadResult uploadSuffix(File file, String suffix) {
|
||||||
|
return upload(file, getPath(properties.getPrefix(), suffix));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取文件元数据
|
* 获取文件元数据
|
||||||
*
|
*
|
||||||
|
@ -39,7 +39,7 @@ public class OssFactory {
|
|||||||
/**
|
/**
|
||||||
* 根据类型获取实例
|
* 根据类型获取实例
|
||||||
*/
|
*/
|
||||||
public static OssClient instance(String configKey) {
|
public static synchronized OssClient instance(String configKey) {
|
||||||
String json = CacheUtils.get(CacheNames.SYS_OSS_CONFIG, configKey);
|
String json = CacheUtils.get(CacheNames.SYS_OSS_CONFIG, configKey);
|
||||||
if (json == null) {
|
if (json == null) {
|
||||||
throw new OssException("系统异常, '" + configKey + "'配置信息不存在!");
|
throw new OssException("系统异常, '" + configKey + "'配置信息不存在!");
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>ruoyi-vue-plus</artifactId>
|
<artifactId>ruoyi-vue-plus</artifactId>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<version>4.7.0</version>
|
<version>4.8.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
@ -24,15 +24,15 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aliyun</groupId>
|
<groupId>org.dromara.sms4j</groupId>
|
||||||
<artifactId>dysmsapi20170525</artifactId>
|
<artifactId>sms4j-spring-boot-starter</artifactId>
|
||||||
<optional>true</optional>
|
<exclusions>
|
||||||
</dependency>
|
<!-- 排除京东短信内存在的fastjson等待作者后续修复 -->
|
||||||
|
<exclusion>
|
||||||
<dependency>
|
<groupId>com.alibaba</groupId>
|
||||||
<groupId>com.tencentcloudapi</groupId>
|
<artifactId>fastjson</artifactId>
|
||||||
<artifactId>tencentcloud-sdk-java-sms</artifactId>
|
</exclusion>
|
||||||
<optional>true</optional>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@ -1,45 +1,12 @@
|
|||||||
package com.ruoyi.sms.config;
|
package com.ruoyi.sms.config;
|
||||||
|
|
||||||
import com.ruoyi.sms.config.properties.SmsProperties;
|
|
||||||
import com.ruoyi.sms.core.AliyunSmsTemplate;
|
|
||||||
import com.ruoyi.sms.core.SmsTemplate;
|
|
||||||
import com.ruoyi.sms.core.TencentSmsTemplate;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 短信配置类
|
* 短信配置类
|
||||||
*
|
*
|
||||||
* @author Lion Li
|
* @author Lion Li
|
||||||
* @version 4.2.0
|
* @version 4.2.0
|
||||||
*/
|
*/
|
||||||
@Configuration
|
//@Configuration // 暂时用不上 留着后续扩展使用
|
||||||
public class SmsConfig {
|
public class SmsConfig {
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@ConditionalOnProperty(value = "sms.enabled", havingValue = "true")
|
|
||||||
@ConditionalOnClass(com.aliyun.dysmsapi20170525.Client.class)
|
|
||||||
static class AliyunSmsConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public SmsTemplate aliyunSmsTemplate(SmsProperties smsProperties) {
|
|
||||||
return new AliyunSmsTemplate(smsProperties);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@ConditionalOnProperty(value = "sms.enabled", havingValue = "true")
|
|
||||||
@ConditionalOnClass(com.tencentcloudapi.sms.v20190711.SmsClient.class)
|
|
||||||
static class TencentSmsConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public SmsTemplate tencentSmsTemplate(SmsProperties smsProperties) {
|
|
||||||
return new TencentSmsTemplate(smsProperties);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,47 +1,20 @@
|
|||||||
package com.ruoyi.sms.config.properties;
|
//package com.ruoyi.sms.config.properties;
|
||||||
|
//
|
||||||
import lombok.Data;
|
//import lombok.Data;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
//import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.stereotype.Component;
|
//import org.springframework.stereotype.Component;
|
||||||
|
//
|
||||||
/**
|
///**
|
||||||
* SMS短信 配置属性
|
// * SMS短信 配置属性
|
||||||
*
|
// *
|
||||||
* @author Lion Li
|
// * @author Lion Li
|
||||||
* @version 4.2.0
|
// * @version 4.2.0
|
||||||
*/
|
// */
|
||||||
@Data
|
//@Data
|
||||||
@Component
|
//@Component
|
||||||
@ConfigurationProperties(prefix = "sms")
|
//@ConfigurationProperties(prefix = "sms")
|
||||||
public class SmsProperties {
|
//public class SmsProperties {
|
||||||
|
//
|
||||||
private Boolean enabled;
|
// private Boolean enabled;
|
||||||
|
//
|
||||||
/**
|
//}
|
||||||
* 配置节点
|
|
||||||
* 阿里云 dysmsapi.aliyuncs.com
|
|
||||||
* 腾讯云 sms.tencentcloudapi.com
|
|
||||||
*/
|
|
||||||
private String endpoint;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* key
|
|
||||||
*/
|
|
||||||
private String accessKeyId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 密匙
|
|
||||||
*/
|
|
||||||
private String accessKeySecret;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 短信签名
|
|
||||||
*/
|
|
||||||
private String signName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 短信应用ID (腾讯专属)
|
|
||||||
*/
|
|
||||||
private String sdkAppId;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
package com.ruoyi.sms.core;
|
|
||||||
|
|
||||||
import com.aliyun.dysmsapi20170525.Client;
|
|
||||||
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
|
|
||||||
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
|
|
||||||
import com.aliyun.teaopenapi.models.Config;
|
|
||||||
import com.ruoyi.common.utils.JsonUtils;
|
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
|
||||||
import com.ruoyi.sms.config.properties.SmsProperties;
|
|
||||||
import com.ruoyi.sms.entity.SmsResult;
|
|
||||||
import com.ruoyi.sms.exception.SmsException;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aliyun 短信模板
|
|
||||||
*
|
|
||||||
* @author Lion Li
|
|
||||||
* @version 4.2.0
|
|
||||||
*/
|
|
||||||
public class AliyunSmsTemplate implements SmsTemplate {
|
|
||||||
|
|
||||||
private SmsProperties properties;
|
|
||||||
|
|
||||||
private Client client;
|
|
||||||
|
|
||||||
@SneakyThrows(Exception.class)
|
|
||||||
public AliyunSmsTemplate(SmsProperties smsProperties) {
|
|
||||||
this.properties = smsProperties;
|
|
||||||
Config config = new Config()
|
|
||||||
// 您的AccessKey ID
|
|
||||||
.setAccessKeyId(smsProperties.getAccessKeyId())
|
|
||||||
// 您的AccessKey Secret
|
|
||||||
.setAccessKeySecret(smsProperties.getAccessKeySecret())
|
|
||||||
// 访问的域名
|
|
||||||
.setEndpoint(smsProperties.getEndpoint());
|
|
||||||
this.client = new Client(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SmsResult send(String phones, String templateId, Map<String, String> param) {
|
|
||||||
if (StringUtils.isBlank(phones)) {
|
|
||||||
throw new SmsException("手机号不能为空");
|
|
||||||
}
|
|
||||||
if (StringUtils.isBlank(templateId)) {
|
|
||||||
throw new SmsException("模板ID不能为空");
|
|
||||||
}
|
|
||||||
SendSmsRequest req = new SendSmsRequest()
|
|
||||||
.setPhoneNumbers(phones)
|
|
||||||
.setSignName(properties.getSignName())
|
|
||||||
.setTemplateCode(templateId)
|
|
||||||
.setTemplateParam(JsonUtils.toJsonString(param));
|
|
||||||
try {
|
|
||||||
SendSmsResponse resp = client.sendSms(req);
|
|
||||||
return SmsResult.builder()
|
|
||||||
.isSuccess("OK".equals(resp.getBody().getCode()))
|
|
||||||
.message(resp.getBody().getMessage())
|
|
||||||
.response(JsonUtils.toJsonString(resp))
|
|
||||||
.build();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SmsException(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
package com.ruoyi.sms.core;
|
|
||||||
|
|
||||||
import com.ruoyi.sms.entity.SmsResult;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 短信模板
|
|
||||||
*
|
|
||||||
* @author Lion Li
|
|
||||||
* @version 4.2.0
|
|
||||||
*/
|
|
||||||
public interface SmsTemplate {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送短信
|
|
||||||
*
|
|
||||||
* @param phones 电话号(多个逗号分割)
|
|
||||||
* @param templateId 模板id
|
|
||||||
* @param param 模板对应参数
|
|
||||||
* 阿里 需使用 模板变量名称对应内容 例如: code=1234
|
|
||||||
* 腾讯 需使用 模板变量顺序对应内容 例如: 1=1234, 1为模板内第一个参数
|
|
||||||
*/
|
|
||||||
SmsResult send(String phones, String templateId, Map<String, String> param);
|
|
||||||
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
package com.ruoyi.sms.core;
|
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
|
||||||
import cn.hutool.core.util.ArrayUtil;
|
|
||||||
import com.ruoyi.common.utils.JsonUtils;
|
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
|
||||||
import com.ruoyi.sms.config.properties.SmsProperties;
|
|
||||||
import com.ruoyi.sms.entity.SmsResult;
|
|
||||||
import com.ruoyi.sms.exception.SmsException;
|
|
||||||
import com.tencentcloudapi.common.Credential;
|
|
||||||
import com.tencentcloudapi.common.profile.ClientProfile;
|
|
||||||
import com.tencentcloudapi.common.profile.HttpProfile;
|
|
||||||
import com.tencentcloudapi.sms.v20190711.SmsClient;
|
|
||||||
import com.tencentcloudapi.sms.v20190711.models.SendSmsRequest;
|
|
||||||
import com.tencentcloudapi.sms.v20190711.models.SendSmsResponse;
|
|
||||||
import com.tencentcloudapi.sms.v20190711.models.SendStatus;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tencent 短信模板
|
|
||||||
*
|
|
||||||
* @author Lion Li
|
|
||||||
* @version 4.2.0
|
|
||||||
*/
|
|
||||||
public class TencentSmsTemplate implements SmsTemplate {
|
|
||||||
|
|
||||||
private SmsProperties properties;
|
|
||||||
|
|
||||||
private SmsClient client;
|
|
||||||
|
|
||||||
@SneakyThrows(Exception.class)
|
|
||||||
public TencentSmsTemplate(SmsProperties smsProperties) {
|
|
||||||
this.properties = smsProperties;
|
|
||||||
Credential credential = new Credential(smsProperties.getAccessKeyId(), smsProperties.getAccessKeySecret());
|
|
||||||
HttpProfile httpProfile = new HttpProfile();
|
|
||||||
httpProfile.setEndpoint(smsProperties.getEndpoint());
|
|
||||||
ClientProfile clientProfile = new ClientProfile();
|
|
||||||
clientProfile.setHttpProfile(httpProfile);
|
|
||||||
this.client = new SmsClient(credential, "", clientProfile);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SmsResult send(String phones, String templateId, Map<String, String> param) {
|
|
||||||
if (StringUtils.isBlank(phones)) {
|
|
||||||
throw new SmsException("手机号不能为空");
|
|
||||||
}
|
|
||||||
if (StringUtils.isBlank(templateId)) {
|
|
||||||
throw new SmsException("模板ID不能为空");
|
|
||||||
}
|
|
||||||
SendSmsRequest req = new SendSmsRequest();
|
|
||||||
Set<String> set = Arrays.stream(phones.split(StringUtils.SEPARATOR)).map(p -> "+86" + p).collect(Collectors.toSet());
|
|
||||||
req.setPhoneNumberSet(ArrayUtil.toArray(set, String.class));
|
|
||||||
if (CollUtil.isNotEmpty(param)) {
|
|
||||||
req.setTemplateParamSet(ArrayUtil.toArray(param.values(), String.class));
|
|
||||||
}
|
|
||||||
req.setTemplateID(templateId);
|
|
||||||
req.setSign(properties.getSignName());
|
|
||||||
req.setSmsSdkAppid(properties.getSdkAppId());
|
|
||||||
try {
|
|
||||||
SendSmsResponse resp = client.SendSms(req);
|
|
||||||
SmsResult.SmsResultBuilder builder = SmsResult.builder()
|
|
||||||
.isSuccess(true)
|
|
||||||
.message("send success")
|
|
||||||
.response(JsonUtils.toJsonString(resp));
|
|
||||||
for (SendStatus sendStatus : resp.getSendStatusSet()) {
|
|
||||||
if (!"Ok".equals(sendStatus.getCode())) {
|
|
||||||
builder.isSuccess(false).message(sendStatus.getMessage());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new SmsException(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package com.ruoyi.sms.entity;
|
|
||||||
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传返回体
|
|
||||||
*
|
|
||||||
* @author Lion Li
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
public class SmsResult {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否成功
|
|
||||||
*/
|
|
||||||
private boolean isSuccess;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 响应消息
|
|
||||||
*/
|
|
||||||
private String message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 实际响应体
|
|
||||||
* <p>
|
|
||||||
* 可自行转换为 SDK 对应的 SendSmsResponse
|
|
||||||
*/
|
|
||||||
private String response;
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
package com.ruoyi.sms.exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sms异常类
|
|
||||||
*
|
|
||||||
* @author Lion Li
|
|
||||||
*/
|
|
||||||
public class SmsException extends RuntimeException {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
public SmsException(String msg) {
|
|
||||||
super(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>ruoyi-vue-plus</artifactId>
|
<artifactId>ruoyi-vue-plus</artifactId>
|
||||||
<groupId>com.ruoyi</groupId>
|
<groupId>com.ruoyi</groupId>
|
||||||
<version>4.7.0</version>
|
<version>4.8.2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
@ -19,7 +19,8 @@ import java.util.List;
|
|||||||
public interface SysRoleMapper extends BaseMapperPlus<SysRoleMapper, SysRole, SysRole> {
|
public interface SysRoleMapper extends BaseMapperPlus<SysRoleMapper, SysRole, SysRole> {
|
||||||
|
|
||||||
@DataPermission({
|
@DataPermission({
|
||||||
@DataColumn(key = "deptName", value = "d.dept_id")
|
@DataColumn(key = "deptName", value = "d.dept_id"),
|
||||||
|
@DataColumn(key = "userName", value = "us.user_id")
|
||||||
})
|
})
|
||||||
Page<SysRole> selectPageRoleList(@Param("page") Page<SysRole> page, @Param(Constants.WRAPPER) Wrapper<SysRole> queryWrapper);
|
Page<SysRole> selectPageRoleList(@Param("page") Page<SysRole> page, @Param(Constants.WRAPPER) Wrapper<SysRole> queryWrapper);
|
||||||
|
|
||||||
@ -30,7 +31,8 @@ public interface SysRoleMapper extends BaseMapperPlus<SysRoleMapper, SysRole, Sy
|
|||||||
* @return 角色数据集合信息
|
* @return 角色数据集合信息
|
||||||
*/
|
*/
|
||||||
@DataPermission({
|
@DataPermission({
|
||||||
@DataColumn(key = "deptName", value = "d.dept_id")
|
@DataColumn(key = "deptName", value = "d.dept_id"),
|
||||||
|
@DataColumn(key = "userName", value = "us.user_id")
|
||||||
})
|
})
|
||||||
List<SysRole> selectRoleList(@Param(Constants.WRAPPER) Wrapper<SysRole> queryWrapper);
|
List<SysRole> selectRoleList(@Param(Constants.WRAPPER) Wrapper<SysRole> queryWrapper);
|
||||||
|
|
||||||
|
@ -2,12 +2,12 @@ package com.ruoyi.system.service;
|
|||||||
|
|
||||||
import com.ruoyi.common.core.domain.PageQuery;
|
import com.ruoyi.common.core.domain.PageQuery;
|
||||||
import com.ruoyi.common.core.page.TableDataInfo;
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
import com.ruoyi.system.domain.SysOss;
|
|
||||||
import com.ruoyi.system.domain.bo.SysOssBo;
|
import com.ruoyi.system.domain.bo.SysOssBo;
|
||||||
import com.ruoyi.system.domain.vo.SysOssVo;
|
import com.ruoyi.system.domain.vo.SysOssVo;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -27,6 +27,8 @@ public interface ISysOssService {
|
|||||||
|
|
||||||
SysOssVo upload(MultipartFile file);
|
SysOssVo upload(MultipartFile file);
|
||||||
|
|
||||||
|
SysOssVo upload(File file);
|
||||||
|
|
||||||
void download(Long ossId, HttpServletResponse response) throws IOException;
|
void download(Long ossId, HttpServletResponse response) throws IOException;
|
||||||
|
|
||||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||||
|
@ -8,9 +8,9 @@ import cn.hutool.core.util.ObjectUtil;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.ruoyi.common.constant.CacheConstants;
|
import com.ruoyi.common.constant.CacheConstants;
|
||||||
import com.ruoyi.common.constant.Constants;
|
import com.ruoyi.common.constant.Constants;
|
||||||
import com.ruoyi.common.core.domain.event.LogininforEvent;
|
|
||||||
import com.ruoyi.common.core.domain.dto.RoleDTO;
|
import com.ruoyi.common.core.domain.dto.RoleDTO;
|
||||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||||
|
import com.ruoyi.common.core.domain.event.LogininforEvent;
|
||||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||||
import com.ruoyi.common.core.domain.model.XcxLoginUser;
|
import com.ruoyi.common.core.domain.model.XcxLoginUser;
|
||||||
import com.ruoyi.common.enums.DeviceType;
|
import com.ruoyi.common.enums.DeviceType;
|
||||||
@ -71,9 +71,10 @@ public class SysLoginService {
|
|||||||
if (captchaEnabled) {
|
if (captchaEnabled) {
|
||||||
validateCaptcha(username, code, uuid);
|
validateCaptcha(username, code, uuid);
|
||||||
}
|
}
|
||||||
|
// 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可
|
||||||
SysUser user = loadUserByUsername(username);
|
SysUser user = loadUserByUsername(username);
|
||||||
checkLogin(LoginType.PASSWORD, username, () -> !BCrypt.checkpw(password, user.getPassword()));
|
checkLogin(LoginType.PASSWORD, username, () -> !BCrypt.checkpw(password, user.getPassword()));
|
||||||
// 此处可根据登录用户的数据不同 自行创建 loginUser
|
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
|
||||||
LoginUser loginUser = buildLoginUser(user);
|
LoginUser loginUser = buildLoginUser(user);
|
||||||
// 生成token
|
// 生成token
|
||||||
LoginHelper.loginByDevice(loginUser, DeviceType.PC);
|
LoginHelper.loginByDevice(loginUser, DeviceType.PC);
|
||||||
@ -88,7 +89,7 @@ public class SysLoginService {
|
|||||||
SysUser user = loadUserByPhonenumber(phonenumber);
|
SysUser user = loadUserByPhonenumber(phonenumber);
|
||||||
|
|
||||||
checkLogin(LoginType.SMS, user.getUserName(), () -> !validateSmsCode(phonenumber, smsCode));
|
checkLogin(LoginType.SMS, user.getUserName(), () -> !validateSmsCode(phonenumber, smsCode));
|
||||||
// 此处可根据登录用户的数据不同 自行创建 loginUser
|
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
|
||||||
LoginUser loginUser = buildLoginUser(user);
|
LoginUser loginUser = buildLoginUser(user);
|
||||||
// 生成token
|
// 生成token
|
||||||
LoginHelper.loginByDevice(loginUser, DeviceType.APP);
|
LoginHelper.loginByDevice(loginUser, DeviceType.APP);
|
||||||
@ -99,11 +100,11 @@ public class SysLoginService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String emailLogin(String email, String emailCode) {
|
public String emailLogin(String email, String emailCode) {
|
||||||
// 通过手机号查找用户
|
// 通过手邮箱查找用户
|
||||||
SysUser user = loadUserByEmail(email);
|
SysUser user = loadUserByEmail(email);
|
||||||
|
|
||||||
checkLogin(LoginType.EMAIL, user.getUserName(), () -> !validateEmailCode(email, emailCode));
|
checkLogin(LoginType.EMAIL, user.getUserName(), () -> !validateEmailCode(email, emailCode));
|
||||||
// 此处可根据登录用户的数据不同 自行创建 loginUser
|
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
|
||||||
LoginUser loginUser = buildLoginUser(user);
|
LoginUser loginUser = buildLoginUser(user);
|
||||||
// 生成token
|
// 生成token
|
||||||
LoginHelper.loginByDevice(loginUser, DeviceType.APP);
|
LoginHelper.loginByDevice(loginUser, DeviceType.APP);
|
||||||
@ -118,9 +119,11 @@ public class SysLoginService {
|
|||||||
// todo 以下自行实现
|
// todo 以下自行实现
|
||||||
// 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
|
// 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
|
||||||
String openid = "";
|
String openid = "";
|
||||||
|
|
||||||
|
// 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可
|
||||||
SysUser user = loadUserByOpenid(openid);
|
SysUser user = loadUserByOpenid(openid);
|
||||||
|
|
||||||
// 此处可根据登录用户的数据不同 自行创建 loginUser
|
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
|
||||||
XcxLoginUser loginUser = new XcxLoginUser();
|
XcxLoginUser loginUser = new XcxLoginUser();
|
||||||
loginUser.setUserId(user.getUserId());
|
loginUser.setUserId(user.getUserId());
|
||||||
loginUser.setUsername(user.getUserName());
|
loginUser.setUsername(user.getUserName());
|
||||||
@ -140,9 +143,13 @@ public class SysLoginService {
|
|||||||
public void logout() {
|
public void logout() {
|
||||||
try {
|
try {
|
||||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||||
StpUtil.logout();
|
|
||||||
recordLogininfor(loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
|
recordLogininfor(loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
|
||||||
} catch (NotLoginException ignored) {
|
} catch (NotLoginException ignored) {
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
StpUtil.logout();
|
||||||
|
} catch (NotLoginException ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,25 +308,24 @@ public class SysLoginService {
|
|||||||
String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
|
String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
|
||||||
String loginFail = Constants.LOGIN_FAIL;
|
String loginFail = Constants.LOGIN_FAIL;
|
||||||
|
|
||||||
// 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip)
|
// 获取用户登录错误次数,默认为0 (可自定义限制策略 例如: key + username + ip)
|
||||||
Integer errorNumber = RedisUtils.getCacheObject(errorKey);
|
int errorNumber = ObjectUtil.defaultIfNull(RedisUtils.getCacheObject(errorKey), 0);
|
||||||
// 锁定时间内登录 则踢出
|
// 锁定时间内登录 则踢出
|
||||||
if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(maxRetryCount)) {
|
if (errorNumber >= maxRetryCount) {
|
||||||
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
|
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
|
||||||
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
|
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (supplier.get()) {
|
if (supplier.get()) {
|
||||||
// 是否第一次
|
// 错误次数递增
|
||||||
errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1;
|
errorNumber++;
|
||||||
|
RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
|
||||||
// 达到规定错误次数 则锁定登录
|
// 达到规定错误次数 则锁定登录
|
||||||
if (errorNumber.equals(maxRetryCount)) {
|
if (errorNumber >= maxRetryCount) {
|
||||||
RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
|
|
||||||
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
|
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
|
||||||
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
|
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
|
||||||
} else {
|
} else {
|
||||||
// 未达到规定错误次数 则递增
|
// 未达到规定错误次数
|
||||||
RedisUtils.setCacheObject(errorKey, errorNumber);
|
|
||||||
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
|
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
|
||||||
throw new UserException(loginType.getRetryLimitCount(), errorNumber);
|
throw new UserException(loginType.getRetryLimitCount(), errorNumber);
|
||||||
}
|
}
|
||||||
|
@ -49,11 +49,8 @@ public class SysDataScopeServiceImpl implements ISysDataScopeService {
|
|||||||
.apply(DataBaseHelper.findInSet(deptId, "ancestors")));
|
.apply(DataBaseHelper.findInSet(deptId, "ancestors")));
|
||||||
List<Long> ids = StreamUtils.toList(deptList, SysDept::getDeptId);
|
List<Long> ids = StreamUtils.toList(deptList, SysDept::getDeptId);
|
||||||
ids.add(deptId);
|
ids.add(deptId);
|
||||||
List<SysDept> list = deptMapper.selectList(new LambdaQueryWrapper<SysDept>()
|
if (CollUtil.isNotEmpty(ids)) {
|
||||||
.select(SysDept::getDeptId)
|
return StreamUtils.join(ids, Convert::toStr);
|
||||||
.in(SysDept::getDeptId, ids));
|
|
||||||
if (CollUtil.isNotEmpty(list)) {
|
|
||||||
return StreamUtils.join(list, d -> Convert.toStr(d.getDeptId()));
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,8 @@ public class SysDeptServiceImpl implements ISysDeptService, DeptService {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<Tree<Long>> selectDeptTreeList(SysDept dept) {
|
public List<Tree<Long>> selectDeptTreeList(SysDept dept) {
|
||||||
|
// 只查询未禁用部门
|
||||||
|
dept.setStatus(UserConstants.DEPT_NORMAL);
|
||||||
List<SysDept> depts = this.selectDeptList(dept);
|
List<SysDept> depts = this.selectDeptList(dept);
|
||||||
return buildDeptTreeSelect(depts);
|
return buildDeptTreeSelect(depts);
|
||||||
}
|
}
|
||||||
|
@ -116,10 +116,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
|
|||||||
* @param dictType 字典类型
|
* @param dictType 字典类型
|
||||||
* @return 字典类型
|
* @return 字典类型
|
||||||
*/
|
*/
|
||||||
@Cacheable(cacheNames = CacheNames.SYS_DICT, key = "#dictType")
|
|
||||||
@Override
|
@Override
|
||||||
public SysDictType selectDictTypeByType(String dictType) {
|
public SysDictType selectDictTypeByType(String dictType) {
|
||||||
return baseMapper.selectById(new LambdaQueryWrapper<SysDictType>().eq(SysDictType::getDictType, dictType));
|
return baseMapper.selectVoOne(new LambdaQueryWrapper<SysDictType>().eq(SysDictType::getDictType, dictType));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -148,7 +147,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
|
|||||||
List<SysDictData> dictDataList = dictDataMapper.selectList(
|
List<SysDictData> dictDataList = dictDataMapper.selectList(
|
||||||
new LambdaQueryWrapper<SysDictData>().eq(SysDictData::getStatus, UserConstants.DICT_NORMAL));
|
new LambdaQueryWrapper<SysDictData>().eq(SysDictData::getStatus, UserConstants.DICT_NORMAL));
|
||||||
Map<String, List<SysDictData>> dictDataMap = StreamUtils.groupByKey(dictDataList, SysDictData::getDictType);
|
Map<String, List<SysDictData>> dictDataMap = StreamUtils.groupByKey(dictDataList, SysDictData::getDictType);
|
||||||
dictDataMap.forEach((k,v) -> {
|
dictDataMap.forEach((k, v) -> {
|
||||||
List<SysDictData> dictList = StreamUtils.sorted(v, Comparator.comparing(SysDictData::getDictSort));
|
List<SysDictData> dictList = StreamUtils.sorted(v, Comparator.comparing(SysDictData::getDictSort));
|
||||||
CacheUtils.put(CacheNames.SYS_DICT, k, dictList);
|
CacheUtils.put(CacheNames.SYS_DICT, k, dictList);
|
||||||
});
|
});
|
||||||
@ -182,6 +181,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
|
|||||||
public List<SysDictData> insertDictType(SysDictType dict) {
|
public List<SysDictData> insertDictType(SysDictType dict) {
|
||||||
int row = baseMapper.insert(dict);
|
int row = baseMapper.insert(dict);
|
||||||
if (row > 0) {
|
if (row > 0) {
|
||||||
|
// 新增 type 下无 data 数据 返回空防止缓存穿透
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
throw new ServiceException("操作失败");
|
throw new ServiceException("操作失败");
|
||||||
@ -279,4 +279,9 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getAllDictByDictType(String dictType) {
|
||||||
|
List<SysDictData> list = selectDictDataByType(dictType);
|
||||||
|
return StreamUtils.toMap(list, SysDictData::getDictValue, SysDictData::getDictLabel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -437,7 +437,7 @@ public class SysMenuServiceImpl implements ISysMenuService {
|
|||||||
* 内链域名特殊字符替换
|
* 内链域名特殊字符替换
|
||||||
*/
|
*/
|
||||||
public String innerLinkReplaceEach(String path) {
|
public String innerLinkReplaceEach(String path) {
|
||||||
return StringUtils.replaceEach(path, new String[]{Constants.HTTP, Constants.HTTPS, Constants.WWW, "."},
|
return StringUtils.replaceEach(path, new String[]{Constants.HTTP, Constants.HTTPS, Constants.WWW, ".", ":"},
|
||||||
new String[]{"", "", "", "/"});
|
new String[]{"", "", "", "/", "/"});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@ public class SysOperLogServiceImpl implements ISysOperLogService {
|
|||||||
public TableDataInfo<SysOperLog> selectPageOperLogList(SysOperLog operLog, PageQuery pageQuery) {
|
public TableDataInfo<SysOperLog> selectPageOperLogList(SysOperLog operLog, PageQuery pageQuery) {
|
||||||
Map<String, Object> params = operLog.getParams();
|
Map<String, Object> params = operLog.getParams();
|
||||||
LambdaQueryWrapper<SysOperLog> lqw = new LambdaQueryWrapper<SysOperLog>()
|
LambdaQueryWrapper<SysOperLog> lqw = new LambdaQueryWrapper<SysOperLog>()
|
||||||
|
.like(StringUtils.isNotBlank(operLog.getOperIp()), SysOperLog::getOperIp, operLog.getOperIp())
|
||||||
.like(StringUtils.isNotBlank(operLog.getTitle()), SysOperLog::getTitle, operLog.getTitle())
|
.like(StringUtils.isNotBlank(operLog.getTitle()), SysOperLog::getTitle, operLog.getTitle())
|
||||||
.eq(operLog.getBusinessType() != null && operLog.getBusinessType() > 0,
|
.eq(operLog.getBusinessType() != null && operLog.getBusinessType() > 0,
|
||||||
SysOperLog::getBusinessType, operLog.getBusinessType())
|
SysOperLog::getBusinessType, operLog.getBusinessType())
|
||||||
@ -93,6 +94,7 @@ public class SysOperLogServiceImpl implements ISysOperLogService {
|
|||||||
public List<SysOperLog> selectOperLogList(SysOperLog operLog) {
|
public List<SysOperLog> selectOperLogList(SysOperLog operLog) {
|
||||||
Map<String, Object> params = operLog.getParams();
|
Map<String, Object> params = operLog.getParams();
|
||||||
return baseMapper.selectList(new LambdaQueryWrapper<SysOperLog>()
|
return baseMapper.selectList(new LambdaQueryWrapper<SysOperLog>()
|
||||||
|
.like(StringUtils.isNotBlank(operLog.getOperIp()), SysOperLog::getOperIp, operLog.getOperIp())
|
||||||
.like(StringUtils.isNotBlank(operLog.getTitle()), SysOperLog::getTitle, operLog.getTitle())
|
.like(StringUtils.isNotBlank(operLog.getTitle()), SysOperLog::getTitle, operLog.getTitle())
|
||||||
.eq(operLog.getBusinessType() != null && operLog.getBusinessType() > 0,
|
.eq(operLog.getBusinessType() != null && operLog.getBusinessType() > 0,
|
||||||
SysOperLog::getBusinessType, operLog.getBusinessType())
|
SysOperLog::getBusinessType, operLog.getBusinessType())
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user