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` |
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-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 60951994..274b9249 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-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;
+ }
}
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);
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-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: