mirror of
				https://github.com/dromara/RuoYi-Vue-Plus.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	Compare commits
	
		
			130 Commits
		
	
	
		
			a4ad56f0eb
			...
			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 | ||
| 
						 | 
					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>
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
[](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)
 | 
			
		||||
[]()
 | 
			
		||||
[]()
 | 
			
		||||
[]()
 | 
			
		||||
@@ -23,8 +23,9 @@
 | 
			
		||||
> 系统演示: [传送门](https://plus-doc.dromara.org/#/common/demo_system)
 | 
			
		||||
 | 
			
		||||
> 官方前端项目地址: [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)
 | 
			
		||||
> 成员前端项目地址: 基于soybean [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)
 | 
			
		||||
> 成员前端项目地址: 基于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) 文档在华为云上如果打不开大概率是DNS问题 可以尝试切换网络等方式(或者科学上网)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										33
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								pom.xml
									
									
									
									
									
								
							@@ -13,33 +13,32 @@
 | 
			
		||||
    <description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
 | 
			
		||||
 | 
			
		||||
    <properties>
 | 
			
		||||
        <revision>5.3.1</revision>
 | 
			
		||||
        <spring-boot.version>3.4.5</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>
 | 
			
		||||
        <fastexcel.version>1.2.0</fastexcel.version>
 | 
			
		||||
        <velocity.version>2.3</velocity.version>
 | 
			
		||||
        <satoken.version>1.42.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.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>
 | 
			
		||||
        <anyline.version>8.7.2-20250603</anyline.version>
 | 
			
		||||
        <!-- 工作流配置 -->
 | 
			
		||||
        <warm-flow.version>1.7.0</warm-flow.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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
@@ -18,8 +18,6 @@ EXPOSE ${SERVER_PORT}
 | 
			
		||||
EXPOSE ${SNAIL_PORT}
 | 
			
		||||
 | 
			
		||||
ADD ./target/ruoyi-admin.jar ./app.jar
 | 
			
		||||
# 工作流字体文件
 | 
			
		||||
ADD ./zhFonts/ /usr/share/fonts/zhFonts/
 | 
			
		||||
 | 
			
		||||
SHELL ["/bin/bash", "-c"]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -158,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) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,10 +3,7 @@ package org.dromara.web.service.impl;
 | 
			
		||||
import cn.dev33.satoken.stp.StpUtil;
 | 
			
		||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.core.map.MapUtil;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import cn.hutool.http.HttpUtil;
 | 
			
		||||
import cn.hutool.http.Method;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import me.zhyd.oauth.model.AuthResponse;
 | 
			
		||||
