mirror of
				https://github.com/dromara/RuoYi-Vue-Plus.git
				synced 2025-11-04 08:13:44 +08:00 
			
		
		
		
	Compare commits
	
		
			202 Commits
		
	
	
		
			3761473967
			...
			v5.4.1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					64100cf1ff | ||
| 
						 | 
					d501e82541 | ||
| 
						 | 
					3002585e63 | ||
| 
						 | 
					60aca2eef3 | ||
| 
						 | 
					5baf342478 | ||
| 
						 | 
					3f9919fbee | ||
| 
						 | 
					682d8b0099 | ||
| 
						 | 
					bbabffe191 | ||
| 
						 | 
					6722f2eeed | ||
| 
						 | 
					5a9728c868 | ||
| 
						 | 
					eea96e87d9 | ||
| 
						 | 
					e659740cb8 | ||
| 
						 | 
					314909a536 | ||
| 
						 | 
					8f5d60f543 | ||
| 
						 | 
					1ad0d5387b | ||
| 
						 | 
					770c3bd03e | ||
| 
						 | 
					4b04a4bf09 | ||
| 
						 | 
					1598447f6b | ||
| 
						 | 
					a8a1db4463 | ||
| 
						 | 
					4b47053dcf | ||
| 
						 | 
					03054fc1e8 | ||
| 
						 | 
					9dce540a09 | ||
| 
						 | 
					534182deff | ||
| 
						 | 
					4577c45110 | ||
| 
						 | 
					0796791ec9 | ||
| 
						 | 
					84baac0a4f | ||
| 
						 | 
					74d257a610 | ||
| 
						 | 
					cb8fa6ff9a | ||
| 
						 | 
					c157012807 | ||
| 
						 | 
					ffa01bdb3a | ||
| 
						 | 
					3fa572f0a8 | ||
| 
						 | 
					f868de1b7b | ||
| 
						 | 
					fb785dc17f | ||
| 
						 | 
					f3d475438f | ||
| 
						 | 
					d7af327248 | ||
| 
						 | 
					bd88e27c82 | ||
| 
						 | 
					83b6addbba | ||
| 
						 | 
					d51f3b9f4e | ||
| 
						 | 
					9256432532 | ||
| 
						 | 
					97d3a31aba | ||
| 
						 | 
					d9cc85187a | ||
| 
						 | 
					2ff2d89b2d | ||
| 
						 | 
					be2e5059fd | ||
| 
						 | 
					fad91f01ff | ||
| 
						 | 
					8f95374cef | ||
| 
						 | 
					7471fa7ee0 | ||
| 
						 | 
					529f1e5dbb | ||
| 
						 | 
					eff131a1ed | ||
| 
						 | 
					60b0faa3c6 | ||
| 
						 | 
					b2d694b90b | ||
| 
						 | 
					fecc564099 | ||
| 
						 | 
					297e920179 | ||
| 
						 | 
					ea9379a52f | ||
| 
						 | 
					0b0f2ee8ea | ||
| 
						 | 
					6d2f104a43 | ||
| 
						 | 
					7e7d857ba5 | ||
| 
						 | 
					2e50e30778 | ||
| 
						 | 
					daf79683b3 | ||
| 
						 | 
					5849ddc160 | ||
| 
						 | 
					d22b2a10df | ||
| 
						 | 
					957a4d1fcd | ||
| 
						 | 
					c88367939c | ||
| 
						 | 
					a748d0d62c | ||
| 
						 | 
					49ef8378fe | ||
| 
						 | 
					cd531f1d39 | ||
| 
						 | 
					92f73a4a72 | ||
| 
						 | 
					a4e3f7ea5e | ||
| 
						 | 
					26b4561a71 | ||
| 
						 | 
					dbe276a33b | ||
| 
						 | 
					4ab4e1685c | ||
| 
						 | 
					aab87d322c | ||
| 
						 | 
					79ee168293 | ||
| 
						 | 
					10e4b0618c | ||
| 
						 | 
					7c3316e116 | ||
| 
						 | 
					8460316632 | ||
| 
						 | 
					5d356aa6c4 | ||
| 
						 | 
					a776d28294 | ||
| 
						 | 
					a002a4e7a1 | ||
| 
						 | 
					79ec850eca | ||
| 
						 | 
					d1889c42a3 | ||
| 
						 | 
					a7ea096319 | ||
| 
						 | 
					4e3fc7002d | ||
| 
						 | 
					1752695751 | ||
| 
						 | 
					2b89c3f8d0 | ||
| 
						 | 
					6b387b2456 | ||
| 
						 | 
					ffc971cf92 | ||
| 
						 | 
					887d5e85d0 | ||
| 
						 | 
					8c603ff8d7 | ||
| 
						 | 
					a0831dda45 | ||
| 
						 | 
					336b2e8cc3 | ||
| 
						 | 
					cea4855f57 | ||
| 
						 | 
					9fc043b105 | ||
| 
						 | 
					d729c8ecde | ||
| 
						 | 
					b726a91cdb | ||
| 
						 | 
					05d5d9be2c | ||
| 
						 | 
					c40a8b2f0b | ||
| 
						 | 
					8232908b3f | ||
| 
						 | 
					1db0bc83b2 | ||
| 
						 | 
					74a0ec1ec3 | ||
| 
						 | 
					1228e8f3ea | ||
| 
						 | 
					737838d92f | ||
| 
						 | 
					c054029cfc | ||
| 
						 | 
					62bbd78033 | ||
| 
						 | 
					82a5ed632f | ||
| 
						 | 
					52ddccba3e | ||
| 
						 | 
					1a12aecd49 | ||
| 
						 | 
					777ae645c5 | ||
| 
						 | 
					21c87eee9a | ||
| 
						 | 
					0c8ac12e4d | ||
| 
						 | 
					cf871d9387 | ||
| 
						 | 
					90fb26fbf1 | ||
| 
						 | 
					fdfca0b33a | ||
| 
						 | 
					facd3e351f | ||
| 
						 | 
					a4ad56f0eb | ||
| 
						 | 
					b9e5914bab | ||
| 
						 | 
					553fca28a2 | ||
| 
						 | 
					97caabe0a2 | ||
| 
						 | 
					122f2770b2 | ||
| 
						 | 
					748c95b30f | ||
| 
						 | 
					e0672fc753 | ||
| 
						 | 
					5a1523564b | ||
| 
						 | 
					0c2fe34d92 | ||
| 
						 | 
					2dde42168f | ||
| 
						 | 
					a5c2093c76 | ||
| 
						 | 
					ea74803ccc | ||
| 
						 | 
					9df837f047 | ||
| 
						 | 
					d6758dc47b | ||
| 
						 | 
					5a8dc8e1cf | ||
| 
						 | 
					eac7f1b4e2 | ||
| 
						 | 
					3749e7e724 | ||
| 
						 | 
					b709bc0214 | ||
| 
						 | 
					a09414110e | ||
| 
						 | 
					053dc50c4d | ||
| 
						 | 
					5382722867 | ||
| 
						 | 
					5e51077347 | ||
| 
						 | 
					6d44069364 | ||
| 
						 | 
					e1e3843ec0 | ||
| 
						 | 
					15905b7022 | ||
| 
						 | 
					1c5ae2f168 | ||
| 
						 | 
					f29e0223a7 | ||
| 
						 | 
					9fbe3cf399 | ||
| 
						 | 
					e4f1da30fc | ||
| 
						 | 
					21deab4bf1 | ||
| 
						 | 
					d7d7dcbcf7 | ||
| 
						 | 
					3f680385a9 | ||
| 
						 | 
					7ecf4bbf1c | ||
| 
						 | 
					ae65985fbc | ||
| 
						 | 
					2a34c3ebb2 | ||
| 
						 | 
					3b46f8c8cf | ||
| 
						 | 
					7c2efb1aef | ||
| 
						 | 
					ea25474529 | ||
| 
						 | 
					33e1d34ce5 | ||
| 
						 | 
					142fb33d81 | ||
| 
						 | 
					ee6c0388da | ||
| 
						 | 
					c171817d6a | ||
| 
						 | 
					71dddee146 | ||
| 
						 | 
					d456ff64f1 | ||
| 
						 | 
					9e78fcccf7 | ||
| 
						 | 
					878cd7e9f0 | ||
| 
						 | 
					5c9721cfac | ||
| 
						 | 
					31502dccc7 | ||
| 
						 | 
					538aa8d908 | ||
| 
						 | 
					00003b2c57 | ||
| 
						 | 
					a2c238d466 | ||
| 
						 | 
					d89f147c54 | ||
| 
						 | 
					53cf1b2013 | ||
| 
						 | 
					564ab331d7 | ||
| 
						 | 
					a690ece164 | ||
| 
						 | 
					b50904c6ff | ||
| 
						 | 
					70aa14ecf8 | ||
| 
						 | 
					c37b92978a | ||
| 
						 | 
					ef39ad7107 | ||
| 
						 | 
					48d3ef9818 | ||
| 
						 | 
					5bf901cdcd | ||
| 
						 | 
					8e99dd306a | ||
| 
						 | 
					07fdc240d7 | ||
| 
						 | 
					023ceaaf91 | ||
| 
						 | 
					9e551a0b2a | ||
| 
						 | 
					7c3f3523ea | ||
| 
						 | 
					40eac07789 | ||
| 
						 | 
					5868fadbf5 | ||
| 
						 | 
					124bcc4bba | ||
| 
						 | 
					e71d6fa983 | ||
| 
						 | 
					7129ad4fac | ||
| 
						 | 
					16923cc86a | ||
| 
						 | 
					57dd6831d3 | ||
| 
						 | 
					8aa60abb1f | ||
| 
						 | 
					7a9f51fc7a | ||
| 
						 | 
					159e30c982 | ||
| 
						 | 
					7334d91d6b | ||
| 
						 | 
					95c01301f6 | ||
| 
						 | 
					296466fa13 | ||
| 
						 | 
					3c8d864b5f | ||
| 
						 | 
					ea50a57602 | ||
| 
						 | 
					7e14b98676 | ||
| 
						 | 
					015b406001 | ||
| 
						 | 
					098d3347a0 | ||
| 
						 | 
					08d4493994 | ||
| 
						 | 
					367d739e2d | ||
| 
						 | 
					d6688a367d | ||
| 
						 | 
					0b331796e2 | ||
| 
						 | 
					456620b638 | 
@@ -2,7 +2,7 @@
 | 
			
		||||
  <configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
 | 
			
		||||
    <deployment type="dockerfile">
 | 
			
		||||
      <settings>
 | 
			
		||||
        <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.3.1" />
 | 
			
		||||
        <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.4.1" />
 | 
			
		||||
        <option name="buildOnly" value="true" />
 | 
			
		||||
        <option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
 | 
			
		||||
      </settings>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
  <configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
 | 
			
		||||
    <deployment type="dockerfile">
 | 
			
		||||
      <settings>
 | 
			
		||||
        <option name="imageTag" value="ruoyi/ruoyi-server:5.3.1" />
 | 
			
		||||
        <option name="imageTag" value="ruoyi/ruoyi-server:5.4.1" />
 | 
			
		||||
        <option name="buildOnly" value="true" />
 | 
			
		||||
        <option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
 | 
			
		||||
      </settings>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
  <configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
 | 
			
		||||
    <deployment type="dockerfile">
 | 
			
		||||
      <settings>
 | 
			
		||||
        <option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.3.1" />
 | 
			
		||||
        <option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.4.1" />
 | 
			
		||||
        <option name="buildOnly" value="true" />
 | 
			
		||||
        <option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
 | 
			
		||||
      </settings>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								README.md
									
									
									
									
									
								
							@@ -7,10 +7,10 @@
 | 
			
		||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus)
 | 
			
		||||
[](https://github.com/dromara/RuoYi-Vue-Plus)
 | 
			
		||||
[](https://gitcode.com/dromara/RuoYi-Vue-Plus)
 | 
			
		||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE)
 | 
			
		||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/5.X/LICENSE)
 | 
			
		||||
[](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
 | 
			
		||||
<br>
 | 
			
		||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus)
 | 
			
		||||
[](https://gitee.com/dromara/RuoYi-Vue-Plus)
 | 
			
		||||
[]()
 | 
			
		||||
[]()
 | 
			
		||||
[]()
 | 
			
		||||
@@ -22,10 +22,12 @@
 | 
			
		||||
 | 
			
		||||
> 系统演示: [传送门](https://plus-doc.dromara.org/#/common/demo_system)
 | 
			
		||||
 | 
			
		||||
> 官方前端项目地址: [plus-ui](https://gitee.com/JavaLionLi/plus-ui)<br>
 | 
			
		||||
> 成员前端项目地址: 基于vben5 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)
 | 
			
		||||
> 官方前端项目地址: [gitee](https://gitee.com/JavaLionLi/plus-ui) - [github](https://github.com/JavaLionLi/plus-ui) - [gitcode](https://gitcode.com/dromara/plus-ui)<br>
 | 
			
		||||
> 成员前端项目地址: 基于vben5 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)<br>
 | 
			
		||||
> 成员前端项目地址: 基于soybean [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)<br>
 | 
			
		||||
> 成员项目地址: 删除多租户与工作流 [RuoYi-Vue-Plus-Single](https://gitee.com/ColorDreams/RuoYi-Vue-Plus-Single)<br>
 | 
			
		||||
 | 
			
		||||
> 文档地址: [plus-doc](https://plus-doc.dromara.org)
 | 
			
		||||
> 文档地址: [plus-doc](https://plus-doc.dromara.org) 文档在华为云上如果打不开大概率是DNS问题 可以尝试切换网络等方式(或者科学上网)
 | 
			
		||||
 | 
			
		||||
## 赞助商
 | 
			
		||||
 | 
			
		||||
@@ -34,6 +36,7 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
 | 
			
		||||
数舵科技 软件定制开发APP小程序等 - http://www.shuduokeji.com/ <br>
 | 
			
		||||
引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
 | 
			
		||||
<font color="red">**启山商城系统 多租户商城源码可免费商用可二次开发 - https://www.73app.cn/** </font><br>
 | 
			
		||||
Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br>
 | 
			
		||||
[如何成为赞助商 加群联系作者详谈](https://plus-doc.dromara.org/#/common/add_group)
 | 
			
		||||
 | 
			
		||||
# 本框架与RuoYi的功能差异
 | 
			
		||||
@@ -75,7 +78,7 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
 | 
			
		||||
| 邮件          | 采用 mail-api 通用协议支持大部分邮件厂商                                                                                         | 不支持                                                                                |
 | 
			
		||||
| 接口文档        | 采用 SpringDoc、javadoc 无注解零入侵基于java注释<br/>只需把注释写好 无需再写一大堆的文档注解了                                                     | 采用 Springfox 已停止维护 需要编写大量的注解来支持文档生成                                                | 
 | 
			
		||||
| 校验框架        | 采用 Validation 支持注解与工具类校验 注解支持国际化                                                                                  | 仅支持注解 且注解不支持国际化                                                                    |
 | 
			
		||||
| Excel框架     | 采用 Alibaba EasyExcel 基于插件化<br/>框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等                                               | 基于 POI 手写实现 功能有限 复杂 扩展性差                                                           |
 | 
			
		||||
| Excel框架     | 采用 FastExcel(原Alibaba EasyExcel) 基于插件化<br/>框架对其增加了很多功能 例如 自动合并相同内容 自动排列布局 字典翻译等                                   | 基于 POI 手写实现 功能有限 复杂 扩展性差                                                           |
 | 
			
		||||
| 工作流支持       | 支持各种复杂审批 转办 委派 加减签 会签 或签 票签 等功能                                                                                   | 无                                                                                  |
 | 
			
		||||
| 工具类框架       | 采用 Hutool、Lombok 上百种工具覆盖90%的使用需求 基于注解自动生成 get set 等简化框架大量代码                                                       | 手写工具稳定性差易出问题 工具数量有限 代码臃肿需自己手写 get set 等                                            | 
 | 
			
		||||
| 监控框架        | 采用 SpringBoot-Admin 基于SpringBoot官方 actuator 探针机制<br/>实时监控服务状态 框架还为其扩展了在线日志查看监控                                    | 无                                                                                  | 
 | 
			
		||||
@@ -113,7 +116,6 @@ CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
 | 
			
		||||
| 系统接口   | 根据业务代码自动生成相关的api接口文档                                                 | 支持  | 支持               |
 | 
			
		||||
| 服务监控   | 监视集群系统CPU、内存、磁盘、堆栈、在线日志、Spring相关配置等                                  | 支持  | 仅支持单机CPU、内存、磁盘监控 |
 | 
			
		||||
| 缓存监控   | 对系统的缓存信息查询,命令统计等。                                                    | 支持  | 支持               |
 | 
			
		||||
| 在线构建器  | 拖动表单元素生成相应的HTML代码。                                                   | 支持  | 支持               |
 | 
			
		||||
| 使用案例   | 系统的一些功能案例                                                            | 支持  | 不支持              |
 | 
			
		||||
 | 
			
		||||
## 参考文档
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										51
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								pom.xml
									
									
									
									
									
								
							@@ -13,33 +13,32 @@
 | 
			
		||||
    <description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <revision>5.3.1</revision>
 | 
			
		||||
        <spring-boot.version>3.4.4</spring-boot.version>
 | 
			
		||||
        <revision>5.4.1</revision>
 | 
			
		||||
        <spring-boot.version>3.4.7</spring-boot.version>
 | 
			
		||||
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 | 
			
		||||
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 | 
			
		||||
        <java.version>17</java.version>
 | 
			
		||||
        <mybatis.version>3.5.16</mybatis.version>
 | 
			
		||||
        <springdoc.version>2.8.5</springdoc.version>
 | 
			
		||||
        <springdoc.version>2.8.8</springdoc.version>
 | 
			
		||||
        <therapi-javadoc.version>0.15.0</therapi-javadoc.version>
 | 
			
		||||
        <easyexcel.version>4.0.3</easyexcel.version>
 | 
			
		||||
        <fastexcel.version>1.2.0</fastexcel.version>
 | 
			
		||||
        <velocity.version>2.3</velocity.version>
 | 
			
		||||
        <satoken.version>1.40.0</satoken.version>
 | 
			
		||||
        <mybatis-plus.version>3.5.11</mybatis-plus.version>
 | 
			
		||||
        <satoken.version>1.44.0</satoken.version>
 | 
			
		||||
        <mybatis-plus.version>3.5.12</mybatis-plus.version>
 | 
			
		||||
        <p6spy.version>3.9.1</p6spy.version>
 | 
			
		||||
        <hutool.version>5.8.35</hutool.version>
 | 
			
		||||
        <spring-boot-admin.version>3.4.5</spring-boot-admin.version>
 | 
			
		||||
        <redisson.version>3.45.1</redisson.version>
 | 
			
		||||
        <hutool.version>5.8.38</hutool.version>
 | 
			
		||||
        <spring-boot-admin.version>3.4.7</spring-boot-admin.version>
 | 
			
		||||
        <redisson.version>3.50.0</redisson.version>
 | 
			
		||||
        <lock4j.version>2.2.7</lock4j.version>
 | 
			
		||||
        <dynamic-ds.version>4.3.1</dynamic-ds.version>
 | 
			
		||||
        <snailjob.version>1.4.0</snailjob.version>
 | 
			
		||||
        <mapstruct-plus.version>1.4.6</mapstruct-plus.version>
 | 
			
		||||
        <snailjob.version>1.5.0</snailjob.version>
 | 
			
		||||
        <mapstruct-plus.version>1.4.8</mapstruct-plus.version>
 | 
			
		||||
        <mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
 | 
			
		||||
        <lombok.version>1.18.36</lombok.version>
 | 
			
		||||
        <bouncycastle.version>1.76</bouncycastle.version>
 | 
			
		||||
        <bouncycastle.version>1.80</bouncycastle.version>
 | 
			
		||||
        <justauth.version>1.16.7</justauth.version>
 | 
			
		||||
        <!-- 离线IP地址定位库 -->
 | 
			
		||||
        <ip2region.version>2.7.0</ip2region.version>
 | 
			
		||||
 | 
			
		||||
        <!-- OSS 配置 -->
 | 
			
		||||
        <aws.sdk.version>2.28.22</aws.sdk.version>
 | 
			
		||||
        <!-- SMS 配置 -->
 | 
			
		||||
@@ -47,15 +46,15 @@
 | 
			
		||||
        <!-- 限制框架中的fastjson版本 -->
 | 
			
		||||
        <fastjson.version>1.2.83</fastjson.version>
 | 
			
		||||
        <!-- 面向运行时的D-ORM依赖 -->
 | 
			
		||||
        <anyline.version>8.7.2-20250101</anyline.version>
 | 
			
		||||
        <!--工作流配置-->
 | 
			
		||||
        <warm-flow.version>1.6.8</warm-flow.version>
 | 
			
		||||
        <anyline.version>8.7.2-20250603</anyline.version>
 | 
			
		||||
        <!-- 工作流配置 -->
 | 
			
		||||
        <warm-flow.version>1.7.4</warm-flow.version>
 | 
			
		||||
 | 
			
		||||
        <!-- 插件版本 -->
 | 
			
		||||
        <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
 | 
			
		||||
        <maven-war-plugin.version>3.2.2</maven-war-plugin.version>
 | 
			
		||||
        <maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
 | 
			
		||||
        <maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
 | 
			
		||||
        <maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
 | 
			
		||||
        <maven-war-plugin.version>3.4.0</maven-war-plugin.version>
 | 
			
		||||
        <maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
 | 
			
		||||
        <maven-surefire-plugin.version>3.5.3</maven-surefire-plugin.version>
 | 
			
		||||
        <flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
 | 
			
		||||
        <!-- 打包默认跳过测试 -->
 | 
			
		||||
        <skipTests>true</skipTests>
 | 
			
		||||
@@ -166,9 +165,9 @@
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>com.alibaba</groupId>
 | 
			
		||||
                <artifactId>easyexcel</artifactId>
 | 
			
		||||
                <version>${easyexcel.version}</version>
 | 
			
		||||
                <groupId>cn.idev.excel</groupId>
 | 
			
		||||
                <artifactId>fastexcel</artifactId>
 | 
			
		||||
                <version>${fastexcel.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <!-- velocity代码生成使用模板 -->
 | 
			
		||||
@@ -321,12 +320,6 @@
 | 
			
		||||
                <version>${ip2region.version}</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>commons-io</groupId>
 | 
			
		||||
                <artifactId>commons-io</artifactId>
 | 
			
		||||
                <version>2.15.0</version>
 | 
			
		||||
            </dependency>
 | 
			
		||||
 | 
			
		||||
            <dependency>
 | 
			
		||||
                <groupId>com.alibaba</groupId>
 | 
			
		||||
                <artifactId>fastjson</artifactId>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
 | 
			
		||||
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
 | 
			
		||||
#FROM bellsoft/liberica-openjdk-debian:21.0.5-cds
 | 
			
		||||
FROM bellsoft/liberica-openjdk-rocky:17.0.15-cds
 | 
			
		||||
#FROM bellsoft/liberica-openjdk-rocky:21.0.7-cds
 | 
			
		||||
#FROM findepi/graalvm:java17-native
 | 
			
		||||
 | 
			
		||||
LABEL maintainer="Lion Li"
 | 
			
		||||
@@ -11,17 +11,18 @@ RUN mkdir -p /ruoyi/server/logs \
 | 
			
		||||
 | 
			
		||||
WORKDIR /ruoyi/server
 | 
			
		||||
 | 
			
		||||
ENV SERVER_PORT=8080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
 | 
			
		||||
ENV SERVER_PORT=8080 SNAIL_PORT=28080 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
 | 
			
		||||
 | 
			
		||||
EXPOSE ${SERVER_PORT}
 | 
			
		||||
# 暴露 snail job 客户端端口 用于定时任务调度中心通信
 | 
			
		||||
EXPOSE ${SNAIL_PORT}
 | 
			
		||||
 | 
			
		||||
ADD ./target/ruoyi-admin.jar ./app.jar
 | 
			
		||||
# 工作流字体文件
 | 
			
		||||
ADD ./zhFonts/ /usr/share/fonts/zhFonts/
 | 
			
		||||
 | 
			
		||||
SHELL ["/bin/bash", "-c"]
 | 
			
		||||
 | 
			
		||||
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \
 | 
			
		||||
           -Dsnail-job.port=${SNAIL_PORT} \
 | 
			
		||||
           # 应用名称 如果想区分集群节点监控 改成不同的名称即可
 | 
			
		||||
           #-Dskywalking.agent.service_name=ruoyi-server \
 | 
			
		||||
           #-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
package org.dromara.web.listener;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.config.SaTokenConfig;
 | 
			
		||||
import cn.dev33.satoken.listener.SaTokenListener;
 | 
			
		||||
import cn.dev33.satoken.stp.SaLoginModel;
 | 
			
		||||
import cn.dev33.satoken.stp.StpUtil;
 | 
			
		||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
 | 
			
		||||
import cn.hutool.core.convert.Convert;
 | 
			
		||||
import cn.hutool.http.useragent.UserAgent;
 | 
			
		||||
import cn.hutool.http.useragent.UserAgentUtil;
 | 
			
		||||
@@ -35,14 +34,13 @@ import java.time.Duration;
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class UserActionListener implements SaTokenListener {
 | 
			
		||||
 | 
			
		||||
    private final SaTokenConfig tokenConfig;
 | 
			
		||||
    private final SysLoginService loginService;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 每次登录时触发
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
 | 
			
		||||
    public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginParameter loginParameter) {
 | 
			
		||||
        UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
 | 
			
		||||
        String ip = ServletUtils.getClientIP();
 | 
			
		||||
        UserOnlineDTO dto = new UserOnlineDTO();
 | 
			
		||||
@@ -52,17 +50,17 @@ public class UserActionListener implements SaTokenListener {
 | 
			
		||||
        dto.setOs(userAgent.getOs().getName());
 | 
			
		||||
        dto.setLoginTime(System.currentTimeMillis());
 | 
			
		||||
        dto.setTokenId(tokenValue);
 | 
			
		||||
        String username = (String) loginModel.getExtra(LoginHelper.USER_NAME_KEY);
 | 
			
		||||
        String tenantId = (String) loginModel.getExtra(LoginHelper.TENANT_KEY);
 | 
			
		||||
        String username = (String) loginParameter.getExtra(LoginHelper.USER_NAME_KEY);
 | 
			
		||||
        String tenantId = (String) loginParameter.getExtra(LoginHelper.TENANT_KEY);
 | 
			
		||||
        dto.setUserName(username);
 | 
			
		||||
        dto.setClientKey((String) loginModel.getExtra(LoginHelper.CLIENT_KEY));
 | 
			
		||||
        dto.setDeviceType(loginModel.getDevice());
 | 
			
		||||
        dto.setDeptName((String) loginModel.getExtra(LoginHelper.DEPT_NAME_KEY));
 | 
			
		||||
        dto.setClientKey((String) loginParameter.getExtra(LoginHelper.CLIENT_KEY));
 | 
			
		||||
        dto.setDeviceType(loginParameter.getDeviceType());
 | 
			
		||||
        dto.setDeptName((String) loginParameter.getExtra(LoginHelper.DEPT_NAME_KEY));
 | 
			
		||||
        TenantHelper.dynamic(tenantId, () -> {
 | 
			
		||||
            if(tokenConfig.getTimeout() == -1) {
 | 
			
		||||
            if(loginParameter.getTimeout() == -1) {
 | 
			
		||||
                RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto);
 | 
			
		||||
            } else {
 | 
			
		||||
                RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(tokenConfig.getTimeout()));
 | 
			
		||||
                RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, dto, Duration.ofSeconds(loginParameter.getTimeout()));
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        // 记录登录日志
 | 
			
		||||
@@ -74,7 +72,7 @@ public class UserActionListener implements SaTokenListener {
 | 
			
		||||
        logininforEvent.setRequest(ServletUtils.getRequest());
 | 
			
		||||
        SpringUtils.context().publishEvent(logininforEvent);
 | 
			
		||||
        // 更新登录信息
 | 
			
		||||
        loginService.recordLoginInfo((Long) loginModel.getExtra(LoginHelper.USER_KEY), ip);
 | 
			
		||||
        loginService.recordLoginInfo((Long) loginParameter.getExtra(LoginHelper.USER_KEY), ip);
 | 
			
		||||
        log.info("user doLogin, userId:{}, token:{}", loginId, tokenValue);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -160,6 +158,6 @@ public class UserActionListener implements SaTokenListener {
 | 
			
		||||
     * 每次Token续期时触发
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void doRenewTimeout(String tokenValue, Object loginId, long timeout) {
 | 
			
		||||
    public void doRenewTimeout(String loginType, Object loginId, String tokenValue, long timeout) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
package org.dromara.web.service;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.secure.BCrypt;
 | 
			
		||||
import cn.hutool.crypto.digest.BCrypt;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.dromara.common.core.constant.Constants;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
package org.dromara.web.service.impl;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.stp.SaLoginModel;
 | 
			
		||||
import cn.dev33.satoken.stp.StpUtil;
 | 
			
		||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
@@ -58,8 +58,8 @@ public class EmailAuthStrategy implements IAuthStrategy {
 | 
			
		||||
        });
 | 
			
		||||
        loginUser.setClientKey(client.getClientKey());
 | 
			
		||||
        loginUser.setDeviceType(client.getDeviceType());
 | 
			
		||||
        SaLoginModel model = new SaLoginModel();
 | 
			
		||||
        model.setDevice(client.getDeviceType());
 | 
			
		||||
        SaLoginParameter model = new SaLoginParameter();
 | 
			
		||||
        model.setDeviceType(client.getDeviceType());
 | 
			
		||||
        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
 | 
			
		||||
        // 例如: 后台用户30分钟过期 app用户1天过期
 | 
			
		||||
        model.setTimeout(client.getTimeout());
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
package org.dromara.web.service.impl;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.secure.BCrypt;
 | 
			
		||||
import cn.dev33.satoken.stp.SaLoginModel;
 | 
			
		||||
import cn.dev33.satoken.stp.StpUtil;
 | 
			
		||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import cn.hutool.crypto.digest.BCrypt;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
@@ -70,8 +70,8 @@ public class PasswordAuthStrategy implements IAuthStrategy {
 | 
			
		||||
        });
 | 
			
		||||
        loginUser.setClientKey(client.getClientKey());
 | 
			
		||||
        loginUser.setDeviceType(client.getDeviceType());
 | 
			
		||||
        SaLoginModel model = new SaLoginModel();
 | 
			
		||||
        model.setDevice(client.getDeviceType());
 | 
			
		||||
        SaLoginParameter model = new SaLoginParameter();
 | 
			
		||||
        model.setDeviceType(client.getDeviceType());
 | 
			
		||||
        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
 | 
			
		||||
        // 例如: 后台用户30分钟过期 app用户1天过期
 | 
			
		||||
        model.setTimeout(client.getTimeout());
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
package org.dromara.web.service.impl;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.stp.SaLoginModel;
 | 
			
		||||
import cn.dev33.satoken.stp.StpUtil;
 | 
			
		||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
@@ -58,8 +58,8 @@ public class SmsAuthStrategy implements IAuthStrategy {
 | 
			
		||||
        });
 | 
			
		||||
        loginUser.setClientKey(client.getClientKey());
 | 
			
		||||
        loginUser.setDeviceType(client.getDeviceType());
 | 
			
		||||
        SaLoginModel model = new SaLoginModel();
 | 
			
		||||
        model.setDevice(client.getDeviceType());
 | 
			
		||||
        SaLoginParameter model = new SaLoginParameter();
 | 
			
		||||
        model.setDeviceType(client.getDeviceType());
 | 
			
		||||
        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
 | 
			
		||||
        // 例如: 后台用户30分钟过期 app用户1天过期
 | 
			
		||||
        model.setTimeout(client.getTimeout());
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,9 @@
 | 
			
		||||
package org.dromara.web.service.impl;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.stp.SaLoginModel;
 | 
			
		||||
import cn.dev33.satoken.stp.StpUtil;
 | 
			
		||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.core.map.MapUtil;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import cn.hutool.http.HttpUtil;
 | 
			
		||||
import cn.hutool.http.Method;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import me.zhyd.oauth.model.AuthResponse;
 | 
			
		||||
@@ -68,15 +65,6 @@ public class SocialAuthStrategy implements IAuthStrategy {
 | 
			
		||||
            throw new ServiceException(response.getMsg());
 | 
			
		||||
        }
 | 
			
		||||
        AuthUser authUserData = response.getData();
 | 
			
		||||
        if ("GITEE".equals(authUserData.getSource())) {
 | 
			
		||||
            // 如用户使用 gitee 登录顺手 star 给作者一点支持 拒绝白嫖
 | 
			
		||||
            HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Vue-Plus")
 | 
			
		||||
                    .formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
 | 
			
		||||
                    .executeAsync();
 | 
			
		||||
            HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Cloud-Plus")
 | 
			
		||||
                    .formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
 | 
			
		||||
                    .executeAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        List<SysSocialVo> list = sysSocialService.selectByAuthId(authUserData.getSource() + authUserData.getUuid());
 | 
			
		||||
        if (CollUtil.isEmpty(list)) {
 | 
			
		||||
@@ -99,8 +87,8 @@ public class SocialAuthStrategy implements IAuthStrategy {
 | 
			
		||||
        });
 | 
			
		||||
        loginUser.setClientKey(client.getClientKey());
 | 
			
		||||
        loginUser.setDeviceType(client.getDeviceType());
 | 
			
		||||
        SaLoginModel model = new SaLoginModel();
 | 
			
		||||
        model.setDevice(client.getDeviceType());
 | 
			
		||||
        SaLoginParameter model = new SaLoginParameter();
 | 
			
		||||
        model.setDeviceType(client.getDeviceType());
 | 
			
		||||
        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
 | 
			
		||||
        // 例如: 后台用户30分钟过期 app用户1天过期
 | 
			
		||||
        model.setTimeout(client.getTimeout());
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
package org.dromara.web.service.impl;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.stp.SaLoginModel;
 | 
			
		||||
import cn.dev33.satoken.stp.StpUtil;
 | 
			
		||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
@@ -76,8 +76,8 @@ public class XcxAuthStrategy implements IAuthStrategy {
 | 
			
		||||
        loginUser.setDeviceType(client.getDeviceType());
 | 
			
		||||
        loginUser.setOpenid(openid);
 | 
			
		||||
 | 
			
		||||
        SaLoginModel model = new SaLoginModel();
 | 
			
		||||
        model.setDevice(client.getDeviceType());
 | 
			
		||||
        SaLoginParameter model = new SaLoginParameter();
 | 
			
		||||
        model.setDeviceType(client.getDeviceType());
 | 
			
		||||
        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
 | 
			
		||||
        // 例如: 后台用户30分钟过期 app用户1天过期
 | 
			
		||||
        model.setTimeout(client.getTimeout());
 | 
			
		||||
 
 | 
			
		||||
@@ -263,3 +263,10 @@ justauth:
 | 
			
		||||
      client-id: 10**********6
 | 
			
		||||
      client-secret: 1f7d08**********5b7**********29e
 | 
			
		||||
      redirect-uri: ${justauth.address}/social-callback?source=gitlab
 | 
			
		||||
    gitea:
 | 
			
		||||
      # 前端改动 https://gitee.com/JavaLionLi/plus-ui/pulls/204
 | 
			
		||||
      # gitea 服务器地址
 | 
			
		||||
      server-url: https://demo.gitea.com
 | 
			
		||||
      client-id: 10**********6
 | 
			
		||||
      client-secret: 1f7d08**********5b7**********29e
 | 
			
		||||
      redirect-uri: ${justauth.address}/social-callback?source=gitea
 | 
			
		||||
 
 | 
			
		||||
@@ -265,3 +265,10 @@ justauth:
 | 
			
		||||
      client-id: 10**********6
 | 
			
		||||
      client-secret: 1f7d08**********5b7**********29e
 | 
			
		||||
      redirect-uri: ${justauth.address}/social-callback?source=gitlab
 | 
			
		||||
    gitea:
 | 
			
		||||
      # 前端改动 https://gitee.com/JavaLionLi/plus-ui/pulls/204
 | 
			
		||||
      # gitea 服务器地址
 | 
			
		||||
      server-url: https://demo.gitea.com
 | 
			
		||||
      client-id: 10**********6
 | 
			
		||||
      client-secret: 1f7d08**********5b7**********29e
 | 
			
		||||
      redirect-uri: ${justauth.address}/social-callback?source=gitea
 | 
			
		||||
 
 | 
			
		||||
@@ -21,8 +21,8 @@ server:
 | 
			
		||||
      worker: 256
 | 
			
		||||
 | 
			
		||||
captcha:
 | 
			
		||||
  # 是否启用验证码校验
 | 
			
		||||
  enable: true
 | 
			
		||||
  # 页面 <参数设置> 可开启关闭 验证码校验
 | 
			
		||||
  # 验证码类型 math 数组计算 char 字符验证
 | 
			
		||||
  type: MATH
 | 
			
		||||
  # line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
 | 
			
		||||
@@ -110,7 +110,7 @@ security:
 | 
			
		||||
    - /error
 | 
			
		||||
    - /*/api-docs
 | 
			
		||||
    - /*/api-docs/**
 | 
			
		||||
    - /warm-flow-ui/token-name
 | 
			
		||||
    - /warm-flow-ui/config
 | 
			
		||||
 | 
			
		||||
# 多租户配置
 | 
			
		||||
tenant:
 | 
			
		||||
@@ -177,16 +177,13 @@ springdoc:
 | 
			
		||||
  api-docs:
 | 
			
		||||
    # 是否开启接口文档
 | 
			
		||||
    enabled: true
 | 
			
		||||
#  swagger-ui:
 | 
			
		||||
#    # 持久化认证数据
 | 
			
		||||
#    persistAuthorization: true
 | 
			
		||||
  info:
 | 
			
		||||
    # 标题
 | 
			
		||||
    title: '标题:RuoYi-Vue-Plus多租户管理系统_接口文档'
 | 
			
		||||
    # 描述
 | 
			
		||||
    description: '描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...'
 | 
			
		||||
    # 版本
 | 
			
		||||
    version: '版本号: ${ruoyi.version}'
 | 
			
		||||
    version: '版本号: ${project.version}'
 | 
			
		||||
    # 作者信息
 | 
			
		||||
    contact:
 | 
			
		||||
      name: Lion Li
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
3f2ee348-0303-40ca-bf03-03f48d2d2141
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							@@ -1,4 +0,0 @@
 | 
			
		||||
3
 | 
			
		||||
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso10646-1
 | 
			
		||||
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso8859-1
 | 
			
		||||
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-koi8-r
 | 
			
		||||
@@ -1,4 +0,0 @@
 | 
			
		||||
3
 | 
			
		||||
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso10646-1
 | 
			
		||||
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-iso8859-1
 | 
			
		||||
SIMSUN.TTC -misc-simsun-medium-r-normal--0-0-0-0-p-0-koi8-r
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
    </description>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <revision>5.3.1</revision>
 | 
			
		||||
        <revision>5.4.1</revision>
 | 
			
		||||
    </properties>
 | 
			
		||||
 | 
			
		||||
    <dependencyManagement>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,13 +3,14 @@ package org.dromara.common.core.constant;
 | 
			
		||||
/**
 | 
			
		||||
 * 缓存组名称常量
 | 
			
		||||
 * <p>
 | 
			
		||||
 * key 格式为 cacheNames#ttl#maxIdleTime#maxSize
 | 
			
		||||
 * key 格式为 cacheNames#ttl#maxIdleTime#maxSize#local
 | 
			
		||||
 * <p>
 | 
			
		||||
 * ttl 过期时间 如果设置为0则不过期 默认为0
 | 
			
		||||
 * maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
 | 
			
		||||
 * maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
 | 
			
		||||
 * local 默认开启本地缓存为1 关闭本地缓存为0
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500
 | 
			
		||||
 * 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500、test#1h#0#500#0
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
@@ -77,4 +77,9 @@ public interface SystemConstants {
 | 
			
		||||
     */
 | 
			
		||||
    String ROOT_DEPT_ANCESTORS = "0";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 默认部门 ID
 | 
			
		||||
     */
 | 
			
		||||
    Long DEFAULT_DEPT_ID = 100L;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,22 @@ public class ProcessEvent implements Serializable {
 | 
			
		||||
    private String businessId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 状态
 | 
			
		||||
     * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
 | 
			
		||||
     */
 | 
			
		||||
    private Integer nodeType;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 流程节点编码
 | 
			
		||||
     */
 | 
			
		||||
    private String nodeCode;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 流程节点名称
 | 
			
		||||
     */
 | 
			
		||||
    private String nodeName;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 流程状态
 | 
			
		||||
     */
 | 
			
		||||
    private String status;
 | 
			
		||||
 | 
			
		||||
@@ -45,6 +60,6 @@ public class ProcessEvent implements Serializable {
 | 
			
		||||
    /**
 | 
			
		||||
     * 当为true时为申请人节点办理
 | 
			
		||||
     */
 | 
			
		||||
    private boolean submit;
 | 
			
		||||
    private Boolean submit;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,12 +6,12 @@ import java.io.Serial;
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 流程创建任务监听
 | 
			
		||||
 * 流程任务监听
 | 
			
		||||
 *
 | 
			
		||||
 * @author may
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
public class ProcessCreateTaskEvent implements Serializable {
 | 
			
		||||
public class ProcessTaskEvent implements Serializable {
 | 
			
		||||
 | 
			
		||||
    @Serial
 | 
			
		||||
    private static final long serialVersionUID = 1L;
 | 
			
		||||
@@ -27,10 +27,20 @@ public class ProcessCreateTaskEvent implements Serializable {
 | 
			
		||||
    private String flowCode;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 审批节点编码
 | 
			
		||||
     * 节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)
 | 
			
		||||
     */
 | 
			
		||||
    private Integer nodeType;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 流程节点编码
 | 
			
		||||
     */
 | 
			
		||||
    private String nodeCode;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 流程节点名称
 | 
			
		||||
     */
 | 
			
		||||
    private String nodeName;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 任务id
 | 
			
		||||
     */
 | 
			
		||||
@@ -41,4 +51,9 @@ public class ProcessCreateTaskEvent implements Serializable {
 | 
			
		||||
     */
 | 
			
		||||
    private String businessId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 流程状态
 | 
			
		||||
     */
 | 
			
		||||
    private String status;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -18,14 +18,14 @@ public class RegisterBody extends LoginBody {
 | 
			
		||||
     * 用户名
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{user.username.not.blank}")
 | 
			
		||||
    @Length(min = 2, max = 20, message = "{user.username.length.valid}")
 | 
			
		||||
    @Length(min = 2, max = 30, message = "{user.username.length.valid}")
 | 
			
		||||
    private String username;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 用户密码
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "{user.password.not.blank}")
 | 
			
		||||
    @Length(min = 5, max = 20, message = "{user.password.length.valid}")
 | 
			
		||||
    @Length(min = 5, max = 30, message = "{user.password.length.valid}")
 | 
			
		||||
    private String password;
 | 
			
		||||
 | 
			
		||||
    private String userType;
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@ import lombok.Getter;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 设备类型
 | 
			
		||||
 * 针对一套 用户体系
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 */
 | 
			
		||||
@@ -29,9 +28,12 @@ public enum DeviceType {
 | 
			
		||||
    XCX("xcx"),
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * social第三方端
 | 
			
		||||
     * 第三方社交登录平台
 | 
			
		||||
     */
 | 
			
		||||
    SOCIAL("social");
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 设备标识
 | 
			
		||||
     */
 | 
			
		||||
    private final String device;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,11 @@
 | 
			
		||||
package org.dromara.common.core.enums;
 | 
			
		||||
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Getter;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 设备类型
 | 
			
		||||
 * 针对多套 用户体系
 | 
			
		||||
 * 用户类型
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 */
 | 
			
		||||
@@ -15,15 +14,18 @@ import lombok.Getter;
 | 
			
		||||
public enum UserType {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * pc端
 | 
			
		||||
     * 后台系统用户
 | 
			
		||||
     */
 | 
			
		||||
    SYS_USER("sys_user"),
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * app端
 | 
			
		||||
     * 移动客户端用户
 | 
			
		||||
     */
 | 
			
		||||
    APP_USER("app_user");
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 用户类型标识(用于 token、权限识别等)
 | 
			
		||||
     */
 | 
			
		||||
    private final String userType;
 | 
			
		||||
 | 
			
		||||
    public static UserType getUserType(String str) {
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
package org.dromara.common.core.service;
 | 
			
		||||
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 用户权限处理
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 */
 | 
			
		||||
public interface PermissionService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取角色数据权限
 | 
			
		||||
     *
 | 
			
		||||
     * @param userId  用户id
 | 
			
		||||
     * @return 角色权限信息
 | 
			
		||||
     */
 | 
			
		||||
    Set<String> getRolePermission(Long userId);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取菜单数据权限
 | 
			
		||||
     *
 | 
			
		||||
     * @param userId  用户id
 | 
			
		||||
     * @return 菜单权限信息
 | 
			
		||||
     */
 | 
			
		||||
    Set<String> getMenuPermission(Long userId);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -3,6 +3,7 @@ package org.dromara.common.core.service;
 | 
			
		||||
import org.dromara.common.core.domain.dto.UserDTO;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 通用 用户服务
 | 
			
		||||
@@ -91,4 +92,36 @@ public interface UserService {
 | 
			
		||||
     */
 | 
			
		||||
    List<UserDTO> selectUsersByPostIds(List<Long> postIds);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据用户 ID 列表查询用户名称映射关系
 | 
			
		||||
     *
 | 
			
		||||
     * @param userIds 用户 ID 列表
 | 
			
		||||
     * @return Map,其中 key 为用户 ID,value 为对应的用户名称
 | 
			
		||||
     */
 | 
			
		||||
    Map<Long, String> selectUserNamesByIds(List<Long> userIds);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据角色 ID 列表查询角色名称映射关系
 | 
			
		||||
     *
 | 
			
		||||
     * @param roleIds 角色 ID 列表
 | 
			
		||||
     * @return Map,其中 key 为角色 ID,value 为对应的角色名称
 | 
			
		||||
     */
 | 
			
		||||
    Map<Long, String> selectRoleNamesByIds(List<Long> roleIds);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据部门 ID 列表查询部门名称映射关系
 | 
			
		||||
     *
 | 
			
		||||
     * @param deptIds 部门 ID 列表
 | 
			
		||||
     * @return Map,其中 key 为部门 ID,value 为对应的部门名称
 | 
			
		||||
     */
 | 
			
		||||
    Map<Long, String> selectDeptNamesByIds(List<Long> deptIds);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据岗位 ID 列表查询岗位名称映射关系
 | 
			
		||||
     *
 | 
			
		||||
     * @param postIds 岗位 ID 列表
 | 
			
		||||
     * @return Map,其中 key 为岗位 ID,value 为对应的岗位名称
 | 
			
		||||
     */
 | 
			
		||||
    Map<Long, String> selectPostNamesByIds(List<Long> postIds);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -78,9 +78,18 @@ public interface WorkflowService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 办理任务
 | 
			
		||||
     * 系统后台发起审批 无用户信息 需要忽略权限
 | 
			
		||||
     * completeTask.getVariables().put("ignore", true);
 | 
			
		||||
     *
 | 
			
		||||
     * @param completeTask 参数
 | 
			
		||||
     * @return 结果
 | 
			
		||||
     */
 | 
			
		||||
    boolean completeTask(CompleteTaskDTO completeTask);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 办理任务
 | 
			
		||||
     *
 | 
			
		||||
     * @param taskId  任务ID
 | 
			
		||||
     * @param message 办理意见
 | 
			
		||||
     */
 | 
			
		||||
    boolean completeTask(Long taskId, String message);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -175,14 +175,27 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 计算两个日期之间的天数差(以毫秒为单位)
 | 
			
		||||
     * 计算两个时间之间的时间差,并以指定单位返回(绝对值)
 | 
			
		||||
     *
 | 
			
		||||
     * @param date1 第一个日期
 | 
			
		||||
     * @param date2 第二个日期
 | 
			
		||||
     * @return 两个日期之间的天数差的绝对值
 | 
			
		||||
     * @param start 起始时间
 | 
			
		||||
     * @param end   结束时间
 | 
			
		||||
     * @param unit  所需返回的时间单位(DAYS、HOURS、MINUTES、SECONDS、MILLISECONDS、MICROSECONDS、NANOSECONDS)
 | 
			
		||||
     * @return 时间差的绝对值,以指定单位表示
 | 
			
		||||
     */
 | 
			
		||||
    public static int differentDaysByMillisecond(Date date1, Date date2) {
 | 
			
		||||
        return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
 | 
			
		||||
    public static long difference(Date start, Date end, TimeUnit unit) {
 | 
			
		||||
        // 计算时间差,单位为毫秒,取绝对值避免负数
 | 
			
		||||
        long diffInMillis = Math.abs(end.getTime() - start.getTime());
 | 
			
		||||
 | 
			
		||||
        // 根据目标单位转换时间差
 | 
			
		||||
        return switch (unit) {
 | 
			
		||||
            case DAYS -> diffInMillis / TimeUnit.DAYS.toMillis(1);
 | 
			
		||||
            case HOURS -> diffInMillis / TimeUnit.HOURS.toMillis(1);
 | 
			
		||||
            case MINUTES -> diffInMillis / TimeUnit.MINUTES.toMillis(1);
 | 
			
		||||
            case SECONDS -> diffInMillis / TimeUnit.SECONDS.toMillis(1);
 | 
			
		||||
            case MILLISECONDS -> diffInMillis;
 | 
			
		||||
            case MICROSECONDS -> TimeUnit.MILLISECONDS.toMicros(diffInMillis);
 | 
			
		||||
            case NANOSECONDS -> TimeUnit.MILLISECONDS.toNanos(diffInMillis);
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,84 @@
 | 
			
		||||
package org.dromara.common.core.utils;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.lang.PatternPool;
 | 
			
		||||
import cn.hutool.core.net.NetUtil;
 | 
			
		||||
import lombok.AccessLevel;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.dromara.common.core.utils.regex.RegexUtils;
 | 
			
		||||
 | 
			
		||||
import java.net.Inet6Address;
 | 
			
		||||
import java.net.InetAddress;
 | 
			
		||||
import java.net.UnknownHostException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 增强网络相关工具类
 | 
			
		||||
 *
 | 
			
		||||
 * @author 秋辞未寒
 | 
			
		||||
 */
 | 
			
		||||
@Slf4j
 | 
			
		||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
 | 
			
		||||
public class NetUtils extends NetUtil {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 判断是否为IPv6地址
 | 
			
		||||
     *
 | 
			
		||||
     * @param ip IP地址
 | 
			
		||||
     * @return 是否为IPv6地址
 | 
			
		||||
     */
 | 
			
		||||
    public static boolean isIPv6(String ip) {
 | 
			
		||||
        try {
 | 
			
		||||
            // 判断是否为IPv6地址
 | 
			
		||||
            return InetAddress.getByName(ip) instanceof Inet6Address;
 | 
			
		||||
        } catch (UnknownHostException e) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 判断IPv6地址是否为内网地址
 | 
			
		||||
     * <br><br>
 | 
			
		||||
     * 以下地址将归类为本地地址,如有业务场景有需要,请根据需求自行处理:
 | 
			
		||||
     * <pre>
 | 
			
		||||
     * 通配符地址 0:0:0:0:0:0:0:0
 | 
			
		||||
     * 链路本地地址 fe80::/10
 | 
			
		||||
     * 唯一本地地址 fec0::/10
 | 
			
		||||
     * 环回地址 ::1
 | 
			
		||||
     * </pre>
 | 
			
		||||
     *
 | 
			
		||||
     * @param ip IP地址
 | 
			
		||||
     * @return 是否为内网地址
 | 
			
		||||
     */
 | 
			
		||||
    public static boolean isInnerIPv6(String ip) {
 | 
			
		||||
        try {
 | 
			
		||||
            // 判断是否为IPv6地址
 | 
			
		||||
            if (InetAddress.getByName(ip) instanceof Inet6Address inet6Address) {
 | 
			
		||||
                // isAnyLocalAddress 判断是否为通配符地址,通常不会将其视为内网地址,根据业务场景自行处理判断
 | 
			
		||||
                // isLinkLocalAddress 判断是否为链路本地地址,通常不算内网地址,是否划分归属于内网需要根据业务场景自行处理判断
 | 
			
		||||
                // isLoopbackAddress 判断是否为环回地址,与IPv4的 127.0.0.1 同理,用于表示本机
 | 
			
		||||
                // isSiteLocalAddress 判断是否为本地站点地址,IPv6唯一本地地址(Unique Local Addresses,简称ULA)
 | 
			
		||||
                if (inet6Address.isAnyLocalAddress()
 | 
			
		||||
                    || inet6Address.isLinkLocalAddress()
 | 
			
		||||
                    || inet6Address.isLoopbackAddress()
 | 
			
		||||
                    || inet6Address.isSiteLocalAddress()) {
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } catch (UnknownHostException e) {
 | 
			
		||||
            // 注意,isInnerIPv6方法和isIPv6方法的适用范围不同,所以此处不能忽略其异常信息。
 | 
			
		||||
            throw new IllegalArgumentException("Invalid IPv6 address!", e);
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 判断是否为IPv4地址
 | 
			
		||||
     *
 | 
			
		||||
     * @param ip IP地址
 | 
			
		||||
     * @return 是否为IPv4地址
 | 
			
		||||
     */
 | 
			
		||||
    public static boolean isIPv4(String ip) {
 | 
			
		||||
        return RegexUtils.isMatch(PatternPool.IPV4, ip);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -6,6 +6,7 @@ import cn.hutool.core.lang.Validator;
 | 
			
		||||
import cn.hutool.core.util.StrUtil;
 | 
			
		||||
import org.springframework.util.AntPathMatcher;
 | 
			
		||||
 | 
			
		||||
import java.nio.charset.Charset;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
@@ -339,4 +340,26 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 将字符串从源字符集转换为目标字符集
 | 
			
		||||
     *
 | 
			
		||||
     * @param input       原始字符串
 | 
			
		||||
     * @param fromCharset 源字符集
 | 
			
		||||
     * @param toCharset   目标字符集
 | 
			
		||||
     * @return 转换后的字符串
 | 
			
		||||
     */
 | 
			
		||||
    public static String convert(String input, Charset fromCharset, Charset toCharset) {
 | 
			
		||||
        if (isBlank(input)) {
 | 
			
		||||
            return input;
 | 
			
		||||
        }
 | 
			
		||||
        try {
 | 
			
		||||
            // 从源字符集获取字节
 | 
			
		||||
            byte[] bytes = input.getBytes(fromCharset);
 | 
			
		||||
            // 使用目标字符集解码
 | 
			
		||||
            return new String(bytes, toCharset);
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            return input;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,8 @@ import lombok.NoArgsConstructor;
 | 
			
		||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
@@ -60,6 +62,31 @@ public class TreeBuildUtils extends TreeUtil {
 | 
			
		||||
        return TreeUtil.build(list, parentId, DEFAULT_CONFIG, nodeParser);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 构建多根节点的树结构(支持多个顶级节点)
 | 
			
		||||
     *
 | 
			
		||||
     * @param list        原始数据列表
 | 
			
		||||
     * @param getId       获取节点 ID 的方法引用,例如:node -> node.getId()
 | 
			
		||||
     * @param getParentId 获取节点父级 ID 的方法引用,例如:node -> node.getParentId()
 | 
			
		||||
     * @param parser      树节点属性映射器,用于将原始节点 T 转为 Tree 节点
 | 
			
		||||
     * @param <T>         原始数据类型(如实体类、DTO 等)
 | 
			
		||||
     * @param <K>         节点 ID 类型(如 Long、String)
 | 
			
		||||
     * @return 构建完成的树形结构(可能包含多个顶级根节点)
 | 
			
		||||
     */
 | 
			
		||||
    public static <T, K> List<Tree<K>> buildMultiRoot(List<T> list, Function<T, K> getId, Function<T, K> getParentId, NodeParser<T, K> parser) {
 | 
			
		||||
        if (CollUtil.isEmpty(list)) {
 | 
			
		||||
            return CollUtil.newArrayList();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Set<K> rootParentIds = StreamUtils.toSet(list, getParentId);
 | 
			
		||||
        rootParentIds.removeAll(StreamUtils.toSet(list, getId));
 | 
			
		||||
 | 
			
		||||
        // 构建每一个根 parentId 下的树,并合并成最终结果列表
 | 
			
		||||
        return rootParentIds.stream()
 | 
			
		||||
            .flatMap(rootParentId -> TreeUtil.build(list, rootParentId, parser).stream())
 | 
			
		||||
            .collect(Collectors.toList());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取节点列表中所有节点的叶子节点
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
package org.dromara.common.core.utils.ip;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.net.NetUtil;
 | 
			
		||||
import cn.hutool.http.HtmlUtil;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import lombok.AccessLevel;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.dromara.common.core.utils.NetUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 获取地址类
 | 
			
		||||
@@ -16,18 +16,55 @@ import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
 | 
			
		||||
public class AddressUtils {
 | 
			
		||||
 | 
			
		||||
    // 未知IP
 | 
			
		||||
    public static final String UNKNOWN_IP = "XX XX";
 | 
			
		||||
    // 内网地址
 | 
			
		||||
    public static final String LOCAL_ADDRESS = "内网IP";
 | 
			
		||||
    // 未知地址
 | 
			
		||||
    public static final String UNKNOWN = "XX XX";
 | 
			
		||||
    public static final String UNKNOWN_ADDRESS = "未知";
 | 
			
		||||
 | 
			
		||||
    public static String getRealAddressByIP(String ip) {
 | 
			
		||||
        if (StringUtils.isBlank(ip)) {
 | 
			
		||||
            return UNKNOWN;
 | 
			
		||||
        // 处理空串并过滤HTML标签
 | 
			
		||||
        ip = HtmlUtil.cleanHtmlTag(StringUtils.blankToDefault(ip,""));
 | 
			
		||||
        // 判断是否为IPv4
 | 
			
		||||
        if (NetUtils.isIPv4(ip)) {
 | 
			
		||||
            return resolverIPv4Region(ip);
 | 
			
		||||
        }
 | 
			
		||||
        // 判断是否为IPv6
 | 
			
		||||
        if (NetUtils.isIPv6(ip)) {
 | 
			
		||||
            return resolverIPv6Region(ip);
 | 
			
		||||
        }
 | 
			
		||||
        // 如果不是IPv4或IPv6,则返回未知IP
 | 
			
		||||
        return UNKNOWN_IP;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据IPv4地址查询IP归属行政区域
 | 
			
		||||
     * @param ip ipv4地址
 | 
			
		||||
     * @return 归属行政区域
 | 
			
		||||
     */
 | 
			
		||||
    private static String resolverIPv4Region(String ip){
 | 
			
		||||
        // 内网不查询
 | 
			
		||||
        ip = StringUtils.contains(ip, "0:0:0:0:0:0:0:1") ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
 | 
			
		||||
        if (NetUtil.isInnerIP(ip)) {
 | 
			
		||||
            return "内网IP";
 | 
			
		||||
        if (NetUtils.isInnerIP(ip)) {
 | 
			
		||||
            return LOCAL_ADDRESS;
 | 
			
		||||
        }
 | 
			
		||||
        return RegionUtils.getCityInfo(ip);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据IPv6地址查询IP归属行政区域
 | 
			
		||||
     * @param ip ipv6地址
 | 
			
		||||
     * @return 归属行政区域
 | 
			
		||||
     */
 | 
			
		||||
    private static String resolverIPv6Region(String ip){
 | 
			
		||||
        // 内网不查询
 | 
			
		||||
        if (NetUtils.isInnerIPv6(ip)) {
 | 
			
		||||
            return LOCAL_ADDRESS;
 | 
			
		||||
        }
 | 
			
		||||
        log.warn("ip2region不支持IPV6地址解析:{}", ip);
 | 
			
		||||
        // 不支持IPv6,不再进行没有必要的IP地址信息的解析,直接返回
 | 
			
		||||
        // 如有需要,可自行实现IPv6地址信息解析逻辑,并在这里返回
 | 
			
		||||
        return UNKNOWN_ADDRESS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,12 @@
 | 
			
		||||
package org.dromara.common.core.utils.ip;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.io.FileUtil;
 | 
			
		||||
import cn.hutool.core.io.resource.ClassPathResource;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import org.dromara.common.core.exception.ServiceException;
 | 
			
		||||
import org.dromara.common.core.utils.file.FileUtils;
 | 
			
		||||
import cn.hutool.core.io.resource.NoResourceException;
 | 
			
		||||
import cn.hutool.core.io.resource.ResourceUtil;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.dromara.common.core.exception.ServiceException;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.lionsoul.ip2region.xdb.Searcher;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 根据ip地址定位工具类,离线方式
 | 
			
		||||
 * 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a>
 | 
			
		||||
@@ -19,31 +16,19 @@ import java.io.File;
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class RegionUtils {
 | 
			
		||||
 | 
			
		||||
    // IP地址库文件名称
 | 
			
		||||
    public static final String IP_XDB_FILENAME = "ip2region.xdb";
 | 
			
		||||
 | 
			
		||||
    private static final Searcher SEARCHER;
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        String fileName = "/ip2region.xdb";
 | 
			
		||||
        File existFile = FileUtils.file(FileUtil.getTmpDir() + FileUtil.FILE_SEPARATOR + fileName);
 | 
			
		||||
        if (!FileUtils.exist(existFile)) {
 | 
			
		||||
            ClassPathResource fileStream = new ClassPathResource(fileName);
 | 
			
		||||
            if (ObjectUtil.isEmpty(fileStream.getStream())) {
 | 
			
		||||
                throw new ServiceException("RegionUtils初始化失败,原因:IP地址库数据不存在!");
 | 
			
		||||
            }
 | 
			
		||||
            FileUtils.writeFromStream(fileStream.getStream(), existFile);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        String dbPath = existFile.getPath();
 | 
			
		||||
 | 
			
		||||
        // 1、从 dbPath 加载整个 xdb 到内存。
 | 
			
		||||
        byte[] cBuff;
 | 
			
		||||
        try {
 | 
			
		||||
            cBuff = Searcher.loadContentFromFile(dbPath);
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            throw new ServiceException("RegionUtils初始化失败,原因:从ip2region.xdb文件加载内容失败!" + e.getMessage());
 | 
			
		||||
        }
 | 
			
		||||
        // 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
 | 
			
		||||
        try {
 | 
			
		||||
            SEARCHER = Searcher.newWithBuffer(cBuff);
 | 
			
		||||
            // 1、将 ip2region 数据库文件 xdb 从 ClassPath 加载到内存。
 | 
			
		||||
            // 2、基于加载到内存的 xdb 数据创建一个 Searcher 查询对象。
 | 
			
		||||
            SEARCHER = Searcher.newWithBuffer(ResourceUtil.readBytes(IP_XDB_FILENAME));
 | 
			
		||||
            log.info("RegionUtils初始化成功,加载IP地址库数据成功!");
 | 
			
		||||
        } catch (NoResourceException e) {
 | 
			
		||||
            throw new ServiceException("RegionUtils初始化失败,原因:IP地址库数据不存在!");
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            throw new ServiceException("RegionUtils初始化失败,原因:" + e.getMessage());
 | 
			
		||||
        }
 | 
			
		||||
@@ -54,9 +39,8 @@ public class RegionUtils {
 | 
			
		||||
     */
 | 
			
		||||
    public static String getCityInfo(String ip) {
 | 
			
		||||
        try {
 | 
			
		||||
            ip = ip.trim();
 | 
			
		||||
            // 3、执行查询
 | 
			
		||||
            String region = SEARCHER.search(ip);
 | 
			
		||||
            String region = SEARCHER.search(StringUtils.trim(ip));
 | 
			
		||||
            return region.replace("0|", "").replace("|0", "");
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.error("IP地址离线获取城市异常 {}", ip);
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,40 @@
 | 
			
		||||
package org.dromara.common.core.validate.dicts;
 | 
			
		||||
 | 
			
		||||
import jakarta.validation.Constraint;
 | 
			
		||||
import jakarta.validation.Payload;
 | 
			
		||||
 | 
			
		||||
import java.lang.annotation.ElementType;
 | 
			
		||||
import java.lang.annotation.Retention;
 | 
			
		||||
import java.lang.annotation.RetentionPolicy;
 | 
			
		||||
import java.lang.annotation.Target;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 字典项校验注解
 | 
			
		||||
 *
 | 
			
		||||
 * @author AprilWind
 | 
			
		||||
 */
 | 
			
		||||
@Constraint(validatedBy = DictPatternValidator.class)
 | 
			
		||||
@Target({ElementType.FIELD, ElementType.PARAMETER})
 | 
			
		||||
@Retention(RetentionPolicy.RUNTIME)
 | 
			
		||||
public @interface DictPattern {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 字典类型,如 "sys_user_sex"
 | 
			
		||||
     */
 | 
			
		||||
    String dictType();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 分隔符
 | 
			
		||||
     */
 | 
			
		||||
    String separator();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 默认校验失败提示信息
 | 
			
		||||
     */
 | 
			
		||||
    String message() default "字典值无效";
 | 
			
		||||
 | 
			
		||||
    Class<?>[] groups() default {};
 | 
			
		||||
 | 
			
		||||
    Class<? extends Payload>[] payload() default {};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,55 @@
 | 
			
		||||
package org.dromara.common.core.validate.dicts;
 | 
			
		||||
 | 
			
		||||
import jakarta.validation.ConstraintValidator;
 | 
			
		||||
import jakarta.validation.ConstraintValidatorContext;
 | 
			
		||||
import org.dromara.common.core.service.DictService;
 | 
			
		||||
import org.dromara.common.core.utils.SpringUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 自定义字典值校验器
 | 
			
		||||
 *
 | 
			
		||||
 * @author AprilWind
 | 
			
		||||
 */
 | 
			
		||||
public class DictPatternValidator implements ConstraintValidator<DictPattern, String> {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 字典类型
 | 
			
		||||
     */
 | 
			
		||||
    private String dictType;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 分隔符
 | 
			
		||||
     */
 | 
			
		||||
    private String separator = ",";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 初始化校验器,提取注解上的字典类型
 | 
			
		||||
     *
 | 
			
		||||
     * @param annotation 注解实例
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void initialize(DictPattern annotation) {
 | 
			
		||||
        this.dictType = annotation.dictType();
 | 
			
		||||
        if (StringUtils.isNotBlank(annotation.separator())) {
 | 
			
		||||
            this.separator = annotation.separator();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 校验字段值是否为指定字典类型中的合法值
 | 
			
		||||
     *
 | 
			
		||||
     * @param value   被校验的字段值
 | 
			
		||||
     * @param context 校验上下文(可用于构建错误信息)
 | 
			
		||||
     * @return true 表示校验通过(合法字典值),false 表示不通过
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isValid(String value, ConstraintValidatorContext context) {
 | 
			
		||||
        if (StringUtils.isBlank(dictType) || StringUtils.isBlank(value)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        String dictLabel = SpringUtils.getBean(DictService.class).getDictLabel(dictType, value, separator);
 | 
			
		||||
        return StringUtils.isNotBlank(dictLabel);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,37 +1,37 @@
 | 
			
		||||
package org.dromara.common.core.validate.enumd;
 | 
			
		||||
 | 
			
		||||
import jakarta.validation.ConstraintValidator;
 | 
			
		||||
import jakarta.validation.ConstraintValidatorContext;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 自定义枚举校验注解实现
 | 
			
		||||
 *
 | 
			
		||||
 * @author 秋辞未寒
 | 
			
		||||
 * @date 2024-12-09
 | 
			
		||||
 */
 | 
			
		||||
public class EnumPatternValidator implements ConstraintValidator<EnumPattern, String> {
 | 
			
		||||
 | 
			
		||||
    private EnumPattern annotation;;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void initialize(EnumPattern annotation) {
 | 
			
		||||
        ConstraintValidator.super.initialize(annotation);
 | 
			
		||||
        this.annotation = annotation;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
 | 
			
		||||
        if (StringUtils.isNotBlank(value)) {
 | 
			
		||||
            String fieldName = annotation.fieldName();
 | 
			
		||||
            for (Object e : annotation.type().getEnumConstants()) {
 | 
			
		||||
                if (value.equals(ReflectUtils.invokeGetter(e, fieldName))) {
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
package org.dromara.common.core.validate.enumd;
 | 
			
		||||
 | 
			
		||||
import jakarta.validation.ConstraintValidator;
 | 
			
		||||
import jakarta.validation.ConstraintValidatorContext;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 自定义枚举校验注解实现
 | 
			
		||||
 *
 | 
			
		||||
 * @author 秋辞未寒
 | 
			
		||||
 * @date 2024-12-09
 | 
			
		||||
 */
 | 
			
		||||
public class EnumPatternValidator implements ConstraintValidator<EnumPattern, String> {
 | 
			
		||||
 | 
			
		||||
    private EnumPattern annotation;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void initialize(EnumPattern annotation) {
 | 
			
		||||
        ConstraintValidator.super.initialize(annotation);
 | 
			
		||||
        this.annotation = annotation;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
 | 
			
		||||
        if (StringUtils.isNotBlank(value)) {
 | 
			
		||||
            String fieldName = annotation.fieldName();
 | 
			
		||||
            for (Object e : annotation.type().getEnumConstants()) {
 | 
			
		||||
                if (value.equals(ReflectUtils.invokeGetter(e, fieldName))) {
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@ import java.util.Optional;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Swagger 文档配置
 | 
			
		||||
 * 接口文档配置
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import cn.hutool.core.convert.Convert;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.apache.ibatis.executor.parameter.ParameterHandler;
 | 
			
		||||
import org.apache.ibatis.executor.resultset.ResultSetHandler;
 | 
			
		||||
import org.apache.ibatis.plugin.*;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
@@ -39,12 +40,23 @@ public class MybatisDecryptInterceptor implements Interceptor {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Object intercept(Invocation invocation) throws Throwable {
 | 
			
		||||
        // 开始进行参数解密
 | 
			
		||||
        ResultSetHandler resultSetHandler = (ResultSetHandler) invocation.getTarget();
 | 
			
		||||
        Field parameterHandlerField = resultSetHandler.getClass().getDeclaredField("parameterHandler");
 | 
			
		||||
        parameterHandlerField.setAccessible(true);
 | 
			
		||||
        Object target = parameterHandlerField.get(resultSetHandler);
 | 
			
		||||
        if (target instanceof ParameterHandler parameterHandler) {
 | 
			
		||||
            Object parameterObject = parameterHandler.getParameterObject();
 | 
			
		||||
            if (ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)) {
 | 
			
		||||
                this.decryptHandler(parameterObject);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // 获取执行mysql执行结果
 | 
			
		||||
        Object result = invocation.proceed();
 | 
			
		||||
        if (result == null) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        decryptHandler(result);
 | 
			
		||||
        this.decryptHandler(result);
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -108,7 +108,7 @@ public class EncryptUtils {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * sm4加密
 | 
			
		||||
     * SM4加密(Base64编码)
 | 
			
		||||
     *
 | 
			
		||||
     * @param data     待加密数据
 | 
			
		||||
     * @param password 秘钥字符串
 | 
			
		||||
@@ -127,11 +127,11 @@ public class EncryptUtils {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * sm4加密
 | 
			
		||||
     * SM4加密(Hex编码)
 | 
			
		||||
     *
 | 
			
		||||
     * @param data     待加密数据
 | 
			
		||||
     * @param password 秘钥字符串
 | 
			
		||||
     * @return 加密后字符串, 采用Base64编码
 | 
			
		||||
     * @return 加密后字符串, 采用Hex编码
 | 
			
		||||
     */
 | 
			
		||||
    public static String encryptBySm4Hex(String data, String password) {
 | 
			
		||||
        if (StrUtil.isBlank(password)) {
 | 
			
		||||
@@ -148,7 +148,7 @@ public class EncryptUtils {
 | 
			
		||||
    /**
 | 
			
		||||
     * sm4解密
 | 
			
		||||
     *
 | 
			
		||||
     * @param data     待解密数据
 | 
			
		||||
     * @param data     待解密数据(可以是Base64或Hex编码)
 | 
			
		||||
     * @param password 秘钥字符串
 | 
			
		||||
     * @return 解密后字符串
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -22,8 +22,8 @@
 | 
			
		||||
        </dependency>
 | 
			
		||||
 | 
			
		||||
        <dependency>
 | 
			
		||||
            <groupId>com.alibaba</groupId>
 | 
			
		||||
            <artifactId>easyexcel</artifactId>
 | 
			
		||||
            <groupId>cn.idev.excel</groupId>
 | 
			
		||||
            <artifactId>fastexcel</artifactId>
 | 
			
		||||
        </dependency>
 | 
			
		||||
    </dependencies>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,17 +6,13 @@ import java.lang.annotation.RetentionPolicy;
 | 
			
		||||
import java.lang.annotation.Target;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 批注
 | 
			
		||||
 * 批注 此注解仅用于单表头 不支持多层级表头
 | 
			
		||||
 * @author guzhouyanyu
 | 
			
		||||
 */
 | 
			
		||||
@Target({ElementType.FIELD})
 | 
			
		||||
@Retention(RetentionPolicy.RUNTIME)
 | 
			
		||||
public @interface ExcelNotation {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * col index
 | 
			
		||||
     */
 | 
			
		||||
    int index() default -1;
 | 
			
		||||
    /**
 | 
			
		||||
     * 批注内容
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -8,17 +8,13 @@ import java.lang.annotation.RetentionPolicy;
 | 
			
		||||
import java.lang.annotation.Target;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 是否必填
 | 
			
		||||
 * 是否必填 此注解仅用于单表头 不支持多层级表头
 | 
			
		||||
 * @author guzhouyanyu
 | 
			
		||||
 */
 | 
			
		||||
@Target({ElementType.FIELD})
 | 
			
		||||
@Retention(RetentionPolicy.RUNTIME)
 | 
			
		||||
public @interface ExcelRequired {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * col index
 | 
			
		||||
     */
 | 
			
		||||
    int index() default -1;
 | 
			
		||||
    /**
 | 
			
		||||
     * 字体颜色
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -2,12 +2,12 @@ package org.dromara.common.excel.convert;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.convert.Convert;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import com.alibaba.excel.converters.Converter;
 | 
			
		||||
import com.alibaba.excel.enums.CellDataTypeEnum;
 | 
			
		||||
import com.alibaba.excel.metadata.GlobalConfiguration;
 | 
			
		||||
import com.alibaba.excel.metadata.data.ReadCellData;
 | 
			
		||||
import com.alibaba.excel.metadata.data.WriteCellData;
 | 
			
		||||
import com.alibaba.excel.metadata.property.ExcelContentProperty;
 | 
			
		||||
import cn.idev.excel.converters.Converter;
 | 
			
		||||
import cn.idev.excel.enums.CellDataTypeEnum;
 | 
			
		||||
import cn.idev.excel.metadata.GlobalConfiguration;
 | 
			
		||||
import cn.idev.excel.metadata.data.ReadCellData;
 | 
			
		||||
import cn.idev.excel.metadata.data.WriteCellData;
 | 
			
		||||
import cn.idev.excel.metadata.property.ExcelContentProperty;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
 | 
			
		||||
import java.math.BigDecimal;
 | 
			
		||||
 
 | 
			
		||||
@@ -3,12 +3,12 @@ package org.dromara.common.excel.convert;
 | 
			
		||||
import cn.hutool.core.annotation.AnnotationUtil;
 | 
			
		||||
import cn.hutool.core.convert.Convert;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import com.alibaba.excel.converters.Converter;
 | 
			
		||||
import com.alibaba.excel.enums.CellDataTypeEnum;
 | 
			
		||||
import com.alibaba.excel.metadata.GlobalConfiguration;
 | 
			
		||||
import com.alibaba.excel.metadata.data.ReadCellData;
 | 
			
		||||
import com.alibaba.excel.metadata.data.WriteCellData;
 | 
			
		||||
import com.alibaba.excel.metadata.property.ExcelContentProperty;
 | 
			
		||||
import cn.idev.excel.converters.Converter;
 | 
			
		||||
import cn.idev.excel.enums.CellDataTypeEnum;
 | 
			
		||||
import cn.idev.excel.metadata.GlobalConfiguration;
 | 
			
		||||
import cn.idev.excel.metadata.data.ReadCellData;
 | 
			
		||||
import cn.idev.excel.metadata.data.WriteCellData;
 | 
			
		||||
import cn.idev.excel.metadata.property.ExcelContentProperty;
 | 
			
		||||
import org.dromara.common.excel.annotation.ExcelDictFormat;
 | 
			
		||||
import org.dromara.common.core.service.DictService;
 | 
			
		||||
import org.dromara.common.core.utils.SpringUtils;
 | 
			
		||||
 
 | 
			
		||||
@@ -3,12 +3,12 @@ package org.dromara.common.excel.convert;
 | 
			
		||||
import cn.hutool.core.annotation.AnnotationUtil;
 | 
			
		||||
import cn.hutool.core.convert.Convert;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import com.alibaba.excel.converters.Converter;
 | 
			
		||||
import com.alibaba.excel.enums.CellDataTypeEnum;
 | 
			
		||||
import com.alibaba.excel.metadata.GlobalConfiguration;
 | 
			
		||||
import com.alibaba.excel.metadata.data.ReadCellData;
 | 
			
		||||
import com.alibaba.excel.metadata.data.WriteCellData;
 | 
			
		||||
import com.alibaba.excel.metadata.property.ExcelContentProperty;
 | 
			
		||||
import cn.idev.excel.converters.Converter;
 | 
			
		||||
import cn.idev.excel.enums.CellDataTypeEnum;
 | 
			
		||||
import cn.idev.excel.metadata.GlobalConfiguration;
 | 
			
		||||
import cn.idev.excel.metadata.data.ReadCellData;
 | 
			
		||||
import cn.idev.excel.metadata.data.WriteCellData;
 | 
			
		||||
import cn.idev.excel.metadata.property.ExcelContentProperty;
 | 
			
		||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
 | 
			
		||||
import org.dromara.common.excel.annotation.ExcelEnumFormat;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
 
 | 
			
		||||
@@ -3,11 +3,11 @@ package org.dromara.common.excel.core;
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.core.util.ReflectUtil;
 | 
			
		||||
import cn.hutool.core.util.StrUtil;
 | 
			
		||||
import com.alibaba.excel.annotation.ExcelProperty;
 | 
			
		||||
import com.alibaba.excel.metadata.Head;
 | 
			
		||||
import com.alibaba.excel.write.handler.WorkbookWriteHandler;
 | 
			
		||||
import com.alibaba.excel.write.handler.context.WorkbookWriteHandlerContext;
 | 
			
		||||
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
 | 
			
		||||
import cn.idev.excel.annotation.ExcelProperty;
 | 
			
		||||
import cn.idev.excel.metadata.Head;
 | 
			
		||||
import cn.idev.excel.write.handler.WorkbookWriteHandler;
 | 
			
		||||
import cn.idev.excel.write.handler.context.WorkbookWriteHandlerContext;
 | 
			
		||||
import cn.idev.excel.write.merge.AbstractMergeStrategy;
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.SneakyThrows;
 | 
			
		||||
@@ -112,7 +112,13 @@ public class CellMergeStrategy extends AbstractMergeStrategy implements Workbook
 | 
			
		||||
                        }
 | 
			
		||||
                        map.put(field, new RepeatCell(val, i));
 | 
			
		||||
                    } else if (i == list.size() - 1) {
 | 
			
		||||
                        if (i > repeatCell.getCurrent() && isMerge(list, i, field)) {
 | 
			
		||||
                        if (!isMerge(list, i, field)) {
 | 
			
		||||
                            // 如果最后一行不能合并,检查之前的数据是否需要合并
 | 
			
		||||
                            if (i - repeatCell.getCurrent() > 1) {
 | 
			
		||||
                                cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
 | 
			
		||||
                            }
 | 
			
		||||
                        } else if (i > repeatCell.getCurrent()) {
 | 
			
		||||
                            // 如果最后一行可以合并,则直接合并到最后
 | 
			
		||||
                            cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
 | 
			
		||||
                        }
 | 
			
		||||
                    } else if (!isMerge(list, i, field)) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
package org.dromara.common.excel.core;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.util.StrUtil;
 | 
			
		||||
import com.alibaba.excel.context.AnalysisContext;
 | 
			
		||||
import com.alibaba.excel.event.AnalysisEventListener;
 | 
			
		||||
import com.alibaba.excel.exception.ExcelAnalysisException;
 | 
			
		||||
import com.alibaba.excel.exception.ExcelDataConvertException;
 | 
			
		||||
import cn.idev.excel.context.AnalysisContext;
 | 
			
		||||
import cn.idev.excel.event.AnalysisEventListener;
 | 
			
		||||
import cn.idev.excel.exception.ExcelAnalysisException;
 | 
			
		||||
import cn.idev.excel.exception.ExcelDataConvertException;
 | 
			
		||||
import org.dromara.common.core.utils.StreamUtils;
 | 
			
		||||
import org.dromara.common.core.utils.ValidatorUtils;
 | 
			
		||||
import org.dromara.common.json.utils.JsonUtils;
 | 
			
		||||
 
 | 
			
		||||
@@ -5,12 +5,12 @@ import cn.hutool.core.util.ArrayUtil;
 | 
			
		||||
import cn.hutool.core.util.EnumUtil;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import cn.hutool.core.util.StrUtil;
 | 
			
		||||
import com.alibaba.excel.metadata.FieldCache;
 | 
			
		||||
import com.alibaba.excel.metadata.FieldWrapper;
 | 
			
		||||
import com.alibaba.excel.util.ClassUtils;
 | 
			
		||||
import com.alibaba.excel.write.handler.SheetWriteHandler;
 | 
			
		||||
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
 | 
			
		||||
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
 | 
			
		||||
import cn.idev.excel.metadata.FieldCache;
 | 
			
		||||
import cn.idev.excel.metadata.FieldWrapper;
 | 
			
		||||
import cn.idev.excel.util.ClassUtils;
 | 
			
		||||
import cn.idev.excel.write.handler.SheetWriteHandler;
 | 
			
		||||
import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
 | 
			
		||||
import cn.idev.excel.write.metadata.holder.WriteWorkbookHolder;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.apache.poi.ss.usermodel.*;
 | 
			
		||||
import org.apache.poi.ss.util.CellRangeAddressList;
 | 
			
		||||
@@ -175,7 +175,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
 | 
			
		||||
        List<String> firstOptions = options.getOptions();
 | 
			
		||||
        Map<String, List<String>> secoundOptionsMap = options.getNextOptions();
 | 
			
		||||
 | 
			
		||||
        // 采用按行填充数据的方式,避免EasyExcel出现数据无法写入的问题
 | 
			
		||||
        // 采用按行填充数据的方式,避免出现数据无法写入的问题
 | 
			
		||||
        // Attempting to write a row in the range that is already written to disk
 | 
			
		||||
 | 
			
		||||
        // 使用ArrayList记载数据,防止乱序
 | 
			
		||||
@@ -291,9 +291,11 @@ public class ExcelDownHandler implements SheetWriteHandler {
 | 
			
		||||
     * @param value    下拉选可选值
 | 
			
		||||
     */
 | 
			
		||||
    private void dropDownWithSheet(DataValidationHelper helper, Workbook workbook, Sheet sheet, Integer celIndex, List<String> value) {
 | 
			
		||||
        //由于poi的写出相关问题,超过100个会被临时写进硬盘,导致后续内存合并会出Attempting to write a row[] in the range [] that is already written to disk
 | 
			
		||||
        String tmpOptionsSheetName = OPTIONS_SHEET_NAME + "_" + currentOptionsColumnIndex;
 | 
			
		||||
        // 创建下拉数据表
 | 
			
		||||
        Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)))
 | 
			
		||||
            .orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)));
 | 
			
		||||
        Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(tmpOptionsSheetName)))
 | 
			
		||||
            .orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(tmpOptionsSheetName)));
 | 
			
		||||
        // 将下拉表隐藏
 | 
			
		||||
        workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), true);
 | 
			
		||||
        // 完善纵向的一级选项数据表
 | 
			
		||||
@@ -302,9 +304,9 @@ public class ExcelDownHandler implements SheetWriteHandler {
 | 
			
		||||
            // 获取每一选项行,如果没有则创建
 | 
			
		||||
            Row row = Optional.ofNullable(simpleDataSheet.getRow(i))
 | 
			
		||||
                .orElseGet(() -> simpleDataSheet.createRow(finalI));
 | 
			
		||||
            // 获取本级选项对应的选项列,如果没有则创建
 | 
			
		||||
            Cell cell = Optional.ofNullable(row.getCell(currentOptionsColumnIndex))
 | 
			
		||||
                .orElseGet(() -> row.createCell(currentOptionsColumnIndex));
 | 
			
		||||
            // 获取本级选项对应的选项列,如果没有则创建。上述采用多个sheet,默认索引为1列
 | 
			
		||||
            Cell cell = Optional.ofNullable(row.getCell(0))
 | 
			
		||||
                .orElseGet(() -> row.createCell(0));
 | 
			
		||||
            // 设置值
 | 
			
		||||
            cell.setCellValue(value.get(i));
 | 
			
		||||
        }
 | 
			
		||||
@@ -312,13 +314,13 @@ public class ExcelDownHandler implements SheetWriteHandler {
 | 
			
		||||
        // 创建名称管理器
 | 
			
		||||
        Name name = workbook.createName();
 | 
			
		||||
        // 设置名称管理器的别名
 | 
			
		||||
        String nameName = String.format("%s_%d", OPTIONS_SHEET_NAME, celIndex);
 | 
			
		||||
        String nameName = String.format("%s_%d", tmpOptionsSheetName, celIndex);
 | 
			
		||||
        name.setNameName(nameName);
 | 
			
		||||
        // 以纵向第一列创建一级下拉拼接引用位置
 | 
			
		||||
        String function = String.format("%s!$%s$1:$%s$%d",
 | 
			
		||||
            OPTIONS_SHEET_NAME,
 | 
			
		||||
            getExcelColumnName(currentOptionsColumnIndex),
 | 
			
		||||
            getExcelColumnName(currentOptionsColumnIndex),
 | 
			
		||||
            tmpOptionsSheetName,
 | 
			
		||||
            getExcelColumnName(0),
 | 
			
		||||
            getExcelColumnName(0),
 | 
			
		||||
            value.size());
 | 
			
		||||
        // 设置名称管理器的引用位置
 | 
			
		||||
        name.setRefersToFormula(function);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
package org.dromara.common.excel.core;
 | 
			
		||||
 | 
			
		||||
import com.alibaba.excel.read.listener.ReadListener;
 | 
			
		||||
import cn.idev.excel.read.listener.ReadListener;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Excel 导入监听
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,19 @@
 | 
			
		||||
package org.dromara.common.excel.handler;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import com.alibaba.excel.metadata.data.DataFormatData;
 | 
			
		||||
import com.alibaba.excel.metadata.data.WriteCellData;
 | 
			
		||||
import com.alibaba.excel.util.StyleUtil;
 | 
			
		||||
import com.alibaba.excel.write.handler.CellWriteHandler;
 | 
			
		||||
import com.alibaba.excel.write.handler.SheetWriteHandler;
 | 
			
		||||
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
 | 
			
		||||
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
 | 
			
		||||
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
 | 
			
		||||
import com.alibaba.excel.write.metadata.style.WriteFont;
 | 
			
		||||
import cn.idev.excel.annotation.ExcelProperty;
 | 
			
		||||
import cn.idev.excel.metadata.data.DataFormatData;
 | 
			
		||||
import cn.idev.excel.metadata.data.WriteCellData;
 | 
			
		||||
import cn.idev.excel.util.StyleUtil;
 | 
			
		||||
import cn.idev.excel.write.handler.CellWriteHandler;
 | 
			
		||||
import cn.idev.excel.write.handler.SheetWriteHandler;
 | 
			
		||||
import cn.idev.excel.write.handler.context.CellWriteHandlerContext;
 | 
			
		||||
import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
 | 
			
		||||
import cn.idev.excel.write.metadata.style.WriteCellStyle;
 | 
			
		||||
import cn.idev.excel.write.metadata.style.WriteFont;
 | 
			
		||||
import org.apache.poi.ss.usermodel.*;
 | 
			
		||||
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
 | 
			
		||||
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
 | 
			
		||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
 | 
			
		||||
import org.dromara.common.excel.annotation.ExcelNotation;
 | 
			
		||||
import org.dromara.common.excel.annotation.ExcelRequired;
 | 
			
		||||
 | 
			
		||||
@@ -31,12 +31,12 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
 | 
			
		||||
    /**
 | 
			
		||||
     * 批注
 | 
			
		||||
     */
 | 
			
		||||
    private final Map<Integer, String> notationMap;
 | 
			
		||||
    private final Map<String, String> notationMap;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 头列字体颜色
 | 
			
		||||
     */
 | 
			
		||||
    private final Map<Integer, Short> headColumnMap;
 | 
			
		||||
    private final Map<String, Short> headColumnMap;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public DataWriteHandler(Class<?> clazz) {
 | 
			
		||||
@@ -49,15 +49,16 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
 | 
			
		||||
        if (CollUtil.isEmpty(notationMap) && CollUtil.isEmpty(headColumnMap)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // 第一行
 | 
			
		||||
        WriteCellData<?> cellData = context.getFirstCellData();
 | 
			
		||||
        // 第一个格子
 | 
			
		||||
        WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();
 | 
			
		||||
 | 
			
		||||
        DataFormatData dataFormatData = new DataFormatData();
 | 
			
		||||
        // 单元格设置为文本格式
 | 
			
		||||
        dataFormatData.setIndex((short) 49);
 | 
			
		||||
        writeCellStyle.setDataFormatData(dataFormatData);
 | 
			
		||||
 | 
			
		||||
        if (context.getHead()) {
 | 
			
		||||
            DataFormatData dataFormatData = new DataFormatData();
 | 
			
		||||
            // 单元格设置为文本格式
 | 
			
		||||
            dataFormatData.setIndex((short) 49);
 | 
			
		||||
            writeCellStyle.setDataFormatData(dataFormatData);
 | 
			
		||||
            Cell cell = context.getCell();
 | 
			
		||||
            WriteSheetHolder writeSheetHolder = context.getWriteSheetHolder();
 | 
			
		||||
            Sheet sheet = writeSheetHolder.getSheet();
 | 
			
		||||
@@ -67,17 +68,17 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
 | 
			
		||||
            WriteFont headWriteFont = new WriteFont();
 | 
			
		||||
            // 加粗
 | 
			
		||||
            headWriteFont.setBold(true);
 | 
			
		||||
            if (CollUtil.isNotEmpty(headColumnMap) && headColumnMap.containsKey(cell.getColumnIndex())) {
 | 
			
		||||
            if (CollUtil.isNotEmpty(headColumnMap) && headColumnMap.containsKey(cell.getStringCellValue())) {
 | 
			
		||||
                // 设置字体颜色
 | 
			
		||||
                headWriteFont.setColor(headColumnMap.get(cell.getColumnIndex()));
 | 
			
		||||
                headWriteFont.setColor(headColumnMap.get(cell.getStringCellValue()));
 | 
			
		||||
            }
 | 
			
		||||
            writeCellStyle.setWriteFont(headWriteFont);
 | 
			
		||||
            CellStyle cellStyle = StyleUtil.buildCellStyle(workbook, null, writeCellStyle);
 | 
			
		||||
            cell.setCellStyle(cellStyle);
 | 
			
		||||
 | 
			
		||||
            if (CollUtil.isNotEmpty(notationMap) && notationMap.containsKey(cell.getColumnIndex())) {
 | 
			
		||||
            if (CollUtil.isNotEmpty(notationMap) && notationMap.containsKey(cell.getStringCellValue())) {
 | 
			
		||||
                // 批注内容
 | 
			
		||||
                String notationContext = notationMap.get(cell.getColumnIndex());
 | 
			
		||||
                String notationContext = notationMap.get(cell.getStringCellValue());
 | 
			
		||||
                // 创建绘图对象
 | 
			
		||||
                Comment comment = drawing.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), 0, (short) 5, 5));
 | 
			
		||||
                comment.setString(new XSSFRichTextString(notationContext));
 | 
			
		||||
@@ -89,23 +90,16 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取必填列
 | 
			
		||||
     */
 | 
			
		||||
    private static Map<Integer, Short> getRequiredMap(Class<?> clazz) {
 | 
			
		||||
        Map<Integer, Short> requiredMap = new HashMap<>();
 | 
			
		||||
    private static Map<String, Short> getRequiredMap(Class<?> clazz) {
 | 
			
		||||
        Map<String, Short> requiredMap = new HashMap<>();
 | 
			
		||||
        Field[] fields = clazz.getDeclaredFields();
 | 
			
		||||
        // 检查 fields 数组是否为空
 | 
			
		||||
        if (fields.length == 0) {
 | 
			
		||||
            return requiredMap;
 | 
			
		||||
        }
 | 
			
		||||
        Field[] filteredFields = ReflectUtils.getFields(clazz, field -> !"serialVersionUID".equals(field.getName()));
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < filteredFields.length; i++) {
 | 
			
		||||
            Field field = filteredFields[i];
 | 
			
		||||
        for (Field field : fields) {
 | 
			
		||||
            if (!field.isAnnotationPresent(ExcelRequired.class)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            ExcelRequired excelRequired = field.getAnnotation(ExcelRequired.class);
 | 
			
		||||
            int columnIndex =  excelRequired.index() == -1 ? i : excelRequired.index();
 | 
			
		||||
            requiredMap.put(columnIndex, excelRequired.fontColor().getIndex());
 | 
			
		||||
            ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
 | 
			
		||||
            requiredMap.put(excelProperty.value()[0], excelRequired.fontColor().getIndex());
 | 
			
		||||
        }
 | 
			
		||||
        return requiredMap;
 | 
			
		||||
    }
 | 
			
		||||
@@ -113,22 +107,16 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取批注
 | 
			
		||||
     */
 | 
			
		||||
    private static Map<Integer, String> getNotationMap(Class<?> clazz) {
 | 
			
		||||
        Map<Integer, String> notationMap = new HashMap<>();
 | 
			
		||||
    private static Map<String, String> getNotationMap(Class<?> clazz) {
 | 
			
		||||
        Map<String, String> notationMap = new HashMap<>();
 | 
			
		||||
        Field[] fields = clazz.getDeclaredFields();
 | 
			
		||||
        // 检查 fields 数组是否为空
 | 
			
		||||
        if (fields.length == 0) {
 | 
			
		||||
            return notationMap;
 | 
			
		||||
        }
 | 
			
		||||
        Field[] filteredFields = ReflectUtils.getFields(clazz, field -> !"serialVersionUID".equals(field.getName()));
 | 
			
		||||
        for (int i = 0; i < filteredFields.length; i++) {
 | 
			
		||||
            Field field = filteredFields[i];
 | 
			
		||||
        for (Field field : fields) {
 | 
			
		||||
            if (!field.isAnnotationPresent(ExcelNotation.class)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            ExcelNotation excelNotation = field.getAnnotation(ExcelNotation.class);
 | 
			
		||||
            int columnIndex =  excelNotation.index() == -1 ? i : excelNotation.index();
 | 
			
		||||
            notationMap.put(columnIndex, excelNotation.value());
 | 
			
		||||
            ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
 | 
			
		||||
            notationMap.put(excelProperty.value()[0], excelNotation.value());
 | 
			
		||||
        }
 | 
			
		||||
        return notationMap;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -3,13 +3,13 @@ package org.dromara.common.excel.utils;
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.core.io.resource.ClassPathResource;
 | 
			
		||||
import cn.hutool.core.util.IdUtil;
 | 
			
		||||
import com.alibaba.excel.EasyExcel;
 | 
			
		||||
import com.alibaba.excel.ExcelWriter;
 | 
			
		||||
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
 | 
			
		||||
import com.alibaba.excel.write.metadata.WriteSheet;
 | 
			
		||||
import com.alibaba.excel.write.metadata.fill.FillConfig;
 | 
			
		||||
import com.alibaba.excel.write.metadata.fill.FillWrapper;
 | 
			
		||||
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
 | 
			
		||||
import cn.idev.excel.FastExcel;
 | 
			
		||||
import cn.idev.excel.ExcelWriter;
 | 
			
		||||
import cn.idev.excel.write.builder.ExcelWriterSheetBuilder;
 | 
			
		||||
import cn.idev.excel.write.metadata.WriteSheet;
 | 
			
		||||
import cn.idev.excel.write.metadata.fill.FillConfig;
 | 
			
		||||
import cn.idev.excel.write.metadata.fill.FillWrapper;
 | 
			
		||||
import cn.idev.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
 | 
			
		||||
import jakarta.servlet.ServletOutputStream;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import lombok.AccessLevel;
 | 
			
		||||
@@ -43,7 +43,7 @@ public class ExcelUtil {
 | 
			
		||||
     * @return 转换后集合
 | 
			
		||||
     */
 | 
			
		||||
    public static <T> List<T> importExcel(InputStream is, Class<T> clazz) {
 | 
			
		||||
        return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
 | 
			
		||||
        return FastExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -57,7 +57,7 @@ public class ExcelUtil {
 | 
			
		||||
     */
 | 
			
		||||
    public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate) {
 | 
			
		||||
        DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
 | 
			
		||||
        EasyExcel.read(is, clazz, listener).sheet().doRead();
 | 
			
		||||
        FastExcel.read(is, clazz, listener).sheet().doRead();
 | 
			
		||||
        return listener.getExcelResult();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -70,7 +70,7 @@ public class ExcelUtil {
 | 
			
		||||
     * @return 转换后集合
 | 
			
		||||
     */
 | 
			
		||||
    public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener) {
 | 
			
		||||
        EasyExcel.read(is, clazz, listener).sheet().doRead();
 | 
			
		||||
        FastExcel.read(is, clazz, listener).sheet().doRead();
 | 
			
		||||
        return listener.getExcelResult();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -186,7 +186,7 @@ public class ExcelUtil {
 | 
			
		||||
     */
 | 
			
		||||
    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge,
 | 
			
		||||
                                       OutputStream os, List<DropDownOptions> options) {
 | 
			
		||||
        ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
 | 
			
		||||
        ExcelWriterSheetBuilder builder = FastExcel.write(os, clazz)
 | 
			
		||||
            .autoCloseStream(false)
 | 
			
		||||
            // 自动适配
 | 
			
		||||
            .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
 | 
			
		||||
@@ -215,6 +215,9 @@ public class ExcelUtil {
 | 
			
		||||
     */
 | 
			
		||||
    public static <T> void exportTemplate(List<T> data, String filename, String templatePath, HttpServletResponse response) {
 | 
			
		||||
        try {
 | 
			
		||||
            if (CollUtil.isEmpty(data)) {
 | 
			
		||||
                throw new IllegalArgumentException("数据为空");
 | 
			
		||||
            }
 | 
			
		||||
            resetResponse(filename, response);
 | 
			
		||||
            ServletOutputStream os = response.getOutputStream();
 | 
			
		||||
            exportTemplate(data, templatePath, os);
 | 
			
		||||
@@ -233,18 +236,15 @@ public class ExcelUtil {
 | 
			
		||||
     * @param os           输出流
 | 
			
		||||
     */
 | 
			
		||||
    public static <T> void exportTemplate(List<T> data, String templatePath, OutputStream os) {
 | 
			
		||||
        if (CollUtil.isEmpty(data)) {
 | 
			
		||||
            throw new IllegalArgumentException("数据为空");
 | 
			
		||||
        }
 | 
			
		||||
        ClassPathResource templateResource = new ClassPathResource(templatePath);
 | 
			
		||||
        ExcelWriter excelWriter = EasyExcel.write(os)
 | 
			
		||||
        ExcelWriter excelWriter = FastExcel.write(os)
 | 
			
		||||
            .withTemplate(templateResource.getStream())
 | 
			
		||||
            .autoCloseStream(false)
 | 
			
		||||
            // 大数值自动转换 防止失真
 | 
			
		||||
            .registerConverter(new ExcelBigNumberConvert())
 | 
			
		||||
            .registerWriteHandler(new DataWriteHandler(data.get(0).getClass()))
 | 
			
		||||
            .build();
 | 
			
		||||
        WriteSheet writeSheet = EasyExcel.writerSheet().build();
 | 
			
		||||
        WriteSheet writeSheet = FastExcel.writerSheet().build();
 | 
			
		||||
        FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
 | 
			
		||||
        // 单表多数据导出 模板格式为 {.属性}
 | 
			
		||||
        for (T d : data) {
 | 
			
		||||
@@ -265,6 +265,9 @@ public class ExcelUtil {
 | 
			
		||||
     */
 | 
			
		||||
    public static void exportTemplateMultiList(Map<String, Object> data, String filename, String templatePath, HttpServletResponse response) {
 | 
			
		||||
        try {
 | 
			
		||||
            if (CollUtil.isEmpty(data)) {
 | 
			
		||||
                throw new IllegalArgumentException("数据为空");
 | 
			
		||||
            }
 | 
			
		||||
            resetResponse(filename, response);
 | 
			
		||||
            ServletOutputStream os = response.getOutputStream();
 | 
			
		||||
            exportTemplateMultiList(data, templatePath, os);
 | 
			
		||||
@@ -285,6 +288,9 @@ public class ExcelUtil {
 | 
			
		||||
     */
 | 
			
		||||
    public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String filename, String templatePath, HttpServletResponse response) {
 | 
			
		||||
        try {
 | 
			
		||||
            if (CollUtil.isEmpty(data)) {
 | 
			
		||||
                throw new IllegalArgumentException("数据为空");
 | 
			
		||||
            }
 | 
			
		||||
            resetResponse(filename, response);
 | 
			
		||||
            ServletOutputStream os = response.getOutputStream();
 | 
			
		||||
            exportTemplateMultiSheet(data, templatePath, os);
 | 
			
		||||
@@ -303,17 +309,14 @@ public class ExcelUtil {
 | 
			
		||||
     * @param os           输出流
 | 
			
		||||
     */
 | 
			
		||||
    public static void exportTemplateMultiList(Map<String, Object> data, String templatePath, OutputStream os) {
 | 
			
		||||
        if (CollUtil.isEmpty(data)) {
 | 
			
		||||
            throw new IllegalArgumentException("数据为空");
 | 
			
		||||
        }
 | 
			
		||||
        ClassPathResource templateResource = new ClassPathResource(templatePath);
 | 
			
		||||
        ExcelWriter excelWriter = EasyExcel.write(os)
 | 
			
		||||
        ExcelWriter excelWriter = FastExcel.write(os)
 | 
			
		||||
            .withTemplate(templateResource.getStream())
 | 
			
		||||
            .autoCloseStream(false)
 | 
			
		||||
            // 大数值自动转换 防止失真
 | 
			
		||||
            .registerConverter(new ExcelBigNumberConvert())
 | 
			
		||||
            .build();
 | 
			
		||||
        WriteSheet writeSheet = EasyExcel.writerSheet().build();
 | 
			
		||||
        WriteSheet writeSheet = FastExcel.writerSheet().build();
 | 
			
		||||
        for (Map.Entry<String, Object> map : data.entrySet()) {
 | 
			
		||||
            // 设置列表后续还有数据
 | 
			
		||||
            FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
 | 
			
		||||
@@ -337,18 +340,15 @@ public class ExcelUtil {
 | 
			
		||||
     * @param os           输出流
 | 
			
		||||
     */
 | 
			
		||||
    public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String templatePath, OutputStream os) {
 | 
			
		||||
        if (CollUtil.isEmpty(data)) {
 | 
			
		||||
            throw new IllegalArgumentException("数据为空");
 | 
			
		||||
        }
 | 
			
		||||
        ClassPathResource templateResource = new ClassPathResource(templatePath);
 | 
			
		||||
        ExcelWriter excelWriter = EasyExcel.write(os)
 | 
			
		||||
        ExcelWriter excelWriter = FastExcel.write(os)
 | 
			
		||||
            .withTemplate(templateResource.getStream())
 | 
			
		||||
            .autoCloseStream(false)
 | 
			
		||||
            // 大数值自动转换 防止失真
 | 
			
		||||
            .registerConverter(new ExcelBigNumberConvert())
 | 
			
		||||
            .build();
 | 
			
		||||
        for (int i = 0; i < data.size(); i++) {
 | 
			
		||||
            WriteSheet writeSheet = EasyExcel.writerSheet(i).build();
 | 
			
		||||
            WriteSheet writeSheet = FastExcel.writerSheet(i).build();
 | 
			
		||||
            for (Map.Entry<String, Object> map : data.get(i).entrySet()) {
 | 
			
		||||
                // 设置列表后续还有数据
 | 
			
		||||
                FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,9 @@ import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
 | 
			
		||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 | 
			
		||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
 | 
			
		||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
 | 
			
		||||
import org.dromara.common.json.handler.BigNumberSerializer;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.dromara.common.json.handler.BigNumberSerializer;
 | 
			
		||||
import org.dromara.common.json.handler.CustomDateDeserializer;
 | 
			
		||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
 | 
			
		||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
 | 
			
		||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
 | 
			
		||||
@@ -15,6 +16,7 @@ import java.math.BigDecimal;
 | 
			
		||||
import java.math.BigInteger;
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.time.format.DateTimeFormatter;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import java.util.TimeZone;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -38,6 +40,7 @@ public class JacksonConfig {
 | 
			
		||||
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 | 
			
		||||
            javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
 | 
			
		||||
            javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
 | 
			
		||||
            javaTimeModule.addDeserializer(Date.class, new CustomDateDeserializer());
 | 
			
		||||
            builder.modules(javaTimeModule);
 | 
			
		||||
            builder.timeZone(TimeZone.getDefault());
 | 
			
		||||
            log.info("初始化 jackson 配置");
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,31 @@
 | 
			
		||||
package org.dromara.common.json.handler;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.date.DateUtil;
 | 
			
		||||
import com.fasterxml.jackson.core.JsonParser;
 | 
			
		||||
import com.fasterxml.jackson.databind.DeserializationContext;
 | 
			
		||||
import com.fasterxml.jackson.databind.JsonDeserializer;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 自定义 Date 类型反序列化处理器(支持多种格式)
 | 
			
		||||
 *
 | 
			
		||||
 * @author AprilWind
 | 
			
		||||
 */
 | 
			
		||||
public class CustomDateDeserializer extends JsonDeserializer<Date> {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 反序列化逻辑:将字符串转换为 Date 对象
 | 
			
		||||
     *
 | 
			
		||||
     * @param p    JSON 解析器,用于获取字符串值
 | 
			
		||||
     * @param ctxt 上下文环境(可用于获取更多配置)
 | 
			
		||||
     * @return 转换后的 Date 对象,若为空字符串返回 null
 | 
			
		||||
     * @throws IOException 当字符串格式非法或转换失败时抛出
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
 | 
			
		||||
        return DateUtil.parse(p.getText());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
package org.dromara.common.mybatis.core.page;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.http.HttpStatus;
 | 
			
		||||
import com.baomidou.mybatisplus.core.metadata.IPage;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
@@ -88,4 +89,19 @@ public class TableDataInfo<T> implements Serializable {
 | 
			
		||||
        return rspData;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据原始数据列表和分页参数,构建表格分页数据对象(用于假分页)
 | 
			
		||||
     *
 | 
			
		||||
     * @param list 原始数据列表(全部数据)
 | 
			
		||||
     * @param page 分页参数对象(包含当前页码、每页大小等)
 | 
			
		||||
     * @return 构造好的分页结果 TableDataInfo<T>
 | 
			
		||||
     */
 | 
			
		||||
    public static <T> TableDataInfo<T> build(List<T> list, IPage<T> page) {
 | 
			
		||||
        if (CollUtil.isEmpty(list)) {
 | 
			
		||||
            return TableDataInfo.build();
 | 
			
		||||
        }
 | 
			
		||||
        List<T> pageList = CollUtil.page((int) page.getCurrent() - 1, (int) page.getSize(), list);
 | 
			
		||||
        return new TableDataInfo<>(pageList, list.size());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,11 @@ import java.util.Date;
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class InjectionMetaObjectHandler implements MetaObjectHandler {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 如果用户不存在默认注入-1代表无用户
 | 
			
		||||
     */
 | 
			
		||||
    private static final Long DEFAULT_USER_ID = -1L;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 插入填充方法,用于在插入数据时自动填充实体对象中的创建时间、更新时间、创建人、更新人等信息
 | 
			
		||||
     *
 | 
			
		||||
@@ -45,6 +50,11 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
 | 
			
		||||
                        baseEntity.setCreateBy(userId);
 | 
			
		||||
                        baseEntity.setUpdateBy(userId);
 | 
			
		||||
                        baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), loginUser.getDeptId()));
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // 填充创建人、更新人和创建部门信息
 | 
			
		||||
                        baseEntity.setCreateBy(DEFAULT_USER_ID);
 | 
			
		||||
                        baseEntity.setUpdateBy(DEFAULT_USER_ID);
 | 
			
		||||
                        baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), DEFAULT_USER_ID));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
@@ -74,6 +84,8 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
 | 
			
		||||
                Long userId = LoginHelper.getUserId();
 | 
			
		||||
                if (ObjectUtil.isNotNull(userId)) {
 | 
			
		||||
                    baseEntity.setUpdateBy(userId);
 | 
			
		||||
                } else {
 | 
			
		||||
                    baseEntity.setUpdateBy(DEFAULT_USER_ID);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
 | 
			
		||||
@@ -93,7 +105,6 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
 | 
			
		||||
        try {
 | 
			
		||||
            loginUser = LoginHelper.getLoginUser();
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.warn("自动注入警告 => 用户未登录");
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        return loginUser;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
package org.dromara.common.mybatis.handler;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.http.HttpStatus;
 | 
			
		||||
import jakarta.servlet.http.HttpServletRequest;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.dromara.common.core.domain.R;
 | 
			
		||||
@@ -25,7 +26,7 @@ public class MybatisExceptionHandler {
 | 
			
		||||
    public R<Void> handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) {
 | 
			
		||||
        String requestURI = request.getRequestURI();
 | 
			
		||||
        log.error("请求地址'{}',数据库中已存在记录'{}'", requestURI, e.getMessage());
 | 
			
		||||
        return R.fail("数据库中已存在该记录,请联系管理员确认");
 | 
			
		||||
        return R.fail(HttpStatus.HTTP_CONFLICT, "数据库中已存在该记录,请联系管理员确认");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -35,12 +36,12 @@ public class MybatisExceptionHandler {
 | 
			
		||||
    public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
 | 
			
		||||
        String requestURI = request.getRequestURI();
 | 
			
		||||
        String message = e.getMessage();
 | 
			
		||||
        if (StringUtils.contains("CannotFindDataSourceException", message)) {
 | 
			
		||||
        if (StringUtils.contains(message, "CannotFindDataSourceException")) {
 | 
			
		||||
            log.error("请求地址'{}', 未找到数据源", requestURI);
 | 
			
		||||
            return R.fail("未找到数据源,请联系管理员确认");
 | 
			
		||||
            return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "未找到数据源,请联系管理员确认");
 | 
			
		||||
        }
 | 
			
		||||
        log.error("请求地址'{}', Mybatis系统异常", requestURI, e);
 | 
			
		||||
        return R.fail(message);
 | 
			
		||||
        return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, message);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -145,18 +145,25 @@ public class PlusSpringCacheManager implements CacheManager {
 | 
			
		||||
        if (array.length > 3) {
 | 
			
		||||
            config.setMaxSize(Integer.parseInt(array[3]));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
 | 
			
		||||
            return createMap(name, config);
 | 
			
		||||
        int local = 1;
 | 
			
		||||
        if (array.length > 4) {
 | 
			
		||||
            local = Integer.parseInt(array[4]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return createMapCache(name, config);
 | 
			
		||||
        if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
 | 
			
		||||
            return createMap(name, config, local);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return createMapCache(name, config, local);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Cache createMap(String name, CacheConfig config) {
 | 
			
		||||
    private Cache createMap(String name, CacheConfig config, int local) {
 | 
			
		||||
        RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
 | 
			
		||||
 | 
			
		||||
        Cache cache = new CaffeineCacheDecorator(name, new RedissonCache(map, allowNullValues));
 | 
			
		||||
        Cache cache = new RedissonCache(map, allowNullValues);
 | 
			
		||||
        if (local == 1) {
 | 
			
		||||
            cache = new CaffeineCacheDecorator(name, cache);
 | 
			
		||||
        }
 | 
			
		||||
        if (transactionAware) {
 | 
			
		||||
            cache = new TransactionAwareCacheDecorator(cache);
 | 
			
		||||
        }
 | 
			
		||||
@@ -167,10 +174,13 @@ public class PlusSpringCacheManager implements CacheManager {
 | 
			
		||||
        return cache;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Cache createMapCache(String name, CacheConfig config) {
 | 
			
		||||
    private Cache createMapCache(String name, CacheConfig config, int local) {
 | 
			
		||||
        RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
 | 
			
		||||
 | 
			
		||||
        Cache cache = new CaffeineCacheDecorator(name, new RedissonCache(map, config, allowNullValues));
 | 
			
		||||
        Cache cache = new RedissonCache(map, config, allowNullValues);
 | 
			
		||||
        if (local == 1) {
 | 
			
		||||
            cache = new CaffeineCacheDecorator(name, cache);
 | 
			
		||||
        }
 | 
			
		||||
        if (transactionAware) {
 | 
			
		||||
            cache = new TransactionAwareCacheDecorator(cache);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,9 @@ import java.util.function.Function;
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 * @version 3.6.0 新增
 | 
			
		||||
 * @deprecated redisson 新版本已经将队列功能标记删除 一些技术问题无法解决 建议搭建MQ使用
 | 
			
		||||
 */
 | 
			
		||||
@Deprecated
 | 
			
		||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
 | 
			
		||||
public class QueueUtils {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,165 +1,180 @@
 | 
			
		||||
package org.dromara.common.redis.utils;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.date.DatePattern;
 | 
			
		||||
import cn.hutool.core.date.DateUtil;
 | 
			
		||||
import org.dromara.common.core.utils.SpringUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import lombok.AccessLevel;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import org.redisson.api.RIdGenerator;
 | 
			
		||||
import org.redisson.api.RedissonClient;
 | 
			
		||||
 | 
			
		||||
import java.time.Duration;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 发号器工具类
 | 
			
		||||
 *
 | 
			
		||||
 * @author 秋辞未寒
 | 
			
		||||
 * @date 2024-12-10
 | 
			
		||||
 */
 | 
			
		||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
 | 
			
		||||
public class SequenceUtils {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 默认初始值
 | 
			
		||||
     */
 | 
			
		||||
    public static final Long DEFAULT_INIT_VALUE = 1L;
 | 
			
		||||
    /**
 | 
			
		||||
     * 默认步长
 | 
			
		||||
     */
 | 
			
		||||
    public static final Long DEFAULT_STEP_VALUE = 1L;
 | 
			
		||||
    /**
 | 
			
		||||
     * 默认过期时间-天
 | 
			
		||||
     */
 | 
			
		||||
    public static final Duration DEFAULT_EXPIRE_TIME_DAY = Duration.ofDays(1);
 | 
			
		||||
    /**
 | 
			
		||||
     * 默认过期时间-分钟
 | 
			
		||||
     */
 | 
			
		||||
    public static final Duration DEFAULT_EXPIRE_TIME_MINUTE = Duration.ofMinutes(1);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取Redisson客户端实例
 | 
			
		||||
     */
 | 
			
		||||
    private static final RedissonClient REDISSON_CLIENT = SpringUtils.getBean(RedissonClient.class);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取ID生成器
 | 
			
		||||
     *
 | 
			
		||||
     * @param key        业务key
 | 
			
		||||
     * @param expireTime 过期时间
 | 
			
		||||
     * @param initValue  ID初始值
 | 
			
		||||
     * @param stepValue  ID步长
 | 
			
		||||
     * @return ID生成器
 | 
			
		||||
     */
 | 
			
		||||
    private static RIdGenerator getIdGenerator(String key, Duration expireTime, Long initValue, Long stepValue) {
 | 
			
		||||
        if (initValue == null || initValue <= 0) {
 | 
			
		||||
            initValue = DEFAULT_INIT_VALUE;
 | 
			
		||||
        }
 | 
			
		||||
        if (stepValue == null || stepValue <= 0) {
 | 
			
		||||
            stepValue = DEFAULT_STEP_VALUE;
 | 
			
		||||
        }
 | 
			
		||||
        RIdGenerator idGenerator = REDISSON_CLIENT.getIdGenerator(key);
 | 
			
		||||
        // 设置初始值和步长
 | 
			
		||||
        idGenerator.tryInit(initValue, stepValue);
 | 
			
		||||
        // 设置过期时间
 | 
			
		||||
        idGenerator.expire(expireTime);
 | 
			
		||||
        return idGenerator;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取指定业务key的唯一id
 | 
			
		||||
     *
 | 
			
		||||
     * @param key        业务key
 | 
			
		||||
     * @param expireTime 过期时间
 | 
			
		||||
     * @param initValue  ID初始值
 | 
			
		||||
     * @param stepValue  ID步长
 | 
			
		||||
     * @return 唯一id
 | 
			
		||||
     */
 | 
			
		||||
    public static long nextId(String key, Duration expireTime, Long initValue, Long stepValue) {
 | 
			
		||||
        return getIdGenerator(key, expireTime, initValue, stepValue).nextId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取指定业务key的唯一id字符串
 | 
			
		||||
     *
 | 
			
		||||
     * @param key        业务key
 | 
			
		||||
     * @param expireTime 过期时间
 | 
			
		||||
     * @param initValue  ID初始值
 | 
			
		||||
     * @param stepValue  ID步长
 | 
			
		||||
     * @return 唯一id
 | 
			
		||||
     */
 | 
			
		||||
    public static String nextIdStr(String key, Duration expireTime, Long initValue, Long stepValue) {
 | 
			
		||||
        return String.valueOf(nextId(key, expireTime, initValue, stepValue));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取指定业务key的唯一id (ID初始值=1,ID步长=1)
 | 
			
		||||
     *
 | 
			
		||||
     * @param key        业务key
 | 
			
		||||
     * @param expireTime 过期时间
 | 
			
		||||
     * @return 唯一id
 | 
			
		||||
     */
 | 
			
		||||
    public static long nextId(String key, Duration expireTime) {
 | 
			
		||||
        return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取指定业务key的唯一id字符串 (ID初始值=1,ID步长=1)
 | 
			
		||||
     *
 | 
			
		||||
     * @param key        业务key
 | 
			
		||||
     * @param expireTime 过期时间
 | 
			
		||||
     * @return 唯一id
 | 
			
		||||
     */
 | 
			
		||||
    public static String nextIdStr(String key, Duration expireTime) {
 | 
			
		||||
        return String.valueOf(nextId(key, expireTime));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取 yyyyMMdd 开头的唯一id
 | 
			
		||||
     *
 | 
			
		||||
     * @return 唯一id
 | 
			
		||||
     */
 | 
			
		||||
    public static String nextIdDate() {
 | 
			
		||||
        return nextIdDate("");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取 prefix + yyyyMMdd 开头的唯一id
 | 
			
		||||
     *
 | 
			
		||||
     * @param prefix 业务前缀
 | 
			
		||||
     * @return 唯一id
 | 
			
		||||
     */
 | 
			
		||||
    public static String nextIdDate(String prefix) {
 | 
			
		||||
        // 前缀+日期 构建 prefixKey
 | 
			
		||||
        String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_FORMATTER));
 | 
			
		||||
        // 获取下一个id
 | 
			
		||||
        long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_DAY, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
 | 
			
		||||
        // 返回完整id
 | 
			
		||||
        return StringUtils.format("{}{}", prefixKey, nextId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取 yyyyMMddHHmmss 开头的唯一id
 | 
			
		||||
     *
 | 
			
		||||
     * @return 唯一id
 | 
			
		||||
     */
 | 
			
		||||
    public static String nextIdDateTime() {
 | 
			
		||||
        return nextIdDateTime("");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取 prefix + yyyyMMddHHmmss 开头的唯一id
 | 
			
		||||
     *
 | 
			
		||||
     * @param prefix 业务前缀
 | 
			
		||||
     * @return 唯一id
 | 
			
		||||
     */
 | 
			
		||||
    public static String nextIdDateTime(String prefix) {
 | 
			
		||||
        // 前缀+日期时间 构建 prefixKey
 | 
			
		||||
        String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_FORMATTER));
 | 
			
		||||
        // 获取下一个id
 | 
			
		||||
        long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_MINUTE, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
 | 
			
		||||
        // 返回完整id
 | 
			
		||||
        return StringUtils.format("{}{}", prefixKey, nextId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
package org.dromara.common.redis.utils;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.date.DatePattern;
 | 
			
		||||
import cn.hutool.core.date.DateUtil;
 | 
			
		||||
import lombok.AccessLevel;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import org.dromara.common.core.utils.SpringUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.redisson.api.RIdGenerator;
 | 
			
		||||
import org.redisson.api.RedissonClient;
 | 
			
		||||
 | 
			
		||||
import java.time.Duration;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 发号器工具类
 | 
			
		||||
 *
 | 
			
		||||
 * @author 秋辞未寒
 | 
			
		||||
 * @date 2024-12-10
 | 
			
		||||
 */
 | 
			
		||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
 | 
			
		||||
public class SequenceUtils {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 默认初始值
 | 
			
		||||
     */
 | 
			
		||||
    public static final Long DEFAULT_INIT_VALUE = 1L;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 默认步长
 | 
			
		||||
     */
 | 
			
		||||
    public static final Long DEFAULT_STEP_VALUE = 1L;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 默认过期时间-天
 | 
			
		||||
     */
 | 
			
		||||
    public static final Duration DEFAULT_EXPIRE_TIME_DAY = Duration.ofDays(1);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 默认过期时间-分钟
 | 
			
		||||
     */
 | 
			
		||||
    public static final Duration DEFAULT_EXPIRE_TIME_MINUTE = Duration.ofMinutes(1);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取Redisson客户端实例
 | 
			
		||||
     */
 | 
			
		||||
    private static final RedissonClient REDISSON_CLIENT = SpringUtils.getBean(RedissonClient.class);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取ID生成器
 | 
			
		||||
     *
 | 
			
		||||
     * @param key        业务key
 | 
			
		||||
     * @param expireTime 过期时间
 | 
			
		||||
     * @param initValue  ID初始值
 | 
			
		||||
     * @param stepValue  ID步长
 | 
			
		||||
     * @return ID生成器
 | 
			
		||||
     */
 | 
			
		||||
    private static RIdGenerator getIdGenerator(String key, Duration expireTime, Long initValue, Long stepValue) {
 | 
			
		||||
        if (initValue == null || initValue <= 0) {
 | 
			
		||||
            initValue = DEFAULT_INIT_VALUE;
 | 
			
		||||
        }
 | 
			
		||||
        if (stepValue == null || stepValue <= 0) {
 | 
			
		||||
            stepValue = DEFAULT_STEP_VALUE;
 | 
			
		||||
        }
 | 
			
		||||
        RIdGenerator idGenerator = REDISSON_CLIENT.getIdGenerator(key);
 | 
			
		||||
        // 设置初始值和步长
 | 
			
		||||
        idGenerator.tryInit(initValue, stepValue);
 | 
			
		||||
        // 设置过期时间
 | 
			
		||||
        idGenerator.expire(expireTime);
 | 
			
		||||
        return idGenerator;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取指定业务key的唯一id
 | 
			
		||||
     *
 | 
			
		||||
     * @param key        业务key
 | 
			
		||||
     * @param expireTime 过期时间
 | 
			
		||||
     * @param initValue  ID初始值
 | 
			
		||||
     * @param stepValue  ID步长
 | 
			
		||||
     * @return 唯一id
 | 
			
		||||
     */
 | 
			
		||||
    public static long nextId(String key, Duration expireTime, Long initValue, Long stepValue) {
 | 
			
		||||
        return getIdGenerator(key, expireTime, initValue, stepValue).nextId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取指定业务key的唯一id字符串
 | 
			
		||||
     *
 | 
			
		||||
     * @param key        业务key
 | 
			
		||||
     * @param expireTime 过期时间
 | 
			
		||||
     * @param initValue  ID初始值
 | 
			
		||||
     * @param stepValue  ID步长
 | 
			
		||||
     * @return 唯一id
 | 
			
		||||
     */
 | 
			
		||||
    public static String nextIdStr(String key, Duration expireTime, Long initValue, Long stepValue) {
 | 
			
		||||
        return String.valueOf(nextId(key, expireTime, initValue, stepValue));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取指定业务key的唯一id (ID初始值=1,ID步长=1)
 | 
			
		||||
     *
 | 
			
		||||
     * @param key        业务key
 | 
			
		||||
     * @param expireTime 过期时间
 | 
			
		||||
     * @return 唯一id
 | 
			
		||||
     */
 | 
			
		||||
    public static long nextId(String key, Duration expireTime) {
 | 
			
		||||
        return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取指定业务key的唯一id字符串 (ID初始值=1,ID步长=1)
 | 
			
		||||
     *
 | 
			
		||||
     * @param key        业务key
 | 
			
		||||
     * @param expireTime 过期时间
 | 
			
		||||
     * @return 唯一id
 | 
			
		||||
     */
 | 
			
		||||
    public static String nextIdStr(String key, Duration expireTime) {
 | 
			
		||||
        return String.valueOf(nextId(key, expireTime));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取指定业务key的唯一id字符串 (ID初始值=1,ID步长=1),不足位数自动补零
 | 
			
		||||
     *
 | 
			
		||||
     * @param key        业务key
 | 
			
		||||
     * @param expireTime 过期时间
 | 
			
		||||
     * @param width      位数,不足左补0
 | 
			
		||||
     * @return 补零后的唯一id字符串
 | 
			
		||||
     */
 | 
			
		||||
    public static String nextPaddedIdStr(String key, Duration expireTime, Integer width) {
 | 
			
		||||
        return StringUtils.leftPad(nextIdStr(key, expireTime), width, '0');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取 yyyyMMdd 开头的唯一id
 | 
			
		||||
     *
 | 
			
		||||
     * @return 唯一id
 | 
			
		||||
     */
 | 
			
		||||
    public static String nextIdDate() {
 | 
			
		||||
        return nextIdDate("");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取 prefix + yyyyMMdd 开头的唯一id
 | 
			
		||||
     *
 | 
			
		||||
     * @param prefix 业务前缀
 | 
			
		||||
     * @return 唯一id
 | 
			
		||||
     */
 | 
			
		||||
    public static String nextIdDate(String prefix) {
 | 
			
		||||
        // 前缀+日期 构建 prefixKey
 | 
			
		||||
        String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATE_FORMATTER));
 | 
			
		||||
        // 获取下一个id
 | 
			
		||||
        long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_DAY, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
 | 
			
		||||
        // 返回完整id
 | 
			
		||||
        return StringUtils.format("{}{}", prefixKey, nextId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取 yyyyMMddHHmmss 开头的唯一id
 | 
			
		||||
     *
 | 
			
		||||
     * @return 唯一id
 | 
			
		||||
     */
 | 
			
		||||
    public static String nextIdDateTime() {
 | 
			
		||||
        return nextIdDateTime("");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取 prefix + yyyyMMddHHmmss 开头的唯一id
 | 
			
		||||
     *
 | 
			
		||||
     * @param prefix 业务前缀
 | 
			
		||||
     * @return 唯一id
 | 
			
		||||
     */
 | 
			
		||||
    public static String nextIdDateTime(String prefix) {
 | 
			
		||||
        // 前缀+日期时间 构建 prefixKey
 | 
			
		||||
        String prefixKey = StringUtils.format("{}{}", StringUtils.blankToDefault(prefix, ""), DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_FORMATTER));
 | 
			
		||||
        // 获取下一个id
 | 
			
		||||
        long nextId = getIdGenerator(prefixKey, DEFAULT_EXPIRE_TIME_MINUTE, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE).nextId();
 | 
			
		||||
        // 返回完整id
 | 
			
		||||
        return StringUtils.format("{}{}", prefixKey, nextId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
package org.dromara.common.satoken.core.dao;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.dao.SaTokenDao;
 | 
			
		||||
import cn.dev33.satoken.dao.auto.SaTokenDaoBySessionFollowObject;
 | 
			
		||||
import cn.dev33.satoken.util.SaFoxUtil;
 | 
			
		||||
import com.github.benmanes.caffeine.cache.Cache;
 | 
			
		||||
import com.github.benmanes.caffeine.cache.Caffeine;
 | 
			
		||||
@@ -16,10 +16,12 @@ import java.util.concurrent.TimeUnit;
 | 
			
		||||
 * Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一)
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 采用 caffeine + redis 多级缓存 优化并发查询效率
 | 
			
		||||
 * <p>
 | 
			
		||||
 * SaTokenDaoBySessionFollowObject 是 SaTokenDao 子集简化了session方法处理
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 */
 | 
			
		||||
public class PlusSaTokenDao implements SaTokenDao {
 | 
			
		||||
public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
 | 
			
		||||
 | 
			
		||||
    private static final Cache<String, Object> CAFFEINE = Caffeine.newBuilder()
 | 
			
		||||
        // 设置最后一次写入或访问后经过固定时间过期
 | 
			
		||||
@@ -85,7 +87,8 @@ public class PlusSaTokenDao implements SaTokenDao {
 | 
			
		||||
    @Override
 | 
			
		||||
    public long getTimeout(String key) {
 | 
			
		||||
        long timeout = RedisUtils.getTimeToLive(key);
 | 
			
		||||
        return timeout < 0 ? timeout : timeout / 1000;
 | 
			
		||||
        // 加1的目的 解决sa-token使用秒 redis是毫秒导致1秒的精度问题 手动补偿
 | 
			
		||||
        return timeout < 0 ? timeout : timeout / 1000 + 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -106,6 +109,19 @@ public class PlusSaTokenDao implements SaTokenDao {
 | 
			
		||||
        return o;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取 Object (指定反序列化类型),如无返空
 | 
			
		||||
     *
 | 
			
		||||
     * @param key 键名称
 | 
			
		||||
     * @return object
 | 
			
		||||
     */
 | 
			
		||||
    @SuppressWarnings("unchecked cast")
 | 
			
		||||
    @Override
 | 
			
		||||
    public <T> T getObject(String key, Class<T> classType) {
 | 
			
		||||
        Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
 | 
			
		||||
        return (T) o;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 写入Object,并设定存活时间 (单位: 秒)
 | 
			
		||||
     */
 | 
			
		||||
@@ -152,7 +168,8 @@ public class PlusSaTokenDao implements SaTokenDao {
 | 
			
		||||
    @Override
 | 
			
		||||
    public long getObjectTimeout(String key) {
 | 
			
		||||
        long timeout = RedisUtils.getTimeToLive(key);
 | 
			
		||||
        return timeout < 0 ? timeout : timeout / 1000;
 | 
			
		||||
        // 加1的目的 解决sa-token使用秒 redis是毫秒导致1秒的精度问题 手动补偿
 | 
			
		||||
        return timeout < 0 ? timeout : timeout / 1000 + 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -163,7 +180,6 @@ public class PlusSaTokenDao implements SaTokenDao {
 | 
			
		||||
        RedisUtils.expire(key, Duration.ofSeconds(timeout));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 搜索数据
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,13 @@
 | 
			
		||||
package org.dromara.common.satoken.core.service;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.stp.StpInterface;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import org.dromara.common.core.domain.model.LoginUser;
 | 
			
		||||
import org.dromara.common.core.enums.UserType;
 | 
			
		||||
import org.dromara.common.core.exception.ServiceException;
 | 
			
		||||
import org.dromara.common.core.service.PermissionService;
 | 
			
		||||
import org.dromara.common.core.utils.SpringUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.dromara.common.satoken.utils.LoginHelper;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
@@ -21,13 +26,21 @@ public class SaPermissionImpl implements StpInterface {
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<String> getPermissionList(Object loginId, String loginType) {
 | 
			
		||||
        LoginUser loginUser = LoginHelper.getLoginUser();
 | 
			
		||||
        if (ObjectUtil.isNull(loginUser) || !loginUser.getLoginId().equals(loginId)) {
 | 
			
		||||
            PermissionService permissionService = getPermissionService();
 | 
			
		||||
            if (ObjectUtil.isNotNull(permissionService)) {
 | 
			
		||||
                List<String> list = StringUtils.splitList(loginId.toString(), ":");
 | 
			
		||||
                return new ArrayList<>(permissionService.getMenuPermission(Long.parseLong(list.get(1))));
 | 
			
		||||
            } else {
 | 
			
		||||
                throw new ServiceException("PermissionService 实现类不存在");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        UserType userType = UserType.getUserType(loginUser.getUserType());
 | 
			
		||||
        if (userType == UserType.SYS_USER) {
 | 
			
		||||
            return new ArrayList<>(loginUser.getMenuPermission());
 | 
			
		||||
        } else if (userType == UserType.APP_USER) {
 | 
			
		||||
        if (userType == UserType.APP_USER) {
 | 
			
		||||
            // 其他端 自行根据业务编写
 | 
			
		||||
        }
 | 
			
		||||
        return new ArrayList<>();
 | 
			
		||||
        // SYS_USER 默认返回权限
 | 
			
		||||
        return new ArrayList<>(loginUser.getMenuPermission());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -36,12 +49,29 @@ public class SaPermissionImpl implements StpInterface {
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<String> getRoleList(Object loginId, String loginType) {
 | 
			
		||||
        LoginUser loginUser = LoginHelper.getLoginUser();
 | 
			
		||||
        if (ObjectUtil.isNull(loginUser) || !loginUser.getLoginId().equals(loginId)) {
 | 
			
		||||
            PermissionService permissionService = getPermissionService();
 | 
			
		||||
            if (ObjectUtil.isNotNull(permissionService)) {
 | 
			
		||||
                List<String> list = StringUtils.splitList(loginId.toString(), ":");
 | 
			
		||||
                return new ArrayList<>(permissionService.getRolePermission(Long.parseLong(list.get(1))));
 | 
			
		||||
            } else {
 | 
			
		||||
                throw new ServiceException("PermissionService 实现类不存在");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        UserType userType = UserType.getUserType(loginUser.getUserType());
 | 
			
		||||
        if (userType == UserType.SYS_USER) {
 | 
			
		||||
            return new ArrayList<>(loginUser.getRolePermission());
 | 
			
		||||
        } else if (userType == UserType.APP_USER) {
 | 
			
		||||
        if (userType == UserType.APP_USER) {
 | 
			
		||||
            // 其他端 自行根据业务编写
 | 
			
		||||
        }
 | 
			
		||||
        return new ArrayList<>();
 | 
			
		||||
        // SYS_USER 默认返回权限
 | 
			
		||||
        return new ArrayList<>(loginUser.getRolePermission());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private PermissionService getPermissionService() {
 | 
			
		||||
        try {
 | 
			
		||||
            return SpringUtils.getBean(PermissionService.class);
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
package org.dromara.common.satoken.utils;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.session.SaSession;
 | 
			
		||||
import cn.dev33.satoken.stp.SaLoginModel;
 | 
			
		||||
import cn.dev33.satoken.stp.StpUtil;
 | 
			
		||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.core.convert.Convert;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
@@ -47,8 +47,8 @@ public class LoginHelper {
 | 
			
		||||
     * @param loginUser 登录用户信息
 | 
			
		||||
     * @param model     配置参数
 | 
			
		||||
     */
 | 
			
		||||
    public static void login(LoginUser loginUser, SaLoginModel model) {
 | 
			
		||||
        model = ObjectUtil.defaultIfNull(model, new SaLoginModel());
 | 
			
		||||
    public static void login(LoginUser loginUser, SaLoginParameter model) {
 | 
			
		||||
        model = ObjectUtil.defaultIfNull(model, new SaLoginParameter());
 | 
			
		||||
        StpUtil.login(loginUser.getLoginId(),
 | 
			
		||||
            model.setExtra(TENANT_KEY, loginUser.getTenantId())
 | 
			
		||||
                .setExtra(USER_KEY, loginUser.getUserId())
 | 
			
		||||
 
 | 
			
		||||
@@ -11,13 +11,13 @@ import jakarta.servlet.http.HttpServletRequest;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.dromara.common.core.constant.HttpStatus;
 | 
			
		||||
import org.dromara.common.core.exception.SseException;
 | 
			
		||||
import org.dromara.common.core.utils.ServletUtils;
 | 
			
		||||
import org.dromara.common.core.utils.SpringUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.dromara.common.satoken.utils.LoginHelper;
 | 
			
		||||
import org.dromara.common.security.config.properties.SecurityProperties;
 | 
			
		||||
import org.dromara.common.security.handler.AllUrlHandler;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Value;
 | 
			
		||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
 | 
			
		||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
 | 
			
		||||
import org.springframework.context.annotation.Bean;
 | 
			
		||||
@@ -37,6 +37,8 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 | 
			
		||||
public class SecurityConfig implements WebMvcConfigurer {
 | 
			
		||||
 | 
			
		||||
    private final SecurityProperties securityProperties;
 | 
			
		||||
    @Value("${sse.path}")
 | 
			
		||||
    private String ssePath;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 注册sa-token的拦截器
 | 
			
		||||
@@ -54,15 +56,7 @@ public class SecurityConfig implements WebMvcConfigurer {
 | 
			
		||||
                    .check(() -> {
 | 
			
		||||
                        HttpServletRequest request = ServletUtils.getRequest();
 | 
			
		||||
                        // 检查是否登录 是否有token
 | 
			
		||||
                        try {
 | 
			
		||||
                            StpUtil.checkLogin();
 | 
			
		||||
                        } catch (NotLoginException e) {
 | 
			
		||||
                            if (request.getRequestURI().contains("sse")) {
 | 
			
		||||
                                throw new SseException(e.getMessage(), e.getCode());
 | 
			
		||||
                            } else {
 | 
			
		||||
                                throw e;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        StpUtil.checkLogin();
 | 
			
		||||
 | 
			
		||||
                        // 检查 header 与 param 里的 clientid 与 token 里的是否一致
 | 
			
		||||
                        String headerCid = request.getHeader(LoginHelper.CLIENT_KEY);
 | 
			
		||||
@@ -84,7 +78,8 @@ public class SecurityConfig implements WebMvcConfigurer {
 | 
			
		||||
                    });
 | 
			
		||||
            })).addPathPatterns("/**")
 | 
			
		||||
            // 排除不需要拦截的路径
 | 
			
		||||
            .excludePathPatterns(securityProperties.getExcludes());
 | 
			
		||||
            .excludePathPatterns(securityProperties.getExcludes())
 | 
			
		||||
            .excludePathPatterns(ssePath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,154 @@
 | 
			
		||||
package me.zhyd.oauth.request;
 | 
			
		||||
 | 
			
		||||
import com.alibaba.fastjson.JSONObject;
 | 
			
		||||
import me.zhyd.oauth.cache.AuthStateCache;
 | 
			
		||||
import me.zhyd.oauth.config.AuthConfig;
 | 
			
		||||
import me.zhyd.oauth.config.AuthSource;
 | 
			
		||||
import me.zhyd.oauth.enums.AuthResponseStatus;
 | 
			
		||||
import me.zhyd.oauth.enums.AuthUserGender;
 | 
			
		||||
import me.zhyd.oauth.exception.AuthException;
 | 
			
		||||
import me.zhyd.oauth.model.AuthCallback;
 | 
			
		||||
import me.zhyd.oauth.model.AuthToken;
 | 
			
		||||
import me.zhyd.oauth.model.AuthUser;
 | 
			
		||||
import me.zhyd.oauth.utils.HttpUtils;
 | 
			
		||||
import me.zhyd.oauth.utils.StringUtils;
 | 
			
		||||
import me.zhyd.oauth.utils.UrlBuilder;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 企业微信登录父类
 | 
			
		||||
 * </p>
 | 
			
		||||
 *
 | 
			
		||||
 * @author liguanhua (347826496(a)qq.com)
 | 
			
		||||
 * @since 1.15.9
 | 
			
		||||
 */
 | 
			
		||||
public abstract class AbstractAuthWeChatEnterpriseRequest extends AuthDefaultRequest {
 | 
			
		||||
 | 
			
		||||
    public AbstractAuthWeChatEnterpriseRequest(AuthConfig config, AuthSource source) {
 | 
			
		||||
        super(config,source);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public AbstractAuthWeChatEnterpriseRequest(AuthConfig config, AuthSource source, AuthStateCache authStateCache) {
 | 
			
		||||
        super(config, source, authStateCache);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public AuthToken getAccessToken(AuthCallback authCallback) {
 | 
			
		||||
        String response = doGetAuthorizationCode(accessTokenUrl(null));
 | 
			
		||||
 | 
			
		||||
        JSONObject object = this.checkResponse(response);
 | 
			
		||||
 | 
			
		||||
        return AuthToken.builder()
 | 
			
		||||
            .accessToken(object.getString("access_token"))
 | 
			
		||||
            .expireIn(object.getIntValue("expires_in"))
 | 
			
		||||
            .code(authCallback.getCode())
 | 
			
		||||
            .build();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public AuthUser getUserInfo(AuthToken authToken) {
 | 
			
		||||
        String response = doGetUserInfo(authToken);
 | 
			
		||||
        JSONObject object = this.checkResponse(response);
 | 
			
		||||
 | 
			
		||||
        // 返回 OpenId 或其他,均代表非当前企业用户,不支持
 | 
			
		||||
        // https://github.com/justauth/JustAuth/issues/227 修复bug
 | 
			
		||||
        if (!object.containsKey("userid")) {
 | 
			
		||||
            throw new AuthException(AuthResponseStatus.UNIDENTIFIED_PLATFORM, source);
 | 
			
		||||
        }
 | 
			
		||||
        String userId = object.getString("userid");
 | 
			
		||||
        String userTicket = object.getString("user_ticket");
 | 
			
		||||
        JSONObject userDetail = getUserDetail(authToken.getAccessToken(), userId, userTicket);
 | 
			
		||||
 | 
			
		||||
        return AuthUser.builder()
 | 
			
		||||
            .rawUserInfo(userDetail)
 | 
			
		||||
            .username(userDetail.getString("name"))
 | 
			
		||||
            .nickname(userDetail.getString("alias"))
 | 
			
		||||
            .avatar(userDetail.getString("avatar"))
 | 
			
		||||
            .location(userDetail.getString("address"))
 | 
			
		||||
            .email(userDetail.getString("email"))
 | 
			
		||||
            .uuid(userId)
 | 
			
		||||
            .gender(AuthUserGender.getWechatRealGender(userDetail.getString("gender")))
 | 
			
		||||
            .token(authToken)
 | 
			
		||||
            .source(source.toString())
 | 
			
		||||
            .build();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 校验请求结果
 | 
			
		||||
     *
 | 
			
		||||
     * @param response 请求结果
 | 
			
		||||
     * @return 如果请求结果正常,则返回JSONObject
 | 
			
		||||
     */
 | 
			
		||||
    private JSONObject checkResponse(String response) {
 | 
			
		||||
        JSONObject object = JSONObject.parseObject(response);
 | 
			
		||||
 | 
			
		||||
        if (object.containsKey("errcode") && object.getIntValue("errcode") != 0) {
 | 
			
		||||
            throw new AuthException(object.getString("errmsg"), source);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return object;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 返回获取accessToken的url
 | 
			
		||||
     *
 | 
			
		||||
     * @param code 授权码
 | 
			
		||||
     * @return 返回获取accessToken的url
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected String accessTokenUrl(String code) {
 | 
			
		||||
        return UrlBuilder.fromBaseUrl(source.accessToken())
 | 
			
		||||
            .queryParam("corpid", config.getClientId())
 | 
			
		||||
            .queryParam("corpsecret", config.getClientSecret())
 | 
			
		||||
            .build();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 返回获取userInfo的url
 | 
			
		||||
     *
 | 
			
		||||
     * @param authToken 用户授权后的token
 | 
			
		||||
     * @return 返回获取userInfo的url
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected String userInfoUrl(AuthToken authToken) {
 | 
			
		||||
        return UrlBuilder.fromBaseUrl(source.userInfo())
 | 
			
		||||
            .queryParam("access_token", authToken.getAccessToken())
 | 
			
		||||
            .queryParam("code", authToken.getCode())
 | 
			
		||||
            .build();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 用户详情
 | 
			
		||||
     *
 | 
			
		||||
     * @param accessToken accessToken
 | 
			
		||||
     * @param userId      企业内用户id
 | 
			
		||||
     * @param userTicket  成员票据,用于获取用户信息或敏感信息
 | 
			
		||||
     * @return 用户详情
 | 
			
		||||
     */
 | 
			
		||||
    private JSONObject getUserDetail(String accessToken, String userId, String userTicket) {
 | 
			
		||||
        // 用户基础信息
 | 
			
		||||
        String userInfoUrl = UrlBuilder.fromBaseUrl("https://qyapi.weixin.qq.com/cgi-bin/user/get")
 | 
			
		||||
            .queryParam("access_token", accessToken)
 | 
			
		||||
            .queryParam("userid", userId)
 | 
			
		||||
            .build();
 | 
			
		||||
        String userInfoResponse = new HttpUtils(config.getHttpConfig()).get(userInfoUrl).getBody();
 | 
			
		||||
        JSONObject userInfo = checkResponse(userInfoResponse);
 | 
			
		||||
 | 
			
		||||
        // 用户敏感信息
 | 
			
		||||
        if (StringUtils.isNotEmpty(userTicket)) {
 | 
			
		||||
            String userDetailUrl = UrlBuilder.fromBaseUrl("https://qyapi.weixin.qq.com/cgi-bin/auth/getuserdetail")
 | 
			
		||||
                .queryParam("access_token", accessToken)
 | 
			
		||||
                .build();
 | 
			
		||||
            JSONObject param = new JSONObject();
 | 
			
		||||
            param.put("user_ticket", userTicket);
 | 
			
		||||
            String userDetailResponse = new HttpUtils(config.getHttpConfig()).post(userDetailUrl, param.toJSONString()).getBody();
 | 
			
		||||
            JSONObject userDetail = checkResponse(userDetailResponse);
 | 
			
		||||
 | 
			
		||||
            userInfo.putAll(userDetail);
 | 
			
		||||
        }
 | 
			
		||||
        return userInfo;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,92 @@
 | 
			
		||||
package org.dromara.common.social.gitea;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.lang.Dict;
 | 
			
		||||
import cn.hutool.http.HttpRequest;
 | 
			
		||||
import cn.hutool.http.HttpResponse;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import me.zhyd.oauth.cache.AuthStateCache;
 | 
			
		||||
import me.zhyd.oauth.config.AuthConfig;
 | 
			
		||||
import me.zhyd.oauth.exception.AuthException;
 | 
			
		||||
import me.zhyd.oauth.model.AuthCallback;
 | 
			
		||||
import me.zhyd.oauth.model.AuthToken;
 | 
			
		||||
import me.zhyd.oauth.model.AuthUser;
 | 
			
		||||
import me.zhyd.oauth.request.AuthDefaultRequest;
 | 
			
		||||
import org.dromara.common.core.utils.SpringUtils;
 | 
			
		||||
import org.dromara.common.json.utils.JsonUtils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author lcry
 | 
			
		||||
 */
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class AuthGiteaRequest extends AuthDefaultRequest {
 | 
			
		||||
 | 
			
		||||
    public static final String SERVER_URL = SpringUtils.getProperty("justauth.type.gitea.server-url");
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 设定归属域
 | 
			
		||||
     */
 | 
			
		||||
    public AuthGiteaRequest(AuthConfig config) {
 | 
			
		||||
        super(config, AuthGiteaSource.GITEA);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public AuthGiteaRequest(AuthConfig config, AuthStateCache authStateCache) {
 | 
			
		||||
        super(config, AuthGiteaSource.GITEA, authStateCache);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public AuthToken getAccessToken(AuthCallback authCallback) {
 | 
			
		||||
        String body = doPostAuthorizationCode(authCallback.getCode());
 | 
			
		||||
        Dict object = JsonUtils.parseMap(body);
 | 
			
		||||
        // oauth/token 验证异常
 | 
			
		||||
        if (object.containsKey("error")) {
 | 
			
		||||
            throw new AuthException(object.getStr("error_description"));
 | 
			
		||||
        }
 | 
			
		||||
        // user 验证异常
 | 
			
		||||
        if (object.containsKey("message")) {
 | 
			
		||||
            throw new AuthException(object.getStr("message"));
 | 
			
		||||
        }
 | 
			
		||||
        return AuthToken.builder()
 | 
			
		||||
                .accessToken(object.getStr("access_token"))
 | 
			
		||||
                .refreshToken(object.getStr("refresh_token"))
 | 
			
		||||
                .idToken(object.getStr("id_token"))
 | 
			
		||||
                .tokenType(object.getStr("token_type"))
 | 
			
		||||
                .scope(object.getStr("scope"))
 | 
			
		||||
                .build();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected String doPostAuthorizationCode(String code) {
 | 
			
		||||
        HttpRequest request = HttpRequest.post(source.accessToken())
 | 
			
		||||
                .form("client_id", config.getClientId())
 | 
			
		||||
                .form("client_secret", config.getClientSecret())
 | 
			
		||||
                .form("grant_type", "authorization_code")
 | 
			
		||||
                .form("code", code)
 | 
			
		||||
                .form("redirect_uri", config.getRedirectUri());
 | 
			
		||||
        HttpResponse response = request.execute();
 | 
			
		||||
        return response.body();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public AuthUser getUserInfo(AuthToken authToken) {
 | 
			
		||||
        String body = doGetUserInfo(authToken);
 | 
			
		||||
        Dict object = JsonUtils.parseMap(body);
 | 
			
		||||
        // oauth/token 验证异常
 | 
			
		||||
        if (object.containsKey("error")) {
 | 
			
		||||
            throw new AuthException(object.getStr("error_description"));
 | 
			
		||||
        }
 | 
			
		||||
        // user 验证异常
 | 
			
		||||
        if (object.containsKey("message")) {
 | 
			
		||||
            throw new AuthException(object.getStr("message"));
 | 
			
		||||
        }
 | 
			
		||||
        return AuthUser.builder()
 | 
			
		||||
                .uuid(object.getStr("sub"))
 | 
			
		||||
                .username(object.getStr("name"))
 | 
			
		||||
                .nickname(object.getStr("preferred_username"))
 | 
			
		||||
                .avatar(object.getStr("picture"))
 | 
			
		||||
                .email(object.getStr("email"))
 | 
			
		||||
                .token(authToken)
 | 
			
		||||
                .source(source.toString())
 | 
			
		||||
                .build();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,50 @@
 | 
			
		||||
package org.dromara.common.social.gitea;
 | 
			
		||||
 | 
			
		||||
import me.zhyd.oauth.config.AuthSource;
 | 
			
		||||
import me.zhyd.oauth.request.AuthDefaultRequest;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * gitea Oauth2 默认接口说明
 | 
			
		||||
 *
 | 
			
		||||
 * @author lcry
 | 
			
		||||
 */
 | 
			
		||||
public enum AuthGiteaSource implements AuthSource {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 自己搭建的 gitea 私服
 | 
			
		||||
     */
 | 
			
		||||
    GITEA {
 | 
			
		||||
        /**
 | 
			
		||||
         * 授权的api
 | 
			
		||||
         */
 | 
			
		||||
        @Override
 | 
			
		||||
        public String authorize() {
 | 
			
		||||
            return AuthGiteaRequest.SERVER_URL + "/login/oauth/authorize";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * 获取accessToken的api
 | 
			
		||||
         */
 | 
			
		||||
        @Override
 | 
			
		||||
        public String accessToken() {
 | 
			
		||||
            return AuthGiteaRequest.SERVER_URL + "/login/oauth/access_token";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * 获取用户信息的api
 | 
			
		||||
         */
 | 
			
		||||
        @Override
 | 
			
		||||
        public String userInfo() {
 | 
			
		||||
            return AuthGiteaRequest.SERVER_URL + "/login/oauth/userinfo";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * 平台对应的 AuthRequest 实现类,必须继承自 {@link AuthDefaultRequest}
 | 
			
		||||
         */
 | 
			
		||||
        @Override
 | 
			
		||||
        public Class<? extends AuthDefaultRequest> getTargetClass() {
 | 
			
		||||
            return AuthGiteaRequest.class;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,6 +10,7 @@ import me.zhyd.oauth.request.*;
 | 
			
		||||
import org.dromara.common.core.utils.SpringUtils;
 | 
			
		||||
import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
 | 
			
		||||
import org.dromara.common.social.config.properties.SocialProperties;
 | 
			
		||||
import org.dromara.common.social.gitea.AuthGiteaRequest;
 | 
			
		||||
import org.dromara.common.social.maxkey.AuthMaxKeyRequest;
 | 
			
		||||
import org.dromara.common.social.topiam.AuthTopIamRequest;
 | 
			
		||||
 | 
			
		||||
@@ -42,7 +43,7 @@ public class SocialUtils  {
 | 
			
		||||
            .redirectUri(obj.getRedirectUri())
 | 
			
		||||
            .scopes(obj.getScopes());
 | 
			
		||||
        return switch (source.toLowerCase()) {
 | 
			
		||||
            case "dingtalk" -> new AuthDingTalkRequest(builder.build(), STATE_CACHE);
 | 
			
		||||
            case "dingtalk" -> new AuthDingTalkV2Request(builder.build(), STATE_CACHE);
 | 
			
		||||
            case "baidu" -> new AuthBaiduRequest(builder.build(), STATE_CACHE);
 | 
			
		||||
            case "github" -> new AuthGithubRequest(builder.build(), STATE_CACHE);
 | 
			
		||||
            case "gitee" -> new AuthGiteeRequest(builder.build(), STATE_CACHE);
 | 
			
		||||
@@ -60,12 +61,13 @@ public class SocialUtils  {
 | 
			
		||||
            case "renren" -> new AuthRenrenRequest(builder.build(), STATE_CACHE);
 | 
			
		||||
            case "stack_overflow" -> new AuthStackOverflowRequest(builder.stackOverflowKey(obj.getStackOverflowKey()).build(), STATE_CACHE);
 | 
			
		||||
            case "huawei" -> new AuthHuaweiV3Request(builder.build(), STATE_CACHE);
 | 
			
		||||
            case "wechat_enterprise" -> new AuthWeChatEnterpriseQrcodeRequest(builder.agentId(obj.getAgentId()).build(), STATE_CACHE);
 | 
			
		||||
            case "wechat_enterprise" -> new AuthWeChatEnterpriseQrcodeV2Request(builder.agentId(obj.getAgentId()).build(), STATE_CACHE);
 | 
			
		||||
            case "gitlab" -> new AuthGitlabRequest(builder.build(), STATE_CACHE);
 | 
			
		||||
            case "wechat_mp" -> new AuthWeChatMpRequest(builder.build(), STATE_CACHE);
 | 
			
		||||
            case "aliyun" -> new AuthAliyunRequest(builder.build(), STATE_CACHE);
 | 
			
		||||
            case "maxkey" -> new AuthMaxKeyRequest(builder.build(), STATE_CACHE);
 | 
			
		||||
            case "topiam" -> new AuthTopIamRequest(builder.build(), STATE_CACHE);
 | 
			
		||||
            case "gitea" -> new AuthGiteaRequest(builder.build(), STATE_CACHE);
 | 
			
		||||
            default -> throw new AuthException("未获取到有效的Auth配置");
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,7 @@ public class SseController implements DisposableBean {
 | 
			
		||||
     */
 | 
			
		||||
    @GetMapping(value = "${sse.path}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
 | 
			
		||||
    public SseEmitter connect() {
 | 
			
		||||
        StpUtil.checkLogin();
 | 
			
		||||
        String tokenValue = StpUtil.getTokenValue();
 | 
			
		||||
        Long userId = LoginHelper.getUserId();
 | 
			
		||||
        return sseEmitterManager.connect(userId, tokenValue);
 | 
			
		||||
 
 | 
			
		||||
@@ -38,15 +38,30 @@ public class SseEmitterManager {
 | 
			
		||||
        // 每个用户可以有多个 SSE 连接,通过 token 进行区分
 | 
			
		||||
        Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.computeIfAbsent(userId, k -> new ConcurrentHashMap<>());
 | 
			
		||||
 | 
			
		||||
        // 创建一个新的 SseEmitter 实例,超时时间设置为 0 表示无限制
 | 
			
		||||
        SseEmitter emitter = new SseEmitter(0L);
 | 
			
		||||
        // 创建一个新的 SseEmitter 实例,超时时间设置为一天 避免连接之后直接关闭浏览器导致连接停滞
 | 
			
		||||
        SseEmitter emitter = new SseEmitter(86400000L);
 | 
			
		||||
 | 
			
		||||
        emitters.put(token, emitter);
 | 
			
		||||
 | 
			
		||||
        // 当 emitter 完成、超时或发生错误时,从映射表中移除对应的 token
 | 
			
		||||
        emitter.onCompletion(() -> emitters.remove(token));
 | 
			
		||||
        emitter.onTimeout(() -> emitters.remove(token));
 | 
			
		||||
        emitter.onError((e) -> emitters.remove(token));
 | 
			
		||||
        emitter.onCompletion(() -> {
 | 
			
		||||
            SseEmitter remove = emitters.remove(token);
 | 
			
		||||
            if (remove != null) {
 | 
			
		||||
                remove.complete();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        emitter.onTimeout(() -> {
 | 
			
		||||
            SseEmitter remove = emitters.remove(token);
 | 
			
		||||
            if (remove != null) {
 | 
			
		||||
                remove.complete();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        emitter.onError((e) -> {
 | 
			
		||||
            SseEmitter remove = emitters.remove(token);
 | 
			
		||||
            if (remove != null) {
 | 
			
		||||
                remove.complete();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            // 向客户端发送一条连接成功的事件
 | 
			
		||||
@@ -106,7 +121,10 @@ public class SseEmitterManager {
 | 
			
		||||
                        .name("message")
 | 
			
		||||
                        .data(message));
 | 
			
		||||
                } catch (Exception e) {
 | 
			
		||||
                    emitters.remove(entry.getKey());
 | 
			
		||||
                    SseEmitter remove = emitters.remove(entry.getKey());
 | 
			
		||||
                    if (remove != null) {
 | 
			
		||||
                        remove.complete();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -81,6 +81,17 @@ public class TenantSaTokenDao extends PlusSaTokenDao {
 | 
			
		||||
        return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取 Object (指定反序列化类型),如无返空
 | 
			
		||||
     *
 | 
			
		||||
     * @param key 键名称
 | 
			
		||||
     * @return object
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public <T> T getObject(String key, Class<T> classType) {
 | 
			
		||||
        return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key, classType);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 写入Object,并设定存活时间 (单位: 秒)
 | 
			
		||||
     */
 | 
			
		||||
@@ -137,7 +148,6 @@ public class TenantSaTokenDao extends PlusSaTokenDao {
 | 
			
		||||
        RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 搜索数据
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ public class PlusTenantLineHandler implements TenantLineHandler {
 | 
			
		||||
                "gen_table_column"
 | 
			
		||||
            );
 | 
			
		||||
            tables.addAll(excludes);
 | 
			
		||||
            return tables.contains(tableName);
 | 
			
		||||
            return StringUtils.equalsAnyIgnoreCase(tableName, tables.toArray(new String[0]));
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,12 @@
 | 
			
		||||
package org.dromara.common.web.config;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.date.DateUtil;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.dromara.common.web.handler.GlobalExceptionHandler;
 | 
			
		||||
import org.dromara.common.web.interceptor.PlusWebInvokeTimeInterceptor;
 | 
			
		||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
 | 
			
		||||
import org.springframework.context.annotation.Bean;
 | 
			
		||||
import org.springframework.format.FormatterRegistry;
 | 
			
		||||
import org.springframework.web.cors.CorsConfiguration;
 | 
			
		||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
 | 
			
		||||
import org.springframework.web.filter.CorsFilter;
 | 
			
		||||
@@ -11,6 +14,8 @@ import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 | 
			
		||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
 | 
			
		||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 | 
			
		||||
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 通用配置
 | 
			
		||||
 *
 | 
			
		||||
@@ -25,6 +30,17 @@ public class ResourcesConfig implements WebMvcConfigurer {
 | 
			
		||||
        registry.addInterceptor(new PlusWebInvokeTimeInterceptor());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void addFormatters(FormatterRegistry registry) {
 | 
			
		||||
        // 全局日期格式转换配置
 | 
			
		||||
        registry.addConverter(String.class, Date.class, source -> {
 | 
			
		||||
            if (StringUtils.isBlank(source)) {
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
            return DateUtil.parse(source);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ package org.dromara.common.web.handler;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import cn.hutool.http.HttpStatus;
 | 
			
		||||
import com.fasterxml.jackson.core.JsonParseException;
 | 
			
		||||
import jakarta.servlet.ServletException;
 | 
			
		||||
import jakarta.servlet.http.HttpServletRequest;
 | 
			
		||||
import jakarta.validation.ConstraintViolation;
 | 
			
		||||
@@ -14,6 +15,7 @@ import org.dromara.common.core.exception.base.BaseException;
 | 
			
		||||
import org.dromara.common.core.utils.StreamUtils;
 | 
			
		||||
import org.dromara.common.json.utils.JsonUtils;
 | 
			
		||||
import org.springframework.context.support.DefaultMessageSourceResolvable;
 | 
			
		||||
import org.springframework.http.converter.HttpMessageNotReadableException;
 | 
			
		||||
import org.springframework.validation.BindException;
 | 
			
		||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
 | 
			
		||||
import org.springframework.web.bind.MethodArgumentNotValidException;
 | 
			
		||||
@@ -180,4 +182,24 @@ public class GlobalExceptionHandler {
 | 
			
		||||
        return R.fail(message);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * JSON 解析异常(Jackson 在处理 JSON 格式出错时抛出)
 | 
			
		||||
     * 可能是请求体格式非法,也可能是服务端反序列化失败
 | 
			
		||||
     */
 | 
			
		||||
    @ExceptionHandler(JsonParseException.class)
 | 
			
		||||
    public R<Void> handleJsonParseException(JsonParseException e, HttpServletRequest request) {
 | 
			
		||||
        String requestURI = request.getRequestURI();
 | 
			
		||||
        log.error("请求地址'{}' 发生 JSON 解析异常: {}", requestURI, e.getMessage());
 | 
			
		||||
        return R.fail(HttpStatus.HTTP_BAD_REQUEST, "请求数据格式错误(JSON 解析失败):" + e.getMessage());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 请求体读取异常(通常是请求参数格式非法、字段类型不匹配等)
 | 
			
		||||
     */
 | 
			
		||||
    @ExceptionHandler(HttpMessageNotReadableException.class)
 | 
			
		||||
    public R<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException e, HttpServletRequest request) {
 | 
			
		||||
        log.error("请求地址'{}', 参数解析失败: {}", request.getRequestURI(), e.getMessage());
 | 
			
		||||
        return R.fail(HttpStatus.HTTP_BAD_REQUEST, "请求参数格式错误:" + e.getMostSpecificCause().getMessage());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
 | 
			
		||||
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
 | 
			
		||||
#FROM bellsoft/liberica-openjdk-debian:21.0.5-cds
 | 
			
		||||
FROM bellsoft/liberica-openjdk-rocky:17.0.15-cds
 | 
			
		||||
#FROM bellsoft/liberica-openjdk-rocky:21.0.7-cds
 | 
			
		||||
#FROM findepi/graalvm:java17-native
 | 
			
		||||
 | 
			
		||||
LABEL maintainer="Lion Li"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
# 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
 | 
			
		||||
FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
 | 
			
		||||
#FROM bellsoft/liberica-openjdk-debian:21.0.5-cds
 | 
			
		||||
FROM bellsoft/liberica-openjdk-rocky:17.0.15-cds
 | 
			
		||||
#FROM bellsoft/liberica-openjdk-rocky:21.0.7-cds
 | 
			
		||||
#FROM findepi/graalvm:java17-native
 | 
			
		||||
 | 
			
		||||
LABEL maintainer="Lion Li"
 | 
			
		||||
@@ -9,7 +9,7 @@ RUN mkdir -p /ruoyi/snailjob/logs
 | 
			
		||||
 | 
			
		||||
WORKDIR /ruoyi/snailjob
 | 
			
		||||
 | 
			
		||||
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS="-Xms512m -Xmx1024m"
 | 
			
		||||
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS=""
 | 
			
		||||
 | 
			
		||||
EXPOSE 8800
 | 
			
		||||
EXPOSE 17888
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ public class ActuatorAuthFilter implements Filter {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // 验证用户名和密码
 | 
			
		||||
        if (!username.equals(split[0]) && password.equals(split[1])) {
 | 
			
		||||
        if (!username.equals(split[0]) || !password.equals(split[1])) {
 | 
			
		||||
            response.setHeader("WWW-Authenticate", "Basic realm=\"realm\"");
 | 
			
		||||
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
 | 
			
		||||
            return;
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@ public class RedisCacheController {
 | 
			
		||||
     * <p>
 | 
			
		||||
     * cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数
 | 
			
		||||
     */
 | 
			
		||||
    @Cacheable(cacheNames = "demo:cache#60s#10m#20", key = "#key", condition = "#key != null")
 | 
			
		||||
    @Cacheable(cacheNames = "demo:cache#60s#10m#20#1", key = "#key", condition = "#key != null")
 | 
			
		||||
    @GetMapping("/test1")
 | 
			
		||||
    public R<String> test1(String key, String value) {
 | 
			
		||||
        return R.ok("操作成功", value);
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ import org.springframework.web.bind.annotation.RestController;
 | 
			
		||||
@RestController
 | 
			
		||||
@RequestMapping("/demo/websocket")
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class WeSocketController {
 | 
			
		||||
public class WebSocketController {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 发布消息
 | 
			
		||||
@@ -1,11 +1,9 @@
 | 
			
		||||
package org.dromara.demo.controller.queue;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.annotation.SaIgnore;
 | 
			
		||||
import org.dromara.common.core.domain.R;
 | 
			
		||||
import org.dromara.common.redis.utils.QueueUtils;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.redisson.api.RBoundedBlockingQueue;
 | 
			
		||||
import org.dromara.common.core.domain.R;
 | 
			
		||||
import org.dromara.common.redis.utils.QueueUtils;
 | 
			
		||||
import org.springframework.web.bind.annotation.GetMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RestController;
 | 
			
		||||
@@ -20,7 +18,9 @@ import org.springframework.web.bind.annotation.RestController;
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 * @version 3.6.0
 | 
			
		||||
 * @deprecated redisson 新版本已经将队列功能标记删除 一些技术问题无法解决 建议搭建MQ使用
 | 
			
		||||
 */
 | 
			
		||||
@Deprecated
 | 
			
		||||
@Slf4j
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@RestController
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
package org.dromara.demo.controller.queue;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.annotation.SaIgnore;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.dromara.common.core.domain.R;
 | 
			
		||||
@@ -23,8 +22,9 @@ import java.util.concurrent.TimeUnit;
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 * @version 3.6.0
 | 
			
		||||
 * @deprecated redisson 新版本已经将队列功能标记删除 一些技术问题无法解决 建议搭建MQ使用
 | 
			
		||||
 */
 | 
			
		||||
@SaIgnore
 | 
			
		||||
@Deprecated
 | 
			
		||||
@Slf4j
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@RestController
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,9 @@ import lombok.NoArgsConstructor;
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 * @version 3.6.0
 | 
			
		||||
 * @deprecated redisson 新版本已经将队列功能标记删除 一些技术问题无法解决 建议搭建MQ使用
 | 
			
		||||
 */
 | 
			
		||||
@Deprecated
 | 
			
		||||
@Data
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
public class PriorityDemo implements Comparable<PriorityDemo> {
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,9 @@ import org.springframework.web.bind.annotation.RestController;
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 * @version 3.6.0
 | 
			
		||||
 * @deprecated redisson 新版本已经将队列功能标记删除 一些技术问题无法解决 建议搭建MQ使用
 | 
			
		||||
 */
 | 
			
		||||
@Deprecated
 | 
			
		||||
@Slf4j
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@RestController
 | 
			
		||||
 
 | 
			
		||||
@@ -59,4 +59,9 @@ public class TestDemoBo extends BaseEntity {
 | 
			
		||||
    @NotBlank(message = "值不能为空", groups = {AddGroup.class, EditGroup.class})
 | 
			
		||||
    private String value;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 版本
 | 
			
		||||
     */
 | 
			
		||||
    private Long version;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
package org.dromara.demo.domain.bo;
 | 
			
		||||
 | 
			
		||||
import com.alibaba.excel.annotation.ExcelProperty;
 | 
			
		||||
import cn.idev.excel.annotation.ExcelProperty;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
import jakarta.validation.constraints.NotBlank;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
package org.dromara.demo.domain.vo;
 | 
			
		||||
 | 
			
		||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
 | 
			
		||||
import com.alibaba.excel.annotation.ExcelProperty;
 | 
			
		||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
 | 
			
		||||
import cn.idev.excel.annotation.ExcelProperty;
 | 
			
		||||
import jakarta.validation.constraints.NotEmpty;
 | 
			
		||||
import jakarta.validation.constraints.NotNull;
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,8 @@
 | 
			
		||||
package org.dromara.demo.domain.vo;
 | 
			
		||||
 | 
			
		||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
 | 
			
		||||
import com.alibaba.excel.annotation.ExcelProperty;
 | 
			
		||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
 | 
			
		||||
import cn.idev.excel.annotation.ExcelProperty;
 | 
			
		||||
import cn.idev.excel.annotation.format.DateTimeFormat;
 | 
			
		||||
import org.dromara.common.excel.annotation.ExcelNotation;
 | 
			
		||||
import org.dromara.common.excel.annotation.ExcelRequired;
 | 
			
		||||
import org.dromara.common.translation.annotation.Translation;
 | 
			
		||||
@@ -46,7 +47,7 @@ public class TestDemoVo implements Serializable {
 | 
			
		||||
     * 用户id
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelRequired
 | 
			
		||||
    @ExcelProperty(value = "用户id")
 | 
			
		||||
    @ExcelProperty(value = "用户id", index = 5)
 | 
			
		||||
    private Long userId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -73,6 +74,8 @@ public class TestDemoVo implements Serializable {
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建时间
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelRequired
 | 
			
		||||
    @DateTimeFormat("yyyy-MM-dd HH:mm:ss")
 | 
			
		||||
    @ExcelProperty(value = "创建时间")
 | 
			
		||||
    private Date createTime;
 | 
			
		||||
 | 
			
		||||
@@ -108,4 +111,9 @@ public class TestDemoVo implements Serializable {
 | 
			
		||||
    @ExcelProperty(value = "更新人账号")
 | 
			
		||||
    private String updateByName;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 版本
 | 
			
		||||
     */
 | 
			
		||||
    private Long version;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
package org.dromara.demo.domain.vo;
 | 
			
		||||
 | 
			
		||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
 | 
			
		||||
import com.alibaba.excel.annotation.ExcelProperty;
 | 
			
		||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
 | 
			
		||||
import cn.idev.excel.annotation.ExcelProperty;
 | 
			
		||||
import org.dromara.demo.domain.TestTree;
 | 
			
		||||
import io.github.linpeilie.annotations.AutoMapper;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
package org.dromara.demo.listener;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.util.NumberUtil;
 | 
			
		||||
import com.alibaba.excel.context.AnalysisContext;
 | 
			
		||||
import cn.idev.excel.context.AnalysisContext;
 | 
			
		||||
import org.dromara.common.core.utils.ValidatorUtils;
 | 
			
		||||
import org.dromara.common.core.validate.AddGroup;
 | 
			
		||||
import org.dromara.common.core.validate.EditGroup;
 | 
			
		||||
 
 | 
			
		||||
@@ -3,21 +3,21 @@ package org.dromara.generator.controller;
 | 
			
		||||
import cn.dev33.satoken.annotation.SaCheckPermission;
 | 
			
		||||
import cn.hutool.core.convert.Convert;
 | 
			
		||||
import cn.hutool.core.io.IoUtil;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.dromara.common.core.domain.R;
 | 
			
		||||
import org.dromara.common.mybatis.helper.DataBaseHelper;
 | 
			
		||||
import org.dromara.common.web.core.BaseController;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.PageQuery;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
 | 
			
		||||
import org.dromara.common.log.annotation.Log;
 | 
			
		||||
import org.dromara.common.log.enums.BusinessType;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.PageQuery;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
 | 
			
		||||
import org.dromara.common.mybatis.helper.DataBaseHelper;
 | 
			
		||||
import org.dromara.common.web.core.BaseController;
 | 
			
		||||
import org.dromara.generator.domain.GenTable;
 | 
			
		||||
import org.dromara.generator.domain.GenTableColumn;
 | 
			
		||||
import org.dromara.generator.service.IGenTableService;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.springframework.validation.annotation.Validated;
 | 
			
		||||
import org.springframework.web.bind.annotation.*;
 | 
			
		||||
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
@@ -80,11 +80,8 @@ public class GenController extends BaseController {
 | 
			
		||||
    @SaCheckPermission("tool:gen:list")
 | 
			
		||||
    @GetMapping(value = "/column/{tableId}")
 | 
			
		||||
    public TableDataInfo<GenTableColumn> columnList(@PathVariable("tableId") Long tableId) {
 | 
			
		||||
        TableDataInfo<GenTableColumn> dataInfo = new TableDataInfo<>();
 | 
			
		||||
        List<GenTableColumn> list = genTableService.selectGenTableColumnListByTableId(tableId);
 | 
			
		||||
        dataInfo.setRows(list);
 | 
			
		||||
        dataInfo.setTotal(list.size());
 | 
			
		||||
        return dataInfo;
 | 
			
		||||
        return TableDataInfo.build(list);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,6 @@ import com.baomidou.dynamic.datasource.annotation.DSTransactional;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 | 
			
		||||
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
 | 
			
		||||
import com.baomidou.mybatisplus.core.metadata.IPage;
 | 
			
		||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 | 
			
		||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
@@ -168,12 +167,7 @@ public class GenTableServiceImpl implements IGenTableService {
 | 
			
		||||
                return gen;
 | 
			
		||||
            }).sorted(Comparator.comparing(GenTable::getCreateTime).reversed())
 | 
			
		||||
            .toList();
 | 
			
		||||
 | 
			
		||||
        IPage<GenTable> page = pageQuery.build();
 | 
			
		||||
        page.setTotal(tables.size());
 | 
			
		||||
        // 手动分页 set数据
 | 
			
		||||
        page.setRecords(CollUtil.page((int) page.getCurrent() - 1, (int) page.getSize(), tables));
 | 
			
		||||
        return TableDataInfo.build(page);
 | 
			
		||||
        return TableDataInfo.build(tables, pageQuery.build());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -303,13 +297,13 @@ public class GenTableServiceImpl implements IGenTableService {
 | 
			
		||||
        List<GenTableColumn> tableColumns = new ArrayList<>();
 | 
			
		||||
        columns.forEach((columnName, column) -> {
 | 
			
		||||
            GenTableColumn tableColumn = new GenTableColumn();
 | 
			
		||||
            tableColumn.setIsPk(String.valueOf(column.isPrimaryKey()));
 | 
			
		||||
            tableColumn.setIsPk(column.isPrimaryKey() ? "1" : "0");
 | 
			
		||||
            tableColumn.setColumnName(column.getName());
 | 
			
		||||
            tableColumn.setColumnComment(column.getComment());
 | 
			
		||||
            tableColumn.setColumnType(column.getOriginType().toLowerCase());
 | 
			
		||||
            tableColumn.setSort(column.getPosition());
 | 
			
		||||
            tableColumn.setIsRequired(column.isNullable() == 0 ? "1" : "0");
 | 
			
		||||
            tableColumn.setIsIncrement(column.isAutoIncrement() == -1 ? "0" : "1");
 | 
			
		||||
            tableColumn.setIsRequired(column.isNullable() ? "1" : "0");
 | 
			
		||||
            tableColumn.setIsIncrement(column.isAutoIncrement() ? "1" : "0");
 | 
			
		||||
            tableColumns.add(tableColumn);
 | 
			
		||||
        });
 | 
			
		||||
        return tableColumns;
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 | 
			
		||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
import ${packageName}.domain.bo.${ClassName}Bo;
 | 
			
		||||
import ${packageName}.domain.vo.${ClassName}Vo;
 | 
			
		||||
@@ -27,6 +28,7 @@ import java.util.Collection;
 | 
			
		||||
 * @author ${author}
 | 
			
		||||
 * @date ${datetime}
 | 
			
		||||
 */
 | 
			
		||||
@Slf4j
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@Service
 | 
			
		||||
public class ${ClassName}ServiceImpl implements I${ClassName}Service {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,8 @@ package ${packageName}.domain.vo;
 | 
			
		||||
import ${import};
 | 
			
		||||
#end
 | 
			
		||||
import ${packageName}.domain.${ClassName};
 | 
			
		||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
 | 
			
		||||
import com.alibaba.excel.annotation.ExcelProperty;
 | 
			
		||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
 | 
			
		||||
import cn.idev.excel.annotation.ExcelProperty;
 | 
			
		||||
import org.dromara.common.excel.annotation.ExcelDictFormat;
 | 
			
		||||
import org.dromara.common.excel.convert.ExcelDictConvert;
 | 
			
		||||
import io.github.linpeilie.annotations.AutoMapper;
 | 
			
		||||
 
 | 
			
		||||
@@ -80,6 +80,7 @@
 | 
			
		||||
        v-loading="loading"
 | 
			
		||||
        :data="${businessName}List"
 | 
			
		||||
        row-key="${treeCode}"
 | 
			
		||||
        border
 | 
			
		||||
        :default-expand-all="isExpandAll"
 | 
			
		||||
        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
 | 
			
		||||
      >
 | 
			
		||||
 
 | 
			
		||||
@@ -82,7 +82,7 @@
 | 
			
		||||
        </el-row>
 | 
			
		||||
      </template>
 | 
			
		||||
 | 
			
		||||
      <el-table v-loading="loading" :data="${businessName}List" @selection-change="handleSelectionChange">
 | 
			
		||||
      <el-table v-loading="loading" border :data="${businessName}List" @selection-change="handleSelectionChange">
 | 
			
		||||
        <el-table-column type="selection" width="55" align="center" />
 | 
			
		||||
#foreach($column in $columns)
 | 
			
		||||
#set($javaField=$column.javaField)
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
package org.dromara.job.entity;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
import java.math.BigDecimal;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class BillDto {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 账单ID
 | 
			
		||||
     */
 | 
			
		||||
    private Long billId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 账单渠道
 | 
			
		||||
     */
 | 
			
		||||
    private String billChannel;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 账单日期
 | 
			
		||||
     */
 | 
			
		||||
    private String billDate;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 账单金额
 | 
			
		||||
     */
 | 
			
		||||
    private BigDecimal billAmount;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,42 @@
 | 
			
		||||
package org.dromara.job.snailjob;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.date.DateUtil;
 | 
			
		||||
import cn.hutool.core.util.StrUtil;
 | 
			
		||||
import com.aizuda.snailjob.client.job.core.annotation.JobExecutor;
 | 
			
		||||
import com.aizuda.snailjob.client.job.core.dto.JobArgs;
 | 
			
		||||
import com.aizuda.snailjob.client.model.ExecuteResult;
 | 
			
		||||
import com.aizuda.snailjob.common.log.SnailJobLog;
 | 
			
		||||
import org.dromara.common.json.utils.JsonUtils;
 | 
			
		||||
import org.dromara.job.entity.BillDto;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
 | 
			
		||||
import java.math.BigDecimal;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * DAG工作流任务-模拟支付宝账单任务
 | 
			
		||||
 * <a href="https://juejin.cn/post/7487860254114644019"></a>
 | 
			
		||||
 *
 | 
			
		||||
 * @author 老马
 | 
			
		||||
 */
 | 
			
		||||
@Component
 | 
			
		||||
@JobExecutor(name = "alipayBillTask")
 | 
			
		||||
public class AlipayBillTask {
 | 
			
		||||
 | 
			
		||||
    public ExecuteResult jobExecute(JobArgs jobArgs) throws InterruptedException {
 | 
			
		||||
        BillDto billDto = new BillDto();
 | 
			
		||||
        billDto.setBillId(23456789L);
 | 
			
		||||
        billDto.setBillChannel("alipay");
 | 
			
		||||
        // 设置清算日期
 | 
			
		||||
        String settlementDate = (String) jobArgs.getWfContext().get("settlementDate");
 | 
			
		||||
        if (StrUtil.equals(settlementDate, "sysdate")) {
 | 
			
		||||
            settlementDate = DateUtil.today();
 | 
			
		||||
        }
 | 
			
		||||
        billDto.setBillDate(settlementDate);
 | 
			
		||||
        billDto.setBillAmount(new BigDecimal("2345.67"));
 | 
			
		||||
        // 把billDto对象放入上下文进行传递
 | 
			
		||||
        jobArgs.appendContext("alipay", JsonUtils.toJsonString(billDto));
 | 
			
		||||
        SnailJobLog.REMOTE.info("上下文: {}", jobArgs.getWfContext());
 | 
			
		||||
        return ExecuteResult.success(billDto);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user