feat: 完成分享、单会话与发布审批改造

- 增加工作流协作分享与知识库卡片分享入口,统一低版本浏览器复制反馈

- Web 新登录替换旧会话,并保持 API Key 会话隔离

- 发布审批增加必填说明并在审批详情展示

- 账号重置与导入改用可配置默认强密码
This commit is contained in:
2026-07-23 16:09:31 +08:00
parent caa1f07b66
commit 5a42826d44
71 changed files with 3191 additions and 132 deletions

View File

@@ -0,0 +1,55 @@
package tech.easyflow.ai.share;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.Assert.assertTrue;
/**
* V34 工作流分享与审批说明迁移契约测试。
*/
public class WorkflowShareMigrationContractTest {
/**
* 验证迁移同时创建审批说明、工作流修订号和分享记录所需字段及索引。
*
* @throws Exception 迁移文件不可读时抛出
*/
@Test
public void migrationShouldCreateWorkflowShareContracts() throws Exception {
String sql = migrationSql();
assertTrue(sql.contains("ADD COLUMN `application_reason` VARCHAR(500)"));
assertTrue(sql.contains("ADD COLUMN `revision` INT NOT NULL DEFAULT 0"));
assertTrue(sql.contains("CREATE TABLE `tb_workflow_share`"));
assertTrue(sql.contains("`share_key_hash` VARCHAR(64) NOT NULL"));
assertTrue(sql.contains("`status` VARCHAR(32) NOT NULL"));
assertTrue(sql.contains("`dept_id` BIGINT UNSIGNED NULL"));
assertTrue(sql.contains("`modified` DATETIME NULL"));
assertTrue(sql.contains("UNIQUE INDEX `uni_workflow_share_key_hash`"));
assertTrue(sql.contains("INDEX `idx_workflow_share_status` (`workflow_id`, `status`)"));
}
/**
* 读取工作区中的 V34 MySQL 迁移。
*
* @return 迁移 SQL
* @throws Exception 迁移文件不存在或不可读时抛出
*/
private String migrationSql() throws Exception {
Path root = Path.of(System.getProperty("maven.multiModuleProjectDirectory",
Path.of(System.getProperty("user.dir")).toAbsolutePath().toString()));
while (root != null) {
Path migration = root.resolve("easyflow-starter/easyflow-starter-all/src/main/resources/"
+ "db/migration/mysql/V34__mysql_workflow_share_and_approval_reason.sql");
if (Files.isRegularFile(migration)) {
return Files.readString(migration, StandardCharsets.UTF_8);
}
root = root.getParent();
}
throw new IllegalStateException("找不到 V34 工作流分享与审批说明迁移");
}
}

View File

@@ -0,0 +1,92 @@
package tech.easyflow.ai.share;
import org.junit.Assert;
import org.junit.Test;
import tech.easyflow.system.enums.ResourceAction;
import java.util.Date;
/**
* {@link WorkflowSharePolicy} 测试。
*/
public class WorkflowSharePolicyTest {
/**
* 验证相同密钥生成稳定的 SHA-256 摘要。
*/
@Test
public void shouldHashShareKeyDeterministically() {
String first = WorkflowSharePolicy.hashShareKey("share-key");
String second = WorkflowSharePolicy.hashShareKey("share-key");
Assert.assertEquals(first, second);
Assert.assertEquals(64, first.length());
Assert.assertNotEquals("share-key", first);
}
/**
* 验证默认过期时间为创建时间后 30 分钟。
*/
@Test
public void shouldExpireThirtyMinutesAfterCreation() {
Date createdAt = new Date(1_000L);
Date expiresAt = WorkflowSharePolicy.defaultExpiresAt(createdAt);
Assert.assertEquals(30 * 60 * 1_000L, expiresAt.getTime() - createdAt.getTime());
}
/**
* 验证分享授权仅覆盖编辑、运行和发布所需接口。
*/
@Test
public void shouldAllowOnlyCollaborativeWorkflowOperations() {
Assert.assertTrue(WorkflowSharePolicy.isAllowedRequest(
"GET",
"/api/v1/workflow/detail",
ResourceAction.READ
));
Assert.assertTrue(WorkflowSharePolicy.isAllowedRequest(
"POST",
"/api/v1/workflow/update",
ResourceAction.MANAGE
));
Assert.assertTrue(WorkflowSharePolicy.isAllowedRequest(
"POST",
"/api/v1/workflow/runAsync",
ResourceAction.USE
));
Assert.assertTrue(WorkflowSharePolicy.isAllowedRequest(
"POST",
"/api/v1/workflow/submitPublishApproval",
ResourceAction.MANAGE
));
}
/**
* 验证删除、下线和再次分享不在协作授权范围内。
*/
@Test
public void shouldRejectSensitiveWorkflowOperations() {
Assert.assertFalse(WorkflowSharePolicy.isAllowedRequest(
"POST",
"/api/v1/workflow/submitDeleteApproval",
ResourceAction.MANAGE
));
Assert.assertFalse(WorkflowSharePolicy.isAllowedRequest(
"POST",
"/api/v1/workflow/submitOfflineApproval",
ResourceAction.MANAGE
));
Assert.assertFalse(WorkflowSharePolicy.isAllowedRequest(
"POST",
"/api/v1/workflowShare/url/create",
ResourceAction.MANAGE
));
Assert.assertFalse(WorkflowSharePolicy.isAllowedRequest(
"GET",
"/api/v1/workflow/update",
ResourceAction.MANAGE
));
}
}