@@ -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)) {
 | 
			
		||||
 
 | 
			
		||||
@@ -110,7 +110,7 @@ security:
 | 
			
		||||
    - /error
 | 
			
		||||
    - /*/api-docs
 | 
			
		||||
    - /*/api-docs/**
 | 
			
		||||
    - /warm-flow-ui/token-name
 | 
			
		||||
    - /warm-flow-ui/config
 | 
			
		||||
 | 
			
		||||
# 多租户配置
 | 
			
		||||
tenant:
 | 
			
		||||
@@ -183,7 +183,7 @@ springdoc:
 | 
			
		||||
    # 描述
 | 
			
		||||
    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>
 | 
			
		||||
 
 | 
			
		||||
@@ -77,4 +77,9 @@ public interface SystemConstants {
 | 
			
		||||
     */
 | 
			
		||||
    String ROOT_DEPT_ANCESTORS = "0";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 默认部门 ID
 | 
			
		||||
     */
 | 
			
		||||
    Long DEFAULT_DEPT_ID = 100L;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
@@ -51,4 +51,9 @@ public class ProcessCreateTaskEvent implements Serializable {
 | 
			
		||||
     */
 | 
			
		||||
    private String businessId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 流程状态
 | 
			
		||||
     */
 | 
			
		||||
    private String status;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -10,6 +10,8 @@ import lombok.NoArgsConstructor;
 | 
			
		||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
@@ -60,6 +62,31 @@ public class TreeBuildUtils extends TreeUtil {
 | 
			
		||||
        return TreeUtil.build(list, parentId, DEFAULT_CONFIG, nodeParser);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 构建多根节点的树结构(支持多个顶级节点)
 | 
			
		||||
     *
 | 
			
		||||
     * @param list        原始数据列表
 | 
			
		||||
     * @param getId       获取节点 ID 的方法引用,例如:node -> node.getId()
 | 
			
		||||
     * @param getParentId 获取节点父级 ID 的方法引用,例如:node -> node.getParentId()
 | 
			
		||||
     * @param parser      树节点属性映射器,用于将原始节点 T 转为 Tree 节点
 | 
			
		||||
     * @param <T>         原始数据类型(如实体类、DTO 等)
 | 
			
		||||
     * @param <K>         节点 ID 类型(如 Long、String)
 | 
			
		||||
     * @return 构建完成的树形结构(可能包含多个顶级根节点)
 | 
			
		||||
     */
 | 
			
		||||
    public static <T, K> List<Tree<K>> buildMultiRoot(List<T> list, Function<T, K> getId, Function<T, K> getParentId, NodeParser<T, K> parser) {
 | 
			
		||||
        if (CollUtil.isEmpty(list)) {
 | 
			
		||||
            return CollUtil.newArrayList();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Set<K> rootParentIds = StreamUtils.toSet(list, getParentId);
 | 
			
		||||
        rootParentIds.removeAll(StreamUtils.toSet(list, getId));
 | 
			
		||||
 | 
			
		||||
        // 构建每一个根 parentId 下的树,并合并成最终结果列表
 | 
			
		||||
        return rootParentIds.stream()
 | 
			
		||||
            .flatMap(rootParentId -> TreeUtil.build(list, rootParentId, parser).stream())
 | 
			
		||||
            .collect(Collectors.toList());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取节点列表中所有节点的叶子节点
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -16,28 +16,55 @@ import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
@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) {
 | 
			
		||||
        // 处理空串并过滤HTML标签
 | 
			
		||||
        ip = HtmlUtil.cleanHtmlTag(StringUtils.blankToDefault(ip,""));
 | 
			
		||||
        boolean isIPv6 = NetUtils.isIPv6(ip);
 | 
			
		||||
        // 判断是否为IPv4或IPv6,如果不是则返回未知地址
 | 
			
		||||
        if (!NetUtils.isIPv4(ip) && !isIPv6) {
 | 
			
		||||
            return UNKNOWN;
 | 
			
		||||
        // 判断是否为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){
 | 
			
		||||
        // 内网不查询
 | 
			
		||||
        if (NetUtils.isInnerIPv6(ip) || NetUtils.isInnerIP(ip)) {
 | 
			
		||||
            return "内网IP";
 | 
			
		||||
        }
 | 
			
		||||
        // 不支持IPv6,不再进行没有必要的IP地址信息的解析,直接返回
 | 
			
		||||
        if (isIPv6) {
 | 
			
		||||
            log.warn("ip2region不支持IPV6地址解析:{}", ip);
 | 
			
		||||
            // 如有需要,可自行实现IPv6地址信息解析逻辑,并在这里返回
 | 
			
		||||
            return "未知";
 | 
			
		||||
        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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,40 @@
 | 
			
		||||
package org.dromara.common.core.validate.dicts;
 | 
			
		||||
 | 
			
		||||
import jakarta.validation.Constraint;
 | 
			
		||||
import jakarta.validation.Payload;
 | 
			
		||||
 | 
			
		||||
import java.lang.annotation.ElementType;
 | 
			
		||||
import java.lang.annotation.Retention;
 | 
			
		||||
import java.lang.annotation.RetentionPolicy;
 | 
			
		||||
import java.lang.annotation.Target;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 字典项校验注解
 | 
			
		||||
 *
 | 
			
		||||
 * @author AprilWind
 | 
			
		||||
 */
 | 
			
		||||
@Constraint(validatedBy = DictPatternValidator.class)
 | 
			
		||||
@Target({ElementType.FIELD, ElementType.PARAMETER})
 | 
			
		||||
@Retention(RetentionPolicy.RUNTIME)
 | 
			
		||||
public @interface DictPattern {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 字典类型,如 "sys_user_sex"
 | 
			
		||||
     */
 | 
			
		||||
    String dictType();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 分隔符
 | 
			
		||||
     */
 | 
			
		||||
    String separator();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 默认校验失败提示信息
 | 
			
		||||
     */
 | 
			
		||||
    String message() default "字典值无效";
 | 
			
		||||
 | 
			
		||||
    Class<?>[] groups() default {};
 | 
			
		||||
 | 
			
		||||
    Class<? extends Payload>[] payload() default {};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,55 @@
 | 
			
		||||
package org.dromara.common.core.validate.dicts;
 | 
			
		||||
 | 
			
		||||
import jakarta.validation.ConstraintValidator;
 | 
			
		||||
import jakarta.validation.ConstraintValidatorContext;
 | 
			
		||||
import org.dromara.common.core.service.DictService;
 | 
			
		||||
import org.dromara.common.core.utils.SpringUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 自定义字典值校验器
 | 
			
		||||
 *
 | 
			
		||||
 * @author AprilWind
 | 
			
		||||
 */
 | 
			
		||||
public class DictPatternValidator implements ConstraintValidator<DictPattern, String> {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 字典类型
 | 
			
		||||
     */
 | 
			
		||||
    private String dictType;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 分隔符
 | 
			
		||||
     */
 | 
			
		||||
    private String separator = ",";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 初始化校验器,提取注解上的字典类型
 | 
			
		||||
     *
 | 
			
		||||
     * @param annotation 注解实例
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void initialize(DictPattern annotation) {
 | 
			
		||||
        this.dictType = annotation.dictType();
 | 
			
		||||
        if (StringUtils.isNotBlank(annotation.separator())) {
 | 
			
		||||
            this.separator = annotation.separator();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 校验字段值是否为指定字典类型中的合法值
 | 
			
		||||
     *
 | 
			
		||||
     * @param value   被校验的字段值
 | 
			
		||||
     * @param context 校验上下文(可用于构建错误信息)
 | 
			
		||||
     * @return true 表示校验通过(合法字典值),false 表示不通过
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isValid(String value, ConstraintValidatorContext context) {
 | 
			
		||||
        if (StringUtils.isBlank(dictType) || StringUtils.isBlank(value)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        String dictLabel = SpringUtils.getBean(DictService.class).getDictLabel(dictType, value, separator);
 | 
			
		||||
        return StringUtils.isNotBlank(dictLabel);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -13,7 +13,7 @@ import org.dromara.common.core.utils.reflect.ReflectUtils;
 | 
			
		||||
 */
 | 
			
		||||
public class EnumPatternValidator implements ConstraintValidator<EnumPattern, String> {
 | 
			
		||||
 | 
			
		||||
    private EnumPattern annotation;;
 | 
			
		||||
    private EnumPattern annotation;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void initialize(EnumPattern annotation) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    /**
 | 
			
		||||
     * 字体颜色
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
package org.dromara.common.excel.handler;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
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;
 | 
			
		||||
@@ -13,7 +14,6 @@ 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();
 | 
			
		||||
 | 
			
		||||
        if (context.getHead()) {
 | 
			
		||||
            DataFormatData dataFormatData = new DataFormatData();
 | 
			
		||||
            // 单元格设置为文本格式
 | 
			
		||||
            dataFormatData.setIndex((short) 49);
 | 
			
		||||
            writeCellStyle.setDataFormatData(dataFormatData);
 | 
			
		||||
 | 
			
		||||
        if (context.getHead()) {
 | 
			
		||||
            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;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -101,7 +101,7 @@ public class TableDataInfo<T> implements Serializable {
 | 
			
		||||
            return TableDataInfo.build();
 | 
			
		||||
        }
 | 
			
		||||
        List<T> pageList = CollUtil.page((int) page.getCurrent() - 1, (int) page.getSize(), list);
 | 
			
		||||
        return new TableDataInfo(pageList, list.size());
 | 
			
		||||
        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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,10 +2,10 @@ 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.dromara.common.core.utils.SpringUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.redisson.api.RIdGenerator;
 | 
			
		||||
import org.redisson.api.RedissonClient;
 | 
			
		||||
 | 
			
		||||
@@ -24,14 +24,17 @@ 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);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 默认过期时间-分钟
 | 
			
		||||
     */
 | 
			
		||||
@@ -114,6 +117,18 @@ public class SequenceUtils {
 | 
			
		||||
        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
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -43,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);
 | 
			
		||||
@@ -61,7 +61,7 @@ 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);
 | 
			
		||||
 
 | 
			
		||||
@@ -38,8 +38,8 @@ 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);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ public class PlusTenantLineHandler implements TenantLineHandler {
 | 
			
		||||
                "gen_table_column"
 | 
			
		||||
            );
 | 
			
		||||
            tables.addAll(excludes);
 | 
			
		||||
            return StringUtils.containsAnyIgnoreCase(tableName, tables.toArray(new String[0]));
 | 
			
		||||
            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) {
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ package org.dromara.demo.domain.vo;
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -297,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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ import org.dromara.common.core.domain.R;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.redisson.spring.data.connection.RedissonConnectionFactory;
 | 
			
		||||
import org.springframework.data.redis.connection.RedisConnection;
 | 
			
		||||
import org.springframework.data.redis.core.RedisConnectionUtils;
 | 
			
		||||
import org.springframework.web.bind.annotation.GetMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RestController;
 | 
			
		||||
@@ -31,8 +32,8 @@ public class CacheController {
 | 
			
		||||
    @GetMapping()
 | 
			
		||||
    public R<CacheListInfoVo> getInfo() throws Exception {
 | 
			
		||||
        RedisConnection connection = connectionFactory.getConnection();
 | 
			
		||||
        try {
 | 
			
		||||
            Properties commandStats = connection.commands().info("commandstats");
 | 
			
		||||
 | 
			
		||||
            List<Map<String, String>> pieList = new ArrayList<>();
 | 
			
		||||
        if (commandStats != null) {
 | 
			
		||||
            commandStats.stringPropertyNames().forEach(key -> {
 | 
			
		||||
@@ -43,10 +44,13 @@ public class CacheController {
 | 
			
		||||
                pieList.add(data);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return R.ok(new CacheListInfoVo(
 | 
			
		||||
            connection.commands().info(),
 | 
			
		||||
            connection.commands().dbSize(), pieList));
 | 
			
		||||
        } finally {
 | 
			
		||||
            // 归还连接给连接池
 | 
			
		||||
            RedisConnectionUtils.releaseConnection(connection, connectionFactory);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public record CacheListInfoVo(Properties info, Long dbSize, List<Map<String, String>> commandStats) {}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
package org.dromara.system.controller.system;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.annotation.SaCheckPermission;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.dromara.common.core.domain.R;
 | 
			
		||||
import org.dromara.common.excel.utils.ExcelUtil;
 | 
			
		||||
import org.dromara.common.log.annotation.Log;
 | 
			
		||||
@@ -11,11 +13,10 @@ import org.dromara.common.web.core.BaseController;
 | 
			
		||||
import org.dromara.system.domain.bo.SysConfigBo;
 | 
			
		||||
import org.dromara.system.domain.vo.SysConfigVo;
 | 
			
		||||
import org.dromara.system.service.ISysConfigService;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.springframework.validation.annotation.Validated;
 | 
			
		||||
import org.springframework.web.bind.annotation.*;
 | 
			
		||||
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -120,7 +121,7 @@ public class SysConfigController extends BaseController {
 | 
			
		||||
    @Log(title = "参数管理", businessType = BusinessType.DELETE)
 | 
			
		||||
    @DeleteMapping("/{configIds}")
 | 
			
		||||
    public R<Void> remove(@PathVariable Long[] configIds) {
 | 
			
		||||
        configService.deleteConfigByIds(configIds);
 | 
			
		||||
        configService.deleteConfigByIds(Arrays.asList(configIds));
 | 
			
		||||
        return R.ok();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -113,6 +113,9 @@ public class SysDeptController extends BaseController {
 | 
			
		||||
    @Log(title = "部门管理", businessType = BusinessType.DELETE)
 | 
			
		||||
    @DeleteMapping("/{deptId}")
 | 
			
		||||
    public R<Void> remove(@PathVariable Long deptId) {
 | 
			
		||||
        if (SystemConstants.DEFAULT_DEPT_ID.equals(deptId)) {
 | 
			
		||||
            return R.warn("默认部门,不允许删除");
 | 
			
		||||
        }
 | 
			
		||||
        if (deptService.hasChildByDeptId(deptId)) {
 | 
			
		||||
            return R.warn("存在下级部门,不允许删除");
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -2,23 +2,24 @@ package org.dromara.system.controller.system;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.annotation.SaCheckPermission;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import org.dromara.common.log.annotation.Log;
 | 
			
		||||
import org.dromara.common.web.core.BaseController;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.PageQuery;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.dromara.common.core.domain.R;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
 | 
			
		||||
import org.dromara.common.log.enums.BusinessType;
 | 
			
		||||
import org.dromara.common.excel.utils.ExcelUtil;
 | 
			
		||||
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.web.core.BaseController;
 | 
			
		||||
import org.dromara.system.domain.bo.SysDictDataBo;
 | 
			
		||||
import org.dromara.system.domain.vo.SysDictDataVo;
 | 
			
		||||
import org.dromara.system.service.ISysDictDataService;
 | 
			
		||||
import org.dromara.system.service.ISysDictTypeService;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.springframework.validation.annotation.Validated;
 | 
			
		||||
import org.springframework.web.bind.annotation.*;
 | 
			
		||||
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -117,7 +118,7 @@ public class SysDictDataController extends BaseController {
 | 
			
		||||
    @Log(title = "字典类型", businessType = BusinessType.DELETE)
 | 
			
		||||
    @DeleteMapping("/{dictCodes}")
 | 
			
		||||
    public R<Void> remove(@PathVariable Long[] dictCodes) {
 | 
			
		||||
        dictDataService.deleteDictDataByIds(dictCodes);
 | 
			
		||||
        dictDataService.deleteDictDataByIds(Arrays.asList(dictCodes));
 | 
			
		||||
        return R.ok();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
package org.dromara.system.controller.system;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.annotation.SaCheckPermission;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.dromara.common.core.domain.R;
 | 
			
		||||
import org.dromara.common.excel.utils.ExcelUtil;
 | 
			
		||||
import org.dromara.common.log.annotation.Log;
 | 
			
		||||
@@ -11,11 +13,10 @@ import org.dromara.common.web.core.BaseController;
 | 
			
		||||
import org.dromara.system.domain.bo.SysDictTypeBo;
 | 
			
		||||
import org.dromara.system.domain.vo.SysDictTypeVo;
 | 
			
		||||
import org.dromara.system.service.ISysDictTypeService;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.springframework.validation.annotation.Validated;
 | 
			
		||||
import org.springframework.web.bind.annotation.*;
 | 
			
		||||
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -99,7 +100,7 @@ public class SysDictTypeController extends BaseController {
 | 
			
		||||
    @Log(title = "字典类型", businessType = BusinessType.DELETE)
 | 
			
		||||
    @DeleteMapping("/{dictIds}")
 | 
			
		||||
    public R<Void> remove(@PathVariable Long[] dictIds) {
 | 
			
		||||
        dictTypeService.deleteDictTypeByIds(dictIds);
 | 
			
		||||
        dictTypeService.deleteDictTypeByIds(Arrays.asList(dictIds));
 | 
			
		||||
        return R.ok();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ import org.dromara.system.service.ISysMenuService;
 | 
			
		||||
import org.springframework.validation.annotation.Validated;
 | 
			
		||||
import org.springframework.web.bind.annotation.*;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -111,9 +112,14 @@ public class SysMenuController extends BaseController {
 | 
			
		||||
    @GetMapping(value = "/tenantPackageMenuTreeselect/{packageId}")
 | 
			
		||||
    public R<MenuTreeSelectVo> tenantPackageMenuTreeselect(@PathVariable("packageId") Long packageId) {
 | 
			
		||||
        List<SysMenuVo> menus = menuService.selectMenuList(LoginHelper.getUserId());
 | 
			
		||||
        MenuTreeSelectVo selectVo = new MenuTreeSelectVo(
 | 
			
		||||
            menuService.selectMenuListByPackageId(packageId),
 | 
			
		||||
            menuService.buildMenuTreeSelect(menus));
 | 
			
		||||
        List<Tree<Long>> list = menuService.buildMenuTreeSelect(menus);
 | 
			
		||||
        // 删除租户管理菜单
 | 
			
		||||
        list.removeIf(menu -> menu.getId() == 6L);
 | 
			
		||||
        List<Long> ids = new ArrayList<>();
 | 
			
		||||
        if (packageId > 0L) {
 | 
			
		||||
            ids = menuService.selectMenuListByPackageId(packageId);
 | 
			
		||||
        }
 | 
			
		||||
        MenuTreeSelectVo selectVo = new MenuTreeSelectVo(ids, list);
 | 
			
		||||
        return R.ok(selectVo);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -173,4 +179,22 @@ public class SysMenuController extends BaseController {
 | 
			
		||||
    public record MenuTreeSelectVo(List<Long> checkedKeys, List<Tree<Long>> menus) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 批量级联删除菜单
 | 
			
		||||
     *
 | 
			
		||||
     * @param menuIds 菜单ID串
 | 
			
		||||
     */
 | 
			
		||||
    @SaCheckRole(TenantConstants.SUPER_ADMIN_ROLE_KEY)
 | 
			
		||||
    @SaCheckPermission("system:menu:remove")
 | 
			
		||||
    @Log(title = "菜单管理", businessType = BusinessType.DELETE)
 | 
			
		||||
    @DeleteMapping("/cascade/{menuIds}")
 | 
			
		||||
    public R<Void> remove(@PathVariable("menuIds") Long[] menuIds) {
 | 
			
		||||
        List<Long> menuIdList = List.of(menuIds);
 | 
			
		||||
        if (menuService.hasChildByMenuId(menuIdList)) {
 | 
			
		||||
            return R.warn("存在子菜单,不允许删除");
 | 
			
		||||
        }
 | 
			
		||||
        menuService.deleteMenuById(menuIdList);
 | 
			
		||||
        return R.ok();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@ package org.dromara.system.mapper;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 | 
			
		||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
 | 
			
		||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 | 
			
		||||
import org.apache.ibatis.annotations.Param;
 | 
			
		||||
import org.dromara.common.mybatis.annotation.DataColumn;
 | 
			
		||||
@@ -30,18 +29,23 @@ public interface SysDeptMapper extends BaseMapperPlus<SysDept, SysDeptVo> {
 | 
			
		||||
    @DataPermission({
 | 
			
		||||
        @DataColumn(key = "deptName", value = "dept_id")
 | 
			
		||||
    })
 | 
			
		||||
    List<SysDeptVo> selectDeptList(@Param(Constants.WRAPPER) Wrapper<SysDept> queryWrapper);
 | 
			
		||||
    default List<SysDeptVo> selectDeptList(Wrapper<SysDept> queryWrapper) {
 | 
			
		||||
        return this.selectVoList(queryWrapper);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 分页查询部门管理数据
 | 
			
		||||
     *
 | 
			
		||||
     * @param page         分页信息
 | 
			
		||||
     * @param queryWrapper 查询条件
 | 
			
		||||
     * @return 部门信息集合
 | 
			
		||||
     */
 | 
			
		||||
    @DataPermission({
 | 
			
		||||
        @DataColumn(key = "deptName", value = "dept_id"),
 | 
			
		||||
    })
 | 
			
		||||
    Page<SysDeptVo> selectPageDeptList(@Param("page") Page<SysDeptVo> page, @Param(Constants.WRAPPER) Wrapper<SysDept> queryWrapper);
 | 
			
		||||
    default Page<SysDeptVo> selectPageDeptList(Page<SysDept> page, Wrapper<SysDept> queryWrapper) {
 | 
			
		||||
        return this.selectVoPage(page, queryWrapper);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 统计指定部门ID的部门数量
 | 
			
		||||
@@ -52,7 +56,9 @@ public interface SysDeptMapper extends BaseMapperPlus<SysDept, SysDeptVo> {
 | 
			
		||||
    @DataPermission({
 | 
			
		||||
        @DataColumn(key = "deptName", value = "dept_id")
 | 
			
		||||
    })
 | 
			
		||||
    long countDeptById(Long deptId);
 | 
			
		||||
    default long countDeptById(Long deptId) {
 | 
			
		||||
        return this.selectCount(new LambdaQueryWrapper<SysDept>().eq(SysDept::getDeptId, deptId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据父部门ID查询其所有子部门的列表
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,7 @@
 | 
			
		||||
package org.dromara.system.mapper;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
 | 
			
		||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
 | 
			
		||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 | 
			
		||||
import org.apache.ibatis.annotations.Param;
 | 
			
		||||
import org.dromara.common.mybatis.annotation.DataColumn;
 | 
			
		||||
import org.dromara.common.mybatis.annotation.DataPermission;
 | 
			
		||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
 | 
			
		||||
@@ -30,7 +28,9 @@ public interface SysPostMapper extends BaseMapperPlus<SysPost, SysPostVo> {
 | 
			
		||||
        @DataColumn(key = "deptName", value = "dept_id"),
 | 
			
		||||
        @DataColumn(key = "userName", value = "create_by")
 | 
			
		||||
    })
 | 
			
		||||
    Page<SysPostVo> selectPagePostList(@Param("page") Page<SysPostVo> page, @Param(Constants.WRAPPER) Wrapper<SysPost> queryWrapper);
 | 
			
		||||
    default Page<SysPostVo> selectPagePostList(Page<SysPost> page, Wrapper<SysPost> queryWrapper) {
 | 
			
		||||
        return this.selectVoPage(page, queryWrapper);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询用户所属岗位组
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,11 @@
 | 
			
		||||
package org.dromara.system.mapper;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 | 
			
		||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
 | 
			
		||||
import org.dromara.system.domain.SysRoleMenu;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 角色与菜单关联表 数据层
 | 
			
		||||
 *
 | 
			
		||||
@@ -10,4 +13,14 @@ import org.dromara.system.domain.SysRoleMenu;
 | 
			
		||||
 */
 | 
			
		||||
public interface SysRoleMenuMapper extends BaseMapperPlus<SysRoleMenu, SysRoleMenu> {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据菜单ID串删除关联关系
 | 
			
		||||
     *
 | 
			
		||||
     * @param menuIds 菜单ID串
 | 
			
		||||
     * @return 结果
 | 
			
		||||
     */
 | 
			
		||||
    default int deleteByMenuIds(List<Long> menuIds) {
 | 
			
		||||
        return this.delete(new LambdaUpdateWrapper<SysRoleMenu>().in(SysRoleMenu::getMenuId, menuIds));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
package org.dromara.system.mapper;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 | 
			
		||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
 | 
			
		||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 | 
			
		||||
import org.apache.ibatis.annotations.Param;
 | 
			
		||||
@@ -28,10 +29,12 @@ public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
 | 
			
		||||
     * @return 分页的用户信息
 | 
			
		||||
     */
 | 
			
		||||
    @DataPermission({
 | 
			
		||||
        @DataColumn(key = "deptName", value = "u.dept_id"),
 | 
			
		||||
        @DataColumn(key = "userName", value = "u.user_id")
 | 
			
		||||
        @DataColumn(key = "deptName", value = "dept_id"),
 | 
			
		||||
        @DataColumn(key = "userName", value = "user_id")
 | 
			
		||||
    })
 | 
			
		||||
    Page<SysUserVo> selectPageUserList(@Param("page") Page<SysUser> page, @Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
 | 
			
		||||
    default Page<SysUserVo> selectPageUserList(Page<SysUser> page, Wrapper<SysUser> queryWrapper) {
 | 
			
		||||
        return this.selectVoPage(page, queryWrapper);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询用户列表,并进行数据权限控制
 | 
			
		||||
@@ -43,7 +46,9 @@ public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
 | 
			
		||||
        @DataColumn(key = "deptName", value = "dept_id"),
 | 
			
		||||
        @DataColumn(key = "userName", value = "user_id")
 | 
			
		||||
    })
 | 
			
		||||
    List<SysUserVo> selectUserList(@Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
 | 
			
		||||
    default List<SysUserVo> selectUserList(Wrapper<SysUser> queryWrapper) {
 | 
			
		||||
        return this.selectVoList(queryWrapper);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据条件分页查询用户列表
 | 
			
		||||
@@ -60,6 +65,7 @@ public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据条件分页查询已配用户角色列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param page         分页信息
 | 
			
		||||
     * @param queryWrapper 查询条件
 | 
			
		||||
     * @return 用户信息集合信息
 | 
			
		||||
     */
 | 
			
		||||
@@ -91,7 +97,9 @@ public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
 | 
			
		||||
        @DataColumn(key = "deptName", value = "dept_id"),
 | 
			
		||||
        @DataColumn(key = "userName", value = "user_id")
 | 
			
		||||
    })
 | 
			
		||||
    long countUserById(Long userId);
 | 
			
		||||
    default long countUserById(Long userId) {
 | 
			
		||||
        return this.selectCount(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserId, userId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据条件更新用户数据
 | 
			
		||||
 
 | 
			
		||||
@@ -69,7 +69,7 @@ public interface ISysConfigService {
 | 
			
		||||
     *
 | 
			
		||||
     * @param configIds 需要删除的参数ID
 | 
			
		||||
     */
 | 
			
		||||
    void deleteConfigByIds(Long[] configIds);
 | 
			
		||||
    void deleteConfigByIds(List<Long> configIds);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 重置参数缓存数据
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@ public interface ISysDictDataService {
 | 
			
		||||
     *
 | 
			
		||||
     * @param dictCodes 需要删除的字典数据ID
 | 
			
		||||
     */
 | 
			
		||||
    void deleteDictDataByIds(Long[] dictCodes);
 | 
			
		||||
    void deleteDictDataByIds(List<Long> dictCodes);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 新增保存字典数据信息
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,7 @@ public interface ISysDictTypeService {
 | 
			
		||||
     *
 | 
			
		||||
     * @param dictIds 需要删除的字典ID
 | 
			
		||||
     */
 | 
			
		||||
    void deleteDictTypeByIds(Long[] dictIds);
 | 
			
		||||
    void deleteDictTypeByIds(List<Long> dictIds);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 重置字典缓存数据
 | 
			
		||||
 
 | 
			
		||||
@@ -105,6 +105,14 @@ public interface ISysMenuService {
 | 
			
		||||
     */
 | 
			
		||||
    boolean hasChildByMenuId(Long menuId);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否存在菜单子节点
 | 
			
		||||
     *
 | 
			
		||||
     * @param menuIds 菜单ID串
 | 
			
		||||
     * @return 结果 true 存在 false 不存在
 | 
			
		||||
     */
 | 
			
		||||
    boolean hasChildByMenuId(List<Long> menuIds);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询菜单是否存在角色
 | 
			
		||||
     *
 | 
			
		||||
@@ -137,6 +145,14 @@ public interface ISysMenuService {
 | 
			
		||||
     */
 | 
			
		||||
    int deleteMenuById(Long menuId);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 批量删除菜单管理信息
 | 
			
		||||
     *
 | 
			
		||||
     * @param menuIds 菜单ID串
 | 
			
		||||
     * @return 结果
 | 
			
		||||
     */
 | 
			
		||||
    void deleteMenuById(List<Long> menuIds);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 校验菜单名称是否唯一
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
package org.dromara.system.service.impl;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.crypto.SecureUtil;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 | 
			
		||||
@@ -43,11 +44,10 @@ public class SysClientServiceImpl implements ISysClientService {
 | 
			
		||||
    @Override
 | 
			
		||||
    public SysClientVo queryById(Long id) {
 | 
			
		||||
        SysClientVo vo = baseMapper.selectVoById(id);
 | 
			
		||||
        vo.setGrantTypeList(List.of(vo.getGrantType().split(",")));
 | 
			
		||||
        vo.setGrantTypeList(StringUtils.splitList(vo.getGrantType()));
 | 
			
		||||
        return vo;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询客户端管理
 | 
			
		||||
     */
 | 
			
		||||
@@ -64,7 +64,7 @@ public class SysClientServiceImpl implements ISysClientService {
 | 
			
		||||
    public TableDataInfo<SysClientVo> queryPageList(SysClientBo bo, PageQuery pageQuery) {
 | 
			
		||||
        LambdaQueryWrapper<SysClient> lqw = buildQueryWrapper(bo);
 | 
			
		||||
        Page<SysClientVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
 | 
			
		||||
        result.getRecords().forEach(r -> r.setGrantTypeList(List.of(r.getGrantType().split(","))));
 | 
			
		||||
        result.getRecords().forEach(r -> r.setGrantTypeList(StringUtils.splitList(r.getGrantType())));
 | 
			
		||||
        return TableDataInfo.build(result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -93,8 +93,7 @@ public class SysClientServiceImpl implements ISysClientService {
 | 
			
		||||
    @Override
 | 
			
		||||
    public Boolean insertByBo(SysClientBo bo) {
 | 
			
		||||
        SysClient add = MapstructUtils.convert(bo, SysClient.class);
 | 
			
		||||
        validEntityBeforeSave(add);
 | 
			
		||||
        add.setGrantType(String.join(",", bo.getGrantTypeList()));
 | 
			
		||||
        add.setGrantType(CollUtil.join(bo.getGrantTypeList(), StringUtils.SEPARATOR));
 | 
			
		||||
        // 生成clientid
 | 
			
		||||
        String clientKey = bo.getClientKey();
 | 
			
		||||
        String clientSecret = bo.getClientSecret();
 | 
			
		||||
@@ -113,7 +112,6 @@ public class SysClientServiceImpl implements ISysClientService {
 | 
			
		||||
    @Override
 | 
			
		||||
    public Boolean updateByBo(SysClientBo bo) {
 | 
			
		||||
        SysClient update = MapstructUtils.convert(bo, SysClient.class);
 | 
			
		||||
        validEntityBeforeSave(update);
 | 
			
		||||
        update.setGrantType(String.join(",", bo.getGrantTypeList()));
 | 
			
		||||
        return baseMapper.updateById(update) > 0;
 | 
			
		||||
    }
 | 
			
		||||
@@ -130,22 +128,12 @@ public class SysClientServiceImpl implements ISysClientService {
 | 
			
		||||
                .eq(SysClient::getClientId, clientId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 保存前的数据校验
 | 
			
		||||
     */
 | 
			
		||||
    private void validEntityBeforeSave(SysClient entity) {
 | 
			
		||||
        //TODO 做一些数据校验,如唯一约束
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 批量删除客户端管理
 | 
			
		||||
     */
 | 
			
		||||
    @CacheEvict(cacheNames = CacheNames.SYS_CLIENT, allEntries = true)
 | 
			
		||||
    @Override
 | 
			
		||||
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
 | 
			
		||||
        if (isValid) {
 | 
			
		||||
            //TODO 做一些业务上的校验,判断是否需要校验
 | 
			
		||||
        }
 | 
			
		||||
        return baseMapper.deleteByIds(ids) > 0;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,10 +2,10 @@ package org.dromara.system.service.impl;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.convert.Convert;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import com.baomidou.dynamic.datasource.annotation.DS;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 | 
			
		||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 | 
			
		||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.dromara.common.core.constant.CacheNames;
 | 
			
		||||
import org.dromara.common.core.constant.SystemConstants;
 | 
			
		||||
import org.dromara.common.core.exception.ServiceException;
 | 
			
		||||
@@ -23,12 +23,10 @@ import org.dromara.system.domain.bo.SysConfigBo;
 | 
			
		||||
import org.dromara.system.domain.vo.SysConfigVo;
 | 
			
		||||
import org.dromara.system.mapper.SysConfigMapper;
 | 
			
		||||
import org.dromara.system.service.ISysConfigService;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.springframework.cache.annotation.CachePut;
 | 
			
		||||
import org.springframework.cache.annotation.Cacheable;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
@@ -57,7 +55,6 @@ public class SysConfigServiceImpl implements ISysConfigService, ConfigService {
 | 
			
		||||
     * @return 参数配置信息
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    @DS("master")
 | 
			
		||||
    public SysConfigVo selectConfigById(Long configId) {
 | 
			
		||||
        return baseMapper.selectVoById(configId);
 | 
			
		||||
    }
 | 
			
		||||
@@ -83,14 +80,10 @@ public class SysConfigServiceImpl implements ISysConfigService, ConfigService {
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean selectRegisterEnabled(String tenantId) {
 | 
			
		||||
        SysConfig retConfig = TenantHelper.dynamic(tenantId, () -> {
 | 
			
		||||
            return baseMapper.selectOne(new LambdaQueryWrapper<SysConfig>()
 | 
			
		||||
                .eq(SysConfig::getConfigKey, "sys.account.registerUser"));
 | 
			
		||||
        });
 | 
			
		||||
        if (ObjectUtil.isNull(retConfig)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        return Convert.toBool(retConfig.getConfigValue());
 | 
			
		||||
        String configValue = TenantHelper.dynamic(tenantId, () ->
 | 
			
		||||
            this.selectConfigByKey("sys.account.registerUser")
 | 
			
		||||
        );
 | 
			
		||||
        return Convert.toBool(configValue);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -168,15 +161,15 @@ public class SysConfigServiceImpl implements ISysConfigService, ConfigService {
 | 
			
		||||
     * @param configIds 需要删除的参数ID
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void deleteConfigByIds(Long[] configIds) {
 | 
			
		||||
        for (Long configId : configIds) {
 | 
			
		||||
            SysConfig config = baseMapper.selectById(configId);
 | 
			
		||||
    public void deleteConfigByIds(List<Long> configIds) {
 | 
			
		||||
        List<SysConfig> list = baseMapper.selectByIds(configIds);
 | 
			
		||||
        list.forEach(config -> {
 | 
			
		||||
            if (StringUtils.equals(SystemConstants.YES, config.getConfigType())) {
 | 
			
		||||
                throw new ServiceException(String.format("内置参数【%1$s】不能删除 ", config.getConfigKey()));
 | 
			
		||||
                throw new ServiceException(String.format("内置参数【%s】不能删除", config.getConfigKey()));
 | 
			
		||||
            }
 | 
			
		||||
            CacheUtils.evict(CacheNames.SYS_CONFIG, config.getConfigKey());
 | 
			
		||||
        }
 | 
			
		||||
        baseMapper.deleteByIds(Arrays.asList(configIds));
 | 
			
		||||
        });
 | 
			
		||||
        baseMapper.deleteByIds(configIds);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -195,12 +188,10 @@ public class SysConfigServiceImpl implements ISysConfigService, ConfigService {
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean checkConfigKeyUnique(SysConfigBo config) {
 | 
			
		||||
        long configId = ObjectUtils.notNull(config.getConfigId(), -1L);
 | 
			
		||||
        SysConfig info = baseMapper.selectOne(new LambdaQueryWrapper<SysConfig>().eq(SysConfig::getConfigKey, config.getConfigKey()));
 | 
			
		||||
        if (ObjectUtil.isNotNull(info) && info.getConfigId() != configId) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
        boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysConfig>()
 | 
			
		||||
            .eq(SysConfig::getConfigKey, config.getConfigKey())
 | 
			
		||||
            .ne(ObjectUtil.isNotNull(config.getConfigId()), SysConfig::getConfigId, config.getConfigId()));
 | 
			
		||||
        return !exist;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -131,23 +131,17 @@ public class SysDeptServiceImpl implements ISysDeptService, DeptService {
 | 
			
		||||
        if (CollUtil.isEmpty(depts)) {
 | 
			
		||||
            return CollUtil.newArrayList();
 | 
			
		||||
        }
 | 
			
		||||
        // 获取当前列表中每一个节点的parentId,然后在列表中查找是否有id与其parentId对应,若无对应,则表明此时节点列表中,该节点在当前列表中属于顶级节点
 | 
			
		||||
        List<Tree<Long>> treeList = CollUtil.newArrayList();
 | 
			
		||||
        for (SysDeptVo d : depts) {
 | 
			
		||||
            Long parentId = d.getParentId();
 | 
			
		||||
            SysDeptVo sysDeptVo = StreamUtils.findFirst(depts, it -> it.getDeptId().longValue() == parentId);
 | 
			
		||||
            if (ObjectUtil.isNull(sysDeptVo)) {
 | 
			
		||||
                List<Tree<Long>> trees = TreeBuildUtils.build(depts, parentId, (dept, tree) ->
 | 
			
		||||
                    tree.setId(dept.getDeptId())
 | 
			
		||||
                        .setParentId(dept.getParentId())
 | 
			
		||||
                        .setName(dept.getDeptName())
 | 
			
		||||
                        .setWeight(dept.getOrderNum())
 | 
			
		||||
                        .putExtra("disabled", SystemConstants.DISABLE.equals(dept.getStatus())));
 | 
			
		||||
                Tree<Long> tree = StreamUtils.findFirst(trees, it -> it.getId().longValue() == d.getDeptId());
 | 
			
		||||
                treeList.add(tree);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return treeList;
 | 
			
		||||
        return TreeBuildUtils.buildMultiRoot(
 | 
			
		||||
            depts,
 | 
			
		||||
            SysDeptVo::getDeptId,
 | 
			
		||||
            SysDeptVo::getParentId,
 | 
			
		||||
            (node, treeNode) -> treeNode
 | 
			
		||||
                .setId(node.getDeptId())
 | 
			
		||||
                .setParentId(node.getParentId())
 | 
			
		||||
                .setName(node.getDeptName())
 | 
			
		||||
                .setWeight(node.getOrderNum())
 | 
			
		||||
                .putExtra("disabled", SystemConstants.DISABLE.equals(node.getStatus()))
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -4,20 +4,19 @@ import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 | 
			
		||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 | 
			
		||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.dromara.common.core.constant.CacheNames;
 | 
			
		||||
import org.dromara.common.core.utils.MapstructUtils;
 | 
			
		||||
import org.dromara.common.core.utils.ObjectUtils;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.PageQuery;
 | 
			
		||||
import org.dromara.system.domain.SysDictData;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
 | 
			
		||||
import org.dromara.common.core.exception.ServiceException;
 | 
			
		||||
import org.dromara.common.core.utils.MapstructUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.PageQuery;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
 | 
			
		||||
import org.dromara.common.redis.utils.CacheUtils;
 | 
			
		||||
import org.dromara.system.domain.SysDictData;
 | 
			
		||||
import org.dromara.system.domain.bo.SysDictDataBo;
 | 
			
		||||
import org.dromara.system.domain.vo.SysDictDataVo;
 | 
			
		||||
import org.dromara.system.mapper.SysDictDataMapper;
 | 
			
		||||
import org.dromara.system.service.ISysDictDataService;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.springframework.cache.annotation.CachePut;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
@@ -58,7 +57,7 @@ public class SysDictDataServiceImpl implements ISysDictDataService {
 | 
			
		||||
        lqw.eq(bo.getDictSort() != null, SysDictData::getDictSort, bo.getDictSort());
 | 
			
		||||
        lqw.like(StringUtils.isNotBlank(bo.getDictLabel()), SysDictData::getDictLabel, bo.getDictLabel());
 | 
			
		||||
        lqw.eq(StringUtils.isNotBlank(bo.getDictType()), SysDictData::getDictType, bo.getDictType());
 | 
			
		||||
        lqw.orderByAsc(SysDictData::getDictSort);
 | 
			
		||||
        lqw.orderByAsc(SysDictData::getDictSort, SysDictData::getDictCode);
 | 
			
		||||
        return lqw;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -95,12 +94,10 @@ public class SysDictDataServiceImpl implements ISysDictDataService {
 | 
			
		||||
     * @param dictCodes 需要删除的字典数据ID
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void deleteDictDataByIds(Long[] dictCodes) {
 | 
			
		||||
        for (Long dictCode : dictCodes) {
 | 
			
		||||
            SysDictData data = baseMapper.selectById(dictCode);
 | 
			
		||||
            baseMapper.deleteById(dictCode);
 | 
			
		||||
            CacheUtils.evict(CacheNames.SYS_DICT, data.getDictType());
 | 
			
		||||
        }
 | 
			
		||||
    public void deleteDictDataByIds(List<Long> dictCodes) {
 | 
			
		||||
        List<SysDictData> list = baseMapper.selectByIds(dictCodes);
 | 
			
		||||
        baseMapper.deleteByIds(dictCodes);
 | 
			
		||||
        list.forEach(x -> CacheUtils.evict(CacheNames.SYS_DICT, x.getDictType()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -145,13 +142,11 @@ public class SysDictDataServiceImpl implements ISysDictDataService {
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean checkDictDataUnique(SysDictDataBo dict) {
 | 
			
		||||
        Long dictCode = ObjectUtils.notNull(dict.getDictCode(), -1L);
 | 
			
		||||
        SysDictData entity = baseMapper.selectOne(new LambdaQueryWrapper<SysDictData>()
 | 
			
		||||
            .eq(SysDictData::getDictType, dict.getDictType()).eq(SysDictData::getDictValue, dict.getDictValue()));
 | 
			
		||||
        if (ObjectUtil.isNotNull(entity) && !dictCode.equals(entity.getDictCode())) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
        boolean exist = baseMapper.exists(new LambdaQueryWrapper<SysDictData>()
 | 
			
		||||
            .eq(SysDictData::getDictType, dict.getDictType())
 | 
			
		||||
            .eq(SysDictData::getDictValue, dict.getDictValue())
 | 
			
		||||
            .ne(ObjectUtil.isNotNull(dict.getDictCode()), SysDictData::getDictCode, dict.getDictCode()));
 | 
			
		||||
        return !exist;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -98,10 +98,7 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<SysDictDataVo> selectDictDataByType(String dictType) {
 | 
			
		||||
        List<SysDictDataVo> dictDatas = dictDataMapper.selectDictDataByType(dictType);
 | 
			
		||||
        if (CollUtil.isNotEmpty(dictDatas)) {
 | 
			
		||||
            return dictDatas;
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
        return CollUtil.isNotEmpty(dictDatas) ? dictDatas : null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -133,17 +130,20 @@ public class SysDictTypeServiceImpl implements ISysDictTypeService, DictService
 | 
			
		||||
     * @param dictIds 需要删除的字典ID
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void deleteDictTypeByIds(Long[] dictIds) {
 | 
			
		||||
        for (Long dictId : dictIds) {
 | 
			
		||||
            SysDictType dictType = baseMapper.selectById(dictId);
 | 
			
		||||
            if (dictDataMapper.exists(new LambdaQueryWrapper<SysDictData>()
 | 
			
		||||
                .eq(SysDictData::getDictType, dictType.getDictType()))) {
 | 
			
		||||
                throw new ServiceException(String.format("%1$s已分配,不能删除", dictType.getDictName()));
 | 
			
		||||
    public void deleteDictTypeByIds(List<Long> dictIds) {
 | 
			
		||||
        List<SysDictType> list = baseMapper.selectByIds(dictIds);
 | 
			
		||||
        list.forEach(x -> {
 | 
			
		||||
            boolean assigned = dictDataMapper.exists(new LambdaQueryWrapper<SysDictData>()
 | 
			
		||||
                .eq(SysDictData::getDictType, x.getDictType()));
 | 
			
		||||
            if (assigned) {
 | 
			
		||||
                throw new ServiceException(String.format("%1$s已分配,不能删除", x.getDictName()));
 | 
			
		||||
            }
 | 
			
		||||
            CacheUtils.evict(CacheNames.SYS_DICT, dictType.getDictType());
 | 
			
		||||
            CacheUtils.evict(CacheNames.SYS_DICT_TYPE, dictType.getDictType());
 | 
			
		||||
        }
 | 
			
		||||
        baseMapper.deleteByIds(Arrays.asList(dictIds));
 | 
			
		||||
        });
 | 
			
		||||
        baseMapper.deleteByIds(dictIds);
 | 
			
		||||
        list.forEach(x -> {
 | 
			
		||||
            CacheUtils.evict(CacheNames.SYS_DICT, x.getDictType());
 | 
			
		||||
            CacheUtils.evict(CacheNames.SYS_DICT_TYPE, x.getDictType());
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 | 
			
		||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.dromara.common.core.constant.Constants;
 | 
			
		||||
import org.dromara.common.core.constant.SystemConstants;
 | 
			
		||||
import org.dromara.common.core.utils.MapstructUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StreamUtils;
 | 
			
		||||
@@ -28,6 +29,7 @@ import org.dromara.system.mapper.SysRoleMenuMapper;
 | 
			
		||||
import org.dromara.system.mapper.SysTenantPackageMapper;
 | 
			
		||||
import org.dromara.system.service.ISysMenuService;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
import org.springframework.transaction.annotation.Transactional;
 | 
			
		||||
 | 
			
		||||
import java.util.*;
 | 
			
		||||
 | 
			
		||||
@@ -141,7 +143,7 @@ public class SysMenuServiceImpl implements ISysMenuService {
 | 
			
		||||
        } else {
 | 
			
		||||
            menus = baseMapper.selectMenuTreeByUserId(userId);
 | 
			
		||||
        }
 | 
			
		||||
        return getChildPerms(menus, 0);
 | 
			
		||||
        return getChildPerms(menus, Constants.TOP_PARENT_ID);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -173,11 +175,15 @@ public class SysMenuServiceImpl implements ISysMenuService {
 | 
			
		||||
        if (tenantPackage.getMenuCheckStrictly()) {
 | 
			
		||||
            parentIds = baseMapper.selectObjs(new LambdaQueryWrapper<SysMenu>()
 | 
			
		||||
                .select(SysMenu::getParentId)
 | 
			
		||||
                .in(SysMenu::getMenuId, menuIds), x -> {return Convert.toLong(x);});
 | 
			
		||||
                .in(SysMenu::getMenuId, menuIds), x -> {
 | 
			
		||||
                return Convert.toLong(x);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return baseMapper.selectObjs(new LambdaQueryWrapper<SysMenu>()
 | 
			
		||||
            .in(SysMenu::getMenuId, menuIds)
 | 
			
		||||
            .notIn(CollUtil.isNotEmpty(parentIds), SysMenu::getMenuId, parentIds), x -> {return Convert.toLong(x);});
 | 
			
		||||
            .notIn(CollUtil.isNotEmpty(parentIds), SysMenu::getMenuId, parentIds), x -> {
 | 
			
		||||
            return Convert.toLong(x);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -216,7 +222,7 @@ public class SysMenuServiceImpl implements ISysMenuService {
 | 
			
		||||
                children.setQuery(menu.getQueryParam());
 | 
			
		||||
                childrenList.add(children);
 | 
			
		||||
                router.setChildren(childrenList);
 | 
			
		||||
            } else if (menu.getParentId().intValue() == 0 && menu.isInnerLink()) {
 | 
			
		||||
            } else if (menu.getParentId().equals(Constants.TOP_PARENT_ID) && menu.isInnerLink()) {
 | 
			
		||||
                router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));
 | 
			
		||||
                router.setPath("/");
 | 
			
		||||
                List<RouterVo> childrenList = new ArrayList<>();
 | 
			
		||||
@@ -278,6 +284,17 @@ public class SysMenuServiceImpl implements ISysMenuService {
 | 
			
		||||
        return baseMapper.exists(new LambdaQueryWrapper<SysMenu>().eq(SysMenu::getParentId, menuId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否存在菜单子节点
 | 
			
		||||
     *
 | 
			
		||||
     * @param menuIds 菜单ID串
 | 
			
		||||
     * @return 结果
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean hasChildByMenuId(List<Long> menuIds) {
 | 
			
		||||
        return baseMapper.exists(new LambdaQueryWrapper<SysMenu>().in(SysMenu::getParentId, menuIds).notIn(SysMenu::getMenuId, menuIds));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询菜单使用数量
 | 
			
		||||
     *
 | 
			
		||||
@@ -324,6 +341,19 @@ public class SysMenuServiceImpl implements ISysMenuService {
 | 
			
		||||
        return baseMapper.deleteById(menuId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 批量删除菜单管理信息
 | 
			
		||||
     *
 | 
			
		||||
     * @param menuIds 菜单ID串
 | 
			
		||||
     * @return 结果
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    @Transactional(rollbackFor = Exception.class)
 | 
			
		||||
    public void deleteMenuById(List<Long> menuIds) {
 | 
			
		||||
        baseMapper.deleteByIds(menuIds);
 | 
			
		||||
        roleMenuMapper.deleteByMenuIds(menuIds);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 校验菜单名称是否唯一
 | 
			
		||||
     *
 | 
			
		||||
@@ -346,11 +376,11 @@ public class SysMenuServiceImpl implements ISysMenuService {
 | 
			
		||||
     * @param parentId 传入的父节点ID
 | 
			
		||||
     * @return String
 | 
			
		||||
     */
 | 
			
		||||
    private List<SysMenu> getChildPerms(List<SysMenu> list, int parentId) {
 | 
			
		||||
    private List<SysMenu> getChildPerms(List<SysMenu> list, Long parentId) {
 | 
			
		||||
        List<SysMenu> returnList = new ArrayList<>();
 | 
			
		||||
        for (SysMenu t : list) {
 | 
			
		||||
            // 一、根据传入的某个父节点ID,遍历该父节点的所有子节点
 | 
			
		||||
            if (t.getParentId() == parentId) {
 | 
			
		||||
            if (t.getParentId().equals(parentId)) {
 | 
			
		||||
                recursionFn(list, t);
 | 
			
		||||
                returnList.add(t);
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,12 @@
 | 
			
		||||
package org.dromara.system.service.impl;
 | 
			
		||||
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.dromara.common.core.constant.TenantConstants;
 | 
			
		||||
import org.dromara.common.core.service.PermissionService;
 | 
			
		||||
import org.dromara.common.satoken.utils.LoginHelper;
 | 
			
		||||
import org.dromara.system.service.ISysMenuService;
 | 
			
		||||
import org.dromara.system.service.ISysPermissionService;
 | 
			
		||||
import org.dromara.system.service.ISysRoleService;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
@@ -18,7 +19,7 @@ import java.util.Set;
 | 
			
		||||
 */
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@Service
 | 
			
		||||
public class SysPermissionServiceImpl implements ISysPermissionService {
 | 
			
		||||
public class SysPermissionServiceImpl implements ISysPermissionService, PermissionService {
 | 
			
		||||
 | 
			
		||||
    private final ISysRoleService roleService;
 | 
			
		||||
    private final ISysMenuService menuService;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ package org.dromara.system.service.impl;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.convert.Convert;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.dromara.common.core.constant.SystemConstants;
 | 
			
		||||
import org.dromara.common.core.domain.dto.TaskAssigneeDTO;
 | 
			
		||||
import org.dromara.common.core.domain.model.TaskAssigneeBody;
 | 
			
		||||
import org.dromara.common.core.service.TaskAssigneeService;
 | 
			
		||||
@@ -51,6 +52,7 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService {
 | 
			
		||||
        SysRoleBo bo = new SysRoleBo();
 | 
			
		||||
        bo.setRoleKey(taskQuery.getHandlerCode());
 | 
			
		||||
        bo.setRoleName(taskQuery.getHandlerName());
 | 
			
		||||
        bo.setStatus(SystemConstants.NORMAL);
 | 
			
		||||
        Map<String, Object> params = bo.getParams();
 | 
			
		||||
        params.put("beginTime", taskQuery.getBeginTime());
 | 
			
		||||
        params.put("endTime", taskQuery.getEndTime());
 | 
			
		||||
@@ -73,6 +75,7 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService {
 | 
			
		||||
        SysPostBo bo = new SysPostBo();
 | 
			
		||||
        bo.setPostCategory(taskQuery.getHandlerCode());
 | 
			
		||||
        bo.setPostName(taskQuery.getHandlerName());
 | 
			
		||||
        bo.setStatus(SystemConstants.NORMAL);
 | 
			
		||||
        Map<String, Object> params = bo.getParams();
 | 
			
		||||
        params.put("beginTime", taskQuery.getBeginTime());
 | 
			
		||||
        params.put("endTime", taskQuery.getEndTime());
 | 
			
		||||
@@ -96,6 +99,7 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService {
 | 
			
		||||
        SysDeptBo bo = new SysDeptBo();
 | 
			
		||||
        bo.setDeptCategory(taskQuery.getHandlerCode());
 | 
			
		||||
        bo.setDeptName(taskQuery.getHandlerName());
 | 
			
		||||
        bo.setStatus(SystemConstants.NORMAL);
 | 
			
		||||
        Map<String, Object> params = bo.getParams();
 | 
			
		||||
        params.put("beginTime", taskQuery.getBeginTime());
 | 
			
		||||
        params.put("endTime", taskQuery.getEndTime());
 | 
			
		||||
@@ -107,7 +111,6 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService {
 | 
			
		||||
        return new TaskAssigneeDTO(page.getTotal(), handlers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询用户并返回任务指派的列表,支持分页
 | 
			
		||||
     *
 | 
			
		||||
@@ -120,6 +123,7 @@ public class SysTaskAssigneeServiceImpl implements TaskAssigneeService {
 | 
			
		||||
        SysUserBo bo = new SysUserBo();
 | 
			
		||||
        bo.setUserName(taskQuery.getHandlerCode());
 | 
			
		||||
        bo.setNickName(taskQuery.getHandlerName());
 | 
			
		||||
        bo.setStatus(SystemConstants.NORMAL);
 | 
			
		||||
        Map<String, Object> params = bo.getParams();
 | 
			
		||||
        params.put("beginTime", taskQuery.getBeginTime());
 | 
			
		||||
        params.put("endTime", taskQuery.getEndTime());
 | 
			
		||||
 
 | 
			
		||||
@@ -69,16 +69,11 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<SysUserExportVo> selectUserExportList(SysUserBo user) {
 | 
			
		||||
        return baseMapper.selectUserExportList(this.buildQueryWrapper(user));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Wrapper<SysUser> buildQueryWrapper(SysUserBo user) {
 | 
			
		||||
        Map<String, Object> params = user.getParams();
 | 
			
		||||
        QueryWrapper<SysUser> wrapper = Wrappers.query();
 | 
			
		||||
        wrapper.eq("u.del_flag", SystemConstants.NORMAL)
 | 
			
		||||
            .eq(ObjectUtil.isNotNull(user.getUserId()), "u.user_id", user.getUserId())
 | 
			
		||||
            .in(StringUtils.isNotBlank(user.getUserIds()), "u.user_id", StringUtils.splitTo(user.getUserIds(), Convert::toLong))
 | 
			
		||||
            .like(StringUtils.isNotBlank(user.getUserName()), "u.user_name", user.getUserName())
 | 
			
		||||
            .like(StringUtils.isNotBlank(user.getNickName()), "u.nick_name", user.getNickName())
 | 
			
		||||
            .eq(StringUtils.isNotBlank(user.getStatus()), "u.status", user.getStatus())
 | 
			
		||||
            .like(StringUtils.isNotBlank(user.getPhonenumber()), "u.phonenumber", user.getPhonenumber())
 | 
			
		||||
            .between(params.get("beginTime") != null && params.get("endTime") != null,
 | 
			
		||||
@@ -89,8 +84,29 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
 | 
			
		||||
                ids.add(user.getDeptId());
 | 
			
		||||
                w.in("u.dept_id", ids);
 | 
			
		||||
            }).orderByAsc("u.user_id");
 | 
			
		||||
        return baseMapper.selectUserExportList(wrapper);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Wrapper<SysUser> buildQueryWrapper(SysUserBo user) {
 | 
			
		||||
        Map<String, Object> params = user.getParams();
 | 
			
		||||
        LambdaQueryWrapper<SysUser> wrapper = Wrappers.lambdaQuery();
 | 
			
		||||
        wrapper.eq(SysUser::getDelFlag, SystemConstants.NORMAL)
 | 
			
		||||
            .eq(ObjectUtil.isNotNull(user.getUserId()), SysUser::getUserId, user.getUserId())
 | 
			
		||||
            .in(StringUtils.isNotBlank(user.getUserIds()), SysUser::getUserId, StringUtils.splitTo(user.getUserIds(), Convert::toLong))
 | 
			
		||||
            .like(StringUtils.isNotBlank(user.getUserName()), SysUser::getUserName, user.getUserName())
 | 
			
		||||
            .like(StringUtils.isNotBlank(user.getNickName()), SysUser::getNickName, user.getNickName())
 | 
			
		||||
            .eq(StringUtils.isNotBlank(user.getStatus()), SysUser::getStatus, user.getStatus())
 | 
			
		||||
            .like(StringUtils.isNotBlank(user.getPhonenumber()), SysUser::getPhonenumber, user.getPhonenumber())
 | 
			
		||||
            .between(params.get("beginTime") != null && params.get("endTime") != null,
 | 
			
		||||
                SysUser::getCreateTime, params.get("beginTime"), params.get("endTime"))
 | 
			
		||||
            .and(ObjectUtil.isNotNull(user.getDeptId()), w -> {
 | 
			
		||||
                List<SysDept> deptList = deptMapper.selectListByParentId(user.getDeptId());
 | 
			
		||||
                List<Long> ids = StreamUtils.toList(deptList, SysDept::getDeptId);
 | 
			
		||||
                ids.add(user.getDeptId());
 | 
			
		||||
                w.in(SysUser::getDeptId, ids);
 | 
			
		||||
            }).orderByAsc(SysUser::getUserId);
 | 
			
		||||
        if (StringUtils.isNotBlank(user.getExcludeUserIds())) {
 | 
			
		||||
            wrapper.notIn("u.user_id", StringUtils.splitTo(user.getExcludeUserIds(), Convert::toLong));
 | 
			
		||||
            wrapper.notIn(SysUser::getUserId, StringUtils.splitList(user.getExcludeUserIds()));
 | 
			
		||||
        }
 | 
			
		||||
        return wrapper;
 | 
			
		||||
    }
 | 
			
		||||
@@ -634,7 +650,10 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
 | 
			
		||||
            return List.of();
 | 
			
		||||
        }
 | 
			
		||||
        List<SysUserVo> list = baseMapper.selectVoList(new LambdaQueryWrapper<SysUser>()
 | 
			
		||||
            .select(SysUser::getUserId, SysUser::getUserName, SysUser::getNickName, SysUser::getEmail, SysUser::getPhonenumber)
 | 
			
		||||
            .select(SysUser::getUserId, SysUser::getDeptId, SysUser::getUserName,
 | 
			
		||||
                SysUser::getNickName, SysUser::getUserType, SysUser::getEmail,
 | 
			
		||||
                SysUser::getPhonenumber, SysUser::getSex, SysUser::getStatus,
 | 
			
		||||
                SysUser::getCreateTime)
 | 
			
		||||
            .eq(SysUser::getStatus, SystemConstants.NORMAL)
 | 
			
		||||
            .in(SysUser::getUserId, userIds));
 | 
			
		||||
        return BeanUtil.copyToList(list, UserDTO.class);
 | 
			
		||||
@@ -675,7 +694,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
 | 
			
		||||
        // 获取用户ID列表
 | 
			
		||||
        Set<Long> userIds = StreamUtils.toSet(userRoles, SysUserRole::getUserId);
 | 
			
		||||
 | 
			
		||||
        return selectListByIds(new ArrayList<>(userIds));
 | 
			
		||||
        return this.selectListByIds(new ArrayList<>(userIds));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -715,7 +734,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
 | 
			
		||||
        // 获取用户ID列表
 | 
			
		||||
        Set<Long> userIds = StreamUtils.toSet(userPosts, SysUserPost::getUserId);
 | 
			
		||||
 | 
			
		||||
        return selectListByIds(new ArrayList<>(userIds));
 | 
			
		||||
        return this.selectListByIds(new ArrayList<>(userIds));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -7,32 +7,6 @@
 | 
			
		||||
    <resultMap type="org.dromara.system.domain.vo.SysDeptVo" id="SysDeptResult">
 | 
			
		||||
    </resultMap>
 | 
			
		||||
 | 
			
		||||
    <select id="selectDeptList" resultMap="SysDeptResult">
 | 
			
		||||
        select
 | 
			
		||||
        <if test="ew.getSqlSelect != null">
 | 
			
		||||
            ${ew.getSqlSelect}
 | 
			
		||||
        </if>
 | 
			
		||||
        <if test="ew.getSqlSelect == null">
 | 
			
		||||
            *
 | 
			
		||||
        </if>
 | 
			
		||||
        from sys_dept ${ew.getCustomSqlSegment}
 | 
			
		||||
    </select>
 | 
			
		||||
 | 
			
		||||
    <select id="selectPageDeptList" resultMap="SysDeptResult">
 | 
			
		||||
        select
 | 
			
		||||
        <if test="ew.getSqlSelect != null">
 | 
			
		||||
            ${ew.getSqlSelect}
 | 
			
		||||
        </if>
 | 
			
		||||
        <if test="ew.getSqlSelect == null">
 | 
			
		||||
            *
 | 
			
		||||
        </if>
 | 
			
		||||
        from sys_dept ${ew.getCustomSqlSegment}
 | 
			
		||||
    </select>
 | 
			
		||||
 | 
			
		||||
    <select id="countDeptById" resultType="Long">
 | 
			
		||||
        select count(*) from sys_dept where del_flag = '0' and dept_id = #{deptId}
 | 
			
		||||
    </select>
 | 
			
		||||
 | 
			
		||||
    <select id="selectDeptListByRoleId" resultType="Long">
 | 
			
		||||
        select d.dept_id
 | 
			
		||||
        from sys_dept d
 | 
			
		||||
 
 | 
			
		||||
@@ -7,17 +7,6 @@
 | 
			
		||||
    <resultMap type="org.dromara.system.domain.vo.SysPostVo" id="SysPostResult">
 | 
			
		||||
    </resultMap>
 | 
			
		||||
 | 
			
		||||
    <select id="selectPagePostList" resultMap="SysPostResult">
 | 
			
		||||
        select
 | 
			
		||||
        <if test="ew.getSqlSelect != null">
 | 
			
		||||
            ${ew.getSqlSelect}
 | 
			
		||||
        </if>
 | 
			
		||||
        <if test="ew.getSqlSelect == null">
 | 
			
		||||
            *
 | 
			
		||||
        </if>
 | 
			
		||||
        from sys_post ${ew.getCustomSqlSegment}
 | 
			
		||||
    </select>
 | 
			
		||||
 | 
			
		||||
    <select id="selectPostsByUserId" parameterType="Long" resultMap="SysPostResult">
 | 
			
		||||
        select p.post_id, p.dept_id, p.post_name, p.post_code, p.post_category
 | 
			
		||||
        from sys_post p
 | 
			
		||||
 
 | 
			
		||||
@@ -11,32 +11,6 @@
 | 
			
		||||
        <id property="userId" column="user_id"/>
 | 
			
		||||
    </resultMap>
 | 
			
		||||
 | 
			
		||||
    <select id="selectPageUserList" resultMap="SysUserResult">
 | 
			
		||||
        select
 | 
			
		||||
        <if test="ew.getSqlSelect != null">
 | 
			
		||||
            ${ew.getSqlSelect}
 | 
			
		||||
        </if>
 | 
			
		||||
        <if test="ew.getSqlSelect == null">
 | 
			
		||||
            u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex,
 | 
			
		||||
            u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark
 | 
			
		||||
        </if>
 | 
			
		||||
        from sys_user u
 | 
			
		||||
        ${ew.getCustomSqlSegment}
 | 
			
		||||
    </select>
 | 
			
		||||
 | 
			
		||||
    <select id="selectUserList" resultMap="SysUserResult">
 | 
			
		||||
        select
 | 
			
		||||
        <if test="ew.getSqlSelect != null">
 | 
			
		||||
            ${ew.getSqlSelect}
 | 
			
		||||
        </if>
 | 
			
		||||
        <if test="ew.getSqlSelect == null">
 | 
			
		||||
            u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex,
 | 
			
		||||
            u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark
 | 
			
		||||
        </if>
 | 
			
		||||
        from sys_user u
 | 
			
		||||
        ${ew.getCustomSqlSegment}
 | 
			
		||||
    </select>
 | 
			
		||||
 | 
			
		||||
    <select id="selectUserExportList" resultMap="SysUserExportResult">
 | 
			
		||||
        select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex,
 | 
			
		||||
            u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,
 | 
			
		||||
@@ -65,9 +39,4 @@
 | 
			
		||||
        ${ew.getCustomSqlSegment}
 | 
			
		||||
    </select>
 | 
			
		||||
 | 
			
		||||
    <select id="countUserById" resultType="Long">
 | 
			
		||||
        select count(*) from sys_user where del_flag = '0' and user_id = #{userId}
 | 
			
		||||
    </select>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
</mapper>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,21 @@ import java.lang.annotation.Retention;
 | 
			
		||||
import java.lang.annotation.RetentionPolicy;
 | 
			
		||||
import java.lang.annotation.Target;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 自定义条件注解,用于基于配置启用或禁用特定功能
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 该注解只会在配置文件中 `warm-flow.enabled=true` 时,标注了此注解的类或方法才会被 Spring 容器加载
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 示例配置:
 | 
			
		||||
 * <pre>
 | 
			
		||||
 * warm-flow:
 | 
			
		||||
 *   enabled: true  # 设置为 true 时,启用工作流功能
 | 
			
		||||
 * </pre>
 | 
			
		||||
 * <p>
 | 
			
		||||
 * 使用此注解时,可以动态控制工作流功能是否启用,而不需要修改代码逻辑
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 */
 | 
			
		||||
@Retention(RetentionPolicy.RUNTIME)
 | 
			
		||||
@Target({ ElementType.TYPE, ElementType.METHOD })
 | 
			
		||||
@ConditionalOnProperty(value = "warm-flow.enabled", havingValue = "true")
 | 
			
		||||
 
 | 
			
		||||
@@ -13,21 +13,11 @@ public interface FlowConstant {
 | 
			
		||||
     */
 | 
			
		||||
    String INITIATOR = "initiator";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 流程实例id
 | 
			
		||||
     */
 | 
			
		||||
    String PROCESS_INSTANCE_ID = "processInstanceId";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 业务id
 | 
			
		||||
     */
 | 
			
		||||
    String BUSINESS_ID = "businessId";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 任务id
 | 
			
		||||
     */
 | 
			
		||||
    String TASK_ID = "taskId";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 委托
 | 
			
		||||
     */
 | 
			
		||||
@@ -63,4 +53,29 @@ public interface FlowConstant {
 | 
			
		||||
     */
 | 
			
		||||
    Long FLOW_CATEGORY_ID = 100L;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否为申请人提交常量
 | 
			
		||||
     */
 | 
			
		||||
    String SUBMIT = "submit";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 抄送常量
 | 
			
		||||
     */
 | 
			
		||||
    String FLOW_COPY_LIST = "flowCopyList";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 消息类型常量
 | 
			
		||||
     */
 | 
			
		||||
    String MESSAGE_TYPE = "messageType";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 消息通知常量
 | 
			
		||||
     */
 | 
			
		||||
    String MESSAGE_NOTICE = "messageNotice";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 任务状态
 | 
			
		||||
     */
 | 
			
		||||
    String WF_TASK_STATUS = "wf_task_status";
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ import org.dromara.common.log.annotation.Log;
 | 
			
		||||
import org.dromara.common.log.enums.BusinessType;
 | 
			
		||||
import org.dromara.common.web.core.BaseController;
 | 
			
		||||
import org.dromara.workflow.common.ConditionalOnEnable;
 | 
			
		||||
import org.dromara.workflow.common.constant.FlowConstant;
 | 
			
		||||
import org.dromara.workflow.domain.bo.FlowCategoryBo;
 | 
			
		||||
import org.dromara.workflow.domain.vo.FlowCategoryVo;
 | 
			
		||||
import org.dromara.workflow.service.IFlwCategoryService;
 | 
			
		||||
@@ -110,6 +111,9 @@ public class FlwCategoryController extends BaseController {
 | 
			
		||||
    @Log(title = "流程分类", businessType = BusinessType.DELETE)
 | 
			
		||||
    @DeleteMapping("/{categoryId}")
 | 
			
		||||
    public R<Void> remove(@PathVariable Long categoryId) {
 | 
			
		||||
        if (FlowConstant.FLOW_CATEGORY_ID.equals(categoryId)) {
 | 
			
		||||
            return R.warn("默认流程分类,不允许删除");
 | 
			
		||||
        }
 | 
			
		||||
        if (flwCategoryService.hasChildByCategoryId(categoryId)) {
 | 
			
		||||
            return R.warn("存在下级流程分类,不允许删除");
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -127,9 +127,9 @@ public class FlwInstanceController extends BaseController {
 | 
			
		||||
     *
 | 
			
		||||
     * @param businessId 业务id
 | 
			
		||||
     */
 | 
			
		||||
    @GetMapping("/flowImage/{businessId}")
 | 
			
		||||
    public R<Map<String, Object>> flowImage(@PathVariable String businessId) {
 | 
			
		||||
        return R.ok(flwInstanceService.flowImage(businessId));
 | 
			
		||||
    @GetMapping("/flowHisTaskList/{businessId}")
 | 
			
		||||
    public R<Map<String, Object>> flowHisTaskList(@PathVariable String businessId) {
 | 
			
		||||
        return R.ok(flwInstanceService.flowHisTaskList(businessId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,6 @@ public class BackProcessBo implements Serializable {
 | 
			
		||||
    /**
 | 
			
		||||
     * 驳回的节点id(目前未使用,直接驳回到申请人)
 | 
			
		||||
     */
 | 
			
		||||
    @NotBlank(message = "驳回的节点不能为空", groups = AddGroup.class)
 | 
			
		||||
    private String nodeCode;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -70,7 +70,8 @@ public class CompleteTaskBo implements Serializable {
 | 
			
		||||
 | 
			
		||||
    public Map<String, Object> getVariables() {
 | 
			
		||||
        if (variables == null) {
 | 
			
		||||
            return new HashMap<>(16);
 | 
			
		||||
            variables = new HashMap<>(16);
 | 
			
		||||
            return variables;
 | 
			
		||||
        }
 | 
			
		||||
        variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
 | 
			
		||||
        return variables;
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import org.dromara.common.core.validate.AddGroup;
 | 
			
		||||
import org.dromara.common.core.validate.EditGroup;
 | 
			
		||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
 | 
			
		||||
import org.dromara.workflow.domain.TestLeave;
 | 
			
		||||
import org.springframework.format.annotation.DateTimeFormat;
 | 
			
		||||
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
 | 
			
		||||
@@ -40,6 +41,7 @@ public class TestLeaveBo extends BaseEntity {
 | 
			
		||||
     * 开始时间
 | 
			
		||||
     */
 | 
			
		||||
    @NotNull(message = "开始时间不能为空", groups = {AddGroup.class, EditGroup.class})
 | 
			
		||||
    @DateTimeFormat(pattern = "yyyy-MM-dd")
 | 
			
		||||
    @JsonFormat(pattern = "yyyy-MM-dd")
 | 
			
		||||
    private Date startDate;
 | 
			
		||||
 | 
			
		||||
@@ -47,6 +49,7 @@ public class TestLeaveBo extends BaseEntity {
 | 
			
		||||
     * 结束时间
 | 
			
		||||
     */
 | 
			
		||||
    @NotNull(message = "结束时间不能为空", groups = {AddGroup.class, EditGroup.class})
 | 
			
		||||
    @DateTimeFormat(pattern = "yyyy-MM-dd")
 | 
			
		||||
    @JsonFormat(pattern = "yyyy-MM-dd")
 | 
			
		||||
    private Date endDate;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,14 +4,13 @@ import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
 | 
			
		||||
import cn.idev.excel.annotation.ExcelProperty;
 | 
			
		||||
import io.github.linpeilie.annotations.AutoMapper;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.dromara.common.translation.annotation.Translation;
 | 
			
		||||
import org.dromara.workflow.common.constant.FlowConstant;
 | 
			
		||||
import org.dromara.workflow.domain.FlowCategory;
 | 
			
		||||
 | 
			
		||||
import java.io.Serial;
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 流程分类视图对象 wf_category
 | 
			
		||||
@@ -34,13 +33,14 @@ public class FlowCategoryVo implements Serializable {
 | 
			
		||||
    private Long categoryId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 父级id
 | 
			
		||||
     * 父级分类id
 | 
			
		||||
     */
 | 
			
		||||
    private Long parentId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 父类别名称
 | 
			
		||||
     * 父级分类名称
 | 
			
		||||
     */
 | 
			
		||||
    @Translation(type = FlowConstant.CATEGORY_ID_TO_NAME, mapper = "parentId")
 | 
			
		||||
    private String parentName;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -66,9 +66,4 @@ public class FlowCategoryVo implements Serializable {
 | 
			
		||||
    @ExcelProperty(value = "创建时间")
 | 
			
		||||
    private Date createTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 子菜单
 | 
			
		||||
     */
 | 
			
		||||
    private List<FlowCategoryVo> children = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ package org.dromara.workflow.domain.vo;
 | 
			
		||||
 | 
			
		||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
 | 
			
		||||
import cn.idev.excel.annotation.ExcelProperty;
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonFormat;
 | 
			
		||||
import io.github.linpeilie.annotations.AutoMapper;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.dromara.workflow.domain.TestLeave;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
package org.dromara.workflow.handler;
 | 
			
		||||
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.dromara.common.core.domain.event.ProcessCreateTaskEvent;
 | 
			
		||||
import org.dromara.common.core.domain.event.ProcessTaskEvent;
 | 
			
		||||
import org.dromara.common.core.domain.event.ProcessDeleteEvent;
 | 
			
		||||
import org.dromara.common.core.domain.event.ProcessEvent;
 | 
			
		||||
import org.dromara.common.core.utils.SpringUtils;
 | 
			
		||||
@@ -56,19 +56,20 @@ public class FlowProcessEventHandler {
 | 
			
		||||
     * @param instance   实例数据
 | 
			
		||||
     * @param taskId     任务id
 | 
			
		||||
     */
 | 
			
		||||
    public void processCreateTaskHandler(String flowCode, Instance instance, Long taskId) {
 | 
			
		||||
    public void processTaskHandler(String flowCode, Instance instance, Long taskId) {
 | 
			
		||||
        String tenantId = TenantHelper.getTenantId();
 | 
			
		||||
        log.info("【流程任务事件发布】租户ID: {}, 流程编码: {}, 业务ID: {}, 节点类型: {}, 节点编码: {}, 节点名称: {}, 任务ID: {}",
 | 
			
		||||
            tenantId, flowCode, instance.getBusinessId(), instance.getNodeType(), instance.getNodeCode(), instance.getNodeName(), taskId);
 | 
			
		||||
        ProcessCreateTaskEvent processCreateTaskEvent = new ProcessCreateTaskEvent();
 | 
			
		||||
        processCreateTaskEvent.setTenantId(tenantId);
 | 
			
		||||
        processCreateTaskEvent.setFlowCode(flowCode);
 | 
			
		||||
        processCreateTaskEvent.setBusinessId(instance.getBusinessId());
 | 
			
		||||
        processCreateTaskEvent.setNodeType(instance.getNodeType());
 | 
			
		||||
        processCreateTaskEvent.setNodeCode(instance.getNodeCode());
 | 
			
		||||
        processCreateTaskEvent.setNodeName(instance.getNodeName());
 | 
			
		||||
        processCreateTaskEvent.setTaskId(taskId);
 | 
			
		||||
        SpringUtils.context().publishEvent(processCreateTaskEvent);
 | 
			
		||||
        ProcessTaskEvent processTaskEvent = new ProcessTaskEvent();
 | 
			
		||||
        processTaskEvent.setTenantId(tenantId);
 | 
			
		||||
        processTaskEvent.setFlowCode(flowCode);
 | 
			
		||||
        processTaskEvent.setBusinessId(instance.getBusinessId());
 | 
			
		||||
        processTaskEvent.setNodeType(instance.getNodeType());
 | 
			
		||||
        processTaskEvent.setNodeCode(instance.getNodeCode());
 | 
			
		||||
        processTaskEvent.setNodeName(instance.getNodeName());
 | 
			
		||||
        processTaskEvent.setTaskId(taskId);
 | 
			
		||||
        processTaskEvent.setStatus(instance.getFlowStatus());
 | 
			
		||||
        SpringUtils.context().publishEvent(processTaskEvent);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,13 @@
 | 
			
		||||
package org.dromara.workflow.handler;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.dromara.common.satoken.utils.LoginHelper;
 | 
			
		||||
import org.dromara.warm.flow.core.dto.FlowParams;
 | 
			
		||||
import org.dromara.warm.flow.core.handler.PermissionHandler;
 | 
			
		||||
import org.dromara.warm.flow.core.service.impl.TaskServiceImpl;
 | 
			
		||||
import org.dromara.workflow.common.ConditionalOnEnable;
 | 
			
		||||
import org.dromara.workflow.service.IFlwCommonService;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
@@ -23,9 +24,11 @@ import java.util.List;
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class WorkflowPermissionHandler implements PermissionHandler {
 | 
			
		||||
 | 
			
		||||
    private final IFlwCommonService flwCommonService;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 审批前获取当前办理人,办理时会校验的该权限集合
 | 
			
		||||
     * 后续在{@link TaskServiceImpl#checkAuth(Task, FlowParams)} 中调用
 | 
			
		||||
     * 办理人权限标识,比如用户,角色,部门等,用于校验是否有权限办理任务
 | 
			
		||||
     * 后续在{@link FlowParams#getPermissionFlag}  中获取
 | 
			
		||||
     * 返回当前用户权限集合
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -43,4 +46,14 @@ public class WorkflowPermissionHandler implements PermissionHandler {
 | 
			
		||||
        return LoginHelper.getUserIdStr();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 转换办理人,比如设计器中预设了能办理的人,如果其中包含角色或者部门id等,可以通过此接口进行转换成用户id
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<String> convertPermissions(List<String> permissions) {
 | 
			
		||||
        if (CollUtil.isNotEmpty(permissions)) {
 | 
			
		||||
            permissions = flwCommonService.buildUser(permissions);
 | 
			
		||||
        }
 | 
			
		||||
        return permissions;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,28 @@
 | 
			
		||||
package org.dromara.workflow.listener;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.core.map.MapUtil;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.dromara.common.core.enums.BusinessStatusEnum;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.dromara.warm.flow.core.FlowEngine;
 | 
			
		||||
import org.dromara.warm.flow.core.dto.FlowParams;
 | 
			
		||||
import org.dromara.warm.flow.core.entity.Definition;
 | 
			
		||||
import org.dromara.warm.flow.core.entity.Instance;
 | 
			
		||||
import org.dromara.warm.flow.core.entity.Task;
 | 
			
		||||
import org.dromara.warm.flow.core.listener.GlobalListener;
 | 
			
		||||
import org.dromara.warm.flow.core.listener.ListenerVariable;
 | 
			
		||||
import org.dromara.warm.flow.core.service.InsService;
 | 
			
		||||
import org.dromara.warm.flow.orm.entity.FlowInstance;
 | 
			
		||||
import org.dromara.warm.flow.orm.entity.FlowTask;
 | 
			
		||||
import org.dromara.workflow.common.ConditionalOnEnable;
 | 
			
		||||
import org.dromara.workflow.common.constant.FlowConstant;
 | 
			
		||||
import org.dromara.workflow.common.enums.TaskStatusEnum;
 | 
			
		||||
import org.dromara.workflow.domain.bo.FlowCopyBo;
 | 
			
		||||
import org.dromara.workflow.handler.FlowProcessEventHandler;
 | 
			
		||||
import org.dromara.workflow.service.IFlwCommonService;
 | 
			
		||||
import org.dromara.workflow.service.IFlwInstanceService;
 | 
			
		||||
import org.dromara.workflow.service.IFlwTaskService;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
@@ -34,9 +42,11 @@ import java.util.Map;
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
public class WorkflowGlobalListener implements GlobalListener {
 | 
			
		||||
 | 
			
		||||
    private final IFlwTaskService taskService;
 | 
			
		||||
    private final IFlwTaskService flwTaskService;
 | 
			
		||||
    private final IFlwInstanceService instanceService;
 | 
			
		||||
    private final FlowProcessEventHandler flowProcessEventHandler;
 | 
			
		||||
    private final IFlwCommonService flwCommonService;
 | 
			
		||||
    private final InsService insService;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建监听器,任务创建时执行
 | 
			
		||||
@@ -45,13 +55,7 @@ public class WorkflowGlobalListener implements GlobalListener {
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void create(ListenerVariable listenerVariable) {
 | 
			
		||||
        Instance instance = listenerVariable.getInstance();
 | 
			
		||||
        Definition definition = listenerVariable.getDefinition();
 | 
			
		||||
        Task task = listenerVariable.getTask();
 | 
			
		||||
        if (task != null && BusinessStatusEnum.WAITING.getStatus().equals(instance.getFlowStatus())) {
 | 
			
		||||
            // 判断流程状态(发布审批中事件)
 | 
			
		||||
            flowProcessEventHandler.processCreateTaskHandler(definition.getFlowCode(), instance, task.getId());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -70,6 +74,25 @@ public class WorkflowGlobalListener implements GlobalListener {
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void assignment(ListenerVariable listenerVariable) {
 | 
			
		||||
        Map<String, Object> variable = listenerVariable.getVariable();
 | 
			
		||||
        List<Task> nextTasks = listenerVariable.getNextTasks();
 | 
			
		||||
        FlowParams flowParams = listenerVariable.getFlowParams();
 | 
			
		||||
        Definition definition = listenerVariable.getDefinition();
 | 
			
		||||
        Instance instance = listenerVariable.getInstance();
 | 
			
		||||
        String applyNodeCode = flwCommonService.applyNodeCode(definition.getId());
 | 
			
		||||
        for (Task flowTask : nextTasks) {
 | 
			
		||||
            // 如果办理或者退回并行存在需要指定办理人,则直接覆盖办理人
 | 
			
		||||
            if (variable.containsKey(flowTask.getNodeCode()) && (TaskStatusEnum.PASS.getStatus().equals(flowParams.getHisStatus())
 | 
			
		||||
                || TaskStatusEnum.BACK.getStatus().equals(flowParams.getHisStatus()))) {
 | 
			
		||||
                String userIds = variable.get(flowTask.getNodeCode()).toString();
 | 
			
		||||
                flowTask.setPermissionList(List.of(userIds.split(StringUtils.SEPARATOR)));
 | 
			
		||||
                variable.remove(flowTask.getNodeCode());
 | 
			
		||||
            }
 | 
			
		||||
            // 如果是申请节点,则把启动人添加到办理人
 | 
			
		||||
            if (flowTask.getNodeCode().equals(applyNodeCode)) {
 | 
			
		||||
                flowTask.setPermissionList(List.of(instance.getCreateBy()));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -81,8 +104,10 @@ public class WorkflowGlobalListener implements GlobalListener {
 | 
			
		||||
    public void finish(ListenerVariable listenerVariable) {
 | 
			
		||||
        Instance instance = listenerVariable.getInstance();
 | 
			
		||||
        Definition definition = listenerVariable.getDefinition();
 | 
			
		||||
        Task task = listenerVariable.getTask();
 | 
			
		||||
        Map<String, Object> params = new HashMap<>();
 | 
			
		||||
        FlowParams flowParams = listenerVariable.getFlowParams();
 | 
			
		||||
        Map<String, Object> variable = new HashMap<>();
 | 
			
		||||
        if (ObjectUtil.isNotNull(flowParams)) {
 | 
			
		||||
            // 历史任务扩展(通常为附件)
 | 
			
		||||
            params.put("hisTaskExt", flowParams.getHisTaskExt());
 | 
			
		||||
@@ -90,13 +115,55 @@ public class WorkflowGlobalListener implements GlobalListener {
 | 
			
		||||
            params.put("handler", flowParams.getHandler());
 | 
			
		||||
            // 办理意见
 | 
			
		||||
            params.put("message", flowParams.getMessage());
 | 
			
		||||
            variable = flowParams.getVariable();
 | 
			
		||||
        }
 | 
			
		||||
        //申请人提交事件
 | 
			
		||||
        Boolean submit = MapUtil.getBool(variable, FlowConstant.SUBMIT);
 | 
			
		||||
        if (submit != null && submit) {
 | 
			
		||||
            flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, instance.getFlowStatus(), variable, true);
 | 
			
		||||
        } else {
 | 
			
		||||
            // 判断流程状态(发布:撤销,退回,作废,终止,已完成事件)
 | 
			
		||||
            String status = determineFlowStatus(instance);
 | 
			
		||||
            if (StringUtils.isNotBlank(status)) {
 | 
			
		||||
                flowProcessEventHandler.processHandler(definition.getFlowCode(), instance, status, params, false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        //发布任务事件
 | 
			
		||||
        if (task != null) {
 | 
			
		||||
            flowProcessEventHandler.processTaskHandler(definition.getFlowCode(), instance, task.getId());
 | 
			
		||||
        }
 | 
			
		||||
        if (ObjectUtil.isNull(flowParams)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // 只有办理或者退回的时候才执行消息通知和抄送
 | 
			
		||||
        if (TaskStatusEnum.PASS.getStatus().equals(flowParams.getHisStatus())
 | 
			
		||||
            || TaskStatusEnum.BACK.getStatus().equals(flowParams.getHisStatus())) {
 | 
			
		||||
            if (variable != null) {
 | 
			
		||||
                if (variable.containsKey(FlowConstant.FLOW_COPY_LIST)) {
 | 
			
		||||
                    List<FlowCopyBo> flowCopyList = (List<FlowCopyBo>) variable.get(FlowConstant.FLOW_COPY_LIST);
 | 
			
		||||
                    // 添加抄送人
 | 
			
		||||
                    flwTaskService.setCopy(task, flowCopyList);
 | 
			
		||||
                }
 | 
			
		||||
                if (variable.containsKey(FlowConstant.MESSAGE_TYPE)) {
 | 
			
		||||
                    List<String> messageType = (List<String>) variable.get(FlowConstant.MESSAGE_TYPE);
 | 
			
		||||
                    String notice = (String) variable.get(FlowConstant.MESSAGE_NOTICE);
 | 
			
		||||
                    // 消息通知
 | 
			
		||||
                    if (CollUtil.isNotEmpty(messageType)) {
 | 
			
		||||
                        flwCommonService.sendMessage(definition.getFlowName(), instance.getId(), messageType, notice);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                FlowInstance ins = new FlowInstance();
 | 
			
		||||
                Map<String, Object> variableMap = instance.getVariableMap();
 | 
			
		||||
                variableMap.remove(FlowConstant.FLOW_COPY_LIST);
 | 
			
		||||
                variableMap.remove(FlowConstant.MESSAGE_TYPE);
 | 
			
		||||
                variableMap.remove(FlowConstant.MESSAGE_NOTICE);
 | 
			
		||||
                variableMap.remove(FlowConstant.SUBMIT);
 | 
			
		||||
                ins.setId(instance.getId());
 | 
			
		||||
                ins.setVariable(FlowEngine.jsonConvert.objToStr(variableMap));
 | 
			
		||||
                insService.updateById(ins);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据流程实例确定最终状态
 | 
			
		||||
@@ -111,7 +178,7 @@ public class WorkflowGlobalListener implements GlobalListener {
 | 
			
		||||
            return flowStatus;
 | 
			
		||||
        } else {
 | 
			
		||||
            Long instanceId = instance.getId();
 | 
			
		||||
            List<FlowTask> flowTasks = taskService.selectByInstId(instanceId);
 | 
			
		||||
            List<FlowTask> flowTasks = flwTaskService.selectByInstId(instanceId);
 | 
			
		||||
            if (CollUtil.isEmpty(flowTasks)) {
 | 
			
		||||
                String status = BusinessStatusEnum.FINISH.getStatus();
 | 
			
		||||
                // 更新流程状态为已完成
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,9 @@ public interface FlwCategoryMapper extends BaseMapperPlus<FlowCategory, FlowCate
 | 
			
		||||
    @DataPermission({
 | 
			
		||||
        @DataColumn(key = "deptName", value = "createDept")
 | 
			
		||||
    })
 | 
			
		||||
    long countCategoryById(Long categoryId);
 | 
			
		||||
    default long countCategoryById(Long categoryId) {
 | 
			
		||||
        return this.selectCount(new LambdaQueryWrapper<FlowCategory>().eq(FlowCategory::getCategoryId, categoryId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据父流程分类ID查询其所有子流程分类的列表
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,6 @@
 | 
			
		||||
package org.dromara.workflow.service;
 | 
			
		||||
 | 
			
		||||
import org.dromara.warm.flow.core.entity.Instance;
 | 
			
		||||
import org.dromara.warm.flow.core.entity.User;
 | 
			
		||||
import org.dromara.warm.flow.core.service.UserService;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 通用 工作流服务
 | 
			
		||||
@@ -15,30 +9,13 @@ import java.util.Set;
 | 
			
		||||
 */
 | 
			
		||||
public interface IFlwCommonService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取工作流用户service
 | 
			
		||||
     *
 | 
			
		||||
     * @return 工作流用户service
 | 
			
		||||
     */
 | 
			
		||||
    UserService getFlowUserService();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 构建工作流用户
 | 
			
		||||
     *
 | 
			
		||||
     * @param userList 办理用户
 | 
			
		||||
     * @param taskId   任务ID
 | 
			
		||||
     * @param permissionList 办理用户
 | 
			
		||||
     * @return 用户
 | 
			
		||||
     */
 | 
			
		||||
    Set<User> buildUser(List<User> userList, Long taskId);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 构建工作流用户
 | 
			
		||||
     *
 | 
			
		||||
     * @param userIdList 办理用户
 | 
			
		||||
     * @param taskId     任务ID
 | 
			
		||||
     * @return 用户
 | 
			
		||||
     */
 | 
			
		||||
    Set<User> buildFlowUser(List<String> userIdList, Long taskId);
 | 
			
		||||
    List<String> buildUser(List<String> permissionList);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 发送消息
 | 
			
		||||
@@ -57,12 +34,4 @@ public interface IFlwCommonService {
 | 
			
		||||
     * @return 申请人节点编码
 | 
			
		||||
     */
 | 
			
		||||
    String applyNodeCode(Long definitionId);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 合并变量
 | 
			
		||||
     *
 | 
			
		||||
     * @param instance 流程实例
 | 
			
		||||
     * @param variable 变量
 | 
			
		||||
     */
 | 
			
		||||
    void mergeVariable(Instance instance, Map<String, Object> variable);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,6 @@ public interface IFlwDefinitionService {
 | 
			
		||||
     */
 | 
			
		||||
    TableDataInfo<FlowDefinitionVo> unPublishList(FlowDefinition flowDefinition, PageQuery pageQuery);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 发布流程定义
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -107,7 +107,7 @@ public interface IFlwInstanceService {
 | 
			
		||||
     * @param businessId 业务id
 | 
			
		||||
     * @return 结果
 | 
			
		||||
     */
 | 
			
		||||
    Map<String, Object> flowImage(String businessId);
 | 
			
		||||
    Map<String, Object> flowHisTaskList(String businessId);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 按照实例id更新状态
 | 
			
		||||
 
 | 
			
		||||
@@ -11,15 +11,6 @@ import java.util.List;
 | 
			
		||||
 */
 | 
			
		||||
public interface IFlwTaskAssigneeService {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据存储标识符(storageId)解析分配类型和ID,并获取对应的用户列表
 | 
			
		||||
     * 支持单个标识(例如 "user:123" 或 "456"),格式非法将返回空列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param storageId 包含分配类型和ID的字符串
 | 
			
		||||
     * @return 匹配的用户列表,格式非法返回空列表
 | 
			
		||||
     */
 | 
			
		||||
    List<UserDTO> fetchUsersByStorageId(String storageId);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 批量解析多个存储标识符(storageIds),按类型分类并合并查询用户列表
 | 
			
		||||
     * 输入格式支持多个以逗号分隔的标识(如 "user:123,role:456,789")
 | 
			
		||||
@@ -28,6 +19,6 @@ public interface IFlwTaskAssigneeService {
 | 
			
		||||
     * @param storageIds 多个存储标识符字符串(逗号分隔)
 | 
			
		||||
     * @return 合并后的用户列表,去重后返回,非法格式的标识将被跳过
 | 
			
		||||
     */
 | 
			
		||||
    List<UserDTO> fetchUsersByStorageIds(List<String> storageIds);
 | 
			
		||||
    List<UserDTO> fetchUsersByStorageIds(String storageIds);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import org.dromara.common.core.domain.dto.UserDTO;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.PageQuery;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
 | 
			
		||||
import org.dromara.warm.flow.core.entity.Node;
 | 
			
		||||
import org.dromara.warm.flow.core.entity.Task;
 | 
			
		||||
import org.dromara.warm.flow.orm.entity.FlowHisTask;
 | 
			
		||||
import org.dromara.warm.flow.orm.entity.FlowNode;
 | 
			
		||||
import org.dromara.warm.flow.orm.entity.FlowTask;
 | 
			
		||||
@@ -38,6 +39,14 @@ public interface IFlwTaskService {
 | 
			
		||||
     */
 | 
			
		||||
    boolean completeTask(CompleteTaskBo completeTaskBo);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 添加抄送人
 | 
			
		||||
     *
 | 
			
		||||
     * @param task         任务信息
 | 
			
		||||
     * @param flowCopyList 抄送人
 | 
			
		||||
     */
 | 
			
		||||
    void setCopy(Task task, List<FlowCopyBo> flowCopyList);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询当前用户的待办任务
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -26,12 +26,6 @@ public class CategoryNameTranslationImpl implements TranslationInterface<String>
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String translation(Object key, String other) {
 | 
			
		||||
        Long id = null;
 | 
			
		||||
        if (key instanceof String categoryId) {
 | 
			
		||||
            id = Convert.toLong(categoryId);
 | 
			
		||||
        } else if (key instanceof Long categoryId) {
 | 
			
		||||
            id = categoryId;
 | 
			
		||||
        }
 | 
			
		||||
        return flwCategoryService.selectCategoryNameById(id);
 | 
			
		||||
        return flwCategoryService.selectCategoryNameById(Convert.toLong(key));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,10 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.dromara.common.core.constant.SystemConstants;
 | 
			
		||||
import org.dromara.common.core.exception.ServiceException;
 | 
			
		||||
import org.dromara.common.core.utils.*;
 | 
			
		||||
import org.dromara.common.core.utils.MapstructUtils;
 | 
			
		||||
import org.dromara.common.core.utils.ObjectUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.dromara.common.core.utils.TreeBuildUtils;
 | 
			
		||||
import org.dromara.common.mybatis.helper.DataBaseHelper;
 | 
			
		||||
import org.dromara.common.satoken.utils.LoginHelper;
 | 
			
		||||
import org.dromara.warm.flow.core.service.DefService;
 | 
			
		||||
@@ -48,14 +51,7 @@ public class FlwCategoryServiceImpl implements IFlwCategoryService {
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public FlowCategoryVo queryById(Long categoryId) {
 | 
			
		||||
        FlowCategoryVo category = baseMapper.selectVoById(categoryId);
 | 
			
		||||
        if (ObjectUtil.isNull(category)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        FlowCategoryVo parentCategory = baseMapper.selectVoOne(new LambdaQueryWrapper<FlowCategory>()
 | 
			
		||||
            .select(FlowCategory::getCategoryName).eq(FlowCategory::getCategoryId, category.getParentId()));
 | 
			
		||||
        category.setParentName(ObjectUtils.notNullGetter(parentCategory, FlowCategoryVo::getCategoryName));
 | 
			
		||||
        return category;
 | 
			
		||||
        return baseMapper.selectVoById(categoryId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -95,27 +91,20 @@ public class FlwCategoryServiceImpl implements IFlwCategoryService {
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<Tree<String>> selectCategoryTreeList(FlowCategoryBo category) {
 | 
			
		||||
        LambdaQueryWrapper<FlowCategory> lqw = buildQueryWrapper(category);
 | 
			
		||||
        List<FlowCategoryVo> categorys = baseMapper.selectVoList(lqw);
 | 
			
		||||
        if (CollUtil.isEmpty(categorys)) {
 | 
			
		||||
        List<FlowCategoryVo> categoryList = this.queryList(category);
 | 
			
		||||
        if (CollUtil.isEmpty(categoryList)) {
 | 
			
		||||
            return CollUtil.newArrayList();
 | 
			
		||||
        }
 | 
			
		||||
        // 获取当前列表中每一个节点的parentId,然后在列表中查找是否有id与其parentId对应,若无对应,则表明此时节点列表中,该节点在当前列表中属于顶级节点
 | 
			
		||||
        List<Tree<String>> treeList = CollUtil.newArrayList();
 | 
			
		||||
        for (FlowCategoryVo d : categorys) {
 | 
			
		||||
            String parentId = d.getParentId().toString();
 | 
			
		||||
            FlowCategoryVo categoryVo = StreamUtils.findFirst(categorys, it -> it.getCategoryId().toString().equals(parentId));
 | 
			
		||||
            if (ObjectUtil.isNull(categoryVo)) {
 | 
			
		||||
                List<Tree<String>> trees = TreeBuildUtils.build(categorys, parentId, (dept, tree) ->
 | 
			
		||||
                    tree.setId(dept.getCategoryId().toString())
 | 
			
		||||
                        .setParentId(dept.getParentId().toString())
 | 
			
		||||
                        .setName(dept.getCategoryName())
 | 
			
		||||
                        .setWeight(dept.getOrderNum()));
 | 
			
		||||
                Tree<String> tree = StreamUtils.findFirst(trees, it -> it.getId().equals(d.getCategoryId().toString()));
 | 
			
		||||
                treeList.add(tree);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return treeList;
 | 
			
		||||
        return TreeBuildUtils.buildMultiRoot(
 | 
			
		||||
            categoryList,
 | 
			
		||||
            node -> String.valueOf(node.getCategoryId()),
 | 
			
		||||
            node -> String.valueOf(node.getParentId()),
 | 
			
		||||
            (node, treeNode) -> treeNode
 | 
			
		||||
                .setId(String.valueOf(node.getCategoryId()))
 | 
			
		||||
                .setParentId(String.valueOf(node.getParentId()))
 | 
			
		||||
                .setName(node.getCategoryName())
 | 
			
		||||
                .setWeight(node.getOrderNum())
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,247 @@
 | 
			
		||||
package org.dromara.workflow.service.impl;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.core.convert.Convert;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
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.dromara.common.core.domain.dto.UserDTO;
 | 
			
		||||
import org.dromara.common.core.service.DeptService;
 | 
			
		||||
import org.dromara.common.core.service.DictService;
 | 
			
		||||
import org.dromara.common.core.service.UserService;
 | 
			
		||||
import org.dromara.common.core.utils.DateUtils;
 | 
			
		||||
import org.dromara.common.core.utils.ServletUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StreamUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.dromara.warm.flow.core.dto.DefJson;
 | 
			
		||||
import org.dromara.warm.flow.core.dto.NodeJson;
 | 
			
		||||
import org.dromara.warm.flow.core.dto.PromptContent;
 | 
			
		||||
import org.dromara.warm.flow.core.enums.NodeType;
 | 
			
		||||
import org.dromara.warm.flow.core.utils.MapUtil;
 | 
			
		||||
import org.dromara.warm.flow.orm.entity.FlowHisTask;
 | 
			
		||||
import org.dromara.warm.flow.orm.mapper.FlowHisTaskMapper;
 | 
			
		||||
import org.dromara.warm.flow.ui.service.ChartExtService;
 | 
			
		||||
import org.dromara.workflow.common.ConditionalOnEnable;
 | 
			
		||||
import org.dromara.workflow.common.constant.FlowConstant;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 流程图提示信息
 | 
			
		||||
 *
 | 
			
		||||
 * @author AprilWind
 | 
			
		||||
 */
 | 
			
		||||
@ConditionalOnEnable
 | 
			
		||||
@Slf4j
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@Service
 | 
			
		||||
public class FlwChartExtServiceImpl implements ChartExtService {
 | 
			
		||||
 | 
			
		||||
    private final UserService userService;
 | 
			
		||||
    private final DeptService deptService;
 | 
			
		||||
    private final FlowHisTaskMapper flowHisTaskMapper;
 | 
			
		||||
    private final DictService dictService;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置流程图提示信息
 | 
			
		||||
     *
 | 
			
		||||
     * @param defJson 流程定义json对象
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void execute(DefJson defJson) {
 | 
			
		||||
        // 临时修复 后续版本将通过defjson获取流程实例ID
 | 
			
		||||
        String[] parts = ServletUtils.getRequest().getRequestURI().split("/");
 | 
			
		||||
        Long instanceId = Convert.toLong(parts[parts.length - 1]);
 | 
			
		||||
 | 
			
		||||
        // 根据流程实例ID查询所有相关的历史任务列表
 | 
			
		||||
        List<FlowHisTask> flowHisTasks = this.getHisTaskGroupedByNode(instanceId);
 | 
			
		||||
        if (CollUtil.isEmpty(flowHisTasks)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 按节点编号(nodeCode)对历史任务进行分组
 | 
			
		||||
        Map<String, List<FlowHisTask>> groupedByNode = StreamUtils.groupByKey(flowHisTasks, FlowHisTask::getNodeCode);
 | 
			
		||||
 | 
			
		||||
        // 批量查询所有审批人的用户信息
 | 
			
		||||
        List<UserDTO> userDTOList = userService.selectListByIds(StreamUtils.toList(flowHisTasks, e -> Convert.toLong(e.getApprover())));
 | 
			
		||||
 | 
			
		||||
        // 将查询到的用户列表转换为以用户ID为key的映射
 | 
			
		||||
        Map<Long, UserDTO> userMap = StreamUtils.toIdentityMap(userDTOList, UserDTO::getUserId);
 | 
			
		||||
 | 
			
		||||
        Map<String, String> dictType = dictService.getAllDictByDictType(FlowConstant.WF_TASK_STATUS);
 | 
			
		||||
 | 
			
		||||
        // 遍历流程定义中的每个节点,调用处理方法,将对应节点的任务列表及用户信息传入,生成扩展提示内容
 | 
			
		||||
        for (NodeJson nodeJson : defJson.getNodeList()) {
 | 
			
		||||
            // 获取当前节点对应的历史任务列表,如果没有则返回空列表避免空指针
 | 
			
		||||
            List<FlowHisTask> taskList = groupedByNode.get(nodeJson.getNodeCode());
 | 
			
		||||
            if (CollUtil.isEmpty(taskList)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            // 处理当前节点的扩展信息,包括构建审批人提示内容等
 | 
			
		||||
            this.processNodeExtInfo(nodeJson, taskList, userMap, dictType);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 初始化流程图提示信息
 | 
			
		||||
     *
 | 
			
		||||
     * @param defJson 流程定义json对象
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void initPromptContent(DefJson defJson) {
 | 
			
		||||
        defJson.setTopText("流程名称: " + defJson.getFlowName());
 | 
			
		||||
        defJson.getNodeList().forEach(nodeJson -> {
 | 
			
		||||
            nodeJson.setPromptContent(
 | 
			
		||||
                new PromptContent()
 | 
			
		||||
                    // 提示信息
 | 
			
		||||
                    .setInfo(
 | 
			
		||||
                        CollUtil.newArrayList(
 | 
			
		||||
                            new PromptContent.InfoItem()
 | 
			
		||||
                                .setPrefix("任务名称: ")
 | 
			
		||||
                                .setContent(nodeJson.getNodeName())
 | 
			
		||||
                                .setContentStyle(Map.of(
 | 
			
		||||
                                    "border", "1px solid #d1e9ff",
 | 
			
		||||
                                    "backgroundColor", "#e8f4ff",
 | 
			
		||||
                                    "padding", "4px 8px",
 | 
			
		||||
                                    "borderRadius", "4px"
 | 
			
		||||
                                ))
 | 
			
		||||
                                .setRowStyle(Map.of(
 | 
			
		||||
                                    "fontWeight", "bold",
 | 
			
		||||
                                    "margin", "0 0 6px 0",
 | 
			
		||||
                                    "padding", "0 0 8px 0",
 | 
			
		||||
                                    "borderBottom", "1px solid #ccc"
 | 
			
		||||
                                ))
 | 
			
		||||
                        )
 | 
			
		||||
                    )
 | 
			
		||||
                    // 弹窗样式
 | 
			
		||||
                    .setDialogStyle(MapUtil.mergeAll(
 | 
			
		||||
                        "position", "absolute",
 | 
			
		||||
                        "backgroundColor", "#fff",
 | 
			
		||||
                        "border", "1px solid #ccc",
 | 
			
		||||
                        "borderRadius", "4px",
 | 
			
		||||
                        "boxShadow", "0 2px 8px rgba(0, 0, 0, 0.15)",
 | 
			
		||||
                        "padding", "8px 12px",
 | 
			
		||||
                        "fontSize", "14px",
 | 
			
		||||
                        "zIndex", "1000",
 | 
			
		||||
                        "maxWidth", "500px",
 | 
			
		||||
                        "overflowY", "visible",
 | 
			
		||||
                        "overflowX", "hidden",
 | 
			
		||||
                        "color", "#333",
 | 
			
		||||
                        "pointerEvents", "auto",
 | 
			
		||||
                        "scrollbarWidth", "thin"
 | 
			
		||||
                    ))
 | 
			
		||||
            );
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 处理节点的扩展信息,构建用于流程图悬浮提示的内容
 | 
			
		||||
     *
 | 
			
		||||
     * @param nodeJson 当前节点对象
 | 
			
		||||
     * @param taskList 当前节点对应的历史审批任务列表
 | 
			
		||||
     */
 | 
			
		||||
    private void processNodeExtInfo(NodeJson nodeJson, List<FlowHisTask> taskList, Map<Long, UserDTO> userMap, Map<String, String> dictType) {
 | 
			
		||||
 | 
			
		||||
        // 获取节点提示内容对象中的 info 列表,用于追加提示项
 | 
			
		||||
        List<PromptContent.InfoItem> info = nodeJson.getPromptContent().getInfo();
 | 
			
		||||
 | 
			
		||||
        // 遍历所有任务记录,构建提示内容
 | 
			
		||||
        for (FlowHisTask task : taskList) {
 | 
			
		||||
            UserDTO userDTO = userMap.get(Convert.toLong(task.getApprover()));
 | 
			
		||||
            if (ObjectUtil.isEmpty(userDTO)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 查询用户所属部门名称
 | 
			
		||||
            String deptName = deptService.selectDeptNameByIds(Convert.toStr(userDTO.getDeptId()));
 | 
			
		||||
 | 
			
		||||
            // 添加标题项,如:👤 张三(市场部)
 | 
			
		||||
            info.add(new PromptContent.InfoItem()
 | 
			
		||||
                .setPrefix(StringUtils.format("👥 {}({})", userDTO.getNickName(), deptName))
 | 
			
		||||
                .setPrefixStyle(Map.of(
 | 
			
		||||
                    "fontWeight", "bold",
 | 
			
		||||
                    "fontSize", "15px",
 | 
			
		||||
                    "color", "#333"
 | 
			
		||||
                ))
 | 
			
		||||
                .setRowStyle(Map.of(
 | 
			
		||||
                    "margin", "8px 0",
 | 
			
		||||
                    "borderBottom", "1px dashed #ccc"
 | 
			
		||||
                ))
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // 添加具体信息项:账号、耗时、时间
 | 
			
		||||
            info.add(buildInfoItem("用户账号", userDTO.getUserName()));
 | 
			
		||||
            info.add(buildInfoItem("审批状态", dictType.get(task.getFlowStatus())));
 | 
			
		||||
            info.add(buildInfoItem("审批耗时", DateUtils.getTimeDifference(task.getUpdateTime(), task.getCreateTime())));
 | 
			
		||||
            info.add(buildInfoItem("办理时间", DateUtils.formatDateTime(task.getUpdateTime())));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 构建单条提示内容对象 InfoItem,用于悬浮窗显示(key: value)
 | 
			
		||||
     *
 | 
			
		||||
     * @param key   字段名(作为前缀)
 | 
			
		||||
     * @param value 字段值
 | 
			
		||||
     * @return 提示项对象
 | 
			
		||||
     */
 | 
			
		||||
    private PromptContent.InfoItem buildInfoItem(String key, String value) {
 | 
			
		||||
        return new PromptContent.InfoItem()
 | 
			
		||||
            // 前缀
 | 
			
		||||
            .setPrefix(key + ": ")
 | 
			
		||||
            // 前缀样式
 | 
			
		||||
            .setPrefixStyle(Map.of(
 | 
			
		||||
                "textAlign", "right",
 | 
			
		||||
                "color", "#444",
 | 
			
		||||
                "userSelect", "none",
 | 
			
		||||
                "display", "inline-block",
 | 
			
		||||
                "width", "100px",
 | 
			
		||||
                "paddingRight", "8px",
 | 
			
		||||
                "fontWeight", "500",
 | 
			
		||||
                "fontSize", "14px",
 | 
			
		||||
                "lineHeight", "24px",
 | 
			
		||||
                "verticalAlign", "middle"
 | 
			
		||||
            ))
 | 
			
		||||
            // 内容
 | 
			
		||||
            .setContent(value)
 | 
			
		||||
            // 内容样式
 | 
			
		||||
            .setContentStyle(Map.of(
 | 
			
		||||
                "backgroundColor", "#f7faff",
 | 
			
		||||
                "color", "#005cbf",
 | 
			
		||||
                "padding", "4px 8px",
 | 
			
		||||
                "fontSize", "14px",
 | 
			
		||||
                "borderRadius", "4px",
 | 
			
		||||
                "whiteSpace", "normal",
 | 
			
		||||
                "border", "1px solid #d0e5ff",
 | 
			
		||||
                "userSelect", "text",
 | 
			
		||||
                "lineHeight", "20px"
 | 
			
		||||
            ))
 | 
			
		||||
            // 行样式
 | 
			
		||||
            .setRowStyle(Map.of(
 | 
			
		||||
                "color", "#222",
 | 
			
		||||
                "alignItems", "center",
 | 
			
		||||
                "display", "flex",
 | 
			
		||||
                "marginBottom", "6px",
 | 
			
		||||
                "fontWeight", "400",
 | 
			
		||||
                "fontSize", "14px"
 | 
			
		||||
            ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据流程实例ID获取历史任务列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param instanceId 流程实例ID
 | 
			
		||||
     * @return 历史任务列表
 | 
			
		||||
     */
 | 
			
		||||
    public List<FlowHisTask> getHisTaskGroupedByNode(Long instanceId) {
 | 
			
		||||
        LambdaQueryWrapper<FlowHisTask> wrapper = Wrappers.lambdaQuery();
 | 
			
		||||
        wrapper.eq(FlowHisTask::getInstanceId, instanceId)
 | 
			
		||||
            .eq(FlowHisTask::getNodeType, NodeType.BETWEEN.getKey())
 | 
			
		||||
            .orderByDesc(FlowHisTask::getCreateTime, FlowHisTask::getUpdateTime);
 | 
			
		||||
        return flowHisTaskMapper.selectList(wrapper);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -11,26 +11,20 @@ import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.dromara.common.mail.utils.MailUtils;
 | 
			
		||||
import org.dromara.common.sse.dto.SseMessageDto;
 | 
			
		||||
import org.dromara.common.sse.utils.SseMessageUtils;
 | 
			
		||||
import org.dromara.warm.flow.core.FlowEngine;
 | 
			
		||||
import org.dromara.warm.flow.core.entity.Instance;
 | 
			
		||||
import org.dromara.warm.flow.core.entity.Node;
 | 
			
		||||
import org.dromara.warm.flow.core.entity.Task;
 | 
			
		||||
import org.dromara.warm.flow.core.entity.User;
 | 
			
		||||
import org.dromara.warm.flow.core.enums.SkipType;
 | 
			
		||||
import org.dromara.warm.flow.core.service.NodeService;
 | 
			
		||||
import org.dromara.warm.flow.core.service.UserService;
 | 
			
		||||
import org.dromara.warm.flow.core.utils.MapUtil;
 | 
			
		||||
import org.dromara.warm.flow.orm.entity.FlowTask;
 | 
			
		||||
import org.dromara.warm.flow.orm.entity.FlowUser;
 | 
			
		||||
import org.dromara.workflow.common.ConditionalOnEnable;
 | 
			
		||||
import org.dromara.workflow.common.enums.MessageTypeEnum;
 | 
			
		||||
import org.dromara.workflow.common.enums.TaskAssigneeType;
 | 
			
		||||
import org.dromara.workflow.service.IFlwCommonService;
 | 
			
		||||
import org.dromara.workflow.service.IFlwTaskAssigneeService;
 | 
			
		||||
import org.dromara.workflow.service.IFlwTaskService;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -44,85 +38,27 @@ import java.util.stream.Collectors;
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@Service
 | 
			
		||||
public class FlwCommonServiceImpl implements IFlwCommonService {
 | 
			
		||||
    private final UserService userService;
 | 
			
		||||
    private final NodeService nodeService;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取工作流用户service
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public UserService getFlowUserService() {
 | 
			
		||||
        return userService;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 构建工作流用户
 | 
			
		||||
     *
 | 
			
		||||
     * @param userList 办理用户
 | 
			
		||||
     * @param taskId   任务ID
 | 
			
		||||
     * @param permissionList 办理用户
 | 
			
		||||
     * @return 用户
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Set<User> buildUser(List<User> userList, Long taskId) {
 | 
			
		||||
        if (CollUtil.isEmpty(userList)) {
 | 
			
		||||
            return Set.of();
 | 
			
		||||
    public List<String> buildUser(List<String> permissionList) {
 | 
			
		||||
        if (CollUtil.isEmpty(permissionList)) {
 | 
			
		||||
            return List.of();
 | 
			
		||||
        }
 | 
			
		||||
        Set<User> list = new HashSet<>();
 | 
			
		||||
        Set<String> processedBySet = new HashSet<>();
 | 
			
		||||
        IFlwTaskAssigneeService taskAssigneeService = SpringUtils.getBean(IFlwTaskAssigneeService.class);
 | 
			
		||||
        Map<String, List<User>> userListMap = StreamUtils.groupByKey(userList, User::getType);
 | 
			
		||||
        for (Map.Entry<String, List<User>> entry : userListMap.entrySet()) {
 | 
			
		||||
            List<String> processedBys = entry.getValue().stream()
 | 
			
		||||
                .map(User::getProcessedBy)
 | 
			
		||||
                .filter(StringUtils::isNotBlank)
 | 
			
		||||
                .distinct()
 | 
			
		||||
                .collect(Collectors.toList());
 | 
			
		||||
        String processedBys = CollUtil.join(permissionList,  StringUtils.SEPARATOR);
 | 
			
		||||
        // 根据 processedBy 前缀判断处理人类型,分别获取用户列表
 | 
			
		||||
        List<UserDTO> users = taskAssigneeService.fetchUsersByStorageIds(processedBys);
 | 
			
		||||
            // 转换为 FlowUser 并添加到结果集合
 | 
			
		||||
            if (CollUtil.isNotEmpty(users)) {
 | 
			
		||||
                users.forEach(dto -> {
 | 
			
		||||
                    String processedBy = String.valueOf(dto.getUserId());
 | 
			
		||||
                    if (!processedBySet.contains(processedBy)) {
 | 
			
		||||
                        FlowUser flowUser = new FlowUser();
 | 
			
		||||
                        flowUser.setType(entry.getKey());
 | 
			
		||||
                        flowUser.setProcessedBy(processedBy);
 | 
			
		||||
                        flowUser.setAssociated(taskId);
 | 
			
		||||
                        list.add(flowUser);
 | 
			
		||||
                        processedBySet.add(processedBy);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return list;
 | 
			
		||||
 | 
			
		||||
        return StreamUtils.toList(users, userDTO -> String.valueOf(userDTO.getUserId()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 构建工作流用户
 | 
			
		||||
     *
 | 
			
		||||
     * @param userIdList 办理用户
 | 
			
		||||
     * @param taskId     任务ID
 | 
			
		||||
     * @return 用户
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Set<User> buildFlowUser(List<String> userIdList, Long taskId) {
 | 
			
		||||
        if (CollUtil.isEmpty(userIdList)) {
 | 
			
		||||
            return Set.of();
 | 
			
		||||
        }
 | 
			
		||||
        Set<User> list = new HashSet<>();
 | 
			
		||||
        Set<String> processedBySet = new HashSet<>();
 | 
			
		||||
        for (String userId : userIdList) {
 | 
			
		||||
            if (!processedBySet.contains(userId)) {
 | 
			
		||||
                FlowUser flowUser = new FlowUser();
 | 
			
		||||
                flowUser.setType(TaskAssigneeType.APPROVER.getCode());
 | 
			
		||||
                flowUser.setProcessedBy(String.valueOf(userId));
 | 
			
		||||
                flowUser.setAssociated(taskId);
 | 
			
		||||
                list.add(flowUser);
 | 
			
		||||
                processedBySet.add(String.valueOf(userId));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return list;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 发送消息
 | 
			
		||||
@@ -183,14 +119,4 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
 | 
			
		||||
        Node nextNode = nodeService.getNextNode(definitionId, startNode.getNodeCode(), null, SkipType.PASS.getKey());
 | 
			
		||||
        return nextNode.getNodeCode();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void mergeVariable(Instance instance, Map<String, Object> variable) {
 | 
			
		||||
        if (MapUtil.isNotEmpty(variable)) {
 | 
			
		||||
            String variableStr = instance.getVariable();
 | 
			
		||||
            Map<String, Object> deserialize = FlowEngine.jsonConvert.strToMap(variableStr);
 | 
			
		||||
            deserialize.putAll(variable);
 | 
			
		||||
            instance.setVariable(FlowEngine.jsonConvert.objToStr(deserialize));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -78,10 +78,8 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
 | 
			
		||||
        LambdaQueryWrapper<FlowDefinition> wrapper = buildQueryWrapper(flowDefinition);
 | 
			
		||||
        wrapper.eq(FlowDefinition::getIsPublish, PublishStatus.PUBLISHED.getKey());
 | 
			
		||||
        Page<FlowDefinition> page = flowDefinitionMapper.selectPage(pageQuery.build(), wrapper);
 | 
			
		||||
        TableDataInfo<FlowDefinitionVo> build = TableDataInfo.build();
 | 
			
		||||
        build.setRows(BeanUtil.copyToList(page.getRecords(), FlowDefinitionVo.class));
 | 
			
		||||
        build.setTotal(page.getTotal());
 | 
			
		||||
        return build;
 | 
			
		||||
        List<FlowDefinitionVo> list = BeanUtil.copyToList(page.getRecords(), FlowDefinitionVo.class);
 | 
			
		||||
        return new TableDataInfo<>(list, page.getTotal());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -96,10 +94,8 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
 | 
			
		||||
        LambdaQueryWrapper<FlowDefinition> wrapper = buildQueryWrapper(flowDefinition);
 | 
			
		||||
        wrapper.in(FlowDefinition::getIsPublish, Arrays.asList(PublishStatus.UNPUBLISHED.getKey(), PublishStatus.EXPIRED.getKey()));
 | 
			
		||||
        Page<FlowDefinition> page = flowDefinitionMapper.selectPage(pageQuery.build(), wrapper);
 | 
			
		||||
        TableDataInfo<FlowDefinitionVo> build = TableDataInfo.build();
 | 
			
		||||
        build.setRows(BeanUtil.copyToList(page.getRecords(), FlowDefinitionVo.class));
 | 
			
		||||
        build.setTotal(page.getTotal());
 | 
			
		||||
        return build;
 | 
			
		||||
        List<FlowDefinitionVo> list = BeanUtil.copyToList(page.getRecords(), FlowDefinitionVo.class);
 | 
			
		||||
        return new TableDataInfo<>(list, page.getTotal());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private LambdaQueryWrapper<FlowDefinition> buildQueryWrapper(FlowDefinition flowDefinition) {
 | 
			
		||||
 
 | 
			
		||||
@@ -25,8 +25,6 @@ import org.dromara.warm.flow.core.entity.Definition;
 | 
			
		||||
import org.dromara.warm.flow.core.entity.Instance;
 | 
			
		||||
import org.dromara.warm.flow.core.entity.Task;
 | 
			
		||||
import org.dromara.warm.flow.core.enums.NodeType;
 | 
			
		||||
import org.dromara.warm.flow.core.enums.SkipType;
 | 
			
		||||
import org.dromara.warm.flow.core.service.ChartService;
 | 
			
		||||
import org.dromara.warm.flow.core.service.DefService;
 | 
			
		||||
import org.dromara.warm.flow.core.service.InsService;
 | 
			
		||||
import org.dromara.warm.flow.core.service.TaskService;
 | 
			
		||||
@@ -45,7 +43,6 @@ import org.dromara.workflow.domain.vo.FlowInstanceVo;
 | 
			
		||||
import org.dromara.workflow.handler.FlowProcessEventHandler;
 | 
			
		||||
import org.dromara.workflow.mapper.FlwCategoryMapper;
 | 
			
		||||
import org.dromara.workflow.mapper.FlwInstanceMapper;
 | 
			
		||||
import org.dromara.workflow.service.IFlwCommonService;
 | 
			
		||||
import org.dromara.workflow.service.IFlwInstanceService;
 | 
			
		||||
import org.dromara.workflow.service.IFlwTaskService;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
@@ -67,7 +64,6 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
 | 
			
		||||
 | 
			
		||||
    private final InsService insService;
 | 
			
		||||
    private final DefService defService;
 | 
			
		||||
    private final ChartService chartService;
 | 
			
		||||
    private final TaskService taskService;
 | 
			
		||||
    private final FlowHisTaskMapper flowHisTaskMapper;
 | 
			
		||||
    private final FlowInstanceMapper flowInstanceMapper;
 | 
			
		||||
@@ -75,7 +71,6 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
 | 
			
		||||
    private final IFlwTaskService flwTaskService;
 | 
			
		||||
    private final FlwInstanceMapper flwInstanceMapper;
 | 
			
		||||
    private final FlwCategoryMapper flwCategoryMapper;
 | 
			
		||||
    private final IFlwCommonService flwCommonService;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 分页查询正在运行的流程实例
 | 
			
		||||
@@ -248,7 +243,6 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
 | 
			
		||||
            BusinessStatusEnum.checkCancelStatus(instance.getFlowStatus());
 | 
			
		||||
            FlowParams flowParams = FlowParams.build()
 | 
			
		||||
                .message(message)
 | 
			
		||||
                .skipType(SkipType.PASS.getKey())
 | 
			
		||||
                .flowStatus(BusinessStatusEnum.CANCEL.getStatus())
 | 
			
		||||
                .hisStatus(BusinessStatusEnum.CANCEL.getStatus())
 | 
			
		||||
                .handler(userIdStr)
 | 
			
		||||
@@ -281,7 +275,7 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
 | 
			
		||||
     * @param businessId 业务id
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Map<String, Object> flowImage(String businessId) {
 | 
			
		||||
    public Map<String, Object> flowHisTaskList(String businessId) {
 | 
			
		||||
        FlowInstance flowInstance = this.selectInstByBusinessId(businessId);
 | 
			
		||||
        if (ObjectUtil.isNull(flowInstance)) {
 | 
			
		||||
            throw new ServiceException(ExceptionCons.NOT_FOUNT_INSTANCE);
 | 
			
		||||
@@ -310,15 +304,14 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
 | 
			
		||||
        }
 | 
			
		||||
        //历史任务
 | 
			
		||||
        LambdaQueryWrapper<FlowHisTask> wrapper = Wrappers.lambdaQuery();
 | 
			
		||||
        wrapper.eq(FlowHisTask::getInstanceId, instanceId);
 | 
			
		||||
        wrapper.eq(FlowHisTask::getNodeType, NodeType.BETWEEN.getKey());
 | 
			
		||||
        wrapper.orderByDesc(FlowHisTask::getCreateTime).orderByDesc(FlowHisTask::getUpdateTime);
 | 
			
		||||
        wrapper.eq(FlowHisTask::getInstanceId, instanceId)
 | 
			
		||||
            .eq(FlowHisTask::getNodeType, NodeType.BETWEEN.getKey())
 | 
			
		||||
            .orderByDesc(FlowHisTask::getCreateTime, FlowHisTask::getUpdateTime);
 | 
			
		||||
        List<FlowHisTask> flowHisTasks = flowHisTaskMapper.selectList(wrapper);
 | 
			
		||||
        if (CollUtil.isNotEmpty(flowHisTasks)) {
 | 
			
		||||
            list.addAll(BeanUtil.copyToList(flowHisTasks, FlowHisTaskVo.class));
 | 
			
		||||
        }
 | 
			
		||||
        String flowChart = chartService.chartIns(instanceId);
 | 
			
		||||
        return Map.of("list", list, "image", flowChart);
 | 
			
		||||
        return Map.of("list", list, "instanceId", instanceId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -360,7 +353,7 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
 | 
			
		||||
    public void setVariable(Long instanceId, Map<String, Object> variable) {
 | 
			
		||||
        Instance instance = insService.getById(instanceId);
 | 
			
		||||
        if (instance != null) {
 | 
			
		||||
            flwCommonService.mergeVariable(instance, variable);
 | 
			
		||||
            taskService.mergeVariable(instance, variable);
 | 
			
		||||
            insService.updateById(instance);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -422,15 +415,12 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
 | 
			
		||||
            if (instance != null) {
 | 
			
		||||
                BusinessStatusEnum.checkInvalidStatus(instance.getFlowStatus());
 | 
			
		||||
            }
 | 
			
		||||
            List<FlowTask> flowTaskList = flwTaskService.selectByInstId(bo.getId());
 | 
			
		||||
            for (FlowTask flowTask : flowTaskList) {
 | 
			
		||||
            FlowParams flowParams = FlowParams.build()
 | 
			
		||||
                .message(bo.getComment())
 | 
			
		||||
                .flowStatus(BusinessStatusEnum.INVALID.getStatus())
 | 
			
		||||
                .hisStatus(TaskStatusEnum.INVALID.getStatus())
 | 
			
		||||
                .ignore(true);
 | 
			
		||||
                taskService.termination(flowTask.getId(), flowParams);
 | 
			
		||||
            }
 | 
			
		||||
            taskService.terminationByInsId(bo.getId(), flowParams);
 | 
			
		||||
            return true;
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.error(e.getMessage(), e);
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user