From f33d3f07b8bc1efdd4a455eb5f39c67eca9d8e7a Mon Sep 17 00:00:00 2001 From: chenbowen Date: Thu, 25 Sep 2025 12:01:19 +0800 Subject: [PATCH 1/2] =?UTF-8?q?1.=20xxl-job=20=E8=AE=BE=E7=BD=AE=E8=99=9A?= =?UTF-8?q?=E6=8B=9F=E7=94=A8=E6=88=B7=200=20=E7=99=BB=E5=BD=95=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=202.=20Access-Control-Expose-Headers=20=E5=85=81?= =?UTF-8?q?=E8=AE=B8=E6=9A=B4=E9=9C=B2=20content-disposition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../zt-spring-boot-starter-job/pom.xml | 4 + .../quartz/config/XxlJobProperties.java | 38 ++++ .../config/ZtXxlJobAutoConfiguration.java | 17 +- .../XxlJobSystemAuthenticationAspect.java | 119 ++++++++++ .../core/handler/DefaultDBFieldHandler.java | 13 +- .../web/config/ZtWebAutoConfiguration.java | 1 + .../plat/gateway/filter/cors/CorsFilter.java | 1 + .../admin/test/CorsTestController.java | 140 ++++++++++++ .../src/main/resources/static/cors-test.html | 206 ++++++++++++++++++ 9 files changed, 536 insertions(+), 3 deletions(-) create mode 100644 zt-framework/zt-spring-boot-starter-job/src/main/java/com/zt/plat/framework/quartz/core/handler/XxlJobSystemAuthenticationAspect.java create mode 100644 zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/controller/admin/test/CorsTestController.java create mode 100644 zt-module-template/zt-module-template-server/src/main/resources/static/cors-test.html diff --git a/zt-framework/zt-spring-boot-starter-job/pom.xml b/zt-framework/zt-spring-boot-starter-job/pom.xml index a3eab15c..380b7870 100644 --- a/zt-framework/zt-spring-boot-starter-job/pom.xml +++ b/zt-framework/zt-spring-boot-starter-job/pom.xml @@ -44,6 +44,10 @@ jakarta.validation jakarta.validation-api + + com.zt.plat + zt-spring-boot-starter-security + diff --git a/zt-framework/zt-spring-boot-starter-job/src/main/java/com/zt/plat/framework/quartz/config/XxlJobProperties.java b/zt-framework/zt-spring-boot-starter-job/src/main/java/com/zt/plat/framework/quartz/config/XxlJobProperties.java index 01cfcc21..19aef4dd 100644 --- a/zt-framework/zt-spring-boot-starter-job/src/main/java/com/zt/plat/framework/quartz/config/XxlJobProperties.java +++ b/zt-framework/zt-spring-boot-starter-job/src/main/java/com/zt/plat/framework/quartz/config/XxlJobProperties.java @@ -35,6 +35,11 @@ public class XxlJobProperties { @NotNull(message = "执行器配置不能为空") private ExecutorProperties executor; + /** + * 系统用户配置 + */ + private SystemUserProperties systemUser = new SystemUserProperties(); + /** * XXL-Job 调度器配置类 */ @@ -96,4 +101,37 @@ public class XxlJobProperties { } + /** + * XXL-Job 系统用户配置类 + */ + @Data + public static class SystemUserProperties { + + /** + * 系统用户 ID + */ + private Long userId = 0L; + + /** + * 系统用户昵称 + */ + private String nickname = "job"; + + /** + * 系统租户 ID + */ + private Long tenantId = 1L; + + /** + * 系统公司 ID(可选) + */ + private Long companyId; + + /** + * 系统部门 ID(可选) + */ + private Long deptId; + + } + } diff --git a/zt-framework/zt-spring-boot-starter-job/src/main/java/com/zt/plat/framework/quartz/config/ZtXxlJobAutoConfiguration.java b/zt-framework/zt-spring-boot-starter-job/src/main/java/com/zt/plat/framework/quartz/config/ZtXxlJobAutoConfiguration.java index c29c9634..ef5e66d5 100644 --- a/zt-framework/zt-spring-boot-starter-job/src/main/java/com/zt/plat/framework/quartz/config/ZtXxlJobAutoConfiguration.java +++ b/zt-framework/zt-spring-boot-starter-job/src/main/java/com/zt/plat/framework/quartz/config/ZtXxlJobAutoConfiguration.java @@ -1,5 +1,6 @@ package com.zt.plat.framework.quartz.config; +import com.zt.plat.framework.quartz.core.handler.XxlJobSystemAuthenticationAspect; import com.xxl.job.core.executor.XxlJobExecutor; import com.xxl.job.core.executor.impl.XxlJobSpringExecutor; import lombok.extern.slf4j.Slf4j; @@ -9,7 +10,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.scheduling.annotation.EnableScheduling; /** @@ -22,6 +23,7 @@ import org.springframework.scheduling.annotation.EnableScheduling; @ConditionalOnProperty(prefix = "xxl.job", name = "enabled", havingValue = "true", matchIfMissing = true) @EnableConfigurationProperties({XxlJobProperties.class}) @EnableScheduling // 开启 Spring 自带的定时任务 +@EnableAspectJAutoProxy // 开启 AOP @Slf4j public class ZtXxlJobAutoConfiguration { @@ -44,4 +46,17 @@ public class ZtXxlJobAutoConfiguration { return xxlJobExecutor; } + /** + * 配置 XXL-Job 系统认证切面 + * + * 为 @XxlJob 注解的方法提供系统用户认证上下文 + */ + @Bean + @ConditionalOnMissingBean + public XxlJobSystemAuthenticationAspect xxlJobSystemAuthenticationAspect(XxlJobProperties properties) { + log.info("[ZtXxlJobAutoConfiguration][注册 XXL-Job 系统认证切面] systemUserId=[{}], systemTenantId=[{}]", + properties.getSystemUser().getUserId(), properties.getSystemUser().getTenantId()); + return new XxlJobSystemAuthenticationAspect(properties.getSystemUser()); + } + } diff --git a/zt-framework/zt-spring-boot-starter-job/src/main/java/com/zt/plat/framework/quartz/core/handler/XxlJobSystemAuthenticationAspect.java b/zt-framework/zt-spring-boot-starter-job/src/main/java/com/zt/plat/framework/quartz/core/handler/XxlJobSystemAuthenticationAspect.java new file mode 100644 index 00000000..061a3f13 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-job/src/main/java/com/zt/plat/framework/quartz/core/handler/XxlJobSystemAuthenticationAspect.java @@ -0,0 +1,119 @@ +package com.zt.plat.framework.quartz.core.handler; + +import com.zt.plat.framework.common.enums.UserTypeEnum; +import com.zt.plat.framework.quartz.config.XxlJobProperties; +import com.zt.plat.framework.security.core.LoginUser; +import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils; +import com.zt.plat.framework.web.core.util.WebFrameworkUtils; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.core.annotation.Order; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * XXL-Job 系统认证切面 + * + * 为 @XxlJob 注解的方法提供系统用户认证上下文, + * 确保 Job 方法执行时能够获取到用户信息 + * + * @author ZT + */ +@Aspect +@RequiredArgsConstructor +@Slf4j +@Order(-100) // 设置较高的优先级,确保在其他切面之前执行 +public class XxlJobSystemAuthenticationAspect { + + private final XxlJobProperties.SystemUserProperties systemUserConfig; + + @Around("@annotation(com.xxl.job.core.handler.annotation.XxlJob)") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + // 获取当前登录用户 + LoginUser currentUser = SecurityFrameworkUtils.getLoginUser(); + try { + // 如果当前没有登录用户,则设置系统用户上下文 + if (currentUser == null) { + LoginUser systemUser = createSystemUser(); + setLoginUserForJob(systemUser); + log.debug("[XxlJobSystemAuthenticationAspect][XXL-Job 方法执行,设置系统用户上下文] method=[{}], userId=[{}]", + joinPoint.getSignature().toShortString(), systemUser.getId()); + } else { + log.debug("[XxlJobSystemAuthenticationAspect][XXL-Job 方法执行,已存在用户上下文] method=[{}], userId=[{}]", + joinPoint.getSignature().toShortString(), currentUser.getId()); + } + + // 执行目标方法 + return joinPoint.proceed(); + + } + catch (Exception e) { + log.error("[XxlJobSystemAuthenticationAspect][XXL-Job 方法执行异常] method=[{}], error=[{}]", + joinPoint.getSignature().toShortString(), e.getMessage(), e); + throw e; + } + finally { + // 如果是我们设置的系统用户,执行完毕后清理上下文 + if (currentUser == null) { + clearLoginUserForJob(); + log.debug("[XxlJobSystemAuthenticationAspect][XXL-Job 方法执行完毕,清理系统用户上下文] method=[{}]", + joinPoint.getSignature().toShortString()); + } + } + } + + /** + * 创建 XXL-Job 系统用户 + */ + private LoginUser createSystemUser() { + LoginUser systemUser = new LoginUser(); + systemUser.setId(systemUserConfig.getUserId()); + systemUser.setUserType(UserTypeEnum.ADMIN.getValue()); + systemUser.setTenantId(systemUserConfig.getTenantId()); + systemUser.setVisitTenantId(systemUserConfig.getTenantId()); + systemUser.setExpiresTime(LocalDateTime.now().plusDays(1)); + + // 设置用户信息 + Map info = new HashMap<>(); + info.put(LoginUser.INFO_KEY_NICKNAME, systemUserConfig.getNickname()); + info.put(LoginUser.INFO_KEY_TENANT_ID, String.valueOf(systemUserConfig.getTenantId())); + + systemUser.setInfo(info); + + return systemUser; + } + + /** + * 为 Job 设置登录用户到 Spring Security 上下文和 Web 上下文 + */ + private void setLoginUserForJob(LoginUser loginUser) { + // 1. 设置到 Spring Security 上下文 + Authentication authentication = new UsernamePasswordAuthenticationToken( + loginUser, null, Collections.emptyList()); + SecurityContextHolder.getContext().setAuthentication(authentication); + + // 2. 设置到 Web 请求上下文,供 DefaultDBFieldHandler 使用 + HttpServletRequest request = WebFrameworkUtils.getRequest(); + if (request != null) { + WebFrameworkUtils.setLoginUserId(request, loginUser.getId()); + WebFrameworkUtils.setLoginUserType(request, loginUser.getUserType()); + } + } + + /** + * 清理 Job 的登录用户上下文 + */ + private void clearLoginUserForJob() { + SecurityContextHolder.getContext().setAuthentication(null); + } +} \ No newline at end of file diff --git a/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/handler/DefaultDBFieldHandler.java b/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/handler/DefaultDBFieldHandler.java index 174eaca3..6a1acf08 100644 --- a/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/handler/DefaultDBFieldHandler.java +++ b/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/handler/DefaultDBFieldHandler.java @@ -47,7 +47,7 @@ public class DefaultDBFieldHandler implements MetaObjectHandler { baseDO.setUpdateTime(current); } - Long userId = WebFrameworkUtils.getLoginUserId(); + Long userId = getUserId(); String userNickname = SecurityFrameworkUtils.getLoginUserNickname(); // 当前登录用户不为空,创建人为空,则当前登录用户为创建人 if (Objects.nonNull(userId) && Objects.isNull(baseDO.getCreator())) { @@ -81,7 +81,7 @@ public class DefaultDBFieldHandler implements MetaObjectHandler { // 当前登录用户不为空,更新人为空,则当前登录用户为更新人 Object modifier = getFieldValByName("updater", metaObject); - Long userId = WebFrameworkUtils.getLoginUserId(); + Long userId = getUserId(); String userNickname = SecurityFrameworkUtils.getLoginUserNickname(); if (Objects.nonNull(userId) && Objects.isNull(modifier)) { setFieldValByName("updater", userId.toString(), metaObject); @@ -96,6 +96,15 @@ public class DefaultDBFieldHandler implements MetaObjectHandler { } } + private static Long getUserId() { + Long userId = WebFrameworkUtils.getLoginUserId(); + if (userId == null) { + // 如果不是 http 请求发起的操作,获取不到用户,从认证中获取 + userId = SecurityFrameworkUtils.getLoginUserId(); + } + return userId; + } + private void autoFillUserNames(BusinessBaseDO businessBaseDO) { String userNickname = SecurityFrameworkUtils.getLoginUserNickname(); if (Objects.nonNull(userNickname)) { diff --git a/zt-framework/zt-spring-boot-starter-web/src/main/java/com/zt/plat/framework/web/config/ZtWebAutoConfiguration.java b/zt-framework/zt-spring-boot-starter-web/src/main/java/com/zt/plat/framework/web/config/ZtWebAutoConfiguration.java index 8033681e..682f6160 100644 --- a/zt-framework/zt-spring-boot-starter-web/src/main/java/com/zt/plat/framework/web/config/ZtWebAutoConfiguration.java +++ b/zt-framework/zt-spring-boot-starter-web/src/main/java/com/zt/plat/framework/web/config/ZtWebAutoConfiguration.java @@ -88,6 +88,7 @@ public class ZtWebAutoConfiguration implements WebMvcConfigurer { config.addAllowedOriginPattern("*"); // 设置访问源地址 config.addAllowedHeader("*"); // 设置访问源请求头 config.addAllowedMethod("*"); // 设置访问源请求方法 + config.addExposedHeader("content-disposition"); // 暴露 content-disposition 头,用于文件下载 // 创建 UrlBasedCorsConfigurationSource 对象 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); // 对接口配置跨域设置 diff --git a/zt-gateway/src/main/java/com/zt/plat/gateway/filter/cors/CorsFilter.java b/zt-gateway/src/main/java/com/zt/plat/gateway/filter/cors/CorsFilter.java index 547b4522..7cd7af21 100644 --- a/zt-gateway/src/main/java/com/zt/plat/gateway/filter/cors/CorsFilter.java +++ b/zt-gateway/src/main/java/com/zt/plat/gateway/filter/cors/CorsFilter.java @@ -37,6 +37,7 @@ public class CorsFilter implements WebFilter { headers.add("Access-Control-Allow-Origin", ALL); headers.add("Access-Control-Allow-Methods", ALL); headers.add("Access-Control-Allow-Headers", ALL); + headers.add("Access-Control-Expose-Headers", "content-disposition"); // 暴露 content-disposition 头,用于文件下载 headers.add("Access-Control-Max-Age", MAX_AGE); if (request.getMethod() == HttpMethod.OPTIONS) { response.setStatusCode(HttpStatus.OK); diff --git a/zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/controller/admin/test/CorsTestController.java b/zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/controller/admin/test/CorsTestController.java new file mode 100644 index 00000000..c48e75dd --- /dev/null +++ b/zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/controller/admin/test/CorsTestController.java @@ -0,0 +1,140 @@ +package com.zt.plat.module.template.controller.admin.test; + +import com.zt.plat.framework.common.pojo.CommonResult; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.nio.charset.StandardCharsets; + +/** + * CORS 测试控制器 + * + * @author ZT + */ +@Tag(name = "管理后台 - CORS 测试") +@RestController +@RequestMapping("/template/test") +@Slf4j +public class CorsTestController { + + @GetMapping("/download") + @Operation(summary = "测试文件下载响应头", description = "测试 content-disposition 响应头是否能被前端获取") + public ResponseEntity testDownload() { + log.info("[testDownload] 测试 content-disposition 响应头"); + + // 创建响应头 + HttpHeaders headers = new HttpHeaders(); + + // 设置 content-disposition 头,包含文件名 + String filename = "测试文件.txt"; + String encodedFilename = new String(filename.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1); + headers.add("Content-Disposition", "attachment; filename*=UTF-8''" + + java.net.URLEncoder.encode(filename, StandardCharsets.UTF_8) + + "; filename=\"" + encodedFilename + "\""); + + // 设置其他常用的文件下载响应头 + headers.add("Content-Type", "application/octet-stream"); + headers.add("Cache-Control", "no-cache"); + + String responseBody = "这是一个测试文件的内容,用于验证 CORS 配置是否正确。\n" + + "如果前端能够获取到 content-disposition 头信息,说明配置成功。\n" + + "文件名应该是:" + filename; + + return ResponseEntity.ok() + .headers(headers) + .body(responseBody); + } + + @GetMapping("/cors-info") + @Operation(summary = "获取 CORS 配置信息", description = "返回当前 CORS 配置的相关信息") + public CommonResult getCorsInfo() { + log.info("[getCorsInfo] 获取 CORS 配置信息"); + + CorsInfo corsInfo = new CorsInfo(); + corsInfo.setExposedHeaders("content-disposition"); + corsInfo.setAllowedOrigins("*"); + corsInfo.setAllowedMethods("GET, POST, PUT, DELETE, OPTIONS"); + corsInfo.setAllowedHeaders("*"); + corsInfo.setAllowCredentials(true); + corsInfo.setMaxAge(3600L); + corsInfo.setMessage("CORS 配置已启用,content-disposition 头已暴露给前端"); + + return CommonResult.success(corsInfo); + } + + /** + * CORS 配置信息 + */ + public static class CorsInfo { + private String exposedHeaders; + private String allowedOrigins; + private String allowedMethods; + private String allowedHeaders; + private Boolean allowCredentials; + private Long maxAge; + private String message; + + // Getters and Setters + public String getExposedHeaders() { + return exposedHeaders; + } + + public void setExposedHeaders(String exposedHeaders) { + this.exposedHeaders = exposedHeaders; + } + + public String getAllowedOrigins() { + return allowedOrigins; + } + + public void setAllowedOrigins(String allowedOrigins) { + this.allowedOrigins = allowedOrigins; + } + + public String getAllowedMethods() { + return allowedMethods; + } + + public void setAllowedMethods(String allowedMethods) { + this.allowedMethods = allowedMethods; + } + + public String getAllowedHeaders() { + return allowedHeaders; + } + + public void setAllowedHeaders(String allowedHeaders) { + this.allowedHeaders = allowedHeaders; + } + + public Boolean getAllowCredentials() { + return allowCredentials; + } + + public void setAllowCredentials(Boolean allowCredentials) { + this.allowCredentials = allowCredentials; + } + + public Long getMaxAge() { + return maxAge; + } + + public void setMaxAge(Long maxAge) { + this.maxAge = maxAge; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + } +} \ No newline at end of file diff --git a/zt-module-template/zt-module-template-server/src/main/resources/static/cors-test.html b/zt-module-template/zt-module-template-server/src/main/resources/static/cors-test.html new file mode 100644 index 00000000..13255f4a --- /dev/null +++ b/zt-module-template/zt-module-template-server/src/main/resources/static/cors-test.html @@ -0,0 +1,206 @@ + + + + + + CORS content-disposition 测试 + + + +
+

