From 5f55c90e1bed157a8d62d5f58a9645c462e86c51 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Thu, 29 Jan 2026 18:28:00 +0800 Subject: [PATCH 01/11] =?UTF-8?q?=E9=87=8D=E5=86=99=E6=89=8B=E5=8A=A8?= =?UTF-8?q?=E9=92=88=E5=AF=B9=E7=94=A8=E6=88=B7=E4=BB=A5=E5=8F=8A=E7=BB=84?= =?UTF-8?q?=E7=BB=87=E7=9A=84=E5=8D=95=E6=9D=A1=E5=90=8C=E6=AD=A5=E9=80=BB?= =?UTF-8?q?=E8=BE=91=20=E7=99=BB=E5=BD=95=E8=8E=B7=E5=8F=96=20token=20?= =?UTF-8?q?=E6=97=B6=E6=96=B0=E5=A2=9E=E5=AE=A2=E6=88=B7=E7=AB=AF=E7=BC=96?= =?UTF-8?q?=E5=8F=B7=E6=A0=87=E8=AF=86=20=E6=94=AF=E6=8C=81=E6=A0=B9?= =?UTF-8?q?=E6=8D=AE=E5=AE=A2=E6=88=B7=E7=AB=AF=E7=BC=96=E5=8F=B7=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E7=94=A8=E6=88=B7id=E6=89=B9=E9=87=8F=E5=A4=B1?= =?UTF-8?q?=E6=95=88=20token=20=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/system/service/auth/AdminAuthServiceImplTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/auth/AdminAuthServiceImplTest.java b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/auth/AdminAuthServiceImplTest.java index ec8e133c..aacbbe2c 100644 --- a/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/auth/AdminAuthServiceImplTest.java +++ b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/auth/AdminAuthServiceImplTest.java @@ -257,7 +257,7 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest { // 准备参数 String mobile = randomString(); String code = randomString(); - AuthSmsLoginReqVO reqVO = new AuthSmsLoginReqVO(mobile, code); + AuthSmsLoginReqVO reqVO = new AuthSmsLoginReqVO(mobile, code,null); // mock 方法(验证码) when(smsCodeApi.useSmsCode(argThat(smsCodeUseReqDTO -> { assertEquals(mobile, smsCodeUseReqDTO.getMobile()); From 6b3bc5d18e3de3aae71f8ca64320e7c2647de8ce Mon Sep 17 00:00:00 2001 From: ranke <213539@qq.com> Date: Mon, 19 Jan 2026 09:43:01 +0800 Subject: [PATCH 02/11] =?UTF-8?q?userId=E6=94=B9=E4=B8=BA=20workcode=20,?= =?UTF-8?q?=20=E4=BF=AE=E5=A4=8D=E6=95=B0=E6=8D=AE=E6=9D=83=E9=99=90?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98.=20=20http://172.16.46.63:31560/ind?= =?UTF-8?q?ex.php=3Fm=3Dtask&f=3Dview&taskID=3D715?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rule/dept/DeptDataPermissionRule.java | 29 ++++++++++++++++--- .../core/handler/DefaultDBFieldHandler.java | 7 +++-- .../framework/security/core/LoginUser.java | 3 ++ .../core/util/SecurityFrameworkUtils.java | 14 +++++++++ .../oauth2/OAuth2TokenServiceImpl.java | 1 + 5 files changed, 48 insertions(+), 6 deletions(-) diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java index ea4a8a93..4a4befdb 100644 --- a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java @@ -18,19 +18,24 @@ import com.zt.plat.framework.tenant.core.context.CompanyContextHolder; import com.zt.plat.framework.tenant.core.context.DeptContextHolder; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.NullValue; +import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.expression.operators.conditional.OrExpression; import net.sf.jsqlparser.expression.operators.relational.*; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.schema.Table; import net.sf.jsqlparser.statement.select.ParenthesedSelect; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.SelectItem; +import org.apache.commons.lang3.StringUtils; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -67,7 +72,16 @@ public class DeptDataPermissionRule implements DataPermissionRule { private static final String DEPT_COLUMN_NAME = "dept_id"; private static final String USER_COLUMN_NAME = "user_id"; - static final Expression EXPRESSION_NULL = new NullValue(); + static final Expression EXPRESSION_NULL; + + static { + try { + EXPRESSION_NULL = CCJSqlParserUtil.parseCondExpression("1 = 0"); + } catch (JSQLParserException e) { + throw new RuntimeException(e); + } + } + public static final String SYSTEM_USERS = "system_users"; private final PermissionCommonApi permissionApi; @@ -177,7 +191,9 @@ public class DeptDataPermissionRule implements DataPermissionRule { // 情况三,拼接 Dept 和 Company User 的条件,最后组合 Expression deptExpression = buildDeptExpression(tableName, tableAlias, effectiveDeptIds); // Expression deptExpression = buildDeptExpression(tableName, tableAlias, deptDataPermission.getDeptIds()); - Expression userExpression = buildUserExpression(tableName, tableAlias, effectiveSelf, loginUser.getId()); + // 使用工号替换 UserId + String userWorkCode = SecurityFrameworkUtils.getLoginUserWorkCode(); + Expression userExpression = buildUserExpression(tableName, tableAlias, effectiveSelf, loginUser.getId(), userWorkCode); if (deptExpression == null && userExpression == null) { // TODO ZT:获得不到条件的时候,暂时不抛出异常,而是不返回数据 log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]", @@ -241,7 +257,7 @@ public class DeptDataPermissionRule implements DataPermissionRule { new ParenthesedExpressionList(new ExpressionList(CollectionUtils.convertList(deptIds, LongValue::new)))); } - private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) { + private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId, String workCode) { // 如果不查看自己,则无需作为条件 if (Boolean.FALSE.equals(self)) { return null; @@ -250,8 +266,13 @@ public class DeptDataPermissionRule implements DataPermissionRule { if (StrUtil.isEmpty(columnName)) { return null; } + // 拼接条件 - return new EqualsTo(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), new LongValue(userId)); + if (StrUtil.isBlank(workCode)) { + return new EqualsTo(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), new LongValue(userId)); + } else { + return new EqualsTo(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), new StringValue(workCode)); + } } // ==================== 添加配置 ==================== 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 ec449926..b3306ac0 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 @@ -11,6 +11,7 @@ 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.annotation.Resource; +import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.reflection.MetaObject; import org.springframework.context.annotation.Lazy; import org.springframework.util.ReflectionUtils; @@ -48,14 +49,16 @@ public class DefaultDBFieldHandler implements MetaObjectHandler { } Long userId = getUserId(); + String userWorkCode = SecurityFrameworkUtils.getLoginUserWorkCode(); + String savedUserWorkCodeOrUserId = StringUtils.isNotEmpty(userWorkCode) ? userWorkCode : userId == null ? null : userId.toString(); String userNickname = SecurityFrameworkUtils.getLoginUserNickname(); // 当前登录用户不为空,创建人为空,则当前登录用户为创建人 if (Objects.nonNull(userId) && Objects.isNull(baseDO.getCreator())) { - baseDO.setCreator(userId.toString()); + baseDO.setCreator(savedUserWorkCodeOrUserId); } // 当前登录用户不为空,更新人为空,则当前登录用户为更新人 if (Objects.nonNull(userId) && Objects.isNull(baseDO.getUpdater())) { - baseDO.setUpdater(userId.toString()); + baseDO.setUpdater(savedUserWorkCodeOrUserId); } } if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BusinessBaseDO businessBaseDO) { diff --git a/zt-framework/zt-spring-boot-starter-security/src/main/java/com/zt/plat/framework/security/core/LoginUser.java b/zt-framework/zt-spring-boot-starter-security/src/main/java/com/zt/plat/framework/security/core/LoginUser.java index f9b739dd..cf026e49 100644 --- a/zt-framework/zt-spring-boot-starter-security/src/main/java/com/zt/plat/framework/security/core/LoginUser.java +++ b/zt-framework/zt-spring-boot-starter-security/src/main/java/com/zt/plat/framework/security/core/LoginUser.java @@ -31,6 +31,9 @@ public class LoginUser { // 用户关联的岗位信息 public static final String INFO_KEY_POST_IDS = "postIds"; + // 工号 + public static final String INFO_KEY_WORK_CODE = "workCode"; + /** * 用户编号 */ diff --git a/zt-framework/zt-spring-boot-starter-security/src/main/java/com/zt/plat/framework/security/core/util/SecurityFrameworkUtils.java b/zt-framework/zt-spring-boot-starter-security/src/main/java/com/zt/plat/framework/security/core/util/SecurityFrameworkUtils.java index 48a2bac1..e9efb450 100644 --- a/zt-framework/zt-spring-boot-starter-security/src/main/java/com/zt/plat/framework/security/core/util/SecurityFrameworkUtils.java +++ b/zt-framework/zt-spring-boot-starter-security/src/main/java/com/zt/plat/framework/security/core/util/SecurityFrameworkUtils.java @@ -15,6 +15,7 @@ import org.springframework.security.web.authentication.WebAuthenticationDetailsS import org.springframework.util.StringUtils; import java.util.Collections; +import java.util.Map; /** * 安全服务工具类 @@ -93,6 +94,19 @@ public class SecurityFrameworkUtils { return loginUser != null ? loginUser.getVisitCompanyId() : null; } + @Nullable + public static String getLoginUserWorkCode() { + LoginUser loginUser = getLoginUser(); + if (loginUser == null) { + return null; + } + Map info = loginUser.getInfo(); + if (info == null) { + return null; + } + return MapUtil.getStr(info, LoginUser.INFO_KEY_WORK_CODE); + } + /** * 获得当前用户的编号,从上下文中 * diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/OAuth2TokenServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/OAuth2TokenServiceImpl.java index bdf08016..58b325bd 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/OAuth2TokenServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/OAuth2TokenServiceImpl.java @@ -215,6 +215,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { .put(LoginUser.INFO_KEY_TENANT_ID, user.getTenantId().toString()) .put(LoginUser.INFO_KEY_USERNAME, user.getUsername()) .put(LoginUser.INFO_KEY_PHONE, user.getMobile()) + .put(LoginUser.INFO_KEY_WORK_CODE, user.getWorkcode()) .put(LoginUser.INFO_KEY_POST_IDS, CollUtil.isEmpty(user.getPostIds()) ? "[]" : JsonUtils.toJsonString(user.getPostIds())) .build(); } else if (userType.equals(UserTypeEnum.MEMBER.getValue())) { From 61d85988fc03a65106d0b6ac57d3be9fe833c793 Mon Sep 17 00:00:00 2001 From: ranke <213539@qq.com> Date: Thu, 22 Jan 2026 09:35:13 +0800 Subject: [PATCH 03/11] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=80=BB=E7=BA=BF=E8=AE=BF=E9=97=AE=E6=97=A5=E5=BF=97=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E6=98=BE=E7=A4=BA=E7=8A=B6=E6=80=81=E7=A0=81=E9=97=AE?= =?UTF-8?q?=E9=A2=98:=20http://172.16.46.63:31560/index.php=3Fm=3Dtask&f?= =?UTF-8?q?=3Dview&taskID=3D703.=20=20databus=20=20=E6=96=B0=E5=A2=9E=20cl?= =?UTF-8?q?ient=20=E7=BB=9F=E4=B8=80=E5=87=BA=E5=8F=A3=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=AE=A1=E8=AE=A1:=20http://172.16.46.63:315?= =?UTF-8?q?60/index.php=3Fm=3Dtask&f=3Dview&taskID=3D716?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + zt-dependencies/pom.xml | 2 +- zt-module-databus/pom.xml | 1 + .../api/dto/ApiAccessLogCreateReq.java | 152 +++++++++++ .../provider/DatabusAccessLogProviderApi.java | 28 ++ .../zt-module-databus-client/pom.xml | 82 ++++++ .../module/databus/client/DatabusClient.java | 242 ++++++++++++++++++ .../databus/client/RpcConfiguration.java | 16 ++ .../main/resources/META-INF/spring.factories | 3 + .../module/databus/DatabusClientTest.java | 24 ++ .../plat/module/databus/TestApplication.java | 26 ++ .../src/test/resources/application.yaml | 11 + .../api/DatabusAccessLogProviderApiImpl.java | 45 ++++ .../gateway/core/ApiGatewayAccessLogger.java | 1 + .../core/ApiGatewayExecutionService.java | 1 + .../security/GatewaySecurityFilter.java | 37 ++- 16 files changed, 659 insertions(+), 13 deletions(-) create mode 100644 zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/dto/ApiAccessLogCreateReq.java create mode 100644 zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusAccessLogProviderApi.java create mode 100644 zt-module-databus/zt-module-databus-client/pom.xml create mode 100644 zt-module-databus/zt-module-databus-client/src/main/java/com/zt/plat/module/databus/client/DatabusClient.java create mode 100644 zt-module-databus/zt-module-databus-client/src/main/java/com/zt/plat/module/databus/client/RpcConfiguration.java create mode 100644 zt-module-databus/zt-module-databus-client/src/main/resources/META-INF/spring.factories create mode 100644 zt-module-databus/zt-module-databus-client/src/test/java/com/zt/plat/module/databus/DatabusClientTest.java create mode 100644 zt-module-databus/zt-module-databus-client/src/test/java/com/zt/plat/module/databus/TestApplication.java create mode 100644 zt-module-databus/zt-module-databus-client/src/test/resources/application.yaml create mode 100644 zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/api/DatabusAccessLogProviderApiImpl.java diff --git a/.gitignore b/.gitignore index e55eb64b..3d91d080 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,7 @@ package-lock.json # visual studio code .history *.log +logs/** functions/mock .temp/** diff --git a/zt-dependencies/pom.xml b/zt-dependencies/pom.xml index 8212d924..27a6889e 100644 --- a/zt-dependencies/pom.xml +++ b/zt-dependencies/pom.xml @@ -71,7 +71,7 @@ 1.18.1 1.18.36 1.6.3 - 5.8.35 + 5.8.43 6.0.0-M19 4.0.3 2.4.1 diff --git a/zt-module-databus/pom.xml b/zt-module-databus/pom.xml index 904627a5..c06c914b 100644 --- a/zt-module-databus/pom.xml +++ b/zt-module-databus/pom.xml @@ -11,6 +11,7 @@ zt-module-databus-api zt-module-databus-server zt-module-databus-server-app + zt-module-databus-client 4.0.0 diff --git a/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/dto/ApiAccessLogCreateReq.java b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/dto/ApiAccessLogCreateReq.java new file mode 100644 index 00000000..fb3536bb --- /dev/null +++ b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/dto/ApiAccessLogCreateReq.java @@ -0,0 +1,152 @@ +package com.zt.plat.module.databus.api.dto; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.zt.plat.framework.common.util.json.databind.TimestampLocalDateTimeSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 新增Databus API 访问日志请求体。 + */ +@Data +@EqualsAndHashCode +public class ApiAccessLogCreateReq { + + /** + * 主键 + */ + @Schema(description = "主键") + private Long id; + + /** + * HTTP 方法 + */ + @Schema(description = "HTTP 方法") + @NotNull(message = "HTTP 方法不能为空") + private String requestMethod; + + /** + * 请求路径 + */ + @Schema(description = "请求路径") + @NotNull(message = "请求路径不能为空") + private String requestPath; + + /** + * 调用使用的应用标识 + */ + @Schema(description = "调用使用的应用标识") + @NotNull(message = "调用使用的应用标识不能为空") + private String credentialAppId; + + /** + * 多租户编号 + */ + @Schema(description = "多租户编号") + @NotNull(message = "多租户编号不能为空") + private Long tenantId; + + /** + * 查询参数(JSON 字符串) + */ + @Schema(description = "查询参数(JSON 字符串)") + private String requestQuery; + + /** + * 请求头信息(JSON 字符串) + */ + @Schema(description = "请求头信息(JSON 字符串)") + private String requestHeaders; + + /** + * 请求体(JSON 字符串) + */ + @Schema(description = "请求体(JSON 字符串)") + private String requestBody; + + /** + * 响应 HTTP 状态码 + */ + @Schema(description = "响应 HTTP 状态码") + private Integer responseStatus; + + /** + * 响应提示信息 + */ + @Schema(description = "响应提示信息") + private String responseMessage; + + /** + * 响应体(JSON 字符串) + */ + @Schema(description = "响应体(JSON 字符串)") + private String responseBody; + + /** + * 访问状态:0-成功 1-客户端错误 2-服务端错误 3-未知 + */ + @Schema(description = "访问状态:0-成功 1-客户端错误 2-服务端错误 3-未知") + @NotNull(message = "访问状态不能为空") + private Integer status; + + /** + * 业务错误码 + */ + @Schema(description = "业务错误码") + private String errorCode; + + /** + * 错误信息 + */ + @Schema(description = "错误信息") + private String errorMessage; + + /** + * 异常堆栈 + */ + @Schema(description = "异常堆栈") + private String exceptionStack; + + /** + * 客户端 IP + */ + @Schema(description = "客户端 IP") + private String clientIp; + + /** + * User-Agent + */ + @Schema(description = "User-Agent") + private String userAgent; + + /** + * 请求耗时(毫秒) + */ + @Schema(description = "请求耗时(毫秒)") + private Long duration; + + /** + * 请求时间 + */ + @Schema(description = "请求时间") + @JsonSerialize(using = TimestampLocalDateTimeSerializer.class) + private LocalDateTime requestTime; + + /** + * 响应时间 + */ + @Schema(description = "响应时间") + @JsonSerialize(using = TimestampLocalDateTimeSerializer.class) + private LocalDateTime responseTime; + + /** + * 额外调试信息(JSON 字符串) + */ + @Schema(description = "额外调试信息(JSON 字符串)") + private String extra; + +} diff --git a/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusAccessLogProviderApi.java b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusAccessLogProviderApi.java new file mode 100644 index 00000000..a2f80c6e --- /dev/null +++ b/zt-module-databus/zt-module-databus-api/src/main/java/com/zt/plat/module/databus/api/provider/DatabusAccessLogProviderApi.java @@ -0,0 +1,28 @@ +package com.zt.plat.module.databus.api.provider; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.databus.api.dto.ApiAccessLogCreateReq; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; + +import java.util.Map; + +/** + * Databus API访问日志接口 + * 2026/1/20 16:26 + */ +@FeignClient(name = "${databus.provider.log.service:databus-server}") +@Tag(name = "RPC 服务 - Databus API访问日志接口") +public interface DatabusAccessLogProviderApi { + + String PREFIX = "/databus/api/portal/access-log"; + + @PostMapping(PREFIX + "/add") + @Operation(summary = "新增访问日志") + CommonResult add(@RequestHeader Map headers, @RequestBody ApiAccessLogCreateReq req); + +} diff --git a/zt-module-databus/zt-module-databus-client/pom.xml b/zt-module-databus/zt-module-databus-client/pom.xml new file mode 100644 index 00000000..a8abc82f --- /dev/null +++ b/zt-module-databus/zt-module-databus-client/pom.xml @@ -0,0 +1,82 @@ + + + 4.0.0 + + zt-module-databus + com.zt.plat + ${revision} + + zt-module-databus-server-client + jar + ${project.artifactId} + + Databus client, 提供调用第三方服务的能力并记录调用日志。 + + + + + + + com.zt.plat + zt-module-databus-api + ${revision} + + + + cn.hutool + hutool-all + + + + org.springframework.boot + spring-boot-starter-web + provided + + + + org.springframework.boot + spring-boot-starter-aop + provided + + + + org.apache.commons + commons-lang3 + + + + com.github.ben-manes.caffeine + caffeine + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + + diff --git a/zt-module-databus/zt-module-databus-client/src/main/java/com/zt/plat/module/databus/client/DatabusClient.java b/zt-module-databus/zt-module-databus-client/src/main/java/com/zt/plat/module/databus/client/DatabusClient.java new file mode 100644 index 00000000..ad921ceb --- /dev/null +++ b/zt-module-databus/zt-module-databus-client/src/main/java/com/zt/plat/module/databus/client/DatabusClient.java @@ -0,0 +1,242 @@ +package com.zt.plat.module.databus.client; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import cn.hutool.http.Method; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.util.security.CryptoSignatureUtils; +import com.zt.plat.module.databus.api.dto.ApiAccessLogCreateReq; +import com.zt.plat.module.databus.api.provider.DatabusAccessLogProviderApi; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * 数据总线提供的 http 客户端, 通过此客户端发起接口调用,会自动记录请求日志到数据总线 + * 2026/1/20 09:44 + */ +@Component +@Slf4j +public class DatabusClient { + + /** + * 多租户编号 + */ + @Value("${zt.plat.databus.client.tenantId:1}") + private Long tenantId; + + @Resource + private DatabusAccessLogProviderApi databusAccessLogProviderApi; + + private static final int MAX_TEXT_LENGTH = 4000; + + /** + * 发送 get 请求 + * @param urlString 仅接口地址,不带参数,参数由data提供 + * @param data 请求参数 + * @param headers 请求头 + * @return 响应结果 + */ + public String get(String urlString, Map data, Map headers, String appId, String authToken) { + return doRequest(urlString, data, headers, Method.GET, appId, authToken); + } + + /** + * 发送 post 请求 + * @param urlString 仅接口地址,不带参数,参数由data提供 + * @param data 请求数据 + * @param headers 请求头 + * @return 响应结果 + */ + public String post(String urlString, Map data, Map headers, String appId, String authToken) { + return doRequest(urlString, data, headers, Method.POST, appId, authToken); + } + + /** + * 发送 put 请求 + * @param urlString 仅接口地址,不带参数,参数由data提供 + * @param data 请求数据 + * @param headers 请求头 + * @return 响应结果 + */ + public String put(String urlString, Map data, Map headers, String appId, String authToken) { + return doRequest(urlString, data, headers, Method.PUT, appId, authToken); + } + + /** + * 发送 delete 请求 + * @param urlString 仅接口地址,不带参数,参数由data提供 + * @param data 请求数据 + * @param headers 请求头 + * @return 响应结果 + */ + public String delete(String urlString, Map data, Map headers, String appId, String authToken) { + return doRequest(urlString, data, headers, Method.DELETE, appId, authToken); + } + + + /** + * 发送请求到门户(token模式,不证书加密) + * @param urlString 仅接口地址,不带参数,参数由data提供 + * @param data 请求数据 + * @param headers 请求头 + * @param method 请求方式,默认为 GET + * @return 响应结果 + */ + public String doRequest(String urlString, Map data, Map headers, Method method, String appId, String authToken) { + if (method == null) { + method = Method.GET; + } + Assert.hasText(urlString, "接口地址不能为空"); + HttpRequest request; + ApiAccessLogCreateReq logReq = new ApiAccessLogCreateReq(); + if (Method.GET.equals(method) || Method.DELETE.equals(method)) { + logReq.setRequestQuery(JSONUtil.toJsonStr(data)); + } else { + logReq.setRequestBody(JSONUtil.toJsonStr(data)); + } + request = HttpUtil.createRequest(method, urlString).form(data); + if (headers != null && !headers.isEmpty()) { + for (Map.Entry entry : headers.entrySet()) { + request.header(entry.getKey(), entry.getValue(), true); + } + } + + logReq.setTenantId(tenantId); + + logReq.setRequestMethod(method.name()); + logReq.setRequestPath(urlString); + logReq.setCredentialAppId(appId); + logReq.setRequestHeaders(JSONUtil.toJsonStr(headers)); + logReq.setUserAgent(request.header("User-Agent")); + String result; + logReq.setRequestTime(LocalDateTime.now()); + long requestTime = System.currentTimeMillis(); + try (HttpResponse response = request.execute()) { + logReq.setDuration(System.currentTimeMillis() - requestTime); + logReq.setResponseTime(LocalDateTime.now()); + result = response.body(); + logReq.setResponseStatus(response.getStatus()); + logReq.setResponseBody(result); + logReq.setStatus(resolveStatus(response.getStatus())); + Map errorCodeAndMsg = extractErrorCodeAndMsg(result, response.getStatus()); + logReq.setErrorCode(errorCodeAndMsg.get("errorCode")); + logReq.setErrorMessage(errorCodeAndMsg.get("errorMessage")); + addAccessLog(logReq, appId, authToken); + } catch (Exception e) { + // 错误的日志服务端记录了,这里就不再记录了 +// logReq.setStatus(1); +// logReq.setExceptionStack(buildStackTrace( e)); +// addAccessLog(logReq, appId, authToken); + throw new RuntimeException(e); + } + return result; + } + + private void addAccessLog(ApiAccessLogCreateReq logReq, String appId, String authToken) { + String nonce = randomNonce(); + Map headers = new HashMap<>(); + headers.put("tenant-id", String.valueOf(tenantId)); + headers.put("ZT-App-Id", appId); + headers.put("ZT-Timestamp", Long.toString(System.currentTimeMillis())); + headers.put("ZT-Nonce", nonce); + headers.put("ZT-Auth-Token", authToken); + CommonResult response = databusAccessLogProviderApi.add(headers, logReq); + if (response.getCode() != 0) { + throw new RuntimeException("添加访问日志失败: " + response); + } + } + + private static String randomNonce() { + return UUID.randomUUID().toString().replace("-", ""); + } + + private String buildStackTrace(Throwable throwable) { + try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) { + throwable.printStackTrace(pw); + return truncate(sw.toString()); + } catch (Exception ex) { + return throwable.getMessage(); + } + } + + private Integer resolveStatus(Integer httpStatus) { + if (httpStatus == null) { + return 3; + } + if (httpStatus >= 200 && httpStatus < 400) { + return 0; + } + if (httpStatus >= 400 && httpStatus < 500) { + return 1; + } + if (httpStatus >= 500) { + return 2; + } + return 3; + } + + private Map extractErrorCodeAndMsg(String responseBody, Integer responseStatus) { + Map result = new HashMap<>(); + if (!isErrorStatus(responseStatus)) { + return result; + } + if (JSONUtil.isTypeJSONObject(responseBody)) { + JSONObject map = JSONUtil.parseObj(responseBody); + Object errorCode = firstNonNull(map.get("errorCode"), map.get("code")); + errorCode = errorCode == null ? null : truncate(String.valueOf(errorCode)); + if (errorCode != null) { + result.put("errorCode", errorCode.toString()); + } + Object message = firstNonNull(map.get("errorMessage"), map.get("message")); + if (message != null) { + message = truncate(String.valueOf(message)); + result.put("errorMessage", message.toString()); + } + + } + return result; + } + + + private boolean isErrorStatus(Integer responseStatus) { + return responseStatus != null && responseStatus >= 400; + } + + @SafeVarargs + private T firstNonNull(T... candidates) { + if (candidates == null) { + return null; + } + for (T candidate : candidates) { + if (candidate != null) { + return candidate; + } + } + return null; + } + + private String truncate(String text) { + if (!StringUtils.hasText(text)) { + return text; + } + if (text.length() <= MAX_TEXT_LENGTH) { + return text; + } + return text.substring(0, MAX_TEXT_LENGTH); + } + +} diff --git a/zt-module-databus/zt-module-databus-client/src/main/java/com/zt/plat/module/databus/client/RpcConfiguration.java b/zt-module-databus/zt-module-databus-client/src/main/java/com/zt/plat/module/databus/client/RpcConfiguration.java new file mode 100644 index 00000000..08c69d96 --- /dev/null +++ b/zt-module-databus/zt-module-databus-client/src/main/java/com/zt/plat/module/databus/client/RpcConfiguration.java @@ -0,0 +1,16 @@ +package com.zt.plat.module.databus.client; + +/** + * + * 2026/1/21 10:48 + */ + +import com.zt.plat.module.databus.api.provider.DatabusAccessLogProviderApi; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@EnableFeignClients(clients = {DatabusAccessLogProviderApi.class}) +public class RpcConfiguration { + +} diff --git a/zt-module-databus/zt-module-databus-client/src/main/resources/META-INF/spring.factories b/zt-module-databus/zt-module-databus-client/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..5fe820ee --- /dev/null +++ b/zt-module-databus/zt-module-databus-client/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.zt.plat.module.databus.client.DatabusClient,\ +com.zt.plat.module.databus.client.RpcConfiguration \ No newline at end of file diff --git a/zt-module-databus/zt-module-databus-client/src/test/java/com/zt/plat/module/databus/DatabusClientTest.java b/zt-module-databus/zt-module-databus-client/src/test/java/com/zt/plat/module/databus/DatabusClientTest.java new file mode 100644 index 00000000..b26c74d8 --- /dev/null +++ b/zt-module-databus/zt-module-databus-client/src/test/java/com/zt/plat/module/databus/DatabusClientTest.java @@ -0,0 +1,24 @@ +package com.zt.plat.module.databus; + +import com.zt.plat.module.databus.client.DatabusClient; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * + * 2026/1/20 14:29 + */ +@SpringBootTest(classes = TestApplication.class) +public class DatabusClientTest { + + @Autowired + private DatabusClient databusClient; + + @Test + void test() { + String result = databusClient.get("https://www.baidu.com/", null, null, "jwyw2", "a5d7cf609c0b47038ea405c660726ee9"); + System.out.println(result); + } + +} diff --git a/zt-module-databus/zt-module-databus-client/src/test/java/com/zt/plat/module/databus/TestApplication.java b/zt-module-databus/zt-module-databus-client/src/test/java/com/zt/plat/module/databus/TestApplication.java new file mode 100644 index 00000000..fa8ca7d9 --- /dev/null +++ b/zt-module-databus/zt-module-databus-client/src/test/java/com/zt/plat/module/databus/TestApplication.java @@ -0,0 +1,26 @@ +package com.zt.plat.module.databus; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * + * 2026/1/20 14:26 + */ +@SpringBootTest +@SpringBootApplication(exclude = { + DataSourceAutoConfiguration.class, + DataSourceTransactionManagerAutoConfiguration.class, + HibernateJpaAutoConfiguration.class, + JdbcTemplateAutoConfiguration.class, +}) +public class TestApplication { + public static void main(String[] args) { + SpringApplication.run(TestApplication.class, args); + } +} diff --git a/zt-module-databus/zt-module-databus-client/src/test/resources/application.yaml b/zt-module-databus/zt-module-databus-client/src/test/resources/application.yaml new file mode 100644 index 00000000..a47ddc71 --- /dev/null +++ b/zt-module-databus/zt-module-databus-client/src/test/resources/application.yaml @@ -0,0 +1,11 @@ +spring: + cloud: + nacos: + server-addr: 172.16.46.63:30848 # Nacos 服务器地址 + username: nacos # Nacos 账号 + password: P@ssword25 # Nacos 密码 + discovery: # 【配置中心】配置项 + namespace: klw # 命名空间。这里使用 maven Profile 资源过滤进行动态替换 + group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP + metadata: + version: 1.0.0 # 服务实例的版本号,可用于灰度发布 \ No newline at end of file diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/api/DatabusAccessLogProviderApiImpl.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/api/DatabusAccessLogProviderApiImpl.java new file mode 100644 index 00000000..a700c240 --- /dev/null +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/api/DatabusAccessLogProviderApiImpl.java @@ -0,0 +1,45 @@ +package com.zt.plat.module.databus.api; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.util.monitor.TracerUtils; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.framework.common.util.servlet.ServletUtils; +import com.zt.plat.module.databus.api.dto.ApiAccessLogCreateReq; +import com.zt.plat.module.databus.api.provider.DatabusAccessLogProviderApi; +import com.zt.plat.module.databus.dal.dataobject.gateway.ApiAccessLogDO; +import com.zt.plat.module.databus.service.gateway.ApiAccessLogService; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; +import java.util.Map; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +/** + * + * 2026/1/21 10:05 + */ +@RestController +public class DatabusAccessLogProviderApiImpl implements DatabusAccessLogProviderApi { + + @Resource + private ApiAccessLogService apiAccessLogService; + + @Override + @PermitAll + public CommonResult add(Map headers, ApiAccessLogCreateReq req) { + ApiAccessLogDO logDO = new ApiAccessLogDO(); + BeanUtils.copyProperties(req, logDO); + logDO.setTraceId(TracerUtils.getTraceId()); + logDO.setClientIp(ServletUtils.getClientIP()); + logDO.setCreateTime(LocalDateTime.now()); + logDO.setUpdateTime(LocalDateTime.now()); + logDO.setCreator("1"); + logDO.setUpdater("1"); + + apiAccessLogService.create(logDO); + return success(Boolean.TRUE); + } +} diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayAccessLogger.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayAccessLogger.java index 741db3f2..d56e56a6 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayAccessLogger.java +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayAccessLogger.java @@ -159,6 +159,7 @@ public class ApiGatewayAccessLogger { update.setStatus(resolveStatus(status)); update.setResponseTime(LocalDateTime.now()); update.setDuration(calculateDuration(request)); + update.setUpdater("1"); apiAccessLogService.update(update); } catch (Exception ex) { log.warn("更新入口 API 访问日志失败, logId={}", logId, ex); diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayExecutionService.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayExecutionService.java index 5f344b95..e80c29ce 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayExecutionService.java +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/core/ApiGatewayExecutionService.java @@ -98,6 +98,7 @@ public class ApiGatewayExecutionService { } else { responseContext = apiFlowDispatcher.dispatch(context.getApiCode(), context.getApiVersion(), context); } + responseContext.setResponseStatus(resolveStatus(responseContext)); } catch (ServiceException ex) { errorProcessor.applyServiceException(context, ex); accessLogger.onException(context, ex); diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java index 00025b83..d0839a7d 100644 --- a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java +++ b/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/framework/integration/gateway/security/GatewaySecurityFilter.java @@ -77,6 +77,9 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { filterChain.doFilter(request, response); return; } + + // 添加日志接口需要特殊处理 + boolean isNormalProcess = !pathMatcher.match("/databus/api/portal/access-log/**", pathWithinApplication); Long accessLogId = null; ApiGatewayProperties.Security security = properties.getSecurity(); ApiClientCredentialDO credential = null; @@ -95,8 +98,10 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { accessLogger.finalizeEarly(request, HttpStatus.FORBIDDEN.value(), "IP 禁止访问"); return; } - // IP 校验通过后再补录入口日志,避免无租户信息写库 - accessLogId = accessLogger.logEntrance(request); + // IP 校验通过后再补录入口日志,避免无租户信息写库, 非日志添加接口才记录日志 + if (isNormalProcess) { + accessLogId = accessLogger.logEntrance(request); + } if (!security.isEnabled()) { byte[] originalBody = StreamUtils.copyToByteArray(request.getInputStream()); CachedBodyHttpServletRequest passthroughRequest = new CachedBodyHttpServletRequest(request, originalBody); @@ -128,13 +133,17 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { if (nonce.length() < 8) { throw new SecurityValidationException(HttpStatus.BAD_REQUEST, "随机数长度不足"); } - String signature = requireHeader(request, SIGNATURE_HEADER, "缺少签名"); - // 尝试按凭证配置解密请求体,并构建签名载荷进行校验 - byte[] decryptedBody = decryptRequestBody(requestBody, credential, security); - verifySignature(request, decryptedBody, signature, credential, security, appId, timestampHeader); + if (isNormalProcess) { + // 非日志添加接口才处理 + String signature = requireHeader(request, SIGNATURE_HEADER, "缺少签名"); + // 尝试按凭证配置解密请求体,并构建签名载荷进行校验 + byte[] decryptedBody = decryptRequestBody(requestBody, credential, security); + verifySignature(request, decryptedBody, signature, credential, security, appId, timestampHeader); + requestBody = decryptedBody; + } ensureNonce(tenantId, appId, nonce, security); - requestBody = decryptedBody; + } // 使用可重复读取的请求包装,供后续过滤器继续消费 @@ -154,7 +163,9 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { try { filterChain.doFilter(securedRequest, responseWrapper); dispatchedToGateway = true; - encryptResponse(responseWrapper, credential, security); + if (isNormalProcess) { + encryptResponse(responseWrapper, credential, security); + } } finally { responseWrapper.copyBodyToResponse(); } @@ -163,7 +174,7 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { accessLogId = accessLogger.logEntrance(request); } log.warn("[API-PORTAL] 安全校验失败: {}", ex.getMessage()); - writeErrorResponse(response, security, credential, ex.status(), ex.getMessage()); + writeErrorResponse(response, security, credential, ex.status(), ex.getMessage(), isNormalProcess); if (!dispatchedToGateway) { accessLogger.finalizeEarly(request, ex.status().value(), ex.getMessage()); } @@ -172,7 +183,7 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { accessLogId = accessLogger.logEntrance(request); } log.error("[API-PORTAL] 处理安全校验时出现异常", ex); - writeErrorResponse(response, security, credential, HttpStatus.INTERNAL_SERVER_ERROR, "网关安全校验失败"); + writeErrorResponse(response, security, credential, HttpStatus.INTERNAL_SERVER_ERROR, "网关安全校验失败", isNormalProcess); if (!dispatchedToGateway) { accessLogger.finalizeEarly(request, HttpStatus.INTERNAL_SERVER_ERROR.value(), "网关安全校验失败"); } @@ -479,6 +490,7 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { String token = tokenOptional.get(); securedRequest.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token); securedRequest.setHeader(GatewayJwtResolver.HEADER_ZT_AUTH_TOKEN, token); + } private static final class SecurityValidationException extends RuntimeException { @@ -499,7 +511,8 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { ApiGatewayProperties.Security security, ApiClientCredentialDO credential, HttpStatus status, - String message) { + String message, + boolean isNormalProcess) { if (response.isCommitted()) { log.warn("[API-PORTAL] 响应已提交,无法写入安全校验错误: {}", message); return; @@ -514,7 +527,7 @@ public class GatewaySecurityFilter extends OncePerRequestFilter { .response(null) .traceId(traceId) .build(); - if (shouldEncryptErrorResponse(security, credential)) { + if (shouldEncryptErrorResponse(security, credential) && isNormalProcess) { String encryptionKey = credential.getEncryptionKey(); String encryptionType = resolveEncryptionType(credential, security); try { From df2b0f52e31b84df56d6d54026d1f42473dd0b96 Mon Sep 17 00:00:00 2001 From: ranke <213539@qq.com> Date: Thu, 29 Jan 2026 18:53:22 +0800 Subject: [PATCH 04/11] no message --- docs/databus-client使用说明.md | 31 +++++++++++++++++++ .../rule/dept/DeptDataPermissionRule.java | 7 +++-- .../zt-module-databus-client/pom.xml | 2 +- .../module/databus/client/DatabusClient.java | 1 - .../system/api/permission/PermissionApi.java | 5 +++ .../api/permission/PermissionApiImpl.java | 6 ++++ .../config/DataPermissionConfiguration.java | 1 + .../service/permission/PermissionService.java | 8 +++++ .../permission/PermissionServiceImpl.java | 6 ++++ 9 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 docs/databus-client使用说明.md diff --git a/docs/databus-client使用说明.md b/docs/databus-client使用说明.md new file mode 100644 index 00000000..c6eb7b1e --- /dev/null +++ b/docs/databus-client使用说明.md @@ -0,0 +1,31 @@ +# Databus Client 使用说明 + +databus client 最主要用于调用基于http协议的第三方接口时需要记录调用日志到 databus 的情况, 通过databus client 调用第三方接口会将调用日志记录到databus的访问日志中 + +# 使用方法 +1. 添加依赖: +```xml + + com.zt.plat + zt-module-databus-client + 3.0.47-SNAPSHOT + +``` +2. 注入 DatabusClient +```java +@Resource +private DatabusClient databusClient; +``` +3. 方法说明 + * get(...) : 发送 get 请求 + * post(...): 发送 post 请求 + * put(...): 发送 put 请求 + * delete(...): 发送 delete 请求 + * doRequest(...): 发送自定义请求 +4. 方法参数说明(由于所有方法参数都是一样的,所以在此统一说明) + * String urlString: 请求的 http 接口地址(get/delete请求不需要带url参数) + * Map data: 请求的参数(post/put方法会转换为json提交, get/delete会拼接到url上) + * Map headers: 请求头信息 + * String appId: databus 的appid + * String authToken: databus 的访问令牌 + * Method method: doRequest 方法独有,如果要使用 get/post/put/delete 之外的方法,请使用doRequest方法并通过method参数指定 diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java index 4a4befdb..1294e3c8 100644 --- a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java @@ -96,7 +96,6 @@ public class DeptDataPermissionRule implements DataPermissionRule { /** * 基于用户的表字段配置 * 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。 - * key:表名 * value:字段名 */ private final Map userColumns = new HashMap<>(); @@ -262,7 +261,11 @@ public class DeptDataPermissionRule implements DataPermissionRule { if (Boolean.FALSE.equals(self)) { return null; } - String columnName = userColumns.get(tableName); + String userColumnsKey = tableName; + if (StrUtil.isNotBlank(workCode)) { + userColumnsKey = userColumnsKey + "_work_code"; + } + String columnName = userColumns.get(userColumnsKey); if (StrUtil.isEmpty(columnName)) { return null; } diff --git a/zt-module-databus/zt-module-databus-client/pom.xml b/zt-module-databus/zt-module-databus-client/pom.xml index a8abc82f..4ee8c93f 100644 --- a/zt-module-databus/zt-module-databus-client/pom.xml +++ b/zt-module-databus/zt-module-databus-client/pom.xml @@ -8,7 +8,7 @@ com.zt.plat ${revision} - zt-module-databus-server-client + zt-module-databus-client jar ${project.artifactId} diff --git a/zt-module-databus/zt-module-databus-client/src/main/java/com/zt/plat/module/databus/client/DatabusClient.java b/zt-module-databus/zt-module-databus-client/src/main/java/com/zt/plat/module/databus/client/DatabusClient.java index ad921ceb..0954ef21 100644 --- a/zt-module-databus/zt-module-databus-client/src/main/java/com/zt/plat/module/databus/client/DatabusClient.java +++ b/zt-module-databus/zt-module-databus-client/src/main/java/com/zt/plat/module/databus/client/DatabusClient.java @@ -7,7 +7,6 @@ import cn.hutool.http.Method; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.zt.plat.framework.common.pojo.CommonResult; -import com.zt.plat.framework.common.util.security.CryptoSignatureUtils; import com.zt.plat.module.databus.api.dto.ApiAccessLogCreateReq; import com.zt.plat.module.databus.api.provider.DatabusAccessLogProviderApi; import jakarta.annotation.Resource; diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/permission/PermissionApi.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/permission/PermissionApi.java index b30f62f6..87d23136 100644 --- a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/permission/PermissionApi.java +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/permission/PermissionApi.java @@ -56,4 +56,9 @@ public interface PermissionApi extends PermissionCommonApi { @Parameter(name = "userId", description = "用户编号", example = "1", required = true) CommonResult getUserDataPermissionLevel(@RequestParam("userId") Long userId); + @GetMapping(PREFIX + "/get") + @Operation(summary = "通过用户 ID 查询用户是否为超级管理员") + @Parameter(name = "id", description = "用户编号", example = "1", required = true) + CommonResult isSuperAdmin(@RequestParam("id") Long id); + } \ No newline at end of file diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/permission/PermissionApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/permission/PermissionApiImpl.java index c3899330..26c8db0e 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/permission/PermissionApiImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/permission/PermissionApiImpl.java @@ -6,6 +6,7 @@ import com.zt.plat.framework.common.util.object.BeanUtils; import com.zt.plat.module.system.api.permission.dto.*; import com.zt.plat.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleDataScopeReqVO; import com.zt.plat.module.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO; +import com.zt.plat.module.system.dal.dataobject.user.AdminUserDO; import com.zt.plat.module.system.enums.permission.DataScopeEnum; import com.zt.plat.module.system.service.permission.PermissionService; import org.springframework.context.annotation.Primary; @@ -90,4 +91,9 @@ public class PermissionApiImpl implements PermissionApi { public CommonResult getDeptDataPermissionWithRoleCodes(Long userId, String roleCodes) { return success(permissionService.getDeptDataPermissionWithRoleCodes(userId, roleCodes)); } + + @Override + public CommonResult isSuperAdmin(Long id) { + return success(permissionService.isSuperAdmin(id)); + } } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/datapermission/config/DataPermissionConfiguration.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/datapermission/config/DataPermissionConfiguration.java index def9db9b..78725a5e 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/datapermission/config/DataPermissionConfiguration.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/datapermission/config/DataPermissionConfiguration.java @@ -24,6 +24,7 @@ public class DataPermissionConfiguration { rule.addDeptColumn(DeptDO.class, "id"); // user rule.addUserColumn(AdminUserDO.class, "id"); + rule.addUserColumn("system_users_work_code", "workcode"); }; } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionService.java index 6d570ca7..5e1d37d7 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionService.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionService.java @@ -189,4 +189,12 @@ public interface PermissionService { */ Set getByRoleIdAndMenuIds(Set roleIds, Set ids); + /** + * 根据用户ID判断用户是否是超级管理员 + * + * @param userId + * @return + */ + Boolean isSuperAdmin(Long userId); + } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java index 96820ed6..389f3795 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java @@ -559,5 +559,11 @@ public class PermissionServiceImpl implements PermissionService { return result; } + @Override + public Boolean isSuperAdmin(Long userId) { + Set userRoleIds = getUserRoleIdListByUserId(userId); + return roleService.hasAnySuperAdmin(userRoleIds); + } + } From 24352b34db1ddc47f91478522abf268251650636 Mon Sep 17 00:00:00 2001 From: ranke <213539@qq.com> Date: Thu, 29 Jan 2026 18:07:49 +0800 Subject: [PATCH 05/11] =?UTF-8?q?ztcloud-dist=20=E4=B8=AD=20bpm=20?= =?UTF-8?q?=E8=A6=86=E7=9B=96=E5=9B=9E=E6=9D=A5,=E5=8C=85=E5=90=AB=20http:?= =?UTF-8?q?//172.16.46.63:31560/index.php=3Fm=3Dtask&f=3Dview&taskID=3D735?= =?UTF-8?q?=20=E5=92=8C=20http://172.16.46.63:31560/index.php=3Fm=3Dtask&f?= =?UTF-8?q?=3Dview&taskID=3D552=20=E4=B8=AD=E7=9A=84=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zt-module-bpm/pom.xml | 2 + .../Dockerfile | 6 +- .../zt-module-bpm-server-app/pom.xml | 49 ++++++++ .../plat/module/bpm/BpmServerApplication.java | 0 .../src/main/resources/application-dev.yaml | 0 .../src/main/resources/application-local.yaml | 0 .../src/main/resources/application.yaml | 0 .../src/main/resources/logback-spring.xml | 9 ++ zt-module-bpm/zt-module-bpm-server/pom.xml | 19 --- .../task/BpmProcessInstanceController.java | 42 +++++++ .../core/listener/BpmTaskEventListener.java | 2 +- .../task/BpmProcessInstanceServiceImpl.java | 2 +- .../bpm/service/task/BpmTaskService.java | 4 +- .../bpm/service/task/BpmTaskServiceImpl.java | 110 ++++++++++++++---- 14 files changed, 197 insertions(+), 48 deletions(-) rename zt-module-bpm/{zt-module-bpm-server => zt-module-bpm-server-app}/Dockerfile (82%) create mode 100644 zt-module-bpm/zt-module-bpm-server-app/pom.xml rename zt-module-bpm/{zt-module-bpm-server => zt-module-bpm-server-app}/src/main/java/com/zt/plat/module/bpm/BpmServerApplication.java (100%) rename zt-module-bpm/{zt-module-bpm-server => zt-module-bpm-server-app}/src/main/resources/application-dev.yaml (100%) rename zt-module-bpm/{zt-module-bpm-server => zt-module-bpm-server-app}/src/main/resources/application-local.yaml (100%) rename zt-module-bpm/{zt-module-bpm-server => zt-module-bpm-server-app}/src/main/resources/application.yaml (100%) rename zt-module-bpm/{zt-module-bpm-server => zt-module-bpm-server-app}/src/main/resources/logback-spring.xml (90%) diff --git a/zt-module-bpm/pom.xml b/zt-module-bpm/pom.xml index 302788b7..69602408 100644 --- a/zt-module-bpm/pom.xml +++ b/zt-module-bpm/pom.xml @@ -11,7 +11,9 @@ zt-module-bpm-api zt-module-bpm-server + zt-module-bpm-server-app + ${revision} zt-module-bpm pom diff --git a/zt-module-bpm/zt-module-bpm-server/Dockerfile b/zt-module-bpm/zt-module-bpm-server-app/Dockerfile similarity index 82% rename from zt-module-bpm/zt-module-bpm-server/Dockerfile rename to zt-module-bpm/zt-module-bpm-server-app/Dockerfile index 868eec5c..d859b242 100644 --- a/zt-module-bpm/zt-module-bpm-server/Dockerfile +++ b/zt-module-bpm/zt-module-bpm-server-app/Dockerfile @@ -3,10 +3,10 @@ FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre ## 创建目录,并使用它作为工作目录 -RUN mkdir -p /zt-module-bpm-server -WORKDIR /zt-module-bpm-server +RUN mkdir -p /zt-module-bpm-server-app +WORKDIR /zt-module-bpm-server-app ## 将后端项目的 Jar 文件,复制到镜像中 -COPY ./target/zt-module-bpm-server.jar app.jar +COPY ./target/zt-module-bpm-server-app.jar app.jar ## 设置 TZ 时区 ## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 diff --git a/zt-module-bpm/zt-module-bpm-server-app/pom.xml b/zt-module-bpm/zt-module-bpm-server-app/pom.xml new file mode 100644 index 00000000..6b9073b7 --- /dev/null +++ b/zt-module-bpm/zt-module-bpm-server-app/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + com.zt.plat + zt-module-bpm + ${revision} + + zt-module-bpm-server-app + jar + ${project.artifactId} + + bpm 模块启动器。 + + + + + + + com.zt.plat + zt-module-bpm-server + ${revision} + + + + + + + + ${project.artifactId} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/BpmServerApplication.java b/zt-module-bpm/zt-module-bpm-server-app/src/main/java/com/zt/plat/module/bpm/BpmServerApplication.java similarity index 100% rename from zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/BpmServerApplication.java rename to zt-module-bpm/zt-module-bpm-server-app/src/main/java/com/zt/plat/module/bpm/BpmServerApplication.java diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/resources/application-dev.yaml b/zt-module-bpm/zt-module-bpm-server-app/src/main/resources/application-dev.yaml similarity index 100% rename from zt-module-bpm/zt-module-bpm-server/src/main/resources/application-dev.yaml rename to zt-module-bpm/zt-module-bpm-server-app/src/main/resources/application-dev.yaml diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/resources/application-local.yaml b/zt-module-bpm/zt-module-bpm-server-app/src/main/resources/application-local.yaml similarity index 100% rename from zt-module-bpm/zt-module-bpm-server/src/main/resources/application-local.yaml rename to zt-module-bpm/zt-module-bpm-server-app/src/main/resources/application-local.yaml diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/resources/application.yaml b/zt-module-bpm/zt-module-bpm-server-app/src/main/resources/application.yaml similarity index 100% rename from zt-module-bpm/zt-module-bpm-server/src/main/resources/application.yaml rename to zt-module-bpm/zt-module-bpm-server-app/src/main/resources/application.yaml diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/resources/logback-spring.xml b/zt-module-bpm/zt-module-bpm-server-app/src/main/resources/logback-spring.xml similarity index 90% rename from zt-module-bpm/zt-module-bpm-server/src/main/resources/logback-spring.xml rename to zt-module-bpm/zt-module-bpm-server-app/src/main/resources/logback-spring.xml index 51a9a489..656c3bee 100644 --- a/zt-module-bpm/zt-module-bpm-server/src/main/resources/logback-spring.xml +++ b/zt-module-bpm/zt-module-bpm-server-app/src/main/resources/logback-spring.xml @@ -76,5 +76,14 @@ + + + + + + + + + diff --git a/zt-module-bpm/zt-module-bpm-server/pom.xml b/zt-module-bpm/zt-module-bpm-server/pom.xml index 4b151c35..f2829de7 100644 --- a/zt-module-bpm/zt-module-bpm-server/pom.xml +++ b/zt-module-bpm/zt-module-bpm-server/pom.xml @@ -120,23 +120,4 @@ - - - ${project.artifactId} - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring.boot.version} - - - - repackage - - - - - - diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceController.java index d55f27a0..0afdc38b 100644 --- a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceController.java +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/controller/admin/task/BpmProcessInstanceController.java @@ -26,11 +26,17 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; import jakarta.validation.Valid; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.list.SetUniqueList; +import org.apache.commons.lang3.StringUtils; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.migration.ProcessInstanceMigrationBuilder; import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.runtime.ActivityInstance; import org.flowable.task.api.Task; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; @@ -41,6 +47,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import static com.zt.plat.framework.common.pojo.CommonResult.success; import static com.zt.plat.framework.common.util.collection.CollectionUtils.*; @@ -234,4 +241,39 @@ public class BpmProcessInstanceController { return success(processInstanceService.getProcessInstanceBpmnModelView(id)); } + + @Resource + private RuntimeService runtimeService; + + @Resource + private TaskService taskService2; + + @GetMapping("/klwtest") + @Operation(summary = "klw测试") + @PermitAll + public CommonResult klwtest(@RequestParam String processInstanceId, @RequestParam String nodeId) { + List currentActivities = runtimeService + .createActivityInstanceQuery() + .processInstanceId(processInstanceId) + .unfinished() + .list(); +// taskService2.addComment(null, processInstanceId, +// "测试 comment"); + if (CollectionUtils.isNotEmpty(currentActivities)) { + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(processInstanceId) + .moveActivityIdsToSingleActivityId( + currentActivities.stream() + .map(ActivityInstance::getActivityId) + .collect(Collectors.toList()), + nodeId + ) + .changeState(); + } + + + return success("klw测试"); + } + + } diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java index eda52442..71fe3c7c 100644 --- a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java @@ -83,7 +83,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { if (StrUtil.isEmpty(activity.getTaskId())) { return; } - taskService.processTaskCanceled(activity.getTaskId()); + taskService.processTaskCanceled(activity.getTaskId(), null); }); } diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index d675a7e0..ba18ea8d 100644 --- a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -235,7 +235,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @Override public List getNextApprovalNodes(Long loginUserId, BpmApprovalDetailReqVO reqVO) { // 1.1 校验任务存在,且是当前用户的 - Task task = taskService.validateTask(loginUserId, reqVO.getTaskId()); + Task task = taskService.validateTask(loginUserId, reqVO.getTaskId(), false); // 1.2 校验流程实例存在 ProcessInstance instance = getProcessInstance(task.getProcessInstanceId()); if (instance == null) { diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmTaskService.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmTaskService.java index 8211bfe8..ce638266 100644 --- a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmTaskService.java +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmTaskService.java @@ -98,7 +98,7 @@ public interface BpmTaskService { * @param userId 用户 id * @param taskId task id */ - Task validateTask(Long userId, String taskId); + Task validateTask(Long userId, String taskId, boolean isCheckSuperAdmin); /** * 获取任务 @@ -269,7 +269,7 @@ public interface BpmTaskService { * * @param taskId 任务的编号 */ - void processTaskCanceled(String taskId); + void processTaskCanceled(String taskId, String reason); /** * 处理 Task 设置审批人事件,目前是发送审批消息 diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmTaskServiceImpl.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmTaskServiceImpl.java index 416444bd..308d6823 100644 --- a/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/com/zt/plat/module/bpm/service/task/BpmTaskServiceImpl.java @@ -6,12 +6,14 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.util.*; import cn.hutool.extra.spring.SpringUtil; import cn.hutool.json.JSONUtil; +import com.zt.plat.framework.common.pojo.CommonResult; import com.zt.plat.framework.common.pojo.PageResult; import com.zt.plat.framework.common.util.date.DateUtils; import com.zt.plat.framework.common.util.number.NumberUtils; import com.zt.plat.framework.common.util.object.ObjectUtils; import com.zt.plat.framework.common.util.object.PageUtils; import com.zt.plat.framework.datapermission.core.annotation.DataPermission; +import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils; import com.zt.plat.framework.web.core.util.WebFrameworkUtils; import com.zt.plat.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import com.zt.plat.module.bpm.controller.admin.task.vo.task.*; @@ -35,11 +37,13 @@ import com.zt.plat.module.bpm.service.message.BpmMessageService; import com.zt.plat.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO; import com.zt.plat.module.system.api.dept.DeptApi; import com.zt.plat.module.system.api.dept.dto.DeptRespDTO; +import com.zt.plat.module.system.api.permission.PermissionApi; import com.zt.plat.module.system.api.user.AdminUserApi; import com.zt.plat.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.flowable.bpmn.model.*; import org.flowable.engine.HistoryService; import org.flowable.engine.ManagementService; @@ -107,6 +111,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Resource private AdminUserApi adminUserApi; + + @Resource + private PermissionApi permissionApi; + @Resource private DeptApi deptApi; @@ -142,12 +150,17 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Override public BpmTaskRespVO getTodoTask(Long userId, String taskId, String processInstanceId) { + boolean isSelfTask = true; // 1.1 获取指定的用户待办任务 Task todoTask = getMyTodoTask(userId, taskId); // 1.2 获取不到,则获取该流程实例下,第一个用户的待办任务 if (todoTask == null) { todoTask = getMyFirstTodoTask(userId, processInstanceId); } + if (todoTask == null && isSuperAdmin(userId)) { + isSelfTask = false; + todoTask = getMyFirstTodoTaskForAdmin(processInstanceId); + } if (todoTask == null) { return null; } @@ -159,6 +172,19 @@ public class BpmTaskServiceImpl implements BpmTaskService { BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(todoTask.getProcessDefinitionId()); Map buttonsSetting = BpmnModelUtils.parseButtonsSetting( bpmnModel, todoTask.getTaskDefinitionKey()); + // 如果当前登录人是管理员,并且当前任务不属于管理员,则只显示退回,取消和转办按钮 + if (!isSelfTask && buttonsSetting != null) { + Optional.ofNullable(buttonsSetting.get(1)) + .ifPresent(button -> button.setEnable(false)); + Optional.ofNullable(buttonsSetting.get(2)) + .ifPresent(button -> button.setEnable(false)); + Optional.ofNullable(buttonsSetting.get(4)) + .ifPresent(button -> button.setEnable(false)); + Optional.ofNullable(buttonsSetting.get(5)) + .ifPresent(button -> button.setEnable(false)); + Optional.ofNullable(buttonsSetting.get(7)) + .ifPresent(button -> button.setEnable(false)); + } Boolean signEnable = parseSignEnable(bpmnModel, todoTask.getTaskDefinitionKey()); Boolean reasonRequire = parseReasonRequire(bpmnModel, todoTask.getTaskDefinitionKey()); Integer nodeType = parseNodeType(BpmnModelUtils.getFlowElementById(bpmnModel, todoTask.getTaskDefinitionKey())); @@ -203,7 +229,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { /** * 获得用户指定 processInstanceId 流程编号下的首个“待办”(未审批、且可审核)的任务 * - * @param userId 用户编号 + * @param userId 用户编号 * @param processInstanceId 流程编号 * @return 任务 */ @@ -221,12 +247,47 @@ public class BpmTaskServiceImpl implements BpmTaskService { .list(); // 2. 查询我的首个任务 + SecurityFrameworkUtils.getLoginUser(); return CollUtil.findOne(tasks, task -> { return isAssignUserTask(userId, task) // 当前用户为审批人 || isAddSignUserTask(userId, task); // 当前用户为加签人(为了减签) }); } + /** + * 获得指定 processInstanceId 流程编号下的首个“待办”(未审批、且可审核)的任务(超级管理员用) + * + * @param processInstanceId 流程编号 + * @return 任务 + */ + private Task getMyFirstTodoTaskForAdmin(String processInstanceId) { + if (processInstanceId == null) { + return null; + } + // 1. 查询所有任务 + List tasks = taskService.createTaskQuery() + .active() + .processInstanceId(processInstanceId) + .includeTaskLocalVariables() + .includeProcessVariables() + .orderByTaskCreateTime().asc() // 按创建时间升序 + .list(); + + // 2. 查询首个任务 + SecurityFrameworkUtils.getLoginUser(); + return CollUtil.findOne(tasks, task -> { + return true; // 当前用户为超级管理员 + }); + } + + private boolean isSuperAdmin(Long userId) { + CommonResult result = permissionApi.isSuperAdmin(userId); + if (result != null) { + return result.getCheckedData(); + } + return false; + } + @Override public PageResult getTaskDonePage(Long userId, BpmTaskPageReqVO pageVO) { HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery() @@ -301,10 +362,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { } @Override - public Task validateTask(Long userId, String taskId) { + public Task validateTask(Long userId, String taskId, boolean isCheckSuperAdmin) { Task task = validateTaskExist(taskId); // 为什么判断 assignee 非空的情况下? // 例如说:在审批人为空时,我们会有“自动审批通过”的策略,此时 userId 为 null,允许通过 + if (isCheckSuperAdmin && isSuperAdmin(userId)) { + return task; + } if (StrUtil.isNotBlank(task.getAssignee()) && ObjectUtil.notEqual(userId, NumberUtils.parseLong(task.getAssignee()))) { throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF); @@ -528,7 +592,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Transactional(rollbackFor = Exception.class) public void approveTask(Long userId, @Valid BpmTaskApproveReqVO reqVO) { // 1.1 校验任务存在 - Task task = validateTask(userId, reqVO.getId()); + Task task = validateTask(userId, reqVO.getId(), false); // 1.2 校验流程实例存在 ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); if (instance == null) { @@ -601,15 +665,15 @@ public class BpmTaskServiceImpl implements BpmTaskService { /** * 校验选择的下一个节点的审批人,是否合法 - * + *

* 1. 是否有漏选:没有选择审批人 * 2. 是否有多选:非下一个节点 * * @param taskDefinitionKey 当前任务节点标识 - * @param variables 流程变量 - * @param bpmnModel 流程模型 - * @param nextAssignees 下一个节点审批人集合(参数) - * @param processInstance 流程实例 + * @param variables 流程变量 + * @param bpmnModel 流程模型 + * @param nextAssignees 下一个节点审批人集合(参数) + * @param processInstance 流程实例 */ @SuppressWarnings("unchecked") private Map validateAndSetNextAssignees(String taskDefinitionKey, Map variables, BpmnModel bpmnModel, @@ -661,7 +725,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { approveUserSelectAssignees = new HashMap<>(); } approveUserSelectAssignees.put(nextFlowNode.getId(), assignees); - Map> existingApproveUserSelectAssignees = (Map>) variables.get( + Map> existingApproveUserSelectAssignees = (Map>) variables.get( BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_APPROVE_USER_SELECT_ASSIGNEES); if (CollUtil.isNotEmpty(existingApproveUserSelectAssignees)) { approveUserSelectAssignees.putAll(existingApproveUserSelectAssignees); @@ -771,7 +835,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Transactional(rollbackFor = Exception.class) public void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO) { // 1.1 校验任务存在 - Task task = validateTask(userId, reqVO.getId()); + Task task = validateTask(userId, reqVO.getId(), false); // 1.2 校验流程实例存在 ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId()); if (instance == null) { @@ -837,7 +901,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Transactional(rollbackFor = Exception.class) public void returnTask(Long userId, BpmTaskReturnReqVO reqVO) { // 1.1 当前任务 task - Task task = validateTask(userId, reqVO.getId()); + Task task = validateTask(userId, reqVO.getId(), true); if (task.isSuspended()) { throw exception(TASK_IS_PENDING); } @@ -895,6 +959,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { List runExecutionIds = new ArrayList<>(); // 2. 给当前要被退回的 task 数组,设置退回意见 + boolean isSuperAdmin = isSuperAdmin(userId); + String reasonPrefix = isSuperAdmin ? "【管理员操作】" : ""; taskList.forEach(task -> { // 需要排除掉,不需要设置退回意见的任务 if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) { @@ -910,7 +976,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 2.1.2 更新 task 状态 + 原因 updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.RETURN.getStatus(), reqVO.getReason()); } else { // 情况二:别人的任务,进行 CANCEL 标记 - processTaskCanceled(task.getId()); + processTaskCanceled(task.getId(), reasonPrefix + reqVO.getReason()); } }); @@ -931,7 +997,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { public void delegateTask(Long userId, BpmTaskDelegateReqVO reqVO) { String taskId = reqVO.getId(); // 1.1 校验任务 - Task task = validateTask(userId, reqVO.getId()); + Task task = validateTask(userId, reqVO.getId(), false); if (task.getAssignee().equals(reqVO.getDelegateUserId().toString())) { // 校验当前审批人和被委派人不是同一人 throw exception(TASK_DELEGATE_FAIL_USER_REPEAT); } @@ -957,7 +1023,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { public void transferTask(Long userId, BpmTaskTransferReqVO reqVO) { String taskId = reqVO.getId(); // 1.1 校验任务 - Task task = validateTask(userId, reqVO.getId()); + Task task = validateTask(userId, reqVO.getId(), true); if (task.getAssignee().equals(reqVO.getAssigneeUserId().toString())) { // 校验当前审批人和被转派人不是同一人 throw exception(TASK_TRANSFER_FAIL_USER_REPEAT); } @@ -994,7 +1060,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { if (BpmTaskStatusEnum.isEndStatus(otherTaskStatus)) { return; } - processTaskCanceled(task.getId()); + processTaskCanceled(task.getId(), null); }); // 2. 终止流程 @@ -1066,7 +1132,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { * @return 当前任务 */ private TaskEntityImpl validateTaskCanCreateSign(Long userId, BpmTaskSignCreateReqVO reqVO) { - TaskEntityImpl taskEntity = (TaskEntityImpl) validateTask(userId, reqVO.getId()); + TaskEntityImpl taskEntity = (TaskEntityImpl) validateTask(userId, reqVO.getId(), false); // 向前加签和向后加签不能同时存在 if (taskEntity.getScopeType() != null && ObjectUtil.notEqual(taskEntity.getScopeType(), reqVO.getType())) { @@ -1217,7 +1283,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { } // 2. 任务前置通知 - if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())){ + if (ObjUtil.isNotNull(processDefinitionInfo.getTaskBeforeTriggerSetting())) { BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskBeforeTriggerSetting(); BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); @@ -1281,7 +1347,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { * 2. 审批不通过:在 {@link #rejectTask(Long, BpmTaskRejectReqVO)} 不通过时,对于加签的任务,不会被 Flowable 删除,此时需要通过该方法更新状态为已取消 */ @Override - public void processTaskCanceled(String taskId) { + public void processTaskCanceled(String taskId, String reason) { Task task = getTask(taskId); // 1. 可能只是活动,不是任务,所以查询不到 if (task == null) { @@ -1295,7 +1361,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { log.error("[updateTaskStatusWhenCanceled][taskId({}) 处于结果({}),无需进行更新]", taskId, status); return; } - updateTaskStatusAndReason(taskId, BpmTaskStatusEnum.CANCEL.getStatus(), BpmReasonEnum.CANCEL_BY_SYSTEM.getReason()); + updateTaskStatusAndReason(taskId, BpmTaskStatusEnum.CANCEL.getStatus(), StringUtils.isNotBlank(reason) ? reason : BpmReasonEnum.CANCEL_BY_SYSTEM.getReason()); // 补充说明:由于 Task 被删除成 HistoricTask 后,无法通过 taskService.addComment 添加理由,所以无法存储具体的取消理由 } @@ -1344,7 +1410,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { .taskVariableValueEquals(BpmnVariableConstants.TASK_VARIABLE_STATUS, BpmTaskStatusEnum.APPROVE.getStatus()) .finished(); if (BpmAutoApproveTypeEnum.APPROVE_ALL.getType().equals(processDefinitionInfo.getAutoApprovalType()) - && sameAssigneeQuery.count() > 0) { + && sameAssigneeQuery.count() > 0) { getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) .setReason(BpmAutoApproveTypeEnum.APPROVE_ALL.getName())); return; @@ -1356,7 +1422,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { return; } List sourceTaskIds = convertList(BpmnModelUtils.getElementIncomingFlows( // 获取所有上一个节点 - BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey())), + BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey())), SequenceFlow::getSourceRef); if (sameAssigneeQuery.taskDefinitionKeys(sourceTaskIds).count() > 0) { getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) @@ -1451,7 +1517,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { } // 任务后置通知 - if (ObjUtil.isNotNull(processDefinitionInfo.getTaskAfterTriggerSetting())){ + if (ObjUtil.isNotNull(processDefinitionInfo.getTaskAfterTriggerSetting())) { BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getTaskAfterTriggerSetting(); BpmHttpRequestUtils.executeBpmHttpRequest(processInstance, setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); From 860f13914dfd111af54e32703f7b2993ce0e9f8b Mon Sep 17 00:00:00 2001 From: ranke <213539@qq.com> Date: Thu, 29 Jan 2026 18:15:57 +0800 Subject: [PATCH 06/11] =?UTF-8?q?bpm=E5=8A=A0=E5=85=A5maven=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1b493aeb..b7d8729d 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ zt-module-system zt-module-infra - + zt-module-bpm zt-module-report From bcdba608c74e194eb01c080ebeec86d44d61ac32 Mon Sep 17 00:00:00 2001 From: wuzongyong <13203449218@163.com> Date: Fri, 30 Jan 2026 10:06:45 +0800 Subject: [PATCH 07/11] =?UTF-8?q?feat(iwork):=20=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E5=88=9B=E5=BB=BA=E6=97=A5=E5=BF=97=E5=92=8C?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E6=97=A5=E5=BF=97(iwork=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E7=94=A8=E5=8D=B0=E5=86=85=E5=AE=B9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将原 iwork_workflow_log.sql 重命名为 iwork_workflow_log_20260130.sql - 在数据库表中新增回调状态、重试次数、错误信息等字段 - 更新表注释为 iWork 流程日志,表明合并了创建日志和回调日志 - 修改 requestId 字段长度从 64 扩展到 128 - 新增回调相关索引配置 - 删除 IWorkCallbackLogService 相关接口及实现类 - 将 IWorkBizCallbackListener 中的日志服务替换为工作流日志服务 - 在控制器层将回调日志查询统一到工作流日志服务 - 合并 IWorkIntegrationServiceImpl 中的流程日志处理逻辑 - 移除独立的用印流程回调日志实体类 IWorkSealLogDO - 在 IWorkWorkflowLogDO 中增加回调相关字段定义 - 完善工作流日志服务接口和实现类,支持回调状态管理 - 更新流程回调处理逻辑,统一使用工作流日志表进行状态跟踪 --- ...og.sql => iwork_workflow_log_20260130.sql} | 29 +++- sql/dm/system_iwork_seal_log_dm8.sql | 43 ------ .../src/main/resources/application.yaml | 1 - .../iwork/IWorkIntegrationController.java | 15 +- .../iwork/vo/IWorkWorkflowCallbackReqVO.java | 26 ---- .../dal/dataobject/iwork/IWorkSealLogDO.java | 72 ---------- .../dataobject/iwork/IWorkWorkflowLogDO.java | 48 ++++++- .../dal/mysql/iwork/IWorkSealLogMapper.java | 27 ---- .../mysql/iwork/IWorkWorkflowLogMapper.java | 14 ++ .../mq/iwork/IWorkBizCallbackListener.java | 14 +- .../iwork/IWorkCallbackLogService.java | 21 --- .../iwork/IWorkWorkflowLogService.java | 38 ++++- .../impl/IWorkCallbackLogServiceImpl.java | 130 ------------------ .../impl/IWorkIntegrationServiceImpl.java | 26 +--- .../impl/IWorkWorkflowLogServiceImpl.java | 112 +++++++++++++++ .../impl/IWorkCallbackLogServiceImplTest.java | 63 --------- 16 files changed, 243 insertions(+), 436 deletions(-) rename sql/dm/{iwork_workflow_log.sql => iwork_workflow_log_20260130.sql} (54%) delete mode 100644 sql/dm/system_iwork_seal_log_dm8.sql delete mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkWorkflowCallbackReqVO.java delete mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/iwork/IWorkSealLogDO.java delete mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/iwork/IWorkSealLogMapper.java delete mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkCallbackLogService.java delete mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkCallbackLogServiceImpl.java delete mode 100644 zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkCallbackLogServiceImplTest.java diff --git a/sql/dm/iwork_workflow_log.sql b/sql/dm/iwork_workflow_log_20260130.sql similarity index 54% rename from sql/dm/iwork_workflow_log.sql rename to sql/dm/iwork_workflow_log_20260130.sql index 6af5cd50..1f0074fc 100644 --- a/sql/dm/iwork_workflow_log.sql +++ b/sql/dm/iwork_workflow_log_20260130.sql @@ -1,12 +1,19 @@ --- iWork 流程创建日志表(达梦数据库) +-- iWork 流程日志表(达梦数据库) +-- 合并了流程创建日志和回调日志 CREATE TABLE system_iwork_workflow_log ( id BIGINT NOT NULL, - request_id VARCHAR(64) NOT NULL, + request_id VARCHAR(128) NOT NULL, workflow_id BIGINT, business_code VARCHAR(128), biz_callback_key VARCHAR(255), raw_request VARCHAR(2000), status VARCHAR(32), + callback_status INTEGER, + retry_count INTEGER DEFAULT 0, + max_retry INTEGER, + last_error_message VARCHAR(512), + raw_callback VARCHAR(2000), + last_callback_time TIMESTAMP, creator VARCHAR(64) DEFAULT '', create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updater VARCHAR(64) DEFAULT '', @@ -17,14 +24,20 @@ CREATE TABLE system_iwork_workflow_log ( ); -- 添加注释 -COMMENT ON TABLE system_iwork_workflow_log IS 'iWork 流程创建日志'; +COMMENT ON TABLE system_iwork_workflow_log IS 'iWork 流程日志'; COMMENT ON COLUMN system_iwork_workflow_log.id IS '主键'; -COMMENT ON COLUMN system_iwork_workflow_log.request_id IS 'iWork 返回的请求编号'; +COMMENT ON COLUMN system_iwork_workflow_log.request_id IS 'iWork 请求编号'; COMMENT ON COLUMN system_iwork_workflow_log.workflow_id IS '流程模板 ID'; COMMENT ON COLUMN system_iwork_workflow_log.business_code IS '业务编码'; COMMENT ON COLUMN system_iwork_workflow_log.biz_callback_key IS '业务回调标识'; -COMMENT ON COLUMN system_iwork_workflow_log.raw_request IS '创建请求原始参数'; +COMMENT ON COLUMN system_iwork_workflow_log.raw_request IS '创建请求原文'; COMMENT ON COLUMN system_iwork_workflow_log.status IS '流程状态'; +COMMENT ON COLUMN system_iwork_workflow_log.callback_status IS '回调处理状态'; +COMMENT ON COLUMN system_iwork_workflow_log.retry_count IS '已重试次数'; +COMMENT ON COLUMN system_iwork_workflow_log.max_retry IS '最大重试次数'; +COMMENT ON COLUMN system_iwork_workflow_log.last_error_message IS '最后错误信息'; +COMMENT ON COLUMN system_iwork_workflow_log.raw_callback IS '回调原文'; +COMMENT ON COLUMN system_iwork_workflow_log.last_callback_time IS '最近回调时间'; COMMENT ON COLUMN system_iwork_workflow_log.creator IS '创建者'; COMMENT ON COLUMN system_iwork_workflow_log.create_time IS '创建时间'; COMMENT ON COLUMN system_iwork_workflow_log.updater IS '更新者'; @@ -33,6 +46,8 @@ COMMENT ON COLUMN system_iwork_workflow_log.deleted IS '是否删除'; COMMENT ON COLUMN system_iwork_workflow_log.tenant_id IS '租户编号'; -- 创建唯一索引 -CREATE UNIQUE INDEX uk_request_id ON system_iwork_workflow_log(request_id); - +CREATE UNIQUE INDEX uk_iwork_workflow_log_request_id ON system_iwork_workflow_log(request_id); +-- 创建普通索引 +-- CREATE INDEX idx_iwork_workflow_log_business_code ON system_iwork_workflow_log(business_code); +-- CREATE INDEX idx_iwork_workflow_log_biz_callback_key ON system_iwork_workflow_log(biz_callback_key); diff --git a/sql/dm/system_iwork_seal_log_dm8.sql b/sql/dm/system_iwork_seal_log_dm8.sql deleted file mode 100644 index 5451ee8e..00000000 --- a/sql/dm/system_iwork_seal_log_dm8.sql +++ /dev/null @@ -1,43 +0,0 @@ --- iWork 用印回调日志(DM8) --- 表:system_iwork_seal_log --- 序列:system_iwork_seal_log_seq - --- 清理旧对象(若存在) -DROP TABLE IF EXISTS system_iwork_seal_log; - -CREATE TABLE system_iwork_seal_log ( - id BIGINT NOT NULL, - request_id VARCHAR(128) NOT NULL, - business_code VARCHAR(128), - biz_callback_key VARCHAR(255), - status INTEGER, - retry_count INTEGER DEFAULT 0, - max_retry INTEGER, - last_error_message VARCHAR(512), - raw_callback VARCHAR(2000), - last_callback_time DATETIME, - creator VARCHAR(64), - create_time DATETIME DEFAULT SYSDATE, - updater VARCHAR(64), - update_time DATETIME DEFAULT SYSDATE, - deleted SMALLINT DEFAULT 0 NOT NULL, - PRIMARY KEY (id), - UNIQUE (request_id) -); - -COMMENT ON TABLE system_iwork_seal_log IS 'iWork 用印回调日志'; -COMMENT ON COLUMN system_iwork_seal_log.id IS '主键'; -COMMENT ON COLUMN system_iwork_seal_log.request_id IS 'iWork requestId 唯一标识'; -COMMENT ON COLUMN system_iwork_seal_log.business_code IS '业务单号'; -COMMENT ON COLUMN system_iwork_seal_log.biz_callback_key IS '业务回调标识'; -COMMENT ON COLUMN system_iwork_seal_log.status IS '状态枚举'; -COMMENT ON COLUMN system_iwork_seal_log.retry_count IS '已重试次数'; -COMMENT ON COLUMN system_iwork_seal_log.max_retry IS '最大重试次数快照'; -COMMENT ON COLUMN system_iwork_seal_log.last_error_message IS '最后错误信息'; -COMMENT ON COLUMN system_iwork_seal_log.raw_callback IS '回调原文截断'; -COMMENT ON COLUMN system_iwork_seal_log.last_callback_time IS '最近回调时间'; -COMMENT ON COLUMN system_iwork_seal_log.creator IS '创建者'; -COMMENT ON COLUMN system_iwork_seal_log.create_time IS '创建时间'; -COMMENT ON COLUMN system_iwork_seal_log.updater IS '更新者'; -COMMENT ON COLUMN system_iwork_seal_log.update_time IS '最后更新时间'; -COMMENT ON COLUMN system_iwork_seal_log.deleted IS '是否删除'; diff --git a/zt-module-system/zt-module-system-server-app/src/main/resources/application.yaml b/zt-module-system/zt-module-system-server-app/src/main/resources/application.yaml index 482543a6..853e32cd 100644 --- a/zt-module-system/zt-module-system-server-app/src/main/resources/application.yaml +++ b/zt-module-system/zt-module-system-server-app/src/main/resources/application.yaml @@ -229,7 +229,6 @@ zt: - system_seq_dtl - system_seq_rcd - system_sync_log - - system_iwork_seal_log ignore-caches: - user_role_ids - permission_menu_ids diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/IWorkIntegrationController.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/IWorkIntegrationController.java index 4918aa6d..82232f9b 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/IWorkIntegrationController.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/IWorkIntegrationController.java @@ -3,10 +3,11 @@ package com.zt.plat.module.system.controller.admin.integration.iwork; import com.zt.plat.framework.common.pojo.CommonResult; import com.zt.plat.framework.tenant.core.aop.TenantIgnore; import com.zt.plat.module.system.controller.admin.integration.iwork.vo.*; -import com.zt.plat.module.system.service.integration.iwork.IWorkCallbackLogService; import com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationService; import com.zt.plat.module.system.service.integration.iwork.IWorkOrgRestService; import com.zt.plat.module.system.service.integration.iwork.IWorkSyncService; +import com.zt.plat.module.system.service.integration.iwork.IWorkWorkflowLogService; +import com.zt.plat.module.system.dal.dataobject.iwork.IWorkWorkflowLogDO; import lombok.extern.slf4j.Slf4j; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -37,7 +38,7 @@ public class IWorkIntegrationController { private final IWorkIntegrationService integrationService; private final IWorkOrgRestService orgRestService; private final IWorkSyncService syncService; - private final IWorkCallbackLogService callbackLogService; + private final IWorkWorkflowLogService workflowLogService; @PostMapping("/auth/register") @Operation(summary = "注册 iWork 凭证,获取服务端公钥与 secret") @@ -99,17 +100,17 @@ public class IWorkIntegrationController { @PreAuthorize("@ss.hasPermission('system:iwork:log:query')") @PostMapping("/log/page") - @Operation(summary = "iWork 回调日志分页查询") + @Operation(summary = "iWork 流程日志分页查询") public CommonResult> pageLogs(@Valid @RequestBody IWorkCallbackLogPageReqVO reqVO) { - com.zt.plat.framework.common.pojo.PageResult page = callbackLogService.page(reqVO); + com.zt.plat.framework.common.pojo.PageResult page = workflowLogService.page(reqVO); java.util.List mapped = new java.util.ArrayList<>(); - for (com.zt.plat.module.system.dal.dataobject.iwork.IWorkSealLogDO log : page.getList()) { + for (IWorkWorkflowLogDO log : page.getList()) { IWorkCallbackLogRespVO vo = new IWorkCallbackLogRespVO(); vo.setId(log.getId()); vo.setRequestId(log.getRequestId()); vo.setBusinessCode(log.getBusinessCode()); vo.setBizCallbackKey(log.getBizCallbackKey()); - vo.setStatus(log.getStatus()); + vo.setStatus(log.getCallbackStatus()); vo.setRetryCount(log.getRetryCount()); vo.setMaxRetry(log.getMaxRetry()); vo.setLastErrorMessage(log.getLastErrorMessage()); @@ -126,7 +127,7 @@ public class IWorkIntegrationController { @PostMapping("/log/retry") @Operation(summary = "iWork 回调手工重试") public CommonResult retry(@Valid @RequestBody IWorkWorkflowVoidReqVO reqVO) { - callbackLogService.resetAndDispatch(reqVO.getRequestId()); + workflowLogService.resetAndDispatch(reqVO.getRequestId()); return success(true); } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkWorkflowCallbackReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkWorkflowCallbackReqVO.java deleted file mode 100644 index 66cf7d87..00000000 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/integration/iwork/vo/IWorkWorkflowCallbackReqVO.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.zt.plat.module.system.controller.admin.integration.iwork.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import lombok.Data; - -@Schema(description = "iWork 流程回调请求") -@Data -public class IWorkWorkflowCallbackReqVO { - - @Schema(description = "iWork requestId,唯一标识", requiredMode = Schema.RequiredMode.REQUIRED) - @NotBlank(message = "requestId 不能为空") - private String requestId; - - @Schema(description = "业务单号 (ywxtdjbh)") - private String businessCode; - - @Schema(description = "业务回调标识 bizCallbackKey") - private String bizCallbackKey; - - @Schema(description = "回调状态/结果码") - private String status; - - @Schema(description = "原始回调文本(可截断存储)") - private String rawBody; -} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/iwork/IWorkSealLogDO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/iwork/IWorkSealLogDO.java deleted file mode 100644 index 09e1d325..00000000 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/iwork/IWorkSealLogDO.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.zt.plat.module.system.dal.dataobject.iwork; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import com.zt.plat.framework.mybatis.core.dataobject.BaseDO; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import java.time.LocalDateTime; - -/** - * iWork 用印流程回调日志。 - */ -@TableName("system_iwork_seal_log") -@KeySequence("system_iwork_seal_log_seq") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class IWorkSealLogDO extends BaseDO { - - @TableId(type = IdType.ASSIGN_ID) - private Long id; - - /** - * iWork 返回的请求编号,唯一业务标识。 - */ - private String requestId; - - /** - * 业务单号(ywxtdjbh)。 - */ - private String businessCode; - - /** - * 业务回调标识。 - */ - private String bizCallbackKey; - - /** - * 状态枚举,参考 IWorkCallbackStatusEnum。 - */ - private Integer status; - - /** - * 已执行的自动/手工重试次数。 - */ - private Integer retryCount; - - /** - * 最大重试次数(快照)。 - */ - private Integer maxRetry; - - /** - * 最后一次错误信息。 - */ - private String lastErrorMessage; - - /** - * 回调原始负载(截断)。 - */ - private String rawCallback; - - /** - * 最近一次回调时间。 - */ - private LocalDateTime lastCallbackTime; - -} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/iwork/IWorkWorkflowLogDO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/iwork/IWorkWorkflowLogDO.java index 516d2bfa..0fe6e6a6 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/iwork/IWorkWorkflowLogDO.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/iwork/IWorkWorkflowLogDO.java @@ -9,9 +9,11 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; +import java.time.LocalDateTime; + /** - * iWork 流程创建日志。 - * 用于记录流程创建时的关键信息,供回调时查询使用。 + * iWork 流程日志。 + * 合并了流程创建日志和回调日志,记录完整的流程生命周期。 */ @TableName("system_iwork_workflow_log") @KeySequence("system_iwork_workflow_log_seq") @@ -24,27 +26,27 @@ public class IWorkWorkflowLogDO extends BaseDO { private Long id; /** - * iWork 返回的请求编号,唯一业务标识。 + * iWork 请求编号,唯一业务标识 */ private String requestId; /** - * 流程模板 ID。 + * 流程模板 ID */ private Long workflowId; /** - * 业务编码(用于关联业务数据)。 + * 业务编码(用于关联业务数据) */ private String businessCode; /** - * 业务回调标识(用于 MQ 消息路由)。 + * 业务回调标识(用于 MQ 消息路由) */ private String bizCallbackKey; /** - * 创建请求的原始参数(JSON 格式,截断存储)。 + * 创建请求的原始参数(JSON 格式,截断存储) */ private String rawRequest; @@ -52,4 +54,36 @@ public class IWorkWorkflowLogDO extends BaseDO { * 流程状态:CREATED-已创建, CALLBACK_RECEIVED-已收到回调, COMPLETED-已完成 */ private String status; + + // ========== 回调相关字段 ========== + + /** + * 回调处理状态:1-待处理, 2-处理中, 3-成功, 4-重试中, 5-失败 + */ + private Integer callbackStatus; + + /** + * 已重试次数 + */ + private Integer retryCount; + + /** + * 最大重试次数 + */ + private Integer maxRetry; + + /** + * 最后错误信息 + */ + private String lastErrorMessage; + + /** + * 回调原文(JSON 格式,截断存储) + */ + private String rawCallback; + + /** + * 最近回调时间 + */ + private LocalDateTime lastCallbackTime; } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/iwork/IWorkSealLogMapper.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/iwork/IWorkSealLogMapper.java deleted file mode 100644 index 55ac9ee6..00000000 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/iwork/IWorkSealLogMapper.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.zt.plat.module.system.dal.mysql.iwork; - -import com.zt.plat.framework.common.pojo.PageResult; -import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; -import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; -import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkCallbackLogPageReqVO; -import com.zt.plat.module.system.dal.dataobject.iwork.IWorkSealLogDO; -import org.apache.ibatis.annotations.Mapper; - -@Mapper -public interface IWorkSealLogMapper extends BaseMapperX { - - default IWorkSealLogDO selectByRequestId(String requestId) { - return selectOne(IWorkSealLogDO::getRequestId, requestId); - } - - default PageResult selectPage(IWorkCallbackLogPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eqIfPresent(IWorkSealLogDO::getRequestId, reqVO.getRequestId()) - .eqIfPresent(IWorkSealLogDO::getBusinessCode, reqVO.getBusinessCode()) - .eqIfPresent(IWorkSealLogDO::getBizCallbackKey, reqVO.getBizCallbackKey()) - .eqIfPresent(IWorkSealLogDO::getStatus, reqVO.getStatus()) - .betweenIfPresent(IWorkSealLogDO::getCreateTime, reqVO.getCreateTime()) - .betweenIfPresent(IWorkSealLogDO::getLastCallbackTime, reqVO.getLastCallbackTime()) - .orderByDesc(IWorkSealLogDO::getId)); - } -} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/iwork/IWorkWorkflowLogMapper.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/iwork/IWorkWorkflowLogMapper.java index c8158377..750087b9 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/iwork/IWorkWorkflowLogMapper.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/iwork/IWorkWorkflowLogMapper.java @@ -1,6 +1,9 @@ package com.zt.plat.module.system.dal.mysql.iwork; +import com.zt.plat.framework.common.pojo.PageResult; import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkCallbackLogPageReqVO; import com.zt.plat.module.system.dal.dataobject.iwork.IWorkWorkflowLogDO; import org.apache.ibatis.annotations.Mapper; @@ -10,4 +13,15 @@ public interface IWorkWorkflowLogMapper extends BaseMapperX default IWorkWorkflowLogDO selectByRequestId(String requestId) { return selectOne(IWorkWorkflowLogDO::getRequestId, requestId); } + + default PageResult selectPage(IWorkCallbackLogPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(IWorkWorkflowLogDO::getRequestId, reqVO.getRequestId()) + .eqIfPresent(IWorkWorkflowLogDO::getBusinessCode, reqVO.getBusinessCode()) + .eqIfPresent(IWorkWorkflowLogDO::getBizCallbackKey, reqVO.getBizCallbackKey()) + .eqIfPresent(IWorkWorkflowLogDO::getCallbackStatus, reqVO.getStatus()) + .betweenIfPresent(IWorkWorkflowLogDO::getCreateTime, reqVO.getCreateTime()) + .betweenIfPresent(IWorkWorkflowLogDO::getLastCallbackTime, reqVO.getLastCallbackTime()) + .orderByDesc(IWorkWorkflowLogDO::getId)); + } } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/iwork/IWorkBizCallbackListener.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/iwork/IWorkBizCallbackListener.java index c92bfe45..0713f4b9 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/iwork/IWorkBizCallbackListener.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/iwork/IWorkBizCallbackListener.java @@ -1,9 +1,7 @@ package com.zt.plat.module.system.mq.iwork; import com.zt.plat.module.system.framework.integration.iwork.config.IWorkProperties; -import com.zt.plat.module.system.service.integration.iwork.IWorkCallbackLogService; -import com.zt.plat.module.system.mq.iwork.IWorkBizCallbackMessage; -import com.zt.plat.module.system.mq.iwork.IWorkBizCallbackResultMessage; +import com.zt.plat.module.system.service.integration.iwork.IWorkWorkflowLogService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; @@ -24,7 +22,7 @@ import java.util.concurrent.TimeUnit; @RocketMQMessageListener(topic = IWorkBizCallbackResultMessage.TOPIC, consumerGroup = IWorkBizCallbackResultMessage.TOPIC + "_CONSUMER") public class IWorkBizCallbackListener implements RocketMQListener, InitializingBean { - private final IWorkCallbackLogService logService; + private final IWorkWorkflowLogService workflowLogService; private final IWorkProperties properties; private final IWorkBizCallbackProducer producer; private ScheduledExecutorService scheduler; @@ -38,16 +36,16 @@ public class IWorkBizCallbackListener implements RocketMQListener 0 ? message.getMaxAttempts() : properties.getCallback().getRetry().getMaxAttempts(); if (attempt < maxAttempts) { - logService.markFailure(message.getRequestId(), message.getErrorMessage(), true, maxAttempts); + workflowLogService.markCallbackFailure(message.getRequestId(), message.getErrorMessage(), true, maxAttempts); IWorkBizCallbackMessage next = IWorkBizCallbackMessage.builder() .requestId(message.getRequestId()) @@ -60,7 +58,7 @@ public class IWorkBizCallbackListener implements RocketMQListener producer.send(next), delay, TimeUnit.SECONDS); } else { - logService.markFailure(message.getRequestId(), message.getErrorMessage(), false, maxAttempts); + workflowLogService.markCallbackFailure(message.getRequestId(), message.getErrorMessage(), false, maxAttempts); } } } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkCallbackLogService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkCallbackLogService.java deleted file mode 100644 index c87ba5de..00000000 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkCallbackLogService.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.zt.plat.module.system.service.integration.iwork; - -import com.zt.plat.framework.common.pojo.PageResult; -import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkCallbackLogPageReqVO; -import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkWorkflowCallbackReqVO; -import com.zt.plat.module.system.dal.dataobject.iwork.IWorkSealLogDO; - -public interface IWorkCallbackLogService { - - IWorkSealLogDO upsertOnCallback(IWorkWorkflowCallbackReqVO reqVO, int maxRetry, String rawBody); - - void markSuccess(String requestId); - - void markFailure(String requestId, String error, boolean retrying, int maxRetry); - - void incrementRetry(String requestId); - - PageResult page(IWorkCallbackLogPageReqVO reqVO); - - void resetAndDispatch(String requestId); -} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkWorkflowLogService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkWorkflowLogService.java index fd577337..bbce47f2 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkWorkflowLogService.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/IWorkWorkflowLogService.java @@ -1,9 +1,11 @@ package com.zt.plat.module.system.service.integration.iwork; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkCallbackLogPageReqVO; import com.zt.plat.module.system.dal.dataobject.iwork.IWorkWorkflowLogDO; /** - * iWork 流程创建日志 Service + * iWork 流程日志 Service */ public interface IWorkWorkflowLogService { @@ -13,7 +15,7 @@ public interface IWorkWorkflowLogService { void saveWorkflowLog(IWorkWorkflowLogDO logDO); /** - * 根据 requestId 查询流程创建日志 + * 根据 requestId 查询流程日志 */ IWorkWorkflowLogDO getByRequestId(String requestId); @@ -21,4 +23,36 @@ public interface IWorkWorkflowLogService { * 更新流程状态 */ void updateStatus(String requestId, String status); + + // ========== 回调相关方法 ========== + + /** + * 更新回调信息(收到回调时调用) + */ + void updateCallback(String requestId, String rawCallback, int maxRetry); + + /** + * 标记回调成功 + */ + void markCallbackSuccess(String requestId); + + /** + * 标记回调失败 + */ + void markCallbackFailure(String requestId, String errorMessage, boolean retrying, int maxRetry); + + /** + * 增加重试次数 + */ + void incrementRetry(String requestId); + + /** + * 分页查询流程日志 + */ + PageResult page(IWorkCallbackLogPageReqVO reqVO); + + /** + * 重置并重新派发回调 + */ + void resetAndDispatch(String requestId); } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkCallbackLogServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkCallbackLogServiceImpl.java deleted file mode 100644 index d46dbbe6..00000000 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkCallbackLogServiceImpl.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.zt.plat.module.system.service.integration.iwork.impl; - -import com.zt.plat.framework.common.pojo.PageResult; -import com.zt.plat.framework.common.util.json.JsonUtils; -import com.zt.plat.framework.common.util.object.ObjectUtils; -import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkCallbackLogPageReqVO; -import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkWorkflowCallbackReqVO; -import com.zt.plat.module.system.dal.dataobject.iwork.IWorkSealLogDO; -import com.zt.plat.module.system.dal.mysql.iwork.IWorkSealLogMapper; -import com.zt.plat.module.system.framework.integration.iwork.config.IWorkProperties; -import com.zt.plat.module.system.mq.iwork.IWorkBizCallbackMessage; -import com.zt.plat.module.system.mq.iwork.IWorkBizCallbackProducer; -import com.zt.plat.module.system.service.integration.iwork.IWorkCallbackLogService; -import com.zt.plat.module.system.service.integration.iwork.enums.IWorkCallbackStatusEnum; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StringUtils; - -import java.time.LocalDateTime; -import java.util.Optional; - -@Service -@RequiredArgsConstructor -public class IWorkCallbackLogServiceImpl implements IWorkCallbackLogService { - - private static final int RAW_MAX = 2000; - - private final IWorkSealLogMapper logMapper; - private final IWorkBizCallbackProducer producer; - private final IWorkProperties properties; - - @Override - @Transactional(rollbackFor = Exception.class) - public IWorkSealLogDO upsertOnCallback(IWorkWorkflowCallbackReqVO reqVO, int maxRetry, String rawBody) { - IWorkSealLogDO existing = logMapper.selectByRequestId(reqVO.getRequestId()); - IWorkSealLogDO log = Optional.ofNullable(existing).orElseGet(IWorkSealLogDO::new); - log.setRequestId(reqVO.getRequestId()); - log.setBusinessCode(reqVO.getBusinessCode()); - log.setBizCallbackKey(reqVO.getBizCallbackKey()); - log.setStatus(IWorkCallbackStatusEnum.CALLBACK_PENDING.getStatus()); - log.setRetryCount(ObjectUtils.defaultIfNull(log.getRetryCount(), 0)); - log.setMaxRetry(maxRetry); - log.setRawCallback(truncate(rawBody)); - log.setLastCallbackTime(LocalDateTime.now()); - if (log.getId() == null) { - logMapper.insert(log); - } else { - logMapper.updateById(log); - } - return log; - } - - @Override - public void markSuccess(String requestId) { - IWorkSealLogDO log = new IWorkSealLogDO(); - log.setRequestId(requestId); - log.setStatus(IWorkCallbackStatusEnum.CALLBACK_SUCCESS.getStatus()); - log.setLastErrorMessage(null); - log.setLastCallbackTime(LocalDateTime.now()); - logMapper.update(log, new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() - .eq(IWorkSealLogDO::getRequestId, requestId)); - } - - @Override - public void markFailure(String requestId, String error, boolean retrying, int maxRetry) { - IWorkSealLogDO log = new IWorkSealLogDO(); - log.setRequestId(requestId); - log.setStatus(retrying ? IWorkCallbackStatusEnum.CALLBACK_RETRYING.getStatus() : IWorkCallbackStatusEnum.CALLBACK_FAILED.getStatus()); - log.setLastErrorMessage(error); - log.setLastCallbackTime(LocalDateTime.now()); - log.setMaxRetry(maxRetry); - logMapper.update(log, new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() - .eq(IWorkSealLogDO::getRequestId, requestId)); - } - - @Override - public void incrementRetry(String requestId) { - IWorkSealLogDO db = logMapper.selectByRequestId(requestId); - if (db == null) { - return; - } - IWorkSealLogDO log = new IWorkSealLogDO(); - log.setId(db.getId()); - log.setRetryCount(ObjectUtils.defaultIfNull(db.getRetryCount(), 0) + 1); - log.setLastCallbackTime(LocalDateTime.now()); - logMapper.updateById(log); - } - - @Override - public PageResult page(IWorkCallbackLogPageReqVO reqVO) { - return logMapper.selectPage(reqVO); - } - - @Override - public void resetAndDispatch(String requestId) { - IWorkSealLogDO db = logMapper.selectByRequestId(requestId); - if (db == null) { - return; - } - IWorkSealLogDO log = new IWorkSealLogDO(); - log.setId(db.getId()); - log.setRetryCount(0); - log.setStatus(IWorkCallbackStatusEnum.CALLBACK_RETRYING.getStatus()); - log.setLastCallbackTime(LocalDateTime.now()); - logMapper.updateById(log); - - int maxAttempts = properties.getCallback().getRetry().getMaxAttempts(); - Object payload; - try { - payload = JsonUtils.parseObject(db.getRawCallback(), Object.class); - } catch (Exception ex) { - payload = db.getRawCallback(); - } - producer.send(IWorkBizCallbackMessage.builder() - .requestId(db.getRequestId()) - .bizCallbackKey(db.getBizCallbackKey()) - .payload(payload) - .attempt(0) - .maxAttempts(maxAttempts) - .build()); - } - - private String truncate(String raw) { - if (!StringUtils.hasText(raw)) { - return raw; - } - return raw.length() > RAW_MAX ? raw.substring(0, RAW_MAX) : raw; - } -} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkIntegrationServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkIntegrationServiceImpl.java index d184dd7e..67a191c2 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkIntegrationServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkIntegrationServiceImpl.java @@ -20,7 +20,6 @@ import com.zt.plat.module.system.controller.admin.integration.iwork.vo.*; import com.zt.plat.module.system.framework.integration.iwork.config.IWorkProperties; import com.zt.plat.module.system.mq.iwork.IWorkBizCallbackMessage; import com.zt.plat.module.system.mq.iwork.IWorkBizCallbackProducer; -import com.zt.plat.module.system.service.integration.iwork.IWorkCallbackLogService; import com.zt.plat.module.system.service.integration.iwork.IWorkIntegrationService; import com.zt.plat.module.system.service.integration.iwork.IWorkWorkflowLogService; import com.zt.plat.module.system.dal.dataobject.iwork.IWorkWorkflowLogDO; @@ -65,7 +64,6 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService { private final FileApi fileApi; private final BusinessFileApi businessFileApi; - private final IWorkCallbackLogService callbackLogService; private final IWorkWorkflowLogService workflowLogService; private final IWorkBizCallbackProducer bizCallbackProducer; @@ -250,23 +248,17 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService { TenantUtils.execute(tenantId, () -> attachmentIdRef.set(saveCallbackAttachment(fileUrl, reqVO.getFileName(), referenceBusinessFile))); Long attachmentId = attachmentIdRef.get(); - // 3. 更新回调日志 + // 3. 更新流程日志(回调信息 + 状态) int maxRetry = properties.getCallback().getRetry().getMaxAttempts(); String rawBody = buildCallbackRawBody(reqVO); - IWorkWorkflowCallbackReqVO logReqVO = buildCallbackLogReqVO(reqVO); - if (StringUtils.hasText(bizCallbackKey)) { - logReqVO.setBizCallbackKey(bizCallbackKey); - } - callbackLogService.upsertOnCallback(logReqVO, maxRetry, rawBody); - - // 4. 更新流程创建日志状态 if (workflowLog != null) { + workflowLogService.updateCallback(reqVO.getRequestId(), rawBody, maxRetry); String status = StringUtils.hasText(reqVO.getStatus()) ? reqVO.getStatus() : "CALLBACK_RECEIVED"; workflowLogService.updateStatus(reqVO.getRequestId(), status); - log.info("[handleFileCallback] 已更新流程状态: requestId={}, status={}", reqVO.getRequestId(), status); + log.info("[handleFileCallback] 已更新流程日志: requestId={}, status={}", reqVO.getRequestId(), status); } - // 5. 发送 MQ 通知业务系统(仅当 bizCallbackKey 存在时发送) + // 4. 发送 MQ 通知业务系统(仅当 bizCallbackKey 存在时发送) if (StringUtils.hasText(bizCallbackKey)) { IWorkBizCallbackMessage message = IWorkBizCallbackMessage.builder() .requestId(reqVO.getRequestId()) @@ -286,16 +278,6 @@ public class IWorkIntegrationServiceImpl implements IWorkIntegrationService { return attachmentId; } - private IWorkWorkflowCallbackReqVO buildCallbackLogReqVO(IWorkFileCallbackReqVO reqVO) { - IWorkWorkflowCallbackReqVO logReqVO = new IWorkWorkflowCallbackReqVO(); - logReqVO.setRequestId(reqVO.getRequestId()); - logReqVO.setBusinessCode(reqVO.getBusinessCode()); - logReqVO.setBizCallbackKey(reqVO.getBizCallbackKey()); - logReqVO.setStatus(reqVO.getStatus()); - logReqVO.setRawBody(reqVO.getRawBody()); - return logReqVO; - } - private String buildCallbackRawBody(IWorkFileCallbackReqVO reqVO) { if (StringUtils.hasText(reqVO.getRawBody())) { return reqVO.getRawBody(); diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkWorkflowLogServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkWorkflowLogServiceImpl.java index 6f3bd117..6587669c 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkWorkflowLogServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkWorkflowLogServiceImpl.java @@ -1,16 +1,27 @@ package com.zt.plat.module.system.service.integration.iwork.impl; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.json.JsonUtils; +import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkCallbackLogPageReqVO; import com.zt.plat.module.system.dal.dataobject.iwork.IWorkWorkflowLogDO; import com.zt.plat.module.system.dal.mysql.iwork.IWorkWorkflowLogMapper; +import com.zt.plat.module.system.framework.integration.iwork.config.IWorkProperties; +import com.zt.plat.module.system.mq.iwork.IWorkBizCallbackMessage; +import com.zt.plat.module.system.mq.iwork.IWorkBizCallbackProducer; import com.zt.plat.module.system.service.integration.iwork.IWorkWorkflowLogService; +import com.zt.plat.module.system.service.integration.iwork.enums.IWorkCallbackStatusEnum; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; + @Service @RequiredArgsConstructor public class IWorkWorkflowLogServiceImpl implements IWorkWorkflowLogService { private final IWorkWorkflowLogMapper workflowLogMapper; + private final IWorkProperties properties; + private final IWorkBizCallbackProducer bizCallbackProducer; @Override public void saveWorkflowLog(IWorkWorkflowLogDO logDO) { @@ -32,4 +43,105 @@ public class IWorkWorkflowLogServiceImpl implements IWorkWorkflowLogService { workflowLogMapper.updateById(update); } } + + // ========== 回调相关方法 ========== + + @Override + public void updateCallback(String requestId, String rawCallback, int maxRetry) { + IWorkWorkflowLogDO existing = workflowLogMapper.selectByRequestId(requestId); + if (existing != null) { + IWorkWorkflowLogDO update = new IWorkWorkflowLogDO(); + update.setId(existing.getId()); + update.setCallbackStatus(IWorkCallbackStatusEnum.CALLBACK_PENDING.getStatus()); + update.setRawCallback(truncate(rawCallback, 2000)); + update.setMaxRetry(maxRetry); + update.setRetryCount(existing.getRetryCount() != null ? existing.getRetryCount() : 0); + update.setLastCallbackTime(LocalDateTime.now()); + workflowLogMapper.updateById(update); + } + } + + @Override + public void markCallbackSuccess(String requestId) { + IWorkWorkflowLogDO existing = workflowLogMapper.selectByRequestId(requestId); + if (existing != null) { + IWorkWorkflowLogDO update = new IWorkWorkflowLogDO(); + update.setId(existing.getId()); + update.setCallbackStatus(IWorkCallbackStatusEnum.CALLBACK_SUCCESS.getStatus()); + update.setLastErrorMessage(null); + update.setLastCallbackTime(LocalDateTime.now()); + workflowLogMapper.updateById(update); + } + } + + @Override + public void markCallbackFailure(String requestId, String errorMessage, boolean retrying, int maxRetry) { + IWorkWorkflowLogDO existing = workflowLogMapper.selectByRequestId(requestId); + if (existing != null) { + IWorkWorkflowLogDO update = new IWorkWorkflowLogDO(); + update.setId(existing.getId()); + update.setCallbackStatus(retrying + ? IWorkCallbackStatusEnum.CALLBACK_RETRYING.getStatus() + : IWorkCallbackStatusEnum.CALLBACK_FAILED.getStatus()); + update.setLastErrorMessage(errorMessage); + update.setMaxRetry(maxRetry); + update.setLastCallbackTime(LocalDateTime.now()); + workflowLogMapper.updateById(update); + } + } + + @Override + public void incrementRetry(String requestId) { + IWorkWorkflowLogDO existing = workflowLogMapper.selectByRequestId(requestId); + if (existing != null) { + IWorkWorkflowLogDO update = new IWorkWorkflowLogDO(); + update.setId(existing.getId()); + update.setRetryCount((existing.getRetryCount() != null ? existing.getRetryCount() : 0) + 1); + update.setLastCallbackTime(LocalDateTime.now()); + workflowLogMapper.updateById(update); + } + } + + private String truncate(String raw, int maxLen) { + if (raw == null || raw.length() <= maxLen) { + return raw; + } + return raw.substring(0, maxLen); + } + + @Override + public PageResult page(IWorkCallbackLogPageReqVO reqVO) { + return workflowLogMapper.selectPage(reqVO); + } + + @Override + public void resetAndDispatch(String requestId) { + IWorkWorkflowLogDO existing = workflowLogMapper.selectByRequestId(requestId); + if (existing == null) { + return; + } + // 重置状态 + IWorkWorkflowLogDO update = new IWorkWorkflowLogDO(); + update.setId(existing.getId()); + update.setRetryCount(0); + update.setCallbackStatus(IWorkCallbackStatusEnum.CALLBACK_RETRYING.getStatus()); + update.setLastCallbackTime(LocalDateTime.now()); + workflowLogMapper.updateById(update); + + // 重新派发 + int maxAttempts = properties.getCallback().getRetry().getMaxAttempts(); + Object payload; + try { + payload = JsonUtils.parseObject(existing.getRawCallback(), Object.class); + } catch (Exception ex) { + payload = existing.getRawCallback(); + } + bizCallbackProducer.send(IWorkBizCallbackMessage.builder() + .requestId(existing.getRequestId()) + .bizCallbackKey(existing.getBizCallbackKey()) + .payload(payload) + .attempt(0) + .maxAttempts(maxAttempts) + .build()); + } } diff --git a/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkCallbackLogServiceImplTest.java b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkCallbackLogServiceImplTest.java deleted file mode 100644 index f0287595..00000000 --- a/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/integration/iwork/impl/IWorkCallbackLogServiceImplTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.zt.plat.module.system.service.integration.iwork.impl; - -import com.zt.plat.module.system.controller.admin.integration.iwork.vo.IWorkWorkflowCallbackReqVO; -import com.zt.plat.module.system.dal.dataobject.iwork.IWorkSealLogDO; -import com.zt.plat.module.system.dal.mysql.iwork.IWorkSealLogMapper; -import com.zt.plat.module.system.framework.integration.iwork.config.IWorkProperties; -import com.zt.plat.module.system.mq.iwork.IWorkBizCallbackProducer; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -class IWorkCallbackLogServiceImplTest { - - private IWorkSealLogMapper mapper; - private IWorkBizCallbackProducer producer; - private IWorkProperties properties; - private IWorkCallbackLogServiceImpl service; - - @BeforeEach - void setup() { - mapper = mock(IWorkSealLogMapper.class); - producer = mock(IWorkBizCallbackProducer.class); - properties = new IWorkProperties(); - service = new IWorkCallbackLogServiceImpl(mapper, producer, properties); - } - - @Test - void upsertOnCallback_shouldTruncateRaw() { - String longRaw = "x".repeat(2100); - IWorkWorkflowCallbackReqVO req = new IWorkWorkflowCallbackReqVO(); - req.setRequestId("REQ-1"); - req.setBizCallbackKey("key"); - - ArgumentCaptor captor = ArgumentCaptor.forClass(IWorkSealLogDO.class); - when(mapper.selectByRequestId("REQ-1")).thenReturn(null); - - service.upsertOnCallback(req, 3, longRaw); - - verify(mapper).insert(captor.capture()); - IWorkSealLogDO saved = captor.getValue(); - assertThat(saved.getRawCallback()).hasSize(2000); - assertThat(saved.getMaxRetry()).isEqualTo(3); - } - - @Test - void incrementRetry_shouldIncreaseCount() { - IWorkSealLogDO existing = new IWorkSealLogDO(); - existing.setId(1L); - existing.setRequestId("REQ-2"); - existing.setRetryCount(1); - when(mapper.selectByRequestId("REQ-2")).thenReturn(existing); - - service.incrementRetry("REQ-2"); - - ArgumentCaptor captor = ArgumentCaptor.forClass(IWorkSealLogDO.class); - verify(mapper).updateById(captor.capture()); - assertThat(captor.getValue().getRetryCount()).isEqualTo(2); - } -} From 42c01dc0a4cc6ed343c8d6c491acc505f803a97c Mon Sep 17 00:00:00 2001 From: wuzongyong <13203449218@163.com> Date: Fri, 30 Jan 2026 10:12:45 +0800 Subject: [PATCH 08/11] =?UTF-8?q?docs(iwork):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=94=A8=E5=8D=B0=E6=B5=81=E7=A8=8B=E9=9B=86=E6=88=90=E5=BC=80?= =?UTF-8?q?=E5=8F=91=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增完整的 iWork 用印流程集成开发文档 - 包含整体架构图和完整流程时序图 - 详细说明数据库设计和状态流转机制 - 提供 API 接口说明和请求参数定义 - 描述 MQ 消息机制和消息格式定义 - 编写业务模块接入指南和消费者实现示例 - 说明重试机制配置和手工重试接口 - 提供本地开发调试和常见问题解决方案 - 列出相关代码位置便于查阅和维护 --- docs/iWork用印流程集成开发文档.md | 514 ++++++++++++++++++++++++++++++ 1 file changed, 514 insertions(+) create mode 100644 docs/iWork用印流程集成开发文档.md diff --git a/docs/iWork用印流程集成开发文档.md b/docs/iWork用印流程集成开发文档.md new file mode 100644 index 00000000..c435a76c --- /dev/null +++ b/docs/iWork用印流程集成开发文档.md @@ -0,0 +1,514 @@ +# iWork 用印流程集成开发文档 + +## 1. 概述 + +本文档描述了 ZT Cloud 平台与 iWork 系统的用印流程集成方案,包括流程发起、回调处理、消息通知及重试机制。 + +### 1.1 功能特性 + +- **流程发起**:支持用印专用流程和通用流程两种创建方式 +- **回调处理**:接收 iWork 回调,自动通知业务模块 +- **消息队列**:基于 RocketMQ 的异步消息通知机制 +- **自动重试**:失败回调自动重试,支持配置重试次数和间隔 +- **日志追踪**:完整记录流程创建和回调处理全生命周期 + +### 1.2 整体架构 + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ 业务系统 │────▶│ System 模块 │────▶│ iWork 系统 │ +│ (调用方) │ │ (集成层) │ │ (OA 流程) │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + ▲ ▲ │ + │ │ │ + │ └───────────────────────┘ + │ iWork 流程完成后回调 + │ ┌─────────────────┐ + │ │ RocketMQ │ + │ │ (消息队列) │ + └───────────────┴─────────────────┘ +``` + +### 1.3 完整流程时序图 + +``` +┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ +│ 业务系统 │ │ System │ │ iWork │ │RocketMQ│ │业务消费者│ +└───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘ + │ │ │ │ │ + │ 1.发起用印流程 │ │ │ │ + │──────────────▶│ │ │ │ + │ │ 2.创建流程 │ │ │ + │ │──────────────▶│ │ │ + │ │ 返回requestId│ │ │ + │ │◀──────────────│ │ │ + │ 返回结果 │ │ │ │ + │◀──────────────│ │ │ │ + │ │ │ │ │ + │ │ │ 3.OA流程审批 │ │ + │ │ │ (异步进行) │ │ + │ │ │ │ │ + │ │ 4.流程完成回调 │ │ │ + │ │◀──────────────│ │ │ + │ │ │ │ │ + │ │ 5.发送MQ消息 │ │ │ + │ │──────────────────────────────▶│ │ + │ │ │ │ 6.投递消息 │ + │ │ │ │──────────────▶│ + │ │ │ │ │ + │ │ │ │ 7.返回处理结果 │ + │ │ │ │◀──────────────│ + │ │ 8.接收结果 │ │ │ + │ │◀──────────────────────────────│ │ + │ │ │ │ │ + │ │ 9.更新日志状态 │ │ │ + │ │ (成功/重试) │ │ │ + └───────────────┴───────────────┴───────────────┴───────────────┘ +``` + +**流程说明**: + +1. **发起流程**:业务系统调用 System 模块的流程创建接口 +2. **创建流程**:System 模块调用 iWork API 创建 OA 流程,获取 `requestId` +3. **OA 审批**:流程在 iWork 系统中流转(审批、签章等),此过程异步进行 +4. **iWork 回调**:流程完成后,iWork 系统主动回调 System 模块的回调接口 +5. **MQ 通知**:System 模块将回调数据通过 RocketMQ 发送给业务消费者 +6. **业务处理**:业务消费者接收消息并处理(如保存签章文件、更新业务状态) +7. **返回结果**:业务消费者处理完成后,发送处理结果消息 +8. **接收结果**:System 模块接收处理结果 +9. **状态更新**:根据结果更新日志状态,失败则触发重试机制 + +## 2. 数据库设计 + +### 2.1 流程日志表 (system_iwork_workflow_log) + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| id | BIGINT | 主键 | +| request_id | VARCHAR(128) | iWork 请求编号(唯一) | +| workflow_id | BIGINT | 流程模板 ID | +| business_code | VARCHAR(128) | 业务编码 | +| biz_callback_key | VARCHAR(255) | 业务回调标识(MQ tag) | +| raw_request | VARCHAR(2000) | 创建请求原文 | +| status | VARCHAR(32) | 流程状态 | +| callback_status | INTEGER | 回调处理状态 | +| retry_count | INTEGER | 已重试次数 | +| max_retry | INTEGER | 最大重试次数 | +| last_error_message | VARCHAR(512) | 最后错误信息 | +| raw_callback | VARCHAR(2000) | 回调原文 | +| last_callback_time | TIMESTAMP | 最近回调时间 | +| tenant_id | BIGINT | 租户编号 | + +### 2.2 回调状态枚举 (callback_status) + +| 值 | 状态 | 说明 | +|----|------|------| +| 0 | CREATE_PENDING | 创建中 | +| 1 | CREATE_SUCCESS | 创建成功 | +| 2 | CREATE_FAILED | 创建失败 | +| 3 | CALLBACK_PENDING | 回调待处理 | +| 4 | CALLBACK_SUCCESS | 回调处理成功 | +| 5 | CALLBACK_FAILED | 回调处理失败 | +| 6 | CALLBACK_RETRYING | 回调重试中 | +| 7 | CALLBACK_RETRY_FAILED | 回调重试失败 | + +### 2.3 状态流转图 + +``` + ┌─────────────────────────────────────────────────────┐ + │ 流程创建阶段 │ + │ ┌──────────┐ 成功 ┌──────────┐ │ + │ │ PENDING │ ─────────▶ │ SUCCESS │ │ + │ │ (0) │ │ (1) │ │ + │ └──────────┘ └──────────┘ │ + │ │ │ + │ │ 失败 │ + │ ▼ │ + │ ┌──────────┐ │ + │ │ FAILED │ │ + │ │ (2) │ │ + │ └──────────┘ │ + └─────────────────────────────────────────────────────┘ + │ + │ iWork 回调 + ▼ + ┌─────────────────────────────────────────────────────┐ + │ 回调处理阶段 │ + │ ┌──────────┐ 成功 ┌──────────┐ │ + │ │ CALLBACK │ ─────────▶ │ CALLBACK │ │ + │ │ PENDING │ │ SUCCESS │ │ + │ │ (3) │ │ (4) │ │ + │ └──────────┘ └──────────┘ │ + │ │ │ + │ │ 失败 │ + │ ▼ │ + │ ┌──────────┐ 重试中 ┌──────────┐ │ + │ │ CALLBACK │ ◀───────▶ │ CALLBACK │ │ + │ │ FAILED │ │ RETRYING │ │ + │ │ (5) │ │ (6) │ │ + │ └──────────┘ └──────────┘ │ + │ │ │ + │ │ 重试次数耗尽 │ + │ ▼ │ + │ ┌──────────┐ │ + │ │ RETRY │ │ + │ │ FAILED(7)│ │ + │ └──────────┘ │ + └─────────────────────────────────────────────────────┘ +``` + +## 3. API 接口说明 + +### 3.1 用印流程创建 + +**接口地址**:`POST /admin-api/system/integration/iwork/workflow/create` + +**请求参数**: + +```json +{ + "operatorUserId": "1001", + "jbr": "1001", + "yybm": "2001", + "fb": "3001", + "sqsj": "2025-01-30", + "yyqx": "内部使用", + "yyfkUrl": "https://example.com/attachment.pdf", + "yysy": "合同盖章", + "xyywjUrl": "https://example.com/contract.pdf", + "yysx": "公章", + "ywxtdjbh": "DJ-2025-0001", + "bizCallbackKey": "seal-callback" +} +``` + +| 参数 | 必填 | 说明 | +|------|------|------| +| operatorUserId | 是 | 操作人 iWork 用户 ID | +| jbr | 是 | 用印申请人 | +| yybm | 是 | 用印部门 ID | +| fb | 是 | 用印单位(分部 ID) | +| sqsj | 是 | 申请时间 (yyyy-MM-dd) | +| yyqx | 是 | 用印去向 | +| xyywjUrl | 是 | 用印材料附件 URL | +| yysx | 是 | 用印事项 | +| ywxtdjbh | 是 | 业务系统单据编号 | +| bizCallbackKey | 否 | 业务回调标识 | +| yyfkUrl | 否 | 用印依据附件 URL | +| yysy | 否 | 用印事由 | + +### 3.2 通用流程创建 + +**接口地址**:`POST /admin-api/system/integration/iwork/workflow/create-generic` + +**请求参数**: + +```json +{ + "operatorUserId": "1001", + "workflowId": 54, + "payload": { + "requestName": "用印-DJ-2025-0001", + "mainData": [ + {"fieldName": "jbr", "fieldValue": "1001"}, + {"fieldName": "yybm", "fieldValue": "2001"} + ] + }, + "ywxtdjbh": "DJ-2025-0001", + "bizCallbackKey": "seal-callback" +} +``` + +| 参数 | 必填 | 说明 | +|------|------|------| +| operatorUserId | 是 | 操作人 iWork 用户 ID | +| workflowId | 是 | 流程模板 ID | +| payload | 是 | 透传给 iWork 的业务参数 | +| ywxtdjbh | 否 | 业务编码 | +| bizCallbackKey | 否 | 业务回调标识 | + +### 3.3 iWork 回调接口 + +**接口地址**:`POST /admin-api/system/integration/iwork/callback/file` + +**说明**:此接口供 iWork 系统回调,无需认证(@PermitAll, @TenantIgnore) + +**iWork 侧配置**:需要在 iWork 系统中配置回调地址,当流程完成时自动调用此接口。 + +**请求参数**: + +```json +{ + "requestId": "3603649", + "businessCode": "DJ-2025-0001", + "fileUrl": "https://iwork.example.com/signed-file.pdf", + "fileName": "已签章合同.pdf", + "status": "COMPLETED" +} +``` + +| 参数 | 必填 | 说明 | +|------|------|------| +| requestId | 是 | iWork 请求编号(与创建流程时返回的一致) | +| businessCode | 是 | 业务编码(与创建流程时传入的 ywxtdjbh 一致) | +| fileUrl | 是 | 签章后文件 URL | +| fileName | 否 | 文件名称 | +| status | 否 | 业务状态 | + +**回调处理逻辑**: + +1. 根据 `requestId` 查询流程创建日志,获取 `bizCallbackKey` +2. 更新日志状态为 `CALLBACK_PENDING` +3. 发送 MQ 消息通知业务模块(仅当 `bizCallbackKey` 存在时) +4. 返回处理结果 + +## 4. MQ 消息机制 + +### 4.1 消息流程图 + +``` +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ +│ iWork 回调 │───▶│ System 模块 │───▶│ RocketMQ │───▶│ 业务消费者 │ +│ │ │ (Producer) │ │ │ │ │ +└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ + │ + ▼ +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ +│ 更新日志状态 │◀───│ System 模块 │◀───│ RocketMQ │◀───│ 返回处理结果 │ +│ │ │ (Listener) │ │ │ │ │ +└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ +``` + +### 4.2 Topic 定义 + +| Topic | 说明 | +|-------|------| +| SYSTEM_IWORK_BIZ_CALLBACK | 回调通知消息(System → 业务模块) | +| SYSTEM_IWORK_BIZ_CALLBACK_RESULT | 处理结果消息(业务模块 → System) | + +### 4.3 回调通知消息 (IWorkBizCallbackMessage) + +```java +{ + "requestId": "3603649", + "bizCallbackKey": "seal-callback", + "payload": { /* 回调原始数据 */ }, + "attempt": 0, + "maxAttempts": 3 +} +``` + +**Tag 规则**:消息 tag = `bizCallbackKey`,业务模块按 tag 订阅 + +### 4.4 处理结果消息 (IWorkBizCallbackResultMessage) + +```java +{ + "requestId": "3603649", + "bizCallbackKey": "seal-callback", + "success": true, + "errorMessage": null, + "attempt": 0, + "maxAttempts": 3, + "payload": { /* 原始数据,用于重试 */ } +} +``` + +## 5. 业务模块接入指南 + +### 5.1 添加依赖 + +```xml + + com.zt.plat + zt-module-system-api + +``` + +### 5.2 实现消费者 + +```java +@Slf4j +@Component +@RequiredArgsConstructor +@RocketMQMessageListener( + topic = IWorkBizCallbackMessage.TOPIC, + consumerGroup = IWorkBizCallbackMessage.TOPIC + "_YOUR_BIZ_KEY", + selectorExpression = "your-biz-callback-key" // 与 bizCallbackKey 一致 +) +public class YourBizCallbackConsumer implements RocketMQListener { + + private final RocketMQTemplate rocketMQTemplate; + + @Override + public void onMessage(IWorkBizCallbackMessage message) { + log.info("收到 iWork 回调: requestId={}", message.getRequestId()); + + IWorkBizCallbackResultMessage result; + try { + // 处理业务逻辑 + processCallback(message); + + result = IWorkBizCallbackResultMessage.builder() + .requestId(message.getRequestId()) + .bizCallbackKey(message.getBizCallbackKey()) + .success(true) + .attempt(message.getAttempt()) + .maxAttempts(message.getMaxAttempts()) + .payload(message.getPayload()) + .build(); + } catch (Exception e) { + log.error("处理回调失败", e); + result = IWorkBizCallbackResultMessage.builder() + .requestId(message.getRequestId()) + .bizCallbackKey(message.getBizCallbackKey()) + .success(false) + .errorMessage(e.getMessage()) + .attempt(message.getAttempt()) + .maxAttempts(message.getMaxAttempts()) + .payload(message.getPayload()) + .build(); + } + + // 发送处理结果 + rocketMQTemplate.syncSend(IWorkBizCallbackResultMessage.TOPIC, result); + } + + private void processCallback(IWorkBizCallbackMessage message) { + // 业务处理逻辑 + // 1. 解析 payload 获取回调数据 + // 2. 更新业务状态 + // 3. 保存签章文件等 + } +} +``` + +### 5.3 关键配置项 + +| 配置项 | 说明 | +|--------|------| +| consumerGroup | 消费者组,建议格式:`TOPIC + "_" + bizCallbackKey` | +| selectorExpression | Tag 过滤,必须与发起流程时的 `bizCallbackKey` 一致 | + +### 5.4 注意事项 + +1. **bizCallbackKey 唯一性**:每个业务场景使用独立的 bizCallbackKey +2. **幂等处理**:消费者需实现幂等,同一 requestId 可能重复投递 +3. **必须返回结果**:处理完成后必须发送 `IWorkBizCallbackResultMessage` +4. **错误信息**:失败时填写 errorMessage,便于问题排查 + +## 6. 重试机制 + +### 6.1 重试流程 + +``` +业务处理失败 → 返回 success=false → System Listener 接收 + ↓ + 检查 attempt < maxAttempts? + ↓ ↓ + 是 否 + ↓ ↓ + 延迟后重新投递 标记最终失败 +``` + +### 6.2 配置参数 + +```yaml +iwork: + callback: + retry: + max-attempts: 3 # 最大重试次数 + delay-seconds: 5 # 重试间隔(秒) +``` + +### 6.3 手工重试 + +**接口地址**:`POST /admin-api/system/integration/iwork/log/retry` + +```json +{ + "requestId": "3603649" +} +``` + +## 7. 日志查询 + +### 7.1 分页查询接口 + +**接口地址**:`POST /admin-api/system/integration/iwork/log/page` + +**请求参数**: + +```json +{ + "requestId": "3603649", + "businessCode": "DJ-2025-0001", + "bizCallbackKey": "seal-callback", + "status": 4, + "pageNo": 1, + "pageSize": 10 +} +``` + +## 8. 本地开发调试 + +### 8.1 隔离测试环境 + +为避免与测试环境消息冲突,本地开发时需修改: + +1. **Listener 消费者组**:添加本地标识后缀 +```java +consumerGroup = IWorkBizCallbackResultMessage.TOPIC + "_CONSUMER_local" +``` + +2. **Listener Tag 过滤**:使用本地专用 tag +```java +selectorExpression = "local_test" +``` + +3. **业务消费者**:同样使用本地专用 bizCallbackKey +```java +selectorExpression = "your-biz-key_local" +``` + +4. **数据库记录**:将 `biz_callback_key` 设为本地专用值 + +### 8.2 调试建议 + +- 使用独立的 `bizCallbackKey` 避免消息串扰 +- 检查 RocketMQ 控制台确认消息投递情况 +- 关注日志中的 `requestId` 进行链路追踪 + +## 9. 常见问题 + +### Q1: 业务消费者收不到消息? + +检查项: +- `selectorExpression` 是否与 `bizCallbackKey` 一致 +- 消费者组名是否正确 +- RocketMQ 连接是否正常 + +### Q2: 收到重复消息? + +可能原因: +- 多个环境的 Listener 都在消费同一 topic +- 解决:使用独立的消费者组和 tag 过滤 + +### Q3: 重试不生效? + +检查项: +- 是否正确返回了 `IWorkBizCallbackResultMessage` +- `success` 字段是否为 `false` +- 配置的 `max-attempts` 是否大于当前 `attempt` + +## 10. 相关代码位置 + +| 组件 | 路径 | +|------|------| +| Controller | `zt-module-system-server/.../controller/admin/integration/iwork/IWorkIntegrationController.java` | +| Service | `zt-module-system-server/.../service/integration/iwork/impl/IWorkIntegrationServiceImpl.java` | +| 日志 Service | `zt-module-system-server/.../service/integration/iwork/impl/IWorkWorkflowLogServiceImpl.java` | +| MQ Producer | `zt-module-system-server/.../mq/iwork/IWorkBizCallbackProducer.java` | +| MQ Listener | `zt-module-system-server/.../mq/iwork/IWorkBizCallbackListener.java` | +| 消息定义 | `zt-module-system-api/.../mq/iwork/IWorkBizCallbackMessage.java` | +| 配置类 | `zt-module-system-server/.../framework/integration/iwork/config/IWorkProperties.java` | From 2e47c66fdac42e5a0b105edbb8c7d0c3bc64565b Mon Sep 17 00:00:00 2001 From: chenbowen Date: Fri, 30 Jan 2026 16:02:48 +0800 Subject: [PATCH 09/11] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=B8=8B=E5=85=B6=E4=BB=96=20schema=20=E5=8C=85=E5=90=AB?= =?UTF-8?q?=E5=90=8C=E5=90=8D=20flowable=20=E8=A1=A8=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=20bpm=20=E6=9C=8D=E5=8A=A1=E7=9A=84=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../engine/impl/db/DbSqlSessionFactory.java | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/zt-module-bpm/zt-module-bpm-server/src/main/java/org/flowable/common/engine/impl/db/DbSqlSessionFactory.java b/zt-module-bpm/zt-module-bpm-server/src/main/java/org/flowable/common/engine/impl/db/DbSqlSessionFactory.java index ed2f0c94..cbea9553 100644 --- a/zt-module-bpm/zt-module-bpm-server/src/main/java/org/flowable/common/engine/impl/db/DbSqlSessionFactory.java +++ b/zt-module-bpm/zt-module-bpm-server/src/main/java/org/flowable/common/engine/impl/db/DbSqlSessionFactory.java @@ -77,7 +77,12 @@ public class DbSqlSessionFactory implements SessionFactory { // 当前系统适配 dm,如果存在 schema 为空的情况,从 connection 获取 try { if (getDatabaseSchema() == null || getDatabaseSchema().length() == 0){ - setDatabaseSchema(dbSqlSession.getSqlSession().getConnection().getSchema()); + String schemaFromUrl = extractSchemaFromJdbcUrl(dbSqlSession.getSqlSession().getConnection()); + if (schemaFromUrl != null && schemaFromUrl.length() > 0) { + setDatabaseSchema(schemaFromUrl); + } else { + setDatabaseSchema(dbSqlSession.getSqlSession().getConnection().getSchema()); + } } dbSqlSession.getSqlSession().getConnection().getSchema(); } catch (SQLException e) { @@ -351,4 +356,39 @@ public class DbSqlSessionFactory implements SessionFactory { public void setUsePrefixId(boolean usePrefixId) { this.usePrefixId = usePrefixId; } + + private String extractSchemaFromJdbcUrl(java.sql.Connection connection) { + if (connection == null) { + return null; + } + try { + String url = connection.getMetaData().getURL(); + if (url == null || url.isEmpty()) { + return null; + } + int queryIndex = url.indexOf('?'); + if (queryIndex < 0 || queryIndex == url.length() - 1) { + return null; + } + String query = url.substring(queryIndex + 1); + String[] parts = query.split("[&;]"); + for (String part : parts) { + int eqIndex = part.indexOf('='); + if (eqIndex <= 0 || eqIndex == part.length() - 1) { + continue; + } + String key = part.substring(0, eqIndex).trim().toLowerCase(Locale.ROOT); + if ("schema".equals(key) || "currentschema".equals(key) || "current_schema".equals(key)) { + String value = part.substring(eqIndex + 1).trim(); + if ((value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))) { + value = value.substring(1, value.length() - 1); + } + return value; + } + } + } catch (SQLException ignored) { + return null; + } + return null; + } } From 394e6b6bbb68b27656bcc7892a46cd2be2796f51 Mon Sep 17 00:00:00 2001 From: ranke <213539@qq.com> Date: Mon, 2 Feb 2026 11:25:01 +0800 Subject: [PATCH 10/11] =?UTF-8?q?=E8=A7=A3=E5=86=B3Bean=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/ZtDataPermissionAutoConfiguration.java | 2 ++ .../config/MenuDataPermissionConfiguration.java | 2 ++ zt-server/src/main/resources/application-dev.yaml | 8 ++++---- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/config/ZtDataPermissionAutoConfiguration.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/config/ZtDataPermissionAutoConfiguration.java index 6c95cc7c..063610e6 100644 --- a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/config/ZtDataPermissionAutoConfiguration.java +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/config/ZtDataPermissionAutoConfiguration.java @@ -14,6 +14,7 @@ import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import java.util.List; @@ -45,6 +46,7 @@ public class ZtDataPermissionAutoConfiguration { } @Bean + @ConditionalOnMissingBean public MenuDataPermissionHandler menuDataPermissionHandler(MybatisPlusInterceptor interceptor) { // 创建菜单数据权限处理器 MenuDataPermissionHandler handler = new MenuDataPermissionHandler(); diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/config/MenuDataPermissionConfiguration.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/config/MenuDataPermissionConfiguration.java index 172fba3d..7f3ad205 100644 --- a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/config/MenuDataPermissionConfiguration.java +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/config/MenuDataPermissionConfiguration.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionIntercepto import com.zt.plat.framework.datapermission.core.menudatapermission.handler.MenuDataPermissionHandler; import com.zt.plat.framework.mybatis.core.util.MyBatisUtils; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -20,6 +21,7 @@ public class MenuDataPermissionConfiguration { @Bean @ConditionalOnBean(MybatisPlusInterceptor.class) + @ConditionalOnMissingBean public MenuDataPermissionHandler menuDataPermissionHandler(MybatisPlusInterceptor interceptor) { // 创建菜单数据权限处理器 MenuDataPermissionHandler handler = new MenuDataPermissionHandler(); diff --git a/zt-server/src/main/resources/application-dev.yaml b/zt-server/src/main/resources/application-dev.yaml index 2a3d7761..c62b86e8 100644 --- a/zt-server/src/main/resources/application-dev.yaml +++ b/zt-server/src/main/resources/application-dev.yaml @@ -47,14 +47,14 @@ spring: primary: master datasource: master: - url: jdbc:dm://172.16.46.247:1050?schema=RUOYI-VUE-PRO + url: jdbc:dm://172.17.11.98:20870?schema=JYGK_TEST username: SYSDBA - password: pgbsci6ddJ6Sqj@e + password: P@ssword25 slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改 lazy: true # 开启懒加载,保证启动速度 - url: jdbc:dm://172.16.46.247:1050?schema=RUOYI-VUE-PRO + url: jdbc:dm://172.17.11.98:20870?schema=JYGK_TEST username: SYSDBA - password: pgbsci6ddJ6Sqj@e + password: P@ssword25 # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 data: From 8a27141edc04c625e226e03c696187bbb493bfa5 Mon Sep 17 00:00:00 2001 From: yangchaojin <549193112@qq.com> Date: Mon, 2 Feb 2026 16:00:07 +0800 Subject: [PATCH 11/11] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E5=8A=A0=E5=AF=86=E6=96=87=E4=BB=B6=E9=A2=84=E8=A7=88=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/file/FileController.java | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/controller/admin/file/FileController.java b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/controller/admin/file/FileController.java index 6b68ebe6..f0057d1c 100644 --- a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/controller/admin/file/FileController.java +++ b/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/controller/admin/file/FileController.java @@ -31,6 +31,7 @@ import org.springframework.web.multipart.MultipartFile; import java.io.IOException; +import static com.zt.plat.framework.common.pojo.CommonResult.error; import static com.zt.plat.framework.common.pojo.CommonResult.success; import static com.zt.plat.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static com.zt.plat.module.infra.framework.file.core.utils.FileTypeUtils.writeAttachment; @@ -52,13 +53,13 @@ public class FileController { private FileService fileService; @GetMapping("/get") - @Operation(summary = "获取文件预览地址", description = "根据 fileId 返回文件预览 url(kkfile),支持加密文件预览,需要传递验证码 code,加密文件预览地址默认5分钟内有效,可在配置文件中添加zt.file.preview-expire-seconds配置") + @Operation(summary = "获取文件预览地址", description = "根据 fileId 返回文件预览 url(kkfile),支持加密文件预览,加密文件预览地址默认5分钟内有效,可在配置文件中添加zt.file.preview-expire-seconds配置有效时间") public CommonResult getPreviewUrl(@RequestParam("fileId") Long fileId, @RequestParam(value = "code", required = false) String code, HttpServletRequest request) throws Exception { FileDO fileDO = fileService.getActiveFileById(fileId); if (fileDO == null) { - return CommonResult.error(HttpStatus.NOT_FOUND.value(), "文件不存在"); + return error(HttpStatus.NOT_FOUND.value(), "文件不存在"); } // 统计下载次数 @@ -68,26 +69,24 @@ public class FileController { FileRespVO fileRespVO = BeanUtils.toBean(fileDO, FileRespVO.class); // 加密文件:塞入“临时解密预览 URL” - if (Boolean.TRUE.equals(fileRespVO.getIsEncrypted())) { // FileDO 通过 aesIv 判断加密 + if (Boolean.TRUE.equals(fileRespVO.getIsEncrypted()) // FileDO 通过 aesIv 判断加密 + && cn.hutool.core.util.StrUtil.isNotBlank(code)) { // 预览文件会调用两次该接口,只有code不为空时候才塞url - if (cn.hutool.core.util.StrUtil.isBlank(code)) { + /*if (cn.hutool.core.util.StrUtil.isBlank(code)) { return CommonResult.error(HttpStatus.BAD_REQUEST.value(), "加密文件预览需要验证码 code"); - } + }*/ + // 验证通过:发放给 kkfile 用的短期 token(kkfile 不带登录态) Long userId = getLoginUserId(); boolean flag = fileService.verifyCode(fileId, userId, code); if(!flag){ - return CommonResult.customize(null, HttpStatus.INTERNAL_SERVER_ERROR.value(), "验证码错误"); + return error(HttpStatus.BAD_REQUEST.value(), "验证码错误"); } - String token = fileService.generatePreviewToken(fileId, userId); - String baseUrl = buildPublicBaseUrl(request); // 见下方函数 - String fullfilename = java.net.URLEncoder .encode(fileDO.getName(), java.nio.charset.StandardCharsets.UTF_8) .replace("+", "%20"); - String decryptUrl = baseUrl + "/admin-api/infra/file/preview-decrypt" + "?fileId=" + fileId + "&token=" + token @@ -215,14 +214,14 @@ public class FileController { try { sendTypeEnum = VerifyCodeSendType.valueOf(sendType.trim().toUpperCase()); } catch (IllegalArgumentException ex) { - return CommonResult.error(HttpStatus.BAD_REQUEST.value(), + return error(HttpStatus.BAD_REQUEST.value(), "sendType 参数不合法,可选:SMS / E_OFFICE"); } } FileDO activeFileById = fileService.getActiveFileById(fileId); if (activeFileById == null) { - return CommonResult.error(HttpStatus.NOT_FOUND.value(), "文件不存在"); + return error(HttpStatus.NOT_FOUND.value(), "文件不存在"); } FileRespVO fileRespVO = BeanUtils.toBean(activeFileById, FileRespVO.class);