mirror of
				https://github.com/dromara/RuoYi-Vue-Plus.git
				synced 2025-10-31 14:23:45 +08:00 
			
		
		
		
	Compare commits
	
		
			56 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 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.0" /> | ||||||
|         <option name="buildOnly" value="true" /> |         <option name="buildOnly" value="true" /> | ||||||
|         <option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" /> |         <option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" /> | ||||||
|       </settings> |       </settings> | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
|   <configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker"> |   <configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker"> | ||||||
|     <deployment type="dockerfile"> |     <deployment type="dockerfile"> | ||||||
|       <settings> |       <settings> | ||||||
|         <option name="imageTag" value="ruoyi/ruoyi-server:4.7.0" /> |         <option name="imageTag" value="ruoyi/ruoyi-server:4.8.0" /> | ||||||
|         <option name="buildOnly" value="true" /> |         <option name="buildOnly" value="true" /> | ||||||
|         <option name="sourceFilePath" value="ruoyi-admin/Dockerfile" /> |         <option name="sourceFilePath" value="ruoyi-admin/Dockerfile" /> | ||||||
|       </settings> |       </settings> | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
|   <configuration default="false" name="ruoyi-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.0" /> | ||||||
|         <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> | ||||||
|   | |||||||
| @@ -10,7 +10,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 +53,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 +125,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) | ||||||
|  |  | ||||||
| ## 加群与捐献 | ## 加群与捐献 | ||||||
|   | |||||||
							
								
								
									
										34
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								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.0</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.0</ruoyi-vue-plus.version> | ||||||
|         <spring-boot.version>2.7.11</spring-boot.version> |         <spring-boot.version>2.7.13</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,9 +22,9 @@ | |||||||
|         <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.1</easyexcel.version> | ||||||
|         <velocity.version>2.3</velocity.version> |         <velocity.version>2.3</velocity.version> | ||||||
|         <satoken.version>1.34.0</satoken.version> |         <satoken.version>1.35.0.RC</satoken.version> | ||||||
|         <mybatis-plus.version>3.5.3.1</mybatis-plus.version> |         <mybatis-plus.version>3.5.3.1</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.18</hutool.version> | ||||||
| @@ -46,8 +46,7 @@ | |||||||
|         <!-- OSS 配置 --> |         <!-- OSS 配置 --> | ||||||
|         <aws-java-sdk-s3.version>1.12.400</aws-java-sdk-s3.version> |         <aws-java-sdk-s3.version>1.12.400</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> | ||||||
| @@ -200,16 +199,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> | ||||||
| @@ -420,8 +414,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 +425,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.0</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)); | ||||||
|   | |||||||
| @@ -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)) { | ||||||
|   | |||||||
| @@ -158,14 +158,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 | ||||||
|   | |||||||
| @@ -161,14 +161,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 | ||||||
|   | |||||||
| @@ -104,8 +104,9 @@ 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 | ||||||
|   # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) |   # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) | ||||||
|   is-concurrent: true |   is-concurrent: true | ||||||
|   # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) |   # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) | ||||||
|   | |||||||
| @@ -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.0</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 ""; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -129,4 +129,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 | ||||||
|  */ |  */ | ||||||
|   | |||||||
| @@ -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,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,370 @@ | |||||||
|  | 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.annotation.ExcelProperty; | ||||||
|  | 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(); | ||||||
|  |         Field[] fields = writeWorkbookHolder.getClazz().getDeclaredFields(); | ||||||
|  |         Workbook workbook = writeWorkbookHolder.getWorkbook(); | ||||||
|  |         int length = fields.length; | ||||||
|  |         for (int i = 0; i < length; i++) { | ||||||
|  |             // 循环实体中的每个属性 | ||||||
|  |             // 可选的下拉值 | ||||||
|  |             List<String> options = new ArrayList<>(); | ||||||
|  |             if (fields[i].isAnnotationPresent(ExcelDictFormat.class)) { | ||||||
|  |                 // 如果指定了@ExcelDictFormat,则使用字典的逻辑 | ||||||
|  |                 ExcelDictFormat format = fields[i].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 (fields[i].isAnnotationPresent(ExcelEnumFormat.class)) { | ||||||
|  |                 // 否则如果指定了@ExcelEnumFormat,则使用枚举的逻辑 | ||||||
|  |                 ExcelEnumFormat format = fields[i].getDeclaredAnnotation(ExcelEnumFormat.class); | ||||||
|  |                 List<Object> values = EnumUtil.getFieldValues(format.enumClass(), format.textField()); | ||||||
|  |                 options = StreamUtils.toList(values, String::valueOf); | ||||||
|  |             } | ||||||
|  |             if (ObjectUtil.isNotEmpty(options)) { | ||||||
|  |                 // 仅当下拉可选项不为空时执行 | ||||||
|  |                 // 获取列下标,默认为当前循环次数 | ||||||
|  |                 int index = i; | ||||||
|  |                 if (fields[i].isAnnotationPresent(ExcelProperty.class)) { | ||||||
|  |                     // 如果指定了列下标,以指定的为主 | ||||||
|  |                     index = fields[i].getDeclaredAnnotation(ExcelProperty.class).index(); | ||||||
|  |                 } | ||||||
|  |                 if (options.size() > 20) { | ||||||
|  |                     // 这里限制如果可选项大于20,则使用额外表形式 | ||||||
|  |                     dropDownWithSheet(helper, workbook, sheet, index, options); | ||||||
|  |                 } else { | ||||||
|  |                     // 否则使用固定值形式 | ||||||
|  |                     dropDownWithSimple(helper, sheet, index, options); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         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 | ||||||
|  */ |  */ | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ public class StreamUtils { | |||||||
|         if (CollUtil.isEmpty(collection)) { |         if (CollUtil.isEmpty(collection)) { | ||||||
|             return CollUtil.newArrayList(); |             return CollUtil.newArrayList(); | ||||||
|         } |         } | ||||||
|  |         // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 | ||||||
|         return collection.stream().filter(function).collect(Collectors.toList()); |         return collection.stream().filter(function).collect(Collectors.toList()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -70,7 +71,8 @@ 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()); |         // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 | ||||||
|  |         return collection.stream().filter(Objects::nonNull).sorted(comparing).collect(Collectors.toList()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -87,7 +89,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 +108,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 +126,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 +147,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 +168,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))); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -188,6 +190,7 @@ public class StreamUtils { | |||||||
|             .stream() |             .stream() | ||||||
|             .map(function) |             .map(function) | ||||||
|             .filter(Objects::nonNull) |             .filter(Objects::nonNull) | ||||||
|  |             // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 | ||||||
|             .collect(Collectors.toList()); |             .collect(Collectors.toList()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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,10 @@ public class ExcelUtil { | |||||||
|             // 合并处理器 |             // 合并处理器 | ||||||
|             builder.registerWriteHandler(new CellMergeStrategy(list, true)); |             builder.registerWriteHandler(new CellMergeStrategy(list, true)); | ||||||
|         } |         } | ||||||
|  |         if (CollUtil.isNotEmpty(options)) { | ||||||
|  |             // 添加下拉框操作 | ||||||
|  |             builder.registerWriteHandler(new ExcelDownHandler(options)); | ||||||
|  |         } | ||||||
|         builder.doWrite(list); |         builder.doWrite(list); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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.0</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> | ||||||
|   | |||||||
| @@ -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 { | ||||||
|   | |||||||
| @@ -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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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.0</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.0</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.0</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.0</version> | ||||||
|     </parent> |     </parent> | ||||||
|     <modelVersion>4.0.0</modelVersion> |     <modelVersion>4.0.0</modelVersion> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ 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; | ||||||
| @@ -25,6 +26,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; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 操作日志记录处理 |  * 操作日志记录处理 | ||||||
| @@ -144,26 +146,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 +183,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 | ||||||
|   | |||||||
| @@ -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()); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -16,8 +16,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; | ||||||
| @@ -108,6 +110,26 @@ public class GlobalExceptionHandler { | |||||||
|         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(MissingPathVariableException.class) | ||||||
|  |     public R<Void> handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) { | ||||||
|  |         String requestURI = request.getRequestURI(); | ||||||
|  |         log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e); | ||||||
|  |         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, e); | ||||||
|  |         return R.fail(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), e.getValue())); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 拦截未知的运行时异常 |      * 拦截未知的运行时异常 | ||||||
|      */ |      */ | ||||||
|   | |||||||
| @@ -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.0</version> | ||||||
|     </parent> |     </parent> | ||||||
|     <modelVersion>4.0.0</modelVersion> |     <modelVersion>4.0.0</modelVersion> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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.0</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.0</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)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 获取文件元数据 |      * 获取文件元数据 | ||||||
|      * |      * | ||||||
|   | |||||||
| @@ -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.0</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.0</version> | ||||||
|     </parent> |     </parent> | ||||||
|     <modelVersion>4.0.0</modelVersion> |     <modelVersion>4.0.0</modelVersion> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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()); | ||||||
| @@ -301,25 +304,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); | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -116,7 +116,6 @@ 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.selectById(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); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| package com.ruoyi.system.service.impl; | package com.ruoyi.system.service.impl; | ||||||
|  |  | ||||||
|  | import cn.hutool.core.bean.BeanUtil; | ||||||
| import cn.hutool.core.convert.Convert; | import cn.hutool.core.convert.Convert; | ||||||
| import cn.hutool.core.io.IoUtil; | import cn.hutool.core.io.IoUtil; | ||||||
| import cn.hutool.core.util.ObjectUtil; | import cn.hutool.core.util.ObjectUtil; | ||||||
| @@ -11,7 +12,6 @@ import com.ruoyi.common.core.domain.PageQuery; | |||||||
| import com.ruoyi.common.core.page.TableDataInfo; | import com.ruoyi.common.core.page.TableDataInfo; | ||||||
| import com.ruoyi.common.core.service.OssService; | import com.ruoyi.common.core.service.OssService; | ||||||
| import com.ruoyi.common.exception.ServiceException; | import com.ruoyi.common.exception.ServiceException; | ||||||
| import com.ruoyi.common.utils.BeanCopyUtils; |  | ||||||
| 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 com.ruoyi.common.utils.spring.SpringUtils; | import com.ruoyi.common.utils.spring.SpringUtils; | ||||||
| @@ -31,9 +31,13 @@ import org.springframework.stereotype.Service; | |||||||
| 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.io.InputStream; | import java.io.InputStream; | ||||||
| import java.util.*; | import java.util.ArrayList; | ||||||
|  | import java.util.Collection; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
| import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -108,7 +112,7 @@ public class SysOssServiceImpl implements ISysOssService, OssService { | |||||||
|         } |         } | ||||||
|         FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName()); |         FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName()); | ||||||
|         response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8"); |         response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8"); | ||||||
|         OssClient storage = OssFactory.instance(); |         OssClient storage = OssFactory.instance(sysOss.getService()); | ||||||
|         try(InputStream inputStream = storage.getObjectContent(sysOss.getUrl())) { |         try(InputStream inputStream = storage.getObjectContent(sysOss.getUrl())) { | ||||||
|             int available = inputStream.available(); |             int available = inputStream.available(); | ||||||
|             IoUtil.copy(inputStream, response.getOutputStream(), available); |             IoUtil.copy(inputStream, response.getOutputStream(), available); | ||||||
| @@ -130,15 +134,28 @@ public class SysOssServiceImpl implements ISysOssService, OssService { | |||||||
|             throw new ServiceException(e.getMessage()); |             throw new ServiceException(e.getMessage()); | ||||||
|         } |         } | ||||||
|         // 保存文件信息 |         // 保存文件信息 | ||||||
|  |         return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public SysOssVo upload(File file) { | ||||||
|  |         String originalfileName = file.getName(); | ||||||
|  |         String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length()); | ||||||
|  |         OssClient storage = OssFactory.instance(); | ||||||
|  |         UploadResult uploadResult = storage.uploadSuffix(file, suffix); | ||||||
|  |         // 保存文件信息 | ||||||
|  |         return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private SysOssVo buildResultEntity(String originalfileName, String suffix, String configKey, UploadResult uploadResult) { | ||||||
|         SysOss oss = new SysOss(); |         SysOss oss = new SysOss(); | ||||||
|         oss.setUrl(uploadResult.getUrl()); |         oss.setUrl(uploadResult.getUrl()); | ||||||
|         oss.setFileSuffix(suffix); |         oss.setFileSuffix(suffix); | ||||||
|         oss.setFileName(uploadResult.getFilename()); |         oss.setFileName(uploadResult.getFilename()); | ||||||
|         oss.setOriginalName(originalfileName); |         oss.setOriginalName(originalfileName); | ||||||
|         oss.setService(storage.getConfigKey()); |         oss.setService(configKey); | ||||||
|         baseMapper.insert(oss); |         baseMapper.insert(oss); | ||||||
|         SysOssVo sysOssVo = new SysOssVo(); |         SysOssVo sysOssVo = BeanUtil.toBean(oss, SysOssVo.class); | ||||||
|         BeanCopyUtils.copy(oss, sysOssVo); |  | ||||||
|         return this.matchingUrl(sysOssVo); |         return this.matchingUrl(sysOssVo); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -186,6 +186,20 @@ public class SysRoleServiceImpl implements ISysRoleService { | |||||||
|         if (ObjectUtil.isNotNull(role.getRoleId()) && role.isAdmin()) { |         if (ObjectUtil.isNotNull(role.getRoleId()) && role.isAdmin()) { | ||||||
|             throw new ServiceException("不允许操作超级管理员角色"); |             throw new ServiceException("不允许操作超级管理员角色"); | ||||||
|         } |         } | ||||||
|  |         // 新增不允许使用 管理员标识符 | ||||||
|  |         if (ObjectUtil.isNull(role.getRoleId()) | ||||||
|  |             && StringUtils.equals(role.getRoleKey(), UserConstants.ADMIN_ROLE_KEY)) { | ||||||
|  |             throw new ServiceException("不允许使用系统内置管理员角色标识符!"); | ||||||
|  |         } | ||||||
|  |         // 修改不允许修改 管理员标识符 | ||||||
|  |         if (ObjectUtil.isNotNull(role.getRoleId())) { | ||||||
|  |             SysRole sysRole = baseMapper.selectById(role.getRoleId()); | ||||||
|  |             // 如果标识符不相等 判断为修改了管理员标识符 | ||||||
|  |             if (!StringUtils.equals(sysRole.getRoleKey(), role.getRoleKey()) | ||||||
|  |                 && StringUtils.equals(sysRole.getRoleKey(), UserConstants.ADMIN_ROLE_KEY)) { | ||||||
|  |                 throw new ServiceException("不允许修改系统内置管理员角色标识符!"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -342,9 +356,9 @@ public class SysRoleServiceImpl implements ISysRoleService { | |||||||
|     @Transactional(rollbackFor = Exception.class) |     @Transactional(rollbackFor = Exception.class) | ||||||
|     public int deleteRoleByIds(Long[] roleIds) { |     public int deleteRoleByIds(Long[] roleIds) { | ||||||
|         for (Long roleId : roleIds) { |         for (Long roleId : roleIds) { | ||||||
|             checkRoleAllowed(new SysRole(roleId)); |  | ||||||
|             checkRoleDataScope(roleId); |  | ||||||
|             SysRole role = selectRoleById(roleId); |             SysRole role = selectRoleById(roleId); | ||||||
|  |             checkRoleAllowed(role); | ||||||
|  |             checkRoleDataScope(roleId); | ||||||
|             if (countUserRoleByRoleId(roleId) > 0) { |             if (countUserRoleByRoleId(roleId) > 0) { | ||||||
|                 throw new ServiceException(String.format("%1$s已分配,不能删除", role.getRoleName())); |                 throw new ServiceException(String.format("%1$s已分配,不能删除", role.getRoleName())); | ||||||
|             } |             } | ||||||
| @@ -420,6 +434,11 @@ public class SysRoleServiceImpl implements ISysRoleService { | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void cleanOnlineUserByRole(Long roleId) { |     public void cleanOnlineUserByRole(Long roleId) { | ||||||
|  |         // 如果角色未绑定用户 直接返回 | ||||||
|  |         Long num = userRoleMapper.selectCount(new LambdaQueryWrapper<SysUserRole>().eq(SysUserRole::getRoleId, roleId)); | ||||||
|  |         if (num == 0) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|         List<String> keys = StpUtil.searchTokenValue("", 0, -1, false); |         List<String> keys = StpUtil.searchTokenValue("", 0, -1, false); | ||||||
|         if (CollUtil.isEmpty(keys)) { |         if (CollUtil.isEmpty(keys)) { | ||||||
|             return; |             return; | ||||||
| @@ -428,11 +447,11 @@ public class SysRoleServiceImpl implements ISysRoleService { | |||||||
|         keys.parallelStream().forEach(key -> { |         keys.parallelStream().forEach(key -> { | ||||||
|             String token = StringUtils.substringAfterLast(key, ":"); |             String token = StringUtils.substringAfterLast(key, ":"); | ||||||
|             // 如果已经过期则跳过 |             // 如果已经过期则跳过 | ||||||
|             if (StpUtil.stpLogic.getTokenActivityTimeoutByToken(token) < -1) { |             if (StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) < -1) { | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             LoginUser loginUser = LoginHelper.getLoginUser(token); |             LoginUser loginUser = LoginHelper.getLoginUser(token); | ||||||
|             if (loginUser.getRoles().stream().anyMatch(r -> r.getRoleId().equals(roleId))) { |             if (ObjectUtil.isNotNull(loginUser) && loginUser.getRoles().stream().anyMatch(r -> r.getRoleId().equals(roleId))) { | ||||||
|                 try { |                 try { | ||||||
|                     StpUtil.logoutByTokenValue(token); |                     StpUtil.logoutByTokenValue(token); | ||||||
|                 } catch (NotLoginException ignored) { |                 } catch (NotLoginException ignored) { | ||||||
|   | |||||||
							
								
								
									
										21
									
								
								ruoyi-ui-vue3/.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								ruoyi-ui-vue3/.editorconfig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | # 告诉EditorConfig插件,这是根文件,不用继续往上查找 | ||||||
|  | root = true | ||||||
|  |  | ||||||
|  | # 匹配全部文件 | ||||||
|  | [*] | ||||||
|  | # 缩进风格,可选space、tab | ||||||
|  | indent_style = space | ||||||
|  | # 缩进的空格数 | ||||||
|  | indent_size = 2 | ||||||
|  | # 设置字符集 | ||||||
|  | charset = utf-8 | ||||||
|  | # 结尾换行符,可选lf、cr、crlf | ||||||
|  | end_of_line = lf | ||||||
|  | # 在文件结尾插入新行 | ||||||
|  | trim_trailing_whitespace = true | ||||||
|  | # 删除一行中的前后空格 | ||||||
|  | insert_final_newline = true | ||||||
|  |  | ||||||
|  | [*.md] | ||||||
|  | insert_final_newline = false | ||||||
|  | trim_trailing_whitespace = false | ||||||
							
								
								
									
										17
									
								
								ruoyi-ui-vue3/.env.development
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								ruoyi-ui-vue3/.env.development
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | # 页面标题 | ||||||
|  | VITE_APP_TITLE = RuoYi-Vue-Plus后台管理系统 | ||||||
|  |  | ||||||
|  | # 开发环境配置 | ||||||
|  | VITE_APP_ENV = 'development' | ||||||
|  |  | ||||||
|  | # 若依管理系统/开发环境 | ||||||
|  | VITE_APP_BASE_API = '/dev-api' | ||||||
|  |  | ||||||
|  | # 应用访问路径 例如使用前缀 /admin/ | ||||||
|  | VITE_APP_CONTEXT_PATH = '/' | ||||||
|  |  | ||||||
|  | # 监控地址 | ||||||
|  | VITE_APP_MONITRO_ADMIN = 'http://localhost:9090/admin/applications' | ||||||
|  |  | ||||||
|  | # xxl-job 控制台地址 | ||||||
|  | VITE_APP_XXL_JOB_ADMIN = 'http://localhost:9100/xxl-job-admin' | ||||||
							
								
								
									
										20
									
								
								ruoyi-ui-vue3/.env.production
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								ruoyi-ui-vue3/.env.production
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | # 页面标题 | ||||||
|  | VITE_APP_TITLE = RuoYi-Vue-Plus后台管理系统 | ||||||
|  |  | ||||||
|  | # 生产环境配置 | ||||||
|  | VITE_APP_ENV = 'production' | ||||||
|  |  | ||||||
|  | # 应用访问路径 例如使用前缀 /admin/ | ||||||
|  | VITE_APP_CONTEXT_PATH = '/' | ||||||
|  |  | ||||||
|  | # 监控地址 | ||||||
|  | VITE_APP_MONITRO_ADMIN = '/admin/applications' | ||||||
|  |  | ||||||
|  | # 监控地址 | ||||||
|  | VITE_APP_XXL_JOB_ADMIN = '/xxl-job-admin' | ||||||
|  |  | ||||||
|  | # 若依管理系统/生产环境 | ||||||
|  | VITE_APP_BASE_API = '/prod-api' | ||||||
|  |  | ||||||
|  | # 是否在打包时开启压缩,支持 gzip 和 brotli | ||||||
|  | VITE_BUILD_COMPRESS = gzip | ||||||
							
								
								
									
										23
									
								
								ruoyi-ui-vue3/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								ruoyi-ui-vue3/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | .DS_Store | ||||||
|  | node_modules/ | ||||||
|  | dist/ | ||||||
|  | npm-debug.log* | ||||||
|  | yarn-debug.log* | ||||||
|  | yarn-error.log* | ||||||
|  | **/*.log | ||||||
|  |  | ||||||
|  | tests/**/coverage/ | ||||||
|  | tests/e2e/reports | ||||||
|  | selenium-debug.log | ||||||
|  |  | ||||||
|  | # Editor directories and files | ||||||
|  | .idea | ||||||
|  | .vscode | ||||||
|  | *.suo | ||||||
|  | *.ntvs* | ||||||
|  | *.njsproj | ||||||
|  | *.sln | ||||||
|  | *.local | ||||||
|  |  | ||||||
|  | package-lock.json | ||||||
|  | yarn.lock | ||||||
							
								
								
									
										83
									
								
								ruoyi-ui-vue3/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								ruoyi-ui-vue3/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | |||||||
|  | ## 平台简介 | ||||||
|  |  | ||||||
|  | * 本仓库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) 版本。 | ||||||
|  | * 配套后端代码仓库地址[RuoYi-Vue-Plus 4.X(注意版本号)](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus) | ||||||
|  | * 5.X后端需要使用此项目 [plus-ui](https://gitee.com/JavaLionLi/plus-ui) | ||||||
|  |  | ||||||
|  | ## 前端运行 | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | # 进入项目目录 | ||||||
|  | cd ruoyi-ui-vue3 | ||||||
|  |  | ||||||
|  | # 安装依赖 | ||||||
|  | npm install --registry=https://registry.npmmirror.com | ||||||
|  |  | ||||||
|  | # 启动服务 | ||||||
|  | npm run dev | ||||||
|  |  | ||||||
|  | # 构建测试环境 yarn build:stage | ||||||
|  | # 构建生产环境 yarn build:prod | ||||||
|  | # 前端访问地址 http://localhost:80 | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## 后端改造 | ||||||
|  | 参考后端代码内 `ruoyi-generator/resources/vm/vue/v3/readme.txt` 说明 | ||||||
|  |  | ||||||
|  | ## 内置功能 | ||||||
|  |  | ||||||
|  | 1.  用户管理:用户是系统操作者,该功能主要完成系统用户配置。 | ||||||
|  | 2.  部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。 | ||||||
|  | 3.  岗位管理:配置系统用户所属担任职务。 | ||||||
|  | 4.  菜单管理:配置系统菜单,操作权限,按钮权限标识等。 | ||||||
|  | 5.  角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。 | ||||||
|  | 6.  字典管理:对系统中经常使用的一些较为固定的数据进行维护。 | ||||||
|  | 7.  参数管理:对系统动态配置常用参数。 | ||||||
|  | 8.  通知公告:系统通知公告信息发布维护。 | ||||||
|  | 9.  操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。 | ||||||
|  | 10. 登录日志:系统登录日志记录查询包含登录异常。 | ||||||
|  | 11. 在线用户:当前系统中活跃用户状态监控。 | ||||||
|  | 12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。 | ||||||
|  | 13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。 | ||||||
|  | 14. 系统接口:根据业务代码自动生成相关的api接口文档。 | ||||||
|  | 15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。 | ||||||
|  | 16. 缓存监控:对系统的缓存信息查询,命令统计等。 | ||||||
|  | 17. 在线构建器:拖动表单元素生成相应的HTML代码。 | ||||||
|  | 18. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。 | ||||||
|  |  | ||||||
|  | ## 演示图 | ||||||
|  |  | ||||||
|  | <table> | ||||||
|  |     <tr> | ||||||
|  |         <td><img src="https://oscimg.oschina.net/oscnet/cd1f90be5f2684f4560c9519c0f2a232ee8.jpg"/></td> | ||||||
|  |         <td><img src="https://oscimg.oschina.net/oscnet/1cbcf0e6f257c7d3a063c0e3f2ff989e4b3.jpg"/></td> | ||||||
|  |     </tr> | ||||||
|  |     <tr> | ||||||
|  |         <td><img src="https://oscimg.oschina.net/oscnet/up-8074972883b5ba0622e13246738ebba237a.png"/></td> | ||||||
|  |         <td><img src="https://oscimg.oschina.net/oscnet/up-9f88719cdfca9af2e58b352a20e23d43b12.png"/></td> | ||||||
|  |     </tr> | ||||||
|  |     <tr> | ||||||
|  |         <td><img src="https://oscimg.oschina.net/oscnet/up-39bf2584ec3a529b0d5a3b70d15c9b37646.png"/></td> | ||||||
|  |         <td><img src="https://oscimg.oschina.net/oscnet/up-936ec82d1f4872e1bc980927654b6007307.png"/></td> | ||||||
|  |     </tr> | ||||||
|  | 	<tr> | ||||||
|  |         <td><img src="https://oscimg.oschina.net/oscnet/up-b2d62ceb95d2dd9b3fbe157bb70d26001e9.png"/></td> | ||||||
|  |         <td><img src="https://oscimg.oschina.net/oscnet/up-d67451d308b7a79ad6819723396f7c3d77a.png"/></td> | ||||||
|  |     </tr>	  | ||||||
|  |     <tr> | ||||||
|  |         <td><img src="https://oscimg.oschina.net/oscnet/5e8c387724954459291aafd5eb52b456f53.jpg"/></td> | ||||||
|  |         <td><img src="https://oscimg.oschina.net/oscnet/644e78da53c2e92a95dfda4f76e6d117c4b.jpg"/></td> | ||||||
|  |     </tr> | ||||||
|  | 	<tr> | ||||||
|  |         <td><img src="https://oscimg.oschina.net/oscnet/up-8370a0d02977eebf6dbf854c8450293c937.png"/></td> | ||||||
|  |         <td><img src="https://oscimg.oschina.net/oscnet/up-49003ed83f60f633e7153609a53a2b644f7.png"/></td> | ||||||
|  |     </tr> | ||||||
|  | 	<tr> | ||||||
|  |         <td><img src="https://oscimg.oschina.net/oscnet/up-d4fe726319ece268d4746602c39cffc0621.png"/></td> | ||||||
|  |         <td><img src="https://oscimg.oschina.net/oscnet/up-c195234bbcd30be6927f037a6755e6ab69c.png"/></td> | ||||||
|  |     </tr> | ||||||
|  |     <tr> | ||||||
|  |         <td><img src="https://oscimg.oschina.net/oscnet/b6115bc8c31de52951982e509930b20684a.jpg"/></td> | ||||||
|  |         <td><img src="https://oscimg.oschina.net/oscnet/up-5e4daac0bb59612c5038448acbcef235e3a.png"/></td> | ||||||
|  |     </tr> | ||||||
|  | </table> | ||||||
							
								
								
									
										12
									
								
								ruoyi-ui-vue3/bin/build.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								ruoyi-ui-vue3/bin/build.bat
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | @echo off | ||||||
|  | echo. | ||||||
|  | echo [<5B><>Ϣ] <20><><EFBFBD><EFBFBD>Web<65><62><EFBFBD>̣<EFBFBD><CCA3><EFBFBD><EFBFBD><EFBFBD>dist<73>ļ<EFBFBD><C4BC><EFBFBD> | ||||||
|  | echo. | ||||||
|  |  | ||||||
|  | %~d0 | ||||||
|  | cd %~dp0 | ||||||
|  |  | ||||||
|  | cd .. | ||||||
|  | yarn build:prod | ||||||
|  |  | ||||||
|  | pause | ||||||
							
								
								
									
										12
									
								
								ruoyi-ui-vue3/bin/package.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								ruoyi-ui-vue3/bin/package.bat
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | @echo off | ||||||
|  | echo. | ||||||
|  | echo [<5B><>Ϣ] <20><>װWeb<65><62><EFBFBD>̣<EFBFBD><CCA3><EFBFBD><EFBFBD><EFBFBD>node_modules<65>ļ<EFBFBD><C4BC><EFBFBD> | ||||||
|  | echo. | ||||||
|  |  | ||||||
|  | %~d0 | ||||||
|  | cd %~dp0 | ||||||
|  |  | ||||||
|  | cd .. | ||||||
|  | yarn --registry=https://registry.npmmirror.com | ||||||
|  |  | ||||||
|  | pause | ||||||
							
								
								
									
										12
									
								
								ruoyi-ui-vue3/bin/run-web.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								ruoyi-ui-vue3/bin/run-web.bat
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | @echo off | ||||||
|  | echo. | ||||||
|  | echo [<5B><>Ϣ] ʹ<><CAB9> Vite <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Web <20><><EFBFBD>̡<EFBFBD> | ||||||
|  | echo. | ||||||
|  |  | ||||||
|  | %~d0 | ||||||
|  | cd %~dp0 | ||||||
|  |  | ||||||
|  | cd .. | ||||||
|  | yarn dev | ||||||
|  |  | ||||||
|  | pause | ||||||
							
								
								
									
										46
									
								
								ruoyi-ui-vue3/html/ie.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								ruoyi-ui-vue3/html/ie.html
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										215
									
								
								ruoyi-ui-vue3/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								ruoyi-ui-vue3/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,215 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html> | ||||||
|  |  | ||||||
|  | <head> | ||||||
|  |   <meta charset="utf-8"> | ||||||
|  |   <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | ||||||
|  |   <meta name="renderer" content="webkit"> | ||||||
|  |   <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> | ||||||
|  |   <link rel="icon" href="/favicon.ico"> | ||||||
|  |   <title>RuoYi-Vue-Plus管理系统</title> | ||||||
|  |   <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]--> | ||||||
|  |   <style> | ||||||
|  |     html, | ||||||
|  |     body, | ||||||
|  |     #app { | ||||||
|  |       height: 100%; | ||||||
|  |       margin: 0px; | ||||||
|  |       padding: 0px; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     .chromeframe { | ||||||
|  |       margin: 0.2em 0; | ||||||
|  |       background: #ccc; | ||||||
|  |       color: #000; | ||||||
|  |       padding: 0.2em 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #loader-wrapper { | ||||||
|  |       position: fixed; | ||||||
|  |       top: 0; | ||||||
|  |       left: 0; | ||||||
|  |       width: 100%; | ||||||
|  |       height: 100%; | ||||||
|  |       z-index: 999999; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #loader { | ||||||
|  |       display: block; | ||||||
|  |       position: relative; | ||||||
|  |       left: 50%; | ||||||
|  |       top: 50%; | ||||||
|  |       width: 150px; | ||||||
|  |       height: 150px; | ||||||
|  |       margin: -75px 0 0 -75px; | ||||||
|  |       border-radius: 50%; | ||||||
|  |       border: 3px solid transparent; | ||||||
|  |       border-top-color: #FFF; | ||||||
|  |       -webkit-animation: spin 2s linear infinite; | ||||||
|  |       -ms-animation: spin 2s linear infinite; | ||||||
|  |       -moz-animation: spin 2s linear infinite; | ||||||
|  |       -o-animation: spin 2s linear infinite; | ||||||
|  |       animation: spin 2s linear infinite; | ||||||
|  |       z-index: 1001; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #loader:before { | ||||||
|  |       content: ""; | ||||||
|  |       position: absolute; | ||||||
|  |       top: 5px; | ||||||
|  |       left: 5px; | ||||||
|  |       right: 5px; | ||||||
|  |       bottom: 5px; | ||||||
|  |       border-radius: 50%; | ||||||
|  |       border: 3px solid transparent; | ||||||
|  |       border-top-color: #FFF; | ||||||
|  |       -webkit-animation: spin 3s linear infinite; | ||||||
|  |       -moz-animation: spin 3s linear infinite; | ||||||
|  |       -o-animation: spin 3s linear infinite; | ||||||
|  |       -ms-animation: spin 3s linear infinite; | ||||||
|  |       animation: spin 3s linear infinite; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #loader:after { | ||||||
|  |       content: ""; | ||||||
|  |       position: absolute; | ||||||
|  |       top: 15px; | ||||||
|  |       left: 15px; | ||||||
|  |       right: 15px; | ||||||
|  |       bottom: 15px; | ||||||
|  |       border-radius: 50%; | ||||||
|  |       border: 3px solid transparent; | ||||||
|  |       border-top-color: #FFF; | ||||||
|  |       -moz-animation: spin 1.5s linear infinite; | ||||||
|  |       -o-animation: spin 1.5s linear infinite; | ||||||
|  |       -ms-animation: spin 1.5s linear infinite; | ||||||
|  |       -webkit-animation: spin 1.5s linear infinite; | ||||||
|  |       animation: spin 1.5s linear infinite; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @-webkit-keyframes spin { | ||||||
|  |       0% { | ||||||
|  |         -webkit-transform: rotate(0deg); | ||||||
|  |         -ms-transform: rotate(0deg); | ||||||
|  |         transform: rotate(0deg); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       100% { | ||||||
|  |         -webkit-transform: rotate(360deg); | ||||||
|  |         -ms-transform: rotate(360deg); | ||||||
|  |         transform: rotate(360deg); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @keyframes spin { | ||||||
|  |       0% { | ||||||
|  |         -webkit-transform: rotate(0deg); | ||||||
|  |         -ms-transform: rotate(0deg); | ||||||
|  |         transform: rotate(0deg); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       100% { | ||||||
|  |         -webkit-transform: rotate(360deg); | ||||||
|  |         -ms-transform: rotate(360deg); | ||||||
|  |         transform: rotate(360deg); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     #loader-wrapper .loader-section { | ||||||
|  |       position: fixed; | ||||||
|  |       top: 0; | ||||||
|  |       width: 51%; | ||||||
|  |       height: 100%; | ||||||
|  |       background: #7171C6; | ||||||
|  |       z-index: 1000; | ||||||
|  |       -webkit-transform: translateX(0); | ||||||
|  |       -ms-transform: translateX(0); | ||||||
|  |       transform: translateX(0); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #loader-wrapper .loader-section.section-left { | ||||||
|  |       left: 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #loader-wrapper .loader-section.section-right { | ||||||
|  |       right: 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     .loaded #loader-wrapper .loader-section.section-left { | ||||||
|  |       -webkit-transform: translateX(-100%); | ||||||
|  |       -ms-transform: translateX(-100%); | ||||||
|  |       transform: translateX(-100%); | ||||||
|  |       -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000); | ||||||
|  |       transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     .loaded #loader-wrapper .loader-section.section-right { | ||||||
|  |       -webkit-transform: translateX(100%); | ||||||
|  |       -ms-transform: translateX(100%); | ||||||
|  |       transform: translateX(100%); | ||||||
|  |       -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000); | ||||||
|  |       transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     .loaded #loader { | ||||||
|  |       opacity: 0; | ||||||
|  |       -webkit-transition: all 0.3s ease-out; | ||||||
|  |       transition: all 0.3s ease-out; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     .loaded #loader-wrapper { | ||||||
|  |       visibility: hidden; | ||||||
|  |       -webkit-transform: translateY(-100%); | ||||||
|  |       -ms-transform: translateY(-100%); | ||||||
|  |       transform: translateY(-100%); | ||||||
|  |       -webkit-transition: all 0.3s 1s ease-out; | ||||||
|  |       transition: all 0.3s 1s ease-out; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     .no-js #loader-wrapper { | ||||||
|  |       display: none; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     .no-js h1 { | ||||||
|  |       color: #222222; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #loader-wrapper .load_title { | ||||||
|  |       font-family: 'Open Sans'; | ||||||
|  |       color: #FFF; | ||||||
|  |       font-size: 19px; | ||||||
|  |       width: 100%; | ||||||
|  |       text-align: center; | ||||||
|  |       z-index: 9999999999999; | ||||||
|  |       position: absolute; | ||||||
|  |       top: 60%; | ||||||
|  |       opacity: 1; | ||||||
|  |       line-height: 30px; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #loader-wrapper .load_title span { | ||||||
|  |       font-weight: normal; | ||||||
|  |       font-style: italic; | ||||||
|  |       font-size: 13px; | ||||||
|  |       color: #FFF; | ||||||
|  |       opacity: 0.5; | ||||||
|  |     } | ||||||
|  |   </style> | ||||||
|  | </head> | ||||||
|  |  | ||||||
|  | <body> | ||||||
|  |   <div id="app"> | ||||||
|  |     <div id="loader-wrapper"> | ||||||
|  |       <div id="loader"></div> | ||||||
|  |       <div class="loader-section section-left"></div> | ||||||
|  |       <div class="loader-section section-right"></div> | ||||||
|  |       <div class="load_title">正在加载系统资源,请耐心等待</div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |   <script type="module" src="/src/main.js"></script> | ||||||
|  | </body> | ||||||
|  |  | ||||||
|  | </html> | ||||||
							
								
								
									
										43
									
								
								ruoyi-ui-vue3/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								ruoyi-ui-vue3/package.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | { | ||||||
|  |   "name": "ruoyi-vue-plus", | ||||||
|  |   "version": "4.8.0", | ||||||
|  |   "description": "RuoYi-Vue-Plus后台管理系统", | ||||||
|  |   "author": "LionLi", | ||||||
|  |   "license": "MIT", | ||||||
|  |   "scripts": { | ||||||
|  |     "dev": "vite", | ||||||
|  |     "build:prod": "vite build", | ||||||
|  |     "preview": "vite preview" | ||||||
|  |   }, | ||||||
|  |   "repository": { | ||||||
|  |     "type": "git", | ||||||
|  |     "url": "https://gitee.com/JavaLionLi/RuoYi-Vue-Plus-UI.git" | ||||||
|  |   }, | ||||||
|  |   "dependencies": { | ||||||
|  |     "@element-plus/icons-vue": "2.0.10", | ||||||
|  |     "@vueup/vue-quill": "1.1.0", | ||||||
|  |     "@vueuse/core": "9.5.0", | ||||||
|  |     "axios": "0.27.2", | ||||||
|  |     "echarts": "5.4.0", | ||||||
|  |     "element-plus": "2.2.27", | ||||||
|  |     "file-saver": "2.0.5", | ||||||
|  |     "fuse.js": "6.6.2", | ||||||
|  |     "js-cookie": "3.0.1", | ||||||
|  |     "jsencrypt": "3.3.1", | ||||||
|  |     "nprogress": "0.2.0", | ||||||
|  |     "pinia": "2.0.22", | ||||||
|  |     "vue": "3.2.45", | ||||||
|  |     "vue-cropper": "1.0.3", | ||||||
|  |     "vue-router": "4.1.4" | ||||||
|  |   }, | ||||||
|  |   "devDependencies": { | ||||||
|  |     "@vitejs/plugin-vue": "3.1.0", | ||||||
|  |     "@vue/compiler-sfc": "3.2.45", | ||||||
|  |     "sass": "1.56.1", | ||||||
|  |     "unplugin-auto-import": "0.11.4", | ||||||
|  |     "vite": "3.2.3", | ||||||
|  |     "vite-plugin-compression": "0.5.1", | ||||||
|  |     "vite-plugin-svg-icons": "2.0.1", | ||||||
|  |     "vite-plugin-vue-setup-extend": "0.4.0" | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								ruoyi-ui-vue3/public/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								ruoyi-ui-vue3/public/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 7.9 KiB | 
							
								
								
									
										15
									
								
								ruoyi-ui-vue3/src/App.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								ruoyi-ui-vue3/src/App.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | <template> | ||||||
|  |   <router-view /> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script setup> | ||||||
|  | import useSettingsStore from '@/store/modules/settings' | ||||||
|  | import { handleThemeStyle } from '@/utils/theme' | ||||||
|  |  | ||||||
|  | onMounted(() => { | ||||||
|  |   nextTick(() => { | ||||||
|  |     // 初始化主题样式 | ||||||
|  |     handleThemeStyle(useSettingsStore().theme) | ||||||
|  |   }) | ||||||
|  | }) | ||||||
|  | </script> | ||||||
							
								
								
									
										54
									
								
								ruoyi-ui-vue3/src/api/demo/demo.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								ruoyi-ui-vue3/src/api/demo/demo.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | import request from '@/utils/request' | ||||||
|  |  | ||||||
|  | // 查询测试单表列表 | ||||||
|  | export function listDemo(query) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/demo/demo/list', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 自定义分页接口 | ||||||
|  | export function pageDemo(query) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/demo/demo/page', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询测试单表详细 | ||||||
|  | export function getDemo(id) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/demo/demo/' + id, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 新增测试单表 | ||||||
|  | export function addDemo(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/demo/demo', | ||||||
|  |     method: 'post', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 修改测试单表 | ||||||
|  | export function updateDemo(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/demo/demo', | ||||||
|  |     method: 'put', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 删除测试单表 | ||||||
|  | export function delDemo(id) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/demo/demo/' + id, | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										44
									
								
								ruoyi-ui-vue3/src/api/demo/tree.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								ruoyi-ui-vue3/src/api/demo/tree.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | import request from '@/utils/request' | ||||||
|  |  | ||||||
|  | // 查询测试树表列表 | ||||||
|  | export function listTree(query) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/demo/tree/list', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询测试树表详细 | ||||||
|  | export function getTree(id) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/demo/tree/' + id, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 新增测试树表 | ||||||
|  | export function addTree(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/demo/tree', | ||||||
|  |     method: 'post', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 修改测试树表 | ||||||
|  | export function updateTree(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/demo/tree', | ||||||
|  |     method: 'put', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 删除测试树表 | ||||||
|  | export function delTree(id) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/demo/tree/' + id, | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										59
									
								
								ruoyi-ui-vue3/src/api/login.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								ruoyi-ui-vue3/src/api/login.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | import request from '@/utils/request' | ||||||
|  |  | ||||||
|  | // 登录方法 | ||||||
|  | export function login(username, password, code, uuid) { | ||||||
|  |   const data = { | ||||||
|  |     username, | ||||||
|  |     password, | ||||||
|  |     code, | ||||||
|  |     uuid | ||||||
|  |   } | ||||||
|  |   return request({ | ||||||
|  |     url: '/login', | ||||||
|  |     headers: { | ||||||
|  |       isToken: false | ||||||
|  |     }, | ||||||
|  |     method: 'post', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 注册方法 | ||||||
|  | export function register(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/register', | ||||||
|  |     headers: { | ||||||
|  |       isToken: false | ||||||
|  |     }, | ||||||
|  |     method: 'post', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 获取用户详细信息 | ||||||
|  | export function getInfo() { | ||||||
|  |   return request({ | ||||||
|  |     url: '/getInfo', | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 退出方法 | ||||||
|  | export function logout() { | ||||||
|  |   return request({ | ||||||
|  |     url: '/logout', | ||||||
|  |     method: 'post' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 获取验证码 | ||||||
|  | export function getCodeImg() { | ||||||
|  |   return request({ | ||||||
|  |     url: '/captchaImage', | ||||||
|  |     headers: { | ||||||
|  |       isToken: false | ||||||
|  |     }, | ||||||
|  |     method: 'get', | ||||||
|  |     timeout: 20000 | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										9
									
								
								ruoyi-ui-vue3/src/api/menu.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								ruoyi-ui-vue3/src/api/menu.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | import request from '@/utils/request' | ||||||
|  |  | ||||||
|  | // 获取路由 | ||||||
|  | export const getRouters = () => { | ||||||
|  |   return request({ | ||||||
|  |     url: '/getRouters', | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										57
									
								
								ruoyi-ui-vue3/src/api/monitor/cache.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								ruoyi-ui-vue3/src/api/monitor/cache.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | |||||||
|  | import request from '@/utils/request' | ||||||
|  |  | ||||||
|  | // 查询缓存详细 | ||||||
|  | export function getCache() { | ||||||
|  |   return request({ | ||||||
|  |     url: '/monitor/cache', | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询缓存名称列表 | ||||||
|  | export function listCacheName() { | ||||||
|  |   return request({ | ||||||
|  |     url: '/monitor/cache/getNames', | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询缓存键名列表 | ||||||
|  | export function listCacheKey(cacheName) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/monitor/cache/getKeys/' + cacheName, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询缓存内容 | ||||||
|  | export function getCacheValue(cacheName, cacheKey) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 清理指定名称缓存 | ||||||
|  | export function clearCacheName(cacheName) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/monitor/cache/clearCacheName/' + cacheName, | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 清理指定键名缓存 | ||||||
|  | export function clearCacheKey(cacheName, cacheKey) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/monitor/cache/clearCacheKey/' + cacheName + '/'  + cacheKey, | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 清理全部缓存 | ||||||
|  | export function clearCacheAll() { | ||||||
|  |   return request({ | ||||||
|  |     url: '/monitor/cache/clearCacheAll', | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								ruoyi-ui-vue3/src/api/monitor/logininfor.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								ruoyi-ui-vue3/src/api/monitor/logininfor.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | import request from '@/utils/request' | ||||||
|  |  | ||||||
|  | // 查询登录日志列表 | ||||||
|  | export function list(query) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/monitor/logininfor/list', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 删除登录日志 | ||||||
|  | export function delLogininfor(infoId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/monitor/logininfor/' + infoId, | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 解锁用户登录状态 | ||||||
|  | export function unlockLogininfor(userName) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/monitor/logininfor/unlock/' + userName, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 清空登录日志 | ||||||
|  | export function cleanLogininfor() { | ||||||
|  |   return request({ | ||||||
|  |     url: '/monitor/logininfor/clean', | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								ruoyi-ui-vue3/src/api/monitor/online.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								ruoyi-ui-vue3/src/api/monitor/online.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | import request from '@/utils/request' | ||||||
|  |  | ||||||
|  | // 查询在线用户列表 | ||||||
|  | export function list(query) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/monitor/online/list', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 强退用户 | ||||||
|  | export function forceLogout(tokenId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/monitor/online/' + tokenId, | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								ruoyi-ui-vue3/src/api/monitor/operlog.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								ruoyi-ui-vue3/src/api/monitor/operlog.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | import request from '@/utils/request' | ||||||
|  |  | ||||||
|  | // 查询操作日志列表 | ||||||
|  | export function list(query) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/monitor/operlog/list', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 删除操作日志 | ||||||
|  | export function delOperlog(operId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/monitor/operlog/' + operId, | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 清空操作日志 | ||||||
|  | export function cleanOperlog() { | ||||||
|  |   return request({ | ||||||
|  |     url: '/monitor/operlog/clean', | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										72
									
								
								ruoyi-ui-vue3/src/api/system/config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								ruoyi-ui-vue3/src/api/system/config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | import request from '@/utils/request' | ||||||
|  |  | ||||||
|  | // 查询参数列表 | ||||||
|  | export function listConfig(query) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/config/list', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询参数详细 | ||||||
|  | export function getConfig(configId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/config/' + configId, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 根据参数键名查询参数值 | ||||||
|  | export function getConfigKey(configKey) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/config/configKey/' + configKey, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 新增参数配置 | ||||||
|  | export function addConfig(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/config', | ||||||
|  |     method: 'post', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 修改参数配置 | ||||||
|  | export function updateConfig(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/config', | ||||||
|  |     method: 'put', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 修改参数配置 | ||||||
|  | export function updateConfigByKey(key, value) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/config/updateByKey', | ||||||
|  |     method: 'put', | ||||||
|  |     data: { | ||||||
|  |       configKey: key, | ||||||
|  |       configValue: value | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 删除参数配置 | ||||||
|  | export function delConfig(configId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/config/' + configId, | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 刷新参数缓存 | ||||||
|  | export function refreshCache() { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/config/refreshCache', | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										52
									
								
								ruoyi-ui-vue3/src/api/system/dept.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								ruoyi-ui-vue3/src/api/system/dept.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | import request from '@/utils/request' | ||||||
|  |  | ||||||
|  | // 查询部门列表 | ||||||
|  | export function listDept(query) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/dept/list', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询部门列表(排除节点) | ||||||
|  | export function listDeptExcludeChild(deptId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/dept/list/exclude/' + deptId, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询部门详细 | ||||||
|  | export function getDept(deptId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/dept/' + deptId, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 新增部门 | ||||||
|  | export function addDept(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/dept', | ||||||
|  |     method: 'post', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 修改部门 | ||||||
|  | export function updateDept(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/dept', | ||||||
|  |     method: 'put', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 删除部门 | ||||||
|  | export function delDept(deptId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/dept/' + deptId, | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										52
									
								
								ruoyi-ui-vue3/src/api/system/dict/data.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								ruoyi-ui-vue3/src/api/system/dict/data.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | import request from '@/utils/request' | ||||||
|  |  | ||||||
|  | // 查询字典数据列表 | ||||||
|  | export function listData(query) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/dict/data/list', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询字典数据详细 | ||||||
|  | export function getData(dictCode) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/dict/data/' + dictCode, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 根据字典类型查询字典数据信息 | ||||||
|  | export function getDicts(dictType) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/dict/data/type/' + dictType, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 新增字典数据 | ||||||
|  | export function addData(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/dict/data', | ||||||
|  |     method: 'post', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 修改字典数据 | ||||||
|  | export function updateData(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/dict/data', | ||||||
|  |     method: 'put', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 删除字典数据 | ||||||
|  | export function delData(dictCode) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/dict/data/' + dictCode, | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										60
									
								
								ruoyi-ui-vue3/src/api/system/dict/type.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								ruoyi-ui-vue3/src/api/system/dict/type.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | import request from '@/utils/request' | ||||||
|  |  | ||||||
|  | // 查询字典类型列表 | ||||||
|  | export function listType(query) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/dict/type/list', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询字典类型详细 | ||||||
|  | export function getType(dictId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/dict/type/' + dictId, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 新增字典类型 | ||||||
|  | export function addType(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/dict/type', | ||||||
|  |     method: 'post', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 修改字典类型 | ||||||
|  | export function updateType(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/dict/type', | ||||||
|  |     method: 'put', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 删除字典类型 | ||||||
|  | export function delType(dictId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/dict/type/' + dictId, | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 刷新字典缓存 | ||||||
|  | export function refreshCache() { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/dict/type/refreshCache', | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 获取字典选择框列表 | ||||||
|  | export function optionselect() { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/dict/type/optionselect', | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										60
									
								
								ruoyi-ui-vue3/src/api/system/menu.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								ruoyi-ui-vue3/src/api/system/menu.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | import request from '@/utils/request' | ||||||
|  |  | ||||||
|  | // 查询菜单列表 | ||||||
|  | export function listMenu(query) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/menu/list', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询菜单详细 | ||||||
|  | export function getMenu(menuId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/menu/' + menuId, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询菜单下拉树结构 | ||||||
|  | export function treeselect() { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/menu/treeselect', | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 根据角色ID查询菜单下拉树结构 | ||||||
|  | export function roleMenuTreeselect(roleId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/menu/roleMenuTreeselect/' + roleId, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 新增菜单 | ||||||
|  | export function addMenu(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/menu', | ||||||
|  |     method: 'post', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 修改菜单 | ||||||
|  | export function updateMenu(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/menu', | ||||||
|  |     method: 'put', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 删除菜单 | ||||||
|  | export function delMenu(menuId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/menu/' + menuId, | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										44
									
								
								ruoyi-ui-vue3/src/api/system/notice.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								ruoyi-ui-vue3/src/api/system/notice.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | import request from '@/utils/request' | ||||||
|  |  | ||||||
|  | // 查询公告列表 | ||||||
|  | export function listNotice(query) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/notice/list', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询公告详细 | ||||||
|  | export function getNotice(noticeId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/notice/' + noticeId, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 新增公告 | ||||||
|  | export function addNotice(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/notice', | ||||||
|  |     method: 'post', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 修改公告 | ||||||
|  | export function updateNotice(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/notice', | ||||||
|  |     method: 'put', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 删除公告 | ||||||
|  | export function delNotice(noticeId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/notice/' + noticeId, | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								ruoyi-ui-vue3/src/api/system/oss.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								ruoyi-ui-vue3/src/api/system/oss.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | import request from '@/utils/request' | ||||||
|  |  | ||||||
|  | // 查询OSS对象存储列表 | ||||||
|  | export function listOss(query) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/oss/list', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询OSS对象基于id串 | ||||||
|  | export function listByIds(ossId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/oss/listByIds/' + ossId, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 删除OSS对象存储 | ||||||
|  | export function delOss(ossId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/oss/' + ossId, | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										58
									
								
								ruoyi-ui-vue3/src/api/system/ossConfig.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								ruoyi-ui-vue3/src/api/system/ossConfig.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | import request from '@/utils/request' | ||||||
|  |  | ||||||
|  | // 查询对象存储配置列表 | ||||||
|  | export function listOssConfig(query) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/oss/config/list', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询对象存储配置详细 | ||||||
|  | export function getOssConfig(ossConfigId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/oss/config/' + ossConfigId, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 新增对象存储配置 | ||||||
|  | export function addOssConfig(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/oss/config', | ||||||
|  |     method: 'post', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 修改对象存储配置 | ||||||
|  | export function updateOssConfig(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/oss/config', | ||||||
|  |     method: 'put', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 删除对象存储配置 | ||||||
|  | export function delOssConfig(ossConfigId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/oss/config/' + ossConfigId, | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 对象存储状态修改 | ||||||
|  | export function changeOssConfigStatus(ossConfigId, status, configKey) { | ||||||
|  |   const data = { | ||||||
|  |     ossConfigId, | ||||||
|  |     status, | ||||||
|  |     configKey | ||||||
|  |   } | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/oss/config/changeStatus', | ||||||
|  |     method: 'put', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										44
									
								
								ruoyi-ui-vue3/src/api/system/post.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								ruoyi-ui-vue3/src/api/system/post.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | import request from '@/utils/request' | ||||||
|  |  | ||||||
|  | // 查询岗位列表 | ||||||
|  | export function listPost(query) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/post/list', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询岗位详细 | ||||||
|  | export function getPost(postId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/post/' + postId, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 新增岗位 | ||||||
|  | export function addPost(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/post', | ||||||
|  |     method: 'post', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 修改岗位 | ||||||
|  | export function updatePost(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/post', | ||||||
|  |     method: 'put', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 删除岗位 | ||||||
|  | export function delPost(postId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/post/' + postId, | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										119
									
								
								ruoyi-ui-vue3/src/api/system/role.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								ruoyi-ui-vue3/src/api/system/role.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | |||||||
|  | import request from '@/utils/request' | ||||||
|  |  | ||||||
|  | // 查询角色列表 | ||||||
|  | export function listRole(query) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/role/list', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询角色详细 | ||||||
|  | export function getRole(roleId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/role/' + roleId, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 新增角色 | ||||||
|  | export function addRole(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/role', | ||||||
|  |     method: 'post', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 修改角色 | ||||||
|  | export function updateRole(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/role', | ||||||
|  |     method: 'put', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 角色数据权限 | ||||||
|  | export function dataScope(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/role/dataScope', | ||||||
|  |     method: 'put', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 角色状态修改 | ||||||
|  | export function changeRoleStatus(roleId, status) { | ||||||
|  |   const data = { | ||||||
|  |     roleId, | ||||||
|  |     status | ||||||
|  |   } | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/role/changeStatus', | ||||||
|  |     method: 'put', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 删除角色 | ||||||
|  | export function delRole(roleId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/role/' + roleId, | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询角色已授权用户列表 | ||||||
|  | export function allocatedUserList(query) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/role/authUser/allocatedList', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询角色未授权用户列表 | ||||||
|  | export function unallocatedUserList(query) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/role/authUser/unallocatedList', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 取消用户授权角色 | ||||||
|  | export function authUserCancel(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/role/authUser/cancel', | ||||||
|  |     method: 'put', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 批量取消用户授权角色 | ||||||
|  | export function authUserCancelAll(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/role/authUser/cancelAll', | ||||||
|  |     method: 'put', | ||||||
|  |     params: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 授权用户选择 | ||||||
|  | export function authUserSelectAll(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/role/authUser/selectAll', | ||||||
|  |     method: 'put', | ||||||
|  |     params: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 根据角色ID查询部门树结构 | ||||||
|  | export function deptTreeSelect(roleId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/role/deptTree/' + roleId, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										135
									
								
								ruoyi-ui-vue3/src/api/system/user.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								ruoyi-ui-vue3/src/api/system/user.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | |||||||
|  | import request from '@/utils/request' | ||||||
|  | import { parseStrEmpty } from "@/utils/ruoyi"; | ||||||
|  |  | ||||||
|  | // 查询用户列表 | ||||||
|  | export function listUser(query) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/user/list', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询用户详细 | ||||||
|  | export function getUser(userId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/user/' + parseStrEmpty(userId), | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 新增用户 | ||||||
|  | export function addUser(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/user', | ||||||
|  |     method: 'post', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 修改用户 | ||||||
|  | export function updateUser(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/user', | ||||||
|  |     method: 'put', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 删除用户 | ||||||
|  | export function delUser(userId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/user/' + userId, | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 用户密码重置 | ||||||
|  | export function resetUserPwd(userId, password) { | ||||||
|  |   const data = { | ||||||
|  |     userId, | ||||||
|  |     password | ||||||
|  |   } | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/user/resetPwd', | ||||||
|  |     method: 'put', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 用户状态修改 | ||||||
|  | export function changeUserStatus(userId, status) { | ||||||
|  |   const data = { | ||||||
|  |     userId, | ||||||
|  |     status | ||||||
|  |   } | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/user/changeStatus', | ||||||
|  |     method: 'put', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询用户个人信息 | ||||||
|  | export function getUserProfile() { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/user/profile', | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 修改用户个人信息 | ||||||
|  | export function updateUserProfile(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/user/profile', | ||||||
|  |     method: 'put', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 用户密码重置 | ||||||
|  | export function updateUserPwd(oldPassword, newPassword) { | ||||||
|  |   const data = { | ||||||
|  |     oldPassword, | ||||||
|  |     newPassword | ||||||
|  |   } | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/user/profile/updatePwd', | ||||||
|  |     method: 'put', | ||||||
|  |     params: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 用户头像上传 | ||||||
|  | export function uploadAvatar(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/user/profile/avatar', | ||||||
|  |     method: 'post', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询授权角色 | ||||||
|  | export function getAuthRole(userId) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/user/authRole/' + userId, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 保存授权角色 | ||||||
|  | export function updateAuthRole(data) { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/user/authRole', | ||||||
|  |     method: 'put', | ||||||
|  |     params: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询部门下拉树结构 | ||||||
|  | export function deptTreeSelect() { | ||||||
|  |   return request({ | ||||||
|  |     url: '/system/user/deptTree', | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										85
									
								
								ruoyi-ui-vue3/src/api/tool/gen.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								ruoyi-ui-vue3/src/api/tool/gen.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | |||||||
|  | import request from '@/utils/request' | ||||||
|  |  | ||||||
|  | // 查询生成表数据 | ||||||
|  | export function listTable(query) { | ||||||
|  |   return request({ | ||||||
|  |     headers: { 'datasource': localStorage.getItem("dataName") }, | ||||||
|  |     url: '/tool/gen/list', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | // 查询db数据库列表 | ||||||
|  | export function listDbTable(query) { | ||||||
|  |   return request({ | ||||||
|  |     headers: { 'datasource': localStorage.getItem("dataName") }, | ||||||
|  |     url: '/tool/gen/db/list', | ||||||
|  |     method: 'get', | ||||||
|  |     params: query | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 查询表详细信息 | ||||||
|  | export function getGenTable(tableId) { | ||||||
|  |   return request({ | ||||||
|  |     headers: { 'datasource': localStorage.getItem("dataName") }, | ||||||
|  |     url: '/tool/gen/' + tableId, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 修改代码生成信息 | ||||||
|  | export function updateGenTable(data) { | ||||||
|  |   return request({ | ||||||
|  |     headers: { 'datasource': localStorage.getItem("dataName") }, | ||||||
|  |     url: '/tool/gen', | ||||||
|  |     method: 'put', | ||||||
|  |     data: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 导入表 | ||||||
|  | export function importTable(data) { | ||||||
|  |   return request({ | ||||||
|  |     headers: { 'datasource': localStorage.getItem("dataName") }, | ||||||
|  |     url: '/tool/gen/importTable', | ||||||
|  |     method: 'post', | ||||||
|  |     params: data | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 预览生成代码 | ||||||
|  | export function previewTable(tableId) { | ||||||
|  |   return request({ | ||||||
|  |     headers: { 'datasource': localStorage.getItem("dataName") }, | ||||||
|  |     url: '/tool/gen/preview/' + tableId, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 删除表数据 | ||||||
|  | export function delTable(tableId) { | ||||||
|  |   return request({ | ||||||
|  |     headers: { 'datasource': localStorage.getItem("dataName") }, | ||||||
|  |     url: '/tool/gen/' + tableId, | ||||||
|  |     method: 'delete' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 生成代码(自定义路径) | ||||||
|  | export function genCode(tableName) { | ||||||
|  |   return request({ | ||||||
|  |     headers: { 'datasource': localStorage.getItem("dataName") }, | ||||||
|  |     url: '/tool/gen/genCode/' + tableName, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // 同步数据库 | ||||||
|  | export function synchDb(tableName) { | ||||||
|  |   return request({ | ||||||
|  |     headers: { 'datasource': localStorage.getItem("dataName") }, | ||||||
|  |     url: '/tool/gen/synchDb/' + tableName, | ||||||
|  |     method: 'get' | ||||||
|  |   }) | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								ruoyi-ui-vue3/src/assets/401_images/401.gif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								ruoyi-ui-vue3/src/assets/401_images/401.gif
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 160 KiB | 
							
								
								
									
										
											BIN
										
									
								
								ruoyi-ui-vue3/src/assets/404_images/404.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								ruoyi-ui-vue3/src/assets/404_images/404.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 96 KiB | 
							
								
								
									
										
											BIN
										
									
								
								ruoyi-ui-vue3/src/assets/404_images/404_cloud.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								ruoyi-ui-vue3/src/assets/404_images/404_cloud.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 4.7 KiB | 
							
								
								
									
										1
									
								
								ruoyi-ui-vue3/src/assets/icons/svg/404.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								ruoyi-ui-vue3/src/assets/icons/svg/404.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | <svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M121.718 73.272v9.953c3.957-7.584 6.199-16.05 6.199-24.995C127.917 26.079 99.273 0 63.958 0 28.644 0 0 26.079 0 58.23c0 .403.028.806.028 1.21l22.97-25.953h13.34l-19.76 27.187h6.42V53.77l13.728-19.477v49.361H22.998V73.272H2.158c5.951 20.284 23.608 36.208 45.998 41.399-1.44 3.3-5.618 11.263-12.565 12.674-8.607 1.764 23.358.428 46.163-13.178 17.519-4.611 31.938-15.849 39.77-30.513h-13.506V73.272H85.02V59.464l22.998-25.977h13.008l-19.429 27.187h6.421v-7.433l13.727-19.402v39.433h-.027zm-78.24 2.822a10.516 10.516 0 0 1-.996-4.535V44.548c0-1.613.332-3.124.996-4.535a11.66 11.66 0 0 1 2.713-3.68c1.134-1.032 2.49-1.864 4.04-2.468 1.55-.605 3.21-.908 4.982-.908h11.292c1.77 0 3.431.303 4.981.908 1.522.604 2.85 1.41 3.986 2.418l-12.26 16.303v-2.898a1.96 1.96 0 0 0-.665-1.512c-.443-.403-.996-.604-1.66-.604-.665 0-1.218.201-1.661.604a1.96 1.96 0 0 0-.664 1.512v9.071L44.364 77.606a10.556 10.556 0 0 1-.886-1.512zm35.73-4.535c0 1.613-.332 3.124-.997 4.535a11.66 11.66 0 0 1-2.712 3.68c-1.134 1.032-2.49 1.864-4.04 2.469-1.55.604-3.21.907-4.982.907H55.185c-1.77 0-3.431-.303-4.981-.907-1.55-.605-2.906-1.437-4.041-2.47a12.49 12.49 0 0 1-1.384-1.512l13.727-18.217v6.375c0 .605.222 1.109.665 1.512.442.403.996.604 1.66.604.664 0 1.218-.201 1.66-.604a1.96 1.96 0 0 0 .665-1.512V53.87L75.97 36.838c.913.932 1.66 1.99 2.214 3.175.664 1.41.996 2.922.996 4.535v27.011h.028z"/></svg> | ||||||
| After Width: | Height: | Size: 1.4 KiB | 
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user