CORS content-disposition 头测试

+ +
+ + +
+ +
+

测试1: 获取 CORS 配置信息

+

这个测试会调用接口获取当前的 CORS 配置信息

+ + +
+ +
+

测试2: 文件下载响应头测试

+

这个测试会检查是否能获取到 content-disposition 响应头

+ + + +
+ +
+

测试结果说明

+
    +
  • 成功: 能够获取到 content-disposition 头,说明 CORS 配置正确
  • +
  • 失败: 无法获取响应头,可能是 CORS 配置问题
  • +
  • 请确保服务已启动,并且地址配置正确
  • +
+
+
+ + + + \ No newline at end of file From 70c8d0d5f76698966ade3498dac4992d41f406fb Mon Sep 17 00:00:00 2001 From: qianshijiang <1965297290@qq.com> Date: Tue, 23 Sep 2025 15:11:09 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=B5=81=E7=A8=8B=E4=BB=BB=E5=8A=A1feign?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E6=B7=BB=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bpm/api/task/BpmProcessInstanceApi.java | 12 +++++++ .../api/task/dto/BpmTaskApproveReqDTO.java | 33 +++++++++++++++++++ .../bpm/api/task/dto/BpmTaskRejectReqDTO.java | 18 ++++++++++ .../api/task/BpmProcessInstanceApiImpl.java | 24 ++++++++++++++ 4 files changed, 87 insertions(+) create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskApproveReqDTO.java create mode 100644 zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskRejectReqDTO.java diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/BpmProcessInstanceApi.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/BpmProcessInstanceApi.java index 061e6774..baf8f52d 100644 --- a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/BpmProcessInstanceApi.java +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/BpmProcessInstanceApi.java @@ -2,6 +2,8 @@ package com.zt.plat.module.bpm.api.task; import com.zt.plat.framework.common.pojo.CommonResult; import com.zt.plat.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; +import com.zt.plat.module.bpm.api.task.dto.BpmTaskApproveReqDTO; +import com.zt.plat.module.bpm.api.task.dto.BpmTaskRejectReqDTO; import com.zt.plat.module.bpm.enums.ApiConstants; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -9,6 +11,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; @@ -24,4 +27,13 @@ public interface BpmProcessInstanceApi { CommonResult createProcessInstance(@RequestParam("userId") Long userId, @Valid @RequestBody BpmProcessInstanceCreateReqDTO reqDTO); + + @PutMapping(PREFIX + "/approveTask") + @Operation(summary = "通过任务") + CommonResult approveTask(@Valid @RequestBody BpmTaskApproveReqDTO reqVO); + + @PutMapping(PREFIX + "/rejectTask") + @Operation(summary = "不通过任务") + CommonResult rejectTask(@Valid @RequestBody BpmTaskRejectReqDTO reqVO); + } diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskApproveReqDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskApproveReqDTO.java new file mode 100644 index 00000000..705f930a --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskApproveReqDTO.java @@ -0,0 +1,33 @@ +package com.zt.plat.module.bpm.api.task.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Schema(description = "RPC 服务 - 通过流程任务的 Request DTO") +@Data +public class BpmTaskApproveReqDTO { + + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "任务编号不能为空") + private String id; + + @Schema(description = "审批意见", example = "不错不错!") + private String reason; + + @Schema(description = "签名", example = "https://www.iocoder.cn/sign.png") + private String signPicUrl; + + @Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED) + private Map variables; + + @Schema(description = "下一个节点审批人", example = "{nodeId:[1, 2]}") + private Map> nextAssignees; // 为什么是 Map,而不是 List 呢?因为下一个节点可能是多个,例如说并行网关的情况 + + // 新增任务变量实例,业务表单 + @Schema(description = "任务变量实例,业务表单", example = "{'formField1': 'value1', 'formField2': 'value2'}") + private Map taskVariables; +} diff --git a/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskRejectReqDTO.java b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskRejectReqDTO.java new file mode 100644 index 00000000..94f73bff --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-api/src/main/java/com/zt/plat/module/bpm/api/task/dto/BpmTaskRejectReqDTO.java @@ -0,0 +1,18 @@ +package com.zt.plat.module.bpm.api.task.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +@Schema(description = "RPC 服务 - 不通过流程任务的 Request DTO") +@Data +public class BpmTaskRejectReqDTO { + + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotEmpty(message = "任务编号不能为空") + private String id; + + @Schema(description = "审批意见", requiredMode = Schema.RequiredMode.REQUIRED, example = "不错不错!") + private String reason; + +} diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/task/BpmProcessInstanceApiImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/task/BpmProcessInstanceApiImpl.java index 52fa15f0..9b84efe8 100644 --- a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/task/BpmProcessInstanceApiImpl.java +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/api/task/BpmProcessInstanceApiImpl.java @@ -1,8 +1,16 @@ package com.zt.plat.module.bpm.api.task; import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.util.object.BeanUtils; import com.zt.plat.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; +import com.zt.plat.module.bpm.api.task.dto.BpmTaskApproveReqDTO; +import com.zt.plat.module.bpm.api.task.dto.BpmTaskRejectReqDTO; +import com.zt.plat.module.bpm.controller.admin.task.vo.task.BpmTaskApproveReqVO; +import com.zt.plat.module.bpm.controller.admin.task.vo.task.BpmTaskRejectReqVO; import com.zt.plat.module.bpm.service.task.BpmProcessInstanceService; +import com.zt.plat.module.bpm.service.task.BpmTaskService; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RestController; @@ -10,6 +18,7 @@ import jakarta.annotation.Resource; import jakarta.validation.Valid; import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.web.core.util.WebFrameworkUtils.getLoginUserId; /** * Flowable 流程实例 Api 实现类 @@ -24,9 +33,24 @@ public class BpmProcessInstanceApiImpl implements BpmProcessInstanceApi { @Resource private BpmProcessInstanceService processInstanceService; + @Resource + private BpmTaskService taskService; + @Override public CommonResult createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO reqDTO) { return success(processInstanceService.createProcessInstance(userId, reqDTO)); } + @Override + public CommonResult approveTask(BpmTaskApproveReqDTO reqVO) { + taskService.approveTask(getLoginUserId(), BeanUtils.toBean(reqVO, BpmTaskApproveReqVO.class)); + return success(true); + } + + @Override + public CommonResult rejectTask(BpmTaskRejectReqDTO reqVO) { + taskService.rejectTask(getLoginUserId(), BeanUtils.toBean(reqVO, BpmTaskRejectReqVO.class)); + return success(true); + } + }