diff --git a/docs/superpowers/plans/2026-05-24-P1-muse-cloud.md b/docs/superpowers/plans/2026-05-24-P1-muse-cloud.md new file mode 100644 index 00000000..90930af1 --- /dev/null +++ b/docs/superpowers/plans/2026-05-24-P1-muse-cloud.md @@ -0,0 +1,968 @@ +# P1: muse-cloud 后端搭建 — 执行计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. + +**目标:** 在 Yudao Cloud fork 基础上搭建 6 个 Muse 业务模块,实现全量 API(229 接口),提供可调用的后端服务。 + +**架构:** Yudao Cloud 微服务 + PostgreSQL 16 + Flyway 迁移。6 个业务模块按依赖顺序实现:content+account → meta → ai+knowledge → market。Java 21 + Spring Boot 3 + JUnit 5 + Testcontainers。 + +**技术栈:** Java 21, Spring Boot 3, PostgreSQL 16, Flyway, Maven, Docker Compose, JUnit 5, Testcontainers, ArchUnit + +--- + +## Step 1: 基础环境搭建 + +### Task 1.1: 确认 Yudao Cloud Fork 状态 + +**仓库路径:** `muse-cloud/` + +- [ ] **Step 1: 检查当前模块结构** + +```bash +ls muse-cloud/muse-module-*/pom.xml +``` + +预期输出: +``` +muse-cloud/muse-module-ai/muse-module-ai-api/pom.xml +muse-cloud/muse-module-ai/muse-module-ai-server/pom.xml +muse-cloud/muse-module-system/muse-module-system-api/pom.xml +muse-cloud/muse-module-system/muse-module-system-server/pom.xml +muse-cloud/muse-module-infra/.../pom.xml +muse-cloud/muse-module-bpm/.../pom.xml +muse-cloud/muse-module-member/.../pom.xml +muse-cloud/muse-module-mp/.../pom.xml +muse-cloud/muse-module-pay/.../pom.xml +muse-cloud/muse-module-report/.../pom.xml +``` + +- [ ] **Step 2: 确认包名是否已改为 com.muse** + +```bash +find muse-cloud -name "*.java" -path "*/com/muse/*" | head -10 +``` + +若仍为 `cn.iocoder.yudao`,需全局替换。 + +- [ ] **Step 3: 确认 pom.xml 中 Java 版本** + +```bash +grep -r "java.version\|maven.compiler" muse-cloud/pom.xml | head -5 +``` + +确保为 Java 21。 + +- [ ] **Step 4: 验证可编译** + +```bash +cd muse-cloud && mvn compile -q 2>&1 | tail -5 +``` + +### Task 1.2: Docker Compose 开发环境 + +**文件:** +- 创建: `muse-cloud/docker-compose.yml` + +- [ ] **Step 1: 编写 docker-compose.yml** + +```yaml +version: '3.8' +services: + postgres: + image: postgres:16-alpine + environment: + POSTGRES_USER: muse + POSTGRES_PASSWORD: muse_dev + POSTGRES_DB: muse + ports: + - "5432:5432" + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U muse"] + interval: 5s + timeout: 5s + retries: 5 + + redis: + image: redis:8-alpine + ports: + - "6379:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 5 + +volumes: + pgdata: +``` + +- [ ] **Step 2: 启动并验证** + +```bash +cd muse-cloud && docker compose up -d +docker compose ps +``` + +预期输出: postgres 和 redis 均为 `running` 状态。 + +- [ ] **Step 3: 提交** + +```bash +git add docker-compose.yml +git commit -m "feat(infra): 添加 Docker Compose 开发环境(PostgreSQL 16 + Redis 8)" +``` + +### Task 1.3: 调整 Yudao 配置适配 PostgreSQL + +**文件:** +- 修改: `muse-cloud/muse-server/src/main/resources/application-local.yaml` + +- [ ] **Step 1: 确认 PostgreSQL 数据源配置** + +```bash +grep -A10 "datasource" muse-cloud/muse-server/src/main/resources/application-local.yaml +``` + +确保 `url` 为 `jdbc:postgresql://localhost:5432/muse`,driver 为 `org.postgresql.Driver`。 + +- [ ] **Step 2: 提交** + +```bash +git add muse-cloud/muse-server/src/main/resources/application-local.yaml +git commit -m "chore(config): 确认 PostgreSQL 数据源配置" +``` + +### Task 1.4: 配置 .idea 团队共享 + +**文件:** +- 创建: `muse-cloud/.idea/` 下多个配置文件 + +- [ ] **Step 1: 创建 .idea/.gitignore** + +```gitignore +# 团队共享 IDE 配置 +# 个人配置不入库 +/shelf/ +/workspace.xml +/httpRequests/ +/dataSources/ +/dataSources.local.xml +``` + +- [ ] **Step 2: 创建代码风格配置** + +```bash +mkdir -p muse-cloud/.idea/codeStyles +cat > muse-cloud/.idea/codeStyles/Project.xml << 'XML' + + + + + + + +XML +``` + +- [ ] **Step 3: 提交** + +```bash +git add muse-cloud/.idea/ +git commit -m "chore(ide): 添加团队共享 .idea 配置" +``` + +--- + +## Step 2: 数据库 Migration + +### Task 2.1: 编写全量 DDL + +**文件:** +- 创建: `muse-cloud/sql/muse/V1__init_content_schema.sql` +- 创建: `muse-cloud/sql/muse/V2__init_account_schema.sql` +- 创建: `muse-cloud/sql/muse/V3__init_meta_schema.sql` +- 创建: `muse-cloud/sql/muse/V4__init_ai_schema.sql` +- 创建: `muse-cloud/sql/muse/V5__init_knowledge_schema.sql` +- 创建: `muse-cloud/sql/muse/V6__init_market_schema.sql` +- 参考: `design-docs/后端-04-统一数据库Schema-v1.md` +- 参考: `design-docs/后端-04a-完整建表SQL.sql` + +- [ ] **Step 1: 创建 Migration 目录** + +```bash +mkdir -p muse-cloud/sql/muse +``` + +- [ ] **Step 2: 编写 Content 模块 DDL (V1)** + +从 `design-docs/后端-04a-完整建表SQL.sql` 提取 Content 相关表(Work, Chapter, Block, BlockSourceAttribution 等),调整表名为 Yudao 约定(加 `muse_` 前缀),添加必备审计字段。 + +```sql +-- V1__init_content_schema.sql +-- Muse Content 模块初始 Schema +-- 对应设计文档: design-docs/后端-04-统一数据库Schema-v1.md + +-- ============================================================ +-- 作品表 +-- ============================================================ +CREATE TABLE muse_work ( + id BIGINT NOT NULL AUTO_INCREMENT, + title VARCHAR(200) NOT NULL, + description VARCHAR(2000), + genre VARCHAR(100), + cover_image_url VARCHAR(500), + status VARCHAR(20) NOT NULL DEFAULT 'draft', + word_count INT NOT NULL DEFAULT 0, + chapter_count INT NOT NULL DEFAULT 0, + creator VARCHAR(64) NOT NULL DEFAULT '', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater VARCHAR(64) NOT NULL DEFAULT '', + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted BIT NOT NULL DEFAULT FALSE, + tenant_id BIGINT NOT NULL DEFAULT 0, + PRIMARY KEY (id) +); + +CREATE INDEX idx_muse_work_creator ON muse_work(creator); +CREATE INDEX idx_muse_work_status ON muse_work(status); + +-- ============================================================ +-- 章节表 +-- ============================================================ +CREATE TABLE muse_chapter ( + id BIGINT NOT NULL AUTO_INCREMENT, + work_id BIGINT NOT NULL, + title VARCHAR(500) NOT NULL, + sort_order INT NOT NULL DEFAULT 0, + status VARCHAR(20) NOT NULL DEFAULT 'draft', + word_count INT NOT NULL DEFAULT 0, + block_count INT NOT NULL DEFAULT 0, + creator VARCHAR(64) NOT NULL DEFAULT '', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater VARCHAR(64) NOT NULL DEFAULT '', + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted BIT NOT NULL DEFAULT FALSE, + tenant_id BIGINT NOT NULL DEFAULT 0, + PRIMARY KEY (id) +); + +CREATE INDEX idx_muse_chapter_work ON muse_chapter(work_id); + +-- ============================================================ +-- Block 表 +-- ============================================================ +CREATE TABLE muse_block ( + id BIGINT NOT NULL AUTO_INCREMENT, + chapter_id BIGINT NOT NULL, + block_type VARCHAR(20) NOT NULL, + title VARCHAR(500), + content MEDIUMTEXT, + sort_order INT NOT NULL DEFAULT 0, + revision INT NOT NULL DEFAULT 1, + word_count INT NOT NULL DEFAULT 0, + creator VARCHAR(64) NOT NULL DEFAULT '', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater VARCHAR(64) NOT NULL DEFAULT '', + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted BIT NOT NULL DEFAULT FALSE, + tenant_id BIGINT NOT NULL DEFAULT 0, + PRIMARY KEY (id) +); + +CREATE INDEX idx_muse_block_chapter ON muse_block(chapter_id); + +-- ============================================================ +-- Block 来源归因表 +-- ============================================================ +CREATE TABLE muse_block_source_attribution ( + id BIGINT NOT NULL AUTO_INCREMENT, + block_id BIGINT NOT NULL, + revision INT NOT NULL, + source_type VARCHAR(50) NOT NULL, + source_id VARCHAR(100), + source_version INT, + authorization_snapshot_id VARCHAR(100), + lineage JSON, + license_restrictions JSON, + creator VARCHAR(64) NOT NULL DEFAULT '', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) +); + +CREATE INDEX idx_muse_block_src_block ON muse_block_source_attribution(block_id); +CREATE INDEX idx_muse_block_src_revision ON muse_block_source_attribution(block_id, revision); +``` + +- [ ] **Step 3: 编写 Account 模块 DDL (V2)** + +```sql +-- V2__init_account_schema.sql +-- Muse Account 模块初始 Schema + +-- 权益表(可变表 + 审计日志) +CREATE TABLE muse_entitlement ( + id BIGINT NOT NULL AUTO_INCREMENT, + user_id BIGINT NOT NULL, + resource_type VARCHAR(100) NOT NULL, + limit_value BIGINT NOT NULL, + used_value BIGINT NOT NULL DEFAULT 0, + expires_at DATETIME, + creator VARCHAR(64) NOT NULL DEFAULT '', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater VARCHAR(64) NOT NULL DEFAULT '', + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted BIT NOT NULL DEFAULT FALSE, + tenant_id BIGINT NOT NULL DEFAULT 0, + PRIMARY KEY (id) +); + +CREATE INDEX idx_muse_entitlement_user ON muse_entitlement(user_id); + +-- 权益审计日志表 +CREATE TABLE muse_entitlement_audit_log ( + id BIGINT NOT NULL AUTO_INCREMENT, + user_id BIGINT NOT NULL, + resource_type VARCHAR(100) NOT NULL, + operation_type VARCHAR(20) NOT NULL, + before_snapshot JSON, + after_snapshot JSON, + reason VARCHAR(500), + command_id VARCHAR(100) NOT NULL, + correlation_id VARCHAR(100), + operator VARCHAR(64) NOT NULL DEFAULT '', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) +); + +CREATE INDEX idx_muse_ent_audit_user ON muse_entitlement_audit_log(user_id); +CREATE INDEX idx_muse_ent_audit_cmd ON muse_entitlement_audit_log(command_id); + +-- 配额表 +CREATE TABLE muse_quota ( + id BIGINT NOT NULL AUTO_INCREMENT, + user_id BIGINT NOT NULL, + resource_type VARCHAR(100) NOT NULL, + total INT NOT NULL DEFAULT 0, + remaining INT NOT NULL DEFAULT 0, + reset_at DATETIME NOT NULL, + creator VARCHAR(64) NOT NULL DEFAULT '', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater VARCHAR(64) NOT NULL DEFAULT '', + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted BIT NOT NULL DEFAULT FALSE, + tenant_id BIGINT NOT NULL DEFAULT 0, + PRIMARY KEY (id) +); + +CREATE INDEX idx_muse_quota_user ON muse_quota(user_id); +``` + +- [ ] **Step 4: 编写 Meta 模块 DDL (V3)** + +```sql +-- V3__init_meta_schema.sql +-- MetaSchema 模块初始 Schema + +CREATE TABLE muse_meta_schema ( + id BIGINT NOT NULL AUTO_INCREMENT, + schema_key VARCHAR(100) NOT NULL, + display_name VARCHAR(200) NOT NULL, + field_type VARCHAR(20) NOT NULL, + scope VARCHAR(20) NOT NULL, + active_version INT NOT NULL DEFAULT 1, + status VARCHAR(20) NOT NULL DEFAULT 'active', + creator VARCHAR(64) NOT NULL DEFAULT '', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updater VARCHAR(64) NOT NULL DEFAULT '', + update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + deleted BIT NOT NULL DEFAULT FALSE, + PRIMARY KEY (id), + UNIQUE KEY uk_schema_key (schema_key) +); + +CREATE TABLE muse_meta_schema_version ( + id BIGINT NOT NULL AUTO_INCREMENT, + schema_id BIGINT NOT NULL, + version INT NOT NULL, + default_value JSON, + is_required BIT NOT NULL DEFAULT FALSE, + enum_values JSON, + validation_rules JSON, + ui_visible BIT NOT NULL DEFAULT TRUE, + ai_context BIT NOT NULL DEFAULT FALSE, + user_editable BIT NOT NULL DEFAULT TRUE, + user_searchable BIT NOT NULL DEFAULT FALSE, + exportable BIT NOT NULL DEFAULT FALSE, + status VARCHAR(20) NOT NULL DEFAULT 'draft', + change_note VARCHAR(500), + creator VARCHAR(64) NOT NULL DEFAULT '', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE KEY uk_schema_version (schema_id, version) +); + +CREATE INDEX idx_muse_meta_schema_ver_sid ON muse_meta_schema_version(schema_id); + +CREATE TABLE muse_protection_node ( + id BIGINT NOT NULL AUTO_INCREMENT, + node_key VARCHAR(100) NOT NULL, + display_name VARCHAR(200) NOT NULL, + required_permissions JSON, + audit_required BIT NOT NULL DEFAULT TRUE, + irremovable_reason VARCHAR(500), + shadow_canonical_boundary VARCHAR(500), + creator VARCHAR(64) NOT NULL DEFAULT '', + create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + UNIQUE KEY uk_protection_node_key (node_key) +); +``` + +- [ ] **Step 5-7: 编写 AI, Knowledge, Market 模块 DDL (V4-V6)** + +从 `design-docs/后端-04a-完整建表SQL.sql` 提取对应表结构,按 Yudao 约定(`muse_` 前缀、审计字段、租户字段)调整。 + +关键表: +- AI: `muse_ai_generation`, `muse_ai_suggestion`, `muse_agent`, `muse_agent_slot_binding`, `muse_prompt`, `muse_quality_policy` +- Knowledge: `muse_knowledge_base`, `muse_knowledge_document`, `muse_knowledge_entity`, `muse_knowledge_relation`, `muse_knowledge_draft`, `muse_knowledge_binding` +- Market: `muse_market_asset`, `muse_market_installation`, `muse_market_publish_request`, `muse_market_appeal` + +- [ ] **Step 8: 配置 Flyway** + +修改 `muse-cloud/muse-server/src/main/resources/application.yaml`,确保 Flyway 启用并指向 `sql/muse` 目录。 + +- [ ] **Step 9: 运行 Migration 并验证** + +```bash +cd muse-cloud && mvn flyway:migrate -pl muse-server +``` + +验证: 所有表创建成功,索引正确。 + +- [ ] **Step 10: 提交** + +```bash +git add muse-cloud/sql/ +git commit -m "feat(db): 添加 6 个模块全量 DDL + Flyway 迁移配置" +``` + +--- + +## Step 3: 业务模块骨架 + +### Task 3.1: 创建 Content 模块 + +**文件:** +- 创建: `muse-cloud/muse-module-content/pom.xml` +- 创建: `muse-cloud/muse-module-content/muse-module-content-api/pom.xml` +- 创建: `muse-cloud/muse-module-content/muse-module-content-server/pom.xml` +- 创建: API 接口包 `com.muse.module.content.api` +- 创建: Server 包结构 `com.muse.module.content.controller/service/dal` + +- [ ] **Step 1: 创建父 pom.xml** + +```xml + + + + 4.0.0 + + com.muse + muse + ${revision} + + muse-module-content + pom + Muse Module Content + 作品、章节、Block、导出模块 + + muse-module-content-api + muse-module-content-server + + +``` + +- [ ] **Step 2: 创建 api 子模块 pom.xml** + +```xml + + + + 4.0.0 + + com.muse + muse-module-content + ${revision} + + muse-module-content-api + jar + Muse Module Content API + Content 模块 API 接口定义(Feign 接口 + DTO) + + + com.muse + muse-common + + + +``` + +- [ ] **Step 3: 创建 server 子模块 pom.xml** + +```xml + + + + 4.0.0 + + com.muse + muse-module-content + ${revision} + + muse-module-content-server + jar + Muse Module Content Server + Content 模块服务实现 + + + com.muse + muse-module-content-api + + + com.muse + muse-module-system-api + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + org.testcontainers + postgresql + test + + + +``` + +- [ ] **Step 4: 创建 API 接口类 (Feign)** + +```java +// muse-module-content/muse-module-content-api/src/main/java/com/muse/module/content/api/ContentApi.java +package com.muse.module.content.api; + +import com.muse.module.content.api.dto.*; +import com.muse.common.result.CommonResult; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * Content 模块 Feign API 接口 + * 对应 OpenAPI: content/openapi.yaml + */ +@FeignClient(name = "muse-module-content") +public interface ContentApi { + + // ===== Work ===== + @GetMapping("/app-api/muse/works") + CommonResult> listWorks( + @RequestParam("pageNo") Integer pageNo, + @RequestParam("pageSize") Integer pageSize, + @RequestParam(value = "status", required = false) String status + ); + + @PostMapping("/app-api/muse/works") + CommonResult createWork(@RequestBody CreateWorkRequest request); + + @GetMapping("/app-api/muse/works/{workId}") + CommonResult getWork(@PathVariable("workId") Long workId); + + @PutMapping("/app-api/muse/works/{workId}") + CommonResult updateWork(@PathVariable("workId") Long workId, @RequestBody UpdateWorkRequest request); + + @DeleteMapping("/app-api/muse/works/{workId}") + CommonResult deleteWork(@PathVariable("workId") Long workId); + + // ===== Chapter ===== + @GetMapping("/app-api/muse/works/{workId}/chapters") + CommonResult> listChapters(@PathVariable("workId") Long workId); + + @PostMapping("/app-api/muse/works/{workId}/chapters") + CommonResult createChapter(@PathVariable("workId") Long workId, @RequestBody CreateChapterRequest request); + + @GetMapping("/app-api/muse/chapters/{chapterId}") + CommonResult getChapter(@PathVariable("chapterId") Long chapterId); + + @PutMapping("/app-api/muse/chapters/{chapterId}") + CommonResult updateChapter(@PathVariable("chapterId") Long chapterId, @RequestBody UpdateChapterRequest request); + + @DeleteMapping("/app-api/muse/chapters/{chapterId}") + CommonResult deleteChapter(@PathVariable("chapterId") Long chapterId); + + // ===== Block ===== + @GetMapping("/app-api/muse/chapters/{chapterId}/blocks") + CommonResult> listBlocks(@PathVariable("chapterId") Long chapterId); + + @PostMapping("/app-api/muse/chapters/{chapterId}/blocks") + CommonResult createBlock(@PathVariable("chapterId") Long chapterId, @RequestBody CreateBlockRequest request); + + @PutMapping("/app-api/muse/blocks/{blockId}") + CommonResult saveBlock(@PathVariable("blockId") Long blockId, @RequestBody SaveBlockRequest request); + + @DeleteMapping("/app-api/muse/blocks/{blockId}") + CommonResult deleteBlock(@PathVariable("blockId") Long blockId); +} +``` + +- [ ] **Step 5: 创建 DTO 类** + +从 `docs/api-contracts/generated/java/com/muse/dto/` 复制 Content 相关 DTO 到 `muse-module-content-api/src/main/java/com/muse/module/content/api/dto/`。 + +- [ ] **Step 6: 创建 Server 包结构** + +```bash +mkdir -p muse-cloud/muse-module-content/muse-module-content-server/src/main/java/com/muse/module/content/{controller,service,dal/{mysql,convert}} +mkdir -p muse-cloud/muse-module-content/muse-module-content-server/src/test/java/com/muse/module/content +``` + +- [ ] **Step 7: 在父 pom.xml 注册模块** + +```bash +# 在 muse-cloud/pom.xml 的 中添加 +# muse-module-content +``` + +- [ ] **Step 8: 编译验证** + +```bash +cd muse-cloud && mvn compile -pl muse-module-content -am +``` + +- [ ] **Step 9: 提交** + +```bash +git add muse-cloud/muse-module-content/ muse-cloud/pom.xml +git commit -m "feat(content): 创建 Content 模块骨架(api+server)" +``` + +### Task 3.2-3.6: 创建其余 5 个业务模块骨架 + +按相同模式创建 Account, Meta, AI, Knowledge, Market 五个模块。每个模块: +1. 创建 api/server 子模块 pom.xml +2. 创建 Feign API 接口类(对齐 OpenAPI 定义) +3. 从 `docs/api-contracts/generated/java/com/muse/dto/` 复制 DTO +4. 创建 server 包结构(controller/service/dal) +5. 在父 pom.xml 注册 +6. 编译验证 + +每个模块单独提交。 + +--- + +## Step 4: API 实现(按依赖顺序) + +### Task 4.1: Content API 实现 + +**依赖:** system(用户认证) + +- [ ] **Step 1: 编写 WorkService** + +```java +// muse-module-content-server/src/main/java/com/muse/module/content/service/WorkService.java +package com.muse.module.content.service; + +import com.muse.module.content.api.dto.*; +import org.springframework.data.domain.Page; + +/** + * 作品服务接口 + */ +public interface WorkService { + Page listWorks(Long userId, Integer pageNo, Integer pageSize, String status); + WorkDTO createWork(Long userId, CreateWorkRequest request); + WorkDTO getWork(Long workId); + void updateWork(Long workId, UpdateWorkRequest request); + void deleteWork(Long workId); +} +``` + +- [ ] **Step 2: 编写 WorkService 实现** + +```java +// muse-module-content-server/src/main/java/com/muse/module/content/service/impl/WorkServiceImpl.java +package com.muse.module.content.service.impl; + +import com.muse.module.content.dal.mysql.WorkMapper; +import com.muse.module.content.dal.dataobject.WorkDO; +import com.muse.module.content.service.WorkService; +import com.muse.module.content.api.dto.*; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class WorkServiceImpl implements WorkService { + + private final WorkMapper workMapper; + + public WorkServiceImpl(WorkMapper workMapper) { + this.workMapper = workMapper; + } + + @Override + public Page listWorks(Long userId, Integer pageNo, Integer pageSize, String status) { + // 分页查询作品列表 + } + + @Override + @Transactional + public WorkDTO createWork(Long userId, CreateWorkRequest request) { + WorkDO work = new WorkDO(); + work.setTitle(request.getTitle()); + work.setDescription(request.getDescription()); + work.setGenre(request.getGenre()); + work.setCreator(String.valueOf(userId)); + workMapper.insert(work); + return convertToDTO(work); + } + + // ... 其余方法 +} +``` + +- [ ] **Step 3: 编写 WorkMapper (MyBatis)** + +```java +// muse-module-content-server/src/main/java/com/muse/module/content/dal/mysql/WorkMapper.java +package com.muse.module.content.dal.mysql; + +import com.muse.framework.mybatis.core.mapper.BaseMapperX; +import com.muse.module.content.dal.dataobject.WorkDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface WorkMapper extends BaseMapperX { +} +``` + +- [ ] **Step 4: 编写 WorkController** + +```java +// muse-module-content-server/src/main/java/com/muse/module/content/controller/WorkController.java +package com.muse.module.content.controller; + +import com.muse.module.content.service.WorkService; +import com.muse.module.content.api.dto.*; +import com.muse.common.result.CommonResult; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/app-api/muse/works") +public class WorkController { + + private final WorkService workService; + + public WorkController(WorkService workService) { + this.workService = workService; + } + + @GetMapping + public CommonResult> listWorks( + @RequestParam(defaultValue = "1") Integer pageNo, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String status + ) { + Long userId = SecurityUtils.getLoginUserId(); + return CommonResult.success(workService.listWorks(userId, pageNo, pageSize, status)); + } + + @PostMapping + public CommonResult createWork(@RequestBody CreateWorkRequest request) { + Long userId = SecurityUtils.getLoginUserId(); + return CommonResult.success(workService.createWork(userId, request)); + } + + // ... 其余端点 +} +``` + +- [ ] **Step 5: 编写集成测试** + +```java +// muse-module-content-server/src/test/java/com/muse/module/content/WorkControllerTest.java +package com.muse.module.content; + +import com.muse.module.content.api.dto.*; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@Testcontainers +class WorkControllerTest { + + @Container + static PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:16-alpine") + .withDatabaseName("muse_test") + .withUsername("test") + .withPassword("test"); + + @Autowired + private TestRestTemplate restTemplate; + + @Test + void shouldCreateAndGetWork() { + // 创建作品 + CreateWorkRequest request = new CreateWorkRequest(); + request.setTitle("测试作品"); + + ResponseEntity createResp = restTemplate.postForEntity( + "/app-api/muse/works", request, CommonResult.class + ); + assertEquals(HttpStatus.OK, createResp.getStatusCode()); + assertNotNull(createResp.getBody().getData()); + } +} +``` + +- [ ] **Step 6: 运行测试** + +```bash +cd muse-cloud && mvn test -pl muse-module-content/muse-module-content-server +``` + +预期: 集成测试通过。 + +- [ ] **Step 7: 提交** + +```bash +git add muse-cloud/muse-module-content/ +git commit -m "feat(content): Content API 实现(Work CRUD + 集成测试)" +``` + +### Task 4.2: Chapter/Block API 实现 + +按相同 TDD 模式实现章节和 Block CRUD API: +1. 编写测试(验证 CRUD + 乐观锁冲突 + 排序逻辑) +2. 编写 Service + Controller + Mapper +3. 测试通过 +4. 提交 + +### Task 4.3: Block 保存(乐观锁 + 版本管理) + +核心:`PUT /app-api/muse/blocks/{blockId}` 实现乐观锁更新。 + +```java +@Transactional +public SaveBlockResultDTO saveBlock(Long userId, Long blockId, SaveBlockRequest request) { + // 1. 查询当前 Block + BlockDO block = blockMapper.selectById(blockId); + if (block == null) throw new ResourceNotFoundException("Block not found"); + + // 2. 乐观锁校验:expectedRevision 必须匹配 + if (!request.getExpectedRevision().equals(block.getRevision())) { + throw new RevisionConflictException(block.getRevision()); + } + + // 3. 更新 Block 内容和版本号 + block.setContent(request.getContent()); + block.setRevision(block.getRevision() + 1); + block.setUpdater(String.valueOf(userId)); + blockMapper.updateById(block); + + // 4. 写入来源归因 + BlockSourceAttributionDO attribution = new BlockSourceAttributionDO(); + attribution.setBlockId(blockId); + attribution.setRevision(block.getRevision()); + attribution.setSourceType(request.getSourceType()); + sourceAttributionMapper.insert(attribution); + + // 5. 返回新版本号 + SaveBlockResultDTO result = new SaveBlockResultDTO(); + result.setNewRevision(block.getRevision()); + return result; +} +``` + +### Task 4.4-4.11: 其余 Content 接口实现 + +按 OpenAPI 顺序逐步实现: +4.4: Source Attribution 接口 +4.5: Meta Projections 接口 +4.6: Planning 接口(规划查询/保存/候选创建/确认/丢弃) +4.7: Style Check 接口 +4.8: Import 接口(上传/解析/确认/批量确认) +4.9: Export 接口(创建任务/查询/下载) +4.10: Admin Content 接口(列表/详情/风险治理) +4.11: apply-suggestion 接口(接受 AI 候选后写入 Canonical) + +每个子任务:TDD → 实现 → 测试通过 → 提交。 + +### Task 4.12: Account API 实现 + +**依赖:** content 模块不相关,可独立开始。 + +实现权益查询、配额查询、用量摘要、安全事件、个人中心等接口。 +每个子任务按 TDD 模式:测试 → Service → Controller → Mapper → 验证 → 提交。 + +### Task 4.13: Meta API 实现 + +**依赖:** content + account 已稳定。 + +实现 MetaSchema CRUD、版本管理、草稿/校验/影响预览/发布/激活/回滚/废弃/灰度、保护节点查询、功能链路管理。 + +### Task 4.14: AI API 实现 + +**依赖:** content + meta 已稳定。 + +实现 AI 任务提交、SSE 流式输出、候选管理(列表/详情/接受/拒绝)、智能体管理(创建/版本/试用)、槽位绑定、Admin Prompt/Agent/质量策略管理、来源状态查询/重验。 + +### Task 4.15: Knowledge API 实现 + +**依赖:** content + meta 已稳定。 + +实现知识实体/关系、自动确认逻辑、全局KB管理、用户KB管理、文档上传/解析、发布流程、知识草稿确认/忽略、知识绑定/解绑。 + +### Task 4.16: Market API 实现 + +**依赖:** account + ai + knowledge 已稳定。 + +实现市场资产浏览/推荐、收藏、购买/安装、Handoff 创建/消费、发布草稿/检查/提交、管理端审核/下架/召回、申诉处理。 + +--- + +## 完成标准 + +- [ ] 6 个模块全部 API 自测通过 +- [ ] Domain 层单元测试覆盖率 ≥90%(JUnit 5 + Mockito) +- [ ] Application 层集成测试覆盖率 ≥80%(Testcontainers + PostgreSQL) +- [ ] ArchUnit 规则通过(AI grant/runtime 包隔离检查) +- [ ] Checkstyle / SpotBugs lint 通过 +- [ ] Maven build 成功(含所有测试) \ No newline at end of file diff --git a/docs/superpowers/plans/2026-05-24-P2-muse-studio.md b/docs/superpowers/plans/2026-05-24-P2-muse-studio.md new file mode 100644 index 00000000..7183ee09 --- /dev/null +++ b/docs/superpowers/plans/2026-05-24-P2-muse-studio.md @@ -0,0 +1,786 @@ +# P2: muse-studio 用户端搭建 — 执行计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. + +**目标:** 从零搭建 muse-studio React SPA,实现写作台、作品管理、知识库、智能体、市场、个人中心 6 个功能域。 + +**架构:** Vite + React + TypeScript SPA,TanStack Query 数据层,Zustand 状态管理,Tiptap/ProseMirror 编辑器,MSW Mock API,SSE 双通道实时通信,IndexedDB 安全网。 + +**技术栈:** React 19, TypeScript 5, Vite 6, TanStack Query 5, Zustand 4, React Hook Form 7 + Zod 3, Tiptap 2, Tailwind CSS 3, MSW 2, Vitest + Testing Library + Playwright + +--- + +## Step 1: 工程脚手架 + +### Task 1.1: 初始化 Vite + React + TS 项目 + +**仓库路径:** `muse-studio/`(新建) + +- [ ] **Step 1: 创建项目** + +```bash +npm create vite@latest muse-studio -- --template react-ts +cd muse-studio +``` + +- [ ] **Step 2: 安装核心依赖** + +```bash +pnpm add react@19 react-dom@19 react-router-dom@7 +pnpm add @tanstack/react-query@5 +pnpm add zustand@4 +pnpm add react-hook-form@7 zod@3 @hookform/resolvers@3 +pnpm add @tiptap/react @tiptap/pm @tiptap/starter-kit @tiptap/extension-placeholder +pnpm add -D tailwindcss@3 postcss autoprefixer +pnpm add -D @types/react @types/react-dom +pnpm add -D vitest@2 @testing-library/react @testing-library/jest-dom jsdom +pnpm add -D eslint@9 prettier @typescript-eslint/parser +pnpm add -D msw@2 +pnpm add -D playwright@1 @playwright/test +``` + +- [ ] **Step 3: 配置 Vite** + +```typescript +// muse-studio/vite.config.ts +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import path from 'path' + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + }, + }, + server: { + port: 5173, + proxy: { + '/app-api': { + target: 'http://localhost:48080', + changeOrigin: true, + }, + }, + }, + test: { + globals: true, + environment: 'jsdom', + setupFiles: ['./src/test/setup.ts'], + css: true, + }, +}) +``` + +- [ ] **Step 4: 创建目录结构** + +```bash +mkdir -p muse-studio/src/{features/{editor,agent,knowledge,market,account},components/{ui,layout},hooks,stores,lib,test,types} +``` + +- [ ] **Step 5: 配置 Tailwind** + +```bash +npx tailwindcss init -p +``` + +```javascript +// muse-studio/tailwind.config.js +export default { + content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], + theme: { extend: {} }, + plugins: [], +} +``` + +- [ ] **Step 6: 提交** + +```bash +git init && git add -A && git commit -m "feat(init): 初始化 Vite + React + TS 项目脚手架" +``` + +### Task 1.2: 路由框架 + 布局 + +**文件:** +- 创建: `src/router.tsx` +- 创建: `src/components/layout/AppLayout.tsx` +- 创建: `src/pages/{Workspace,Editor,Knowledge,Agent,Market,Account}Page.tsx` + +- [ ] **Step 1: 编写路由配置** + +```tsx +// src/router.tsx +import { createBrowserRouter } from 'react-router-dom'; +import AppLayout from '@/components/layout/AppLayout'; +import WorkspacePage from '@/pages/WorkspacePage'; +import EditorPage from '@/pages/EditorPage'; +import KnowledgePage from '@/pages/KnowledgePage'; +import AgentPage from '@/pages/AgentPage'; +import MarketPage from '@/pages/MarketPage'; +import AccountPage from '@/pages/AccountPage'; + +export const router = createBrowserRouter([ + { + path: '/', + element: , + children: [ + { index: true, element: }, + { path: 'works/:workId', element: }, + { path: 'works/:workId/editor/:chapterId', element: }, + { path: 'knowledge', element: }, + { path: 'knowledge/:workId', element: }, + { path: 'agents', element: }, + { path: 'market', element: }, + { path: 'account', element: }, + ], + }, +]); +``` + +- [ ] **Step 2: 编写布局组件** + +```tsx +// src/components/layout/AppLayout.tsx +import { Outlet } from 'react-router-dom'; +import Sidebar from './Sidebar'; + +export default function AppLayout() { + return ( +
+ +
+ +
+
+ ); +} +``` + +- [ ] **Step 3: 实现侧边栏导航** + +```tsx +// src/components/layout/Sidebar.tsx +import { NavLink } from 'react-router-dom'; + +const navItems = [ + { to: '/', label: '我的作品', icon: '📝' }, + { to: '/knowledge', label: '知识库', icon: '📚' }, + { to: '/agents', label: '智能体', icon: '🤖' }, + { to: '/market', label: '市场', icon: '🏪' }, + { to: '/account', label: '个人中心', icon: '👤' }, +]; + +export default function Sidebar() { + return ( + + ); +} +``` + +- [ ] **Step 4: 创建占位页面** + +```tsx +// src/pages/WorkspacePage.tsx +export default function WorkspacePage() { + return

我的作品

; +} +``` + +- [ ] **Step 5: Main.tsx 集成路由** + +```tsx +// src/main.tsx +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { RouterProvider } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { router } from './router'; +import './index.css'; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 30_000, + retry: 1, + }, + }, +}); + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + +); +``` + +- [ ] **Step 6: 验证启动** + +```bash +cd muse-studio && pnpm dev +``` + +浏览 `http://localhost:5173`,验证路由导航正常。 + +- [ ] **Step 7: 提交** + +```bash +git add -A && git commit -m "feat(router): 添加路由框架 + 布局组件 + 6 个功能域占位" +``` + +--- + +## Step 2: 核心基础设施 + +### Task 2.1: API 客户端 + 类型集成 + +**文件:** +- 创建: `src/lib/api.ts` +- 创建: `src/types/openapi.ts`(从 OpenAPI 生成物导入) + +- [ ] **Step 1: 创建 API 客户端** + +```typescript +// src/lib/api.ts +import { CommonResult } from '@/types/openapi'; + +const BASE_URL = '/app-api/muse'; + +class ApiError extends Error { + constructor(public code: string, message: string, public status: number) { + super(message); + } +} + +export async function request( + path: string, + options: RequestInit = {} +): Promise { + const response = await fetch(`${BASE_URL}${path}`, { + headers: { + 'Content-Type': 'application/json', + 'X-API-Version': '1', + ...options.headers, + }, + ...options, + }); + + const body: CommonResult = await response.json(); + + if (body.code !== 0) { + throw new ApiError(String(body.code), body.msg, response.status); + } + + return body.data as T; +} + +export const api = { + get: (path: string) => request(path), + post: (path: string, data?: unknown) => + request(path, { + method: 'POST', + body: data ? JSON.stringify(data) : undefined, + }), + put: (path: string, data?: unknown) => + request(path, { + method: 'PUT', + body: data ? JSON.stringify(data) : undefined, + }), + delete: (path: string) => request(path, { method: 'DELETE' }), +}; +``` + +- [ ] **Step 2: 复制类型包** + +```bash +cp docs/api-contracts/generated/typescript/*.ts muse-studio/src/types/ +``` + +- [ ] **Step 3: 编写类型入口** + +```typescript +// src/types/openapi.ts +export type * as Base from './base'; +export type * as Content from './content'; +export type * as AI from './ai'; +export type * as Knowledge from './knowledge'; +export type * as Market from './market'; +export type * as Account from './account'; +export type * as Meta from './meta'; +``` + +- [ ] **Step 4: 提交** + +```bash +git add -A && git commit -m "feat(api): 集成 API 客户端 + OpenAPI 类型包" +``` + +### Task 2.2: MSW Mock API 配置 + +**文件:** +- 创建: `src/test/mocks/handlers/` 下各模块 handler + +- [ ] **Step 1: 安装 MSW** + +```bash +pnpm add -D msw@2 +npx msw init public/ --save +``` + +- [ ] **Step 2: 编写 Content Mock Handlers** + +```typescript +// src/test/mocks/handlers/content.ts +import { http, HttpResponse } from 'msw'; + +export const contentHandlers = [ + // GET /app-api/muse/works — 作品列表 + http.get('/app-api/muse/works', ({ request }) => { + const url = new URL(request.url); + const pageNo = parseInt(url.searchParams.get('pageNo') || '1'); + return HttpResponse.json({ + code: 0, + msg: 'success', + data: { + total: 3, + pageNo, + pageSize: 20, + list: [ + { + id: '1', + title: '星海迷途', + status: 'active', + genre: '科幻', + wordCount: 52300, + chapterCount: 12, + updatedAt: '2026-05-20T10:30:00Z', + }, + { + id: '2', + title: '长安旧事', + status: 'draft', + genre: '历史', + wordCount: 12800, + chapterCount: 3, + updatedAt: '2026-05-24T08:00:00Z', + }, + { + id: '3', + title: '深渊笔记', + status: 'active', + genre: '悬疑', + wordCount: 89500, + chapterCount: 24, + updatedAt: '2026-05-23T18:00:00Z', + }, + ], + }, + }); + }), + + // POST /app-api/muse/works — 创建作品 + http.post('/app-api/muse/works', async ({ request }) => { + const body = await request.json(); + const work = { + id: crypto.randomUUID(), + title: (body as any).title || '未命名作品', + description: (body as any).description || '', + status: 'draft', + genre: (body as any).genre || '', + wordCount: 0, + chapterCount: 0, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + return HttpResponse.json({ code: 0, msg: 'success', data: work }, { status: 201 }); + }), + + // GET /app-api/muse/works/:workId — 作品详情 + http.get('/app-api/muse/works/:workId', ({ params }) => { + return HttpResponse.json({ + code: 0, msg: 'success', + data: { + id: params.workId, + title: '星海迷途', + description: '人类在星际探索中...', + status: 'active', + genre: '科幻', + wordCount: 52300, + chapterCount: 12, + createdAt: '2026-05-01T00:00:00Z', + updatedAt: '2026-05-20T10:30:00Z', + }, + }); + }), +]; +``` + +- [ ] **Step 3: 配置 MSW 入口** + +```typescript +// src/test/mocks/browser.ts +import { setupWorker } from 'msw/browser'; +import { contentHandlers } from './handlers/content'; +import { aiHandlers } from './handlers/ai'; +import { knowledgeHandlers } from './handlers/knowledge'; +import { marketHandlers } from './handlers/market'; +import { accountHandlers } from './handlers/account'; + +export const worker = setupWorker( + ...contentHandlers, + ...aiHandlers, + ...knowledgeHandlers, + ...marketHandlers, + ...accountHandlers +); +``` + +- [ ] **Step 4: 在开发环境启用 MSW** + +```tsx +// src/main.tsx 开头追加 +if (import.meta.env.DEV) { + const { worker } = await import('./test/mocks/browser'); + await worker.start({ onUnhandledRequest: 'bypass' }); +} +``` + +- [ ] **Step 5: 提交** + +```bash +git add -A && git commit -m "feat(mock): 配置 MSW Mock API(Content handlers)" +``` + +### Task 2.3: SSE 双通道客户端 + +**文件:** +- 创建: `src/lib/sse.ts` + +```typescript +// src/lib/sse.ts + +/** + * Muse SSE 双通道设计: + * 1. AI stream: 独立连接,用于 AI 生成实时流 + * 2. Event stream: 长连接,用于事件通知,支持 lastEventId 续传 + */ + +export type SSEEventHandler = { + onChunk?: (data: { content: string; sequenceNo: number }) => void; + onQualityCheck?: (data: { dimension: string; score: number; passed: boolean }) => void; + onDone?: (data: { generationId: string; candidateId: string }) => void; + onError?: (data: { code: string; message: string }) => void; +}; + +export function connectAIStream( + url: string, + handlers: SSEEventHandler +): AbortController { + const controller = new AbortController(); + + fetch(url, { + headers: { + 'Accept': 'text/event-stream', + 'X-API-Version': '1', + }, + signal: controller.signal, + }).then(async (response) => { + const reader = response.body!.getReader(); + const decoder = new TextDecoder(); + let buffer = ''; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split('\n'); + buffer = lines.pop() || ''; + + for (const line of lines) { + if (line.startsWith('data: ')) { + const data = JSON.parse(line.slice(6)); + switch (data.type) { + case 'chunk': handlers.onChunk?.(data); break; + case 'quality_check': handlers.onQualityCheck?.(data); break; + case 'done': handlers.onDone?.(data); break; + case 'error': handlers.onError?.(data); break; + } + } + } + } + }).catch((err) => { + if (err.name !== 'AbortError') { + handlers.onError?.({ code: 'SSE_ERROR', message: err.message }); + } + }); + + return controller; +} + +export function connectEventStream( + lastEventId?: string +): { close: () => void; on: (event: string, handler: (data: unknown) => void) => void } { + const eventSource = new EventSource( + `/app-api/muse/events${lastEventId ? `?lastEventId=${lastEventId}` : ''}` + ); + + return { + close: () => eventSource.close(), + on: (event, handler) => { + eventSource.addEventListener(event, (e) => { + handler(JSON.parse(e.data)); + }); + }, + }; +} +``` + +- [ ] **Step: 提交** + +```bash +git add -A && git commit -m "feat(sse): 实现 SSE 双通道客户端(AI stream + Event stream)" +``` + +### Task 2.4: IndexedDB 持久化层 + +**文件:** +- 创建: `src/lib/indexed-db.ts` + +```typescript +// src/lib/indexed-db.ts +const DB_NAME = 'muse-studio'; +const BLOCK_STORE = 'block-drafts'; +const DB_VERSION = 1; + +function openDB(): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(DB_NAME, DB_VERSION); + request.onupgradeneeded = () => { + const db = request.result; + if (!db.objectStoreNames.contains(BLOCK_STORE)) { + db.createObjectStore(BLOCK_STORE, { keyPath: 'blockId' }); + } + }; + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); +} + +export async function saveBlockDraft(blockId: string, content: Record): Promise { + const db = await openDB(); + const tx = db.transaction(BLOCK_STORE, 'readwrite'); + tx.objectStore(BLOCK_STORE).put({ blockId, content, savedAt: Date.now() }); +} + +export async function getBlockDraft(blockId: string): Promise | null> { + const db = await openDB(); + const tx = db.transaction(BLOCK_STORE, 'readonly'); + return new Promise((resolve, reject) => { + const req = tx.objectStore(BLOCK_STORE).get(blockId); + req.onsuccess = () => resolve(req.result?.content ?? null); + req.onerror = () => reject(req.error); + }); +} + +export async function removeBlockDraft(blockId: string): Promise { + const db = await openDB(); + const tx = db.transaction(BLOCK_STORE, 'readwrite'); + tx.objectStore(BLOCK_STORE).delete(blockId); +} +``` + +- [ ] **Step: 提交** + +```bash +git add -A && git commit -m "feat(storage): 实现 IndexedDB 持久化层(Block draft 安全网)" +``` + +### Task 2.5: Zustand 状态管理 + +**文件:** +- 创建: `src/stores/editorStore.ts` +- 创建: `src/stores/uiStore.ts` + +```typescript +// src/stores/editorStore.ts +import { create } from 'zustand'; + +interface EditorState { + activeBlockId: string | null; + isDirty: boolean; + lastSavedAt: Date | null; + setActiveBlock: (blockId: string | null) => void; + markDirty: () => void; + markSaved: () => void; +} + +export const useEditorStore = create((set) => ({ + activeBlockId: null, + isDirty: false, + lastSavedAt: null, + setActiveBlock: (blockId) => set({ activeBlockId: blockId, isDirty: false }), + markDirty: () => set({ isDirty: true }), + markSaved: () => set({ isDirty: false, lastSavedAt: new Date() }), +})); +``` + +```typescript +// src/stores/uiStore.ts +import { create } from 'zustand'; + +interface UIState { + sidebarCollapsed: boolean; + candidatePanelOpen: boolean; + toggleSidebar: () => void; + toggleCandidatePanel: () => void; +} + +export const useUIStore = create((set) => ({ + sidebarCollapsed: false, + candidatePanelOpen: false, + toggleSidebar: () => set((s) => ({ sidebarCollapsed: !s.sidebarCollapsed })), + toggleCandidatePanel: () => set((s) => ({ candidatePanelOpen: !s.candidatePanelOpen })), +})); +``` + +- [ ] **Step: 提交** + +```bash +git add -A && git commit -m "feat(state): 配置 Zustand store(编辑器状态 + UI 状态)" +``` + +--- + +## Step 3: 功能域开发(按优先级) + +### Feature 1: 写作台(核心路径) + +**优先级 1 — 编辑器集成 + AI 生成 + 候选面板** + +- [ ] **Task 3.1: Tiptap 编辑器集成** + +文件: `src/features/editor/components/MuseEditor.tsx` + +```tsx +import { useEditor, EditorContent } from '@tiptap/react'; +import StarterKit from '@tiptap/starter-kit'; +import Placeholder from '@tiptap/extension-placeholder'; +import { useEffect, useCallback } from 'react'; +import { useEditorStore } from '@/stores/editorStore'; +import { saveBlockDraft, getBlockDraft, removeBlockDraft } from '@/lib/indexed-db'; + +interface MuseEditorProps { + blockId: string; + initialContent?: unknown; + revision: number; +} + +export default function MuseEditor({ blockId, initialContent, revision }: MuseEditorProps) { + const { markDirty, markSaved } = useEditorStore(); + + const editor = useEditor({ + extensions: [ + StarterKit, + Placeholder.configure({ placeholder: '开始创作...' }), + ], + content: initialContent as any, + onUpdate: ({ editor }) => { + markDirty(); + // 自动保存到 IndexedDB(debounce 2s) + debouncedSave(blockId, editor.getJSON()); + }, + autofocus: 'end', + }); + + // 自动保存到后端 + IndexedDB 安全网(debounce 2s) + const debouncedSave = useCallback( + debounce(async (blockId: string, content: unknown) => { + // 1. 先保存到 IndexedDB(即时,安全网) + await saveBlockDraft(blockId, content as Record); + // 2. 再调用 API(乐观锁) + try { + const result = await api.put<{ newRevision: number }>( + `/app-api/muse/blocks/${blockId}`, + { content: JSON.stringify(content), expectedRevision: revision } + ); + await removeBlockDraft(blockId); + markSaved(); + } catch (err) { + // API 失败:IndexedDB 已保存,下次恢复 + } + }, 2000), + [revision] + ); + + // 恢复 IndexedDB 草稿 + useEffect(() => { + getBlockDraft(blockId).then((draft) => { + if (draft && editor) { + editor.commands.setContent(draft as any); + } + }); + }, [blockId, editor]); + + return ( +
+ +
+ ); +} + +function debounce any>(fn: T, ms: number): T { + let timer: ReturnType; + return ((...args) => { + clearTimeout(timer); + timer = setTimeout(() => fn(...args), ms); + }) as T; +} +``` + +- [ ] **Task 3.2: AI 生成面板** — 调用 AI SSE stream,展示实时流式输出 + +- [ ] **Task 3.3: 候选面板** — 展示候选列表,支持接受/拒绝,显示 diff + +- [ ] **Task 3.4: 编程式提交** + +```bash +git add -A && git commit -m "feat(editor): Tiptap 编辑器集成 + IndexedDB 安全网 + 自动保存" +``` + +### Feature 2-6: 其余功能域 + +每个功能域按相同 TDD 模式开发: +1. 编写组件测试(Vitest + Testing Library) +2. 实现组件和 Hook +3. 编写 E2E 测试(Playwright) +4. 测试通过 +5. 提交 + +优先级顺序: +- **F2 我的作品 + 工作台**: 作品列表/创建/删除、章节管理、Block 分割 +- **F3 知识库工作台**: 知识面板、实体/关系可视化、知识草稿管理 +- **F4 智能体工作台**: 智能体列表/创建/配置/槽位绑定 +- **F5 市场 + 个人中心**: 市场浏览/安装/绑定、个人中心 + +--- + +## 完成标准 + +- [ ] 6 个功能域页面可交互 +- [ ] 组件单元测试覆盖率 ≥75%(Vitest + Testing Library) +- [ ] Hook 测试覆盖率 ≥80% +- [ ] E2E 测试覆盖核心写作流程(Playwright) +- [ ] ESLint + Prettier + TypeScript 类型检查通过 +- [ ] Vite build 成功 \ No newline at end of file diff --git a/docs/superpowers/plans/2026-05-24-P3-muse-admin.md b/docs/superpowers/plans/2026-05-24-P3-muse-admin.md new file mode 100644 index 00000000..1a399736 --- /dev/null +++ b/docs/superpowers/plans/2026-05-24-P3-muse-admin.md @@ -0,0 +1,244 @@ +# P3: muse-admin 管理端搭建 — 执行计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. + +**目标:** 在 Vben Admin fork 基础上搭建 muse-admin,实现 MetaSchema 管理、系统治理、AI 配置、市场治理、全局知识管理 5 个功能域。 + +**架构:** Vue 3 + Vben Admin + TypeScript,Composition API + SFC script setup,defHttp API 集成,Vben 内置表格/表单组件复用。 + +**技术栈:** Vue 3.5, TypeScript 5, Vben Admin 5, Pinia, Vitest + Vue Test Utils + Playwright + +注: muse-admin 已 fork 完成,存在 `apps/web-antd` 和 `packages/` 结构。 + +--- + +## Step 1: 工程调整 + +### Task 1.1: 业务目录结构建立 + +**仓库路径:** `muse-admin/` + +- [ ] **Step 1: 创建业务模块目录** + +```bash +mkdir -p muse-admin/apps/web-antd/src/views/{governance,ai,knowledge,market,account,jobs,audit} +mkdir -p muse-admin/apps/web-antd/src/api/muse/{governance,ai,knowledge,market,account,jobs} +``` + +- [ ] **Step 2: 检查现有结构** + +```bash +ls muse-admin/apps/web-antd/src/views/ +ls muse-admin/packages/@core/ +``` + +- [ ] **Step 3: 提交** + +```bash +git add muse-admin/apps/web-antd/src/views/governance/ muse-admin/apps/web-antd/src/api/muse/ +git commit -m "feat(struct): 建立管理端业务模块目录(governance/ai/knowledge/market/account)" +``` + +### Task 1.2: API 类型集成 + +- [ ] **Step 1: 复制 OpenAPI 生成的类型** + +```bash +cp docs/api-contracts/generated/typescript/*.ts muse-admin/apps/web-antd/src/api/types/ +``` + +- [ ] **Step 2: 编写 API 客户端** + +```typescript +// muse-admin/apps/web-antd/src/api/muse/client.ts +import { requestClient } from '#/api/request'; + +const BASE = '/admin-api/muse'; + +export const museAdminApi = { + get: (url: string, params?: Record) => + requestClient.get(`${BASE}${url}`, { params }), + + post: (url: string, data?: unknown) => + requestClient.post(`${BASE}${url}`, data), + + put: (url: string, data?: unknown) => + requestClient.put(`${BASE}${url}`, data), + + delete: (url: string) => + requestClient.delete(`${BASE}${url}`), +}; +``` + +- [ ] **Step 3: 提交** + +```bash +git add -A && git commit -m "feat(api): 集成管理端 API 客户端 + OpenAPI 类型包" +``` + +--- + +## Step 2: 管理页面开发(按优先级) + +### Task 2.1: MetaSchema 管理 — 列表页 + +**文件:** +- 创建: `apps/web-antd/src/views/governance/MetaSchemaList.vue` +- 创建: `apps/web-antd/src/api/muse/governance/meta-schema.ts` + +- [ ] **Step 1: 编写 API service** + +```typescript +// apps/web-antd/src/api/muse/governance/meta-schema.ts +import { museAdminApi } from '../client'; + +export interface MetaSchemaSummary { + schemaKey: string; + displayName: string; + fieldType: string; + scope: string; + activeVersion: number; + status: string; +} + +export function listMetaSchemas(params: { + pageNo: number; + pageSize: number; + scope?: string; +}) { + return museAdminApi.get<{ + total: number; + list: MetaSchemaSummary[]; + }>('/governance/meta-schemas', params); +} + +export function getMetaSchema(schemaKey: string) { + return museAdminApi.get(`/governance/meta-schemas/${schemaKey}`); +} + +export function createMetaSchemaDraft(schemaKey: string, data: any) { + return museAdminApi.post(`/governance/meta-schemas/${schemaKey}/drafts`, data); +} + +export function publishMetaSchemaDraft(schemaKey: string, draftVersion: number, data: any) { + return museAdminApi.post( + `/governance/meta-schemas/${schemaKey}/drafts/${draftVersion}/publish`, + data + ); +} +``` + +- [ ] **Step 2: 编写列表页** + +```vue + + + + +``` + +- [ ] **Step 3: 编写测试** + +```typescript +// apps/web-antd/src/views/governance/__tests__/MetaSchemaList.spec.ts +import { describe, it, expect, vi } from 'vitest'; +import { mount } from '@vue/test-utils'; +import MetaSchemaList from '../MetaSchemaList.vue'; + +vi.mock('#/api/muse/governance/meta-schema', () => ({ + listMetaSchemas: vi.fn().mockResolvedValue({ + total: 2, + list: [ + { schemaKey: 'character_name', displayName: '角色名', fieldType: 'string', scope: 'work', activeVersion: 3, status: 'active' }, + { schemaKey: 'world_setting', displayName: '世界设定', fieldType: 'text', scope: 'global', activeVersion: 1, status: 'active' }, + ], + }), +})); + +describe('MetaSchemaList', () => { + it('should render table', () => { + const wrapper = mount(MetaSchemaList); + expect(wrapper.find('.p-4').exists()).toBe(true); + }); +}); +``` + +- [ ] **Step 4: 提交** + +```bash +git add -A && git commit -m "feat(governance): 实现 MetaSchema 列表页(Vben Table + API 集成)" +``` + +### Task 2.2: MetaSchema 管理 — 详情/草稿编辑页 + +实现 MetaSchema 详情页,包含: +- 版本历史时间线 +- 当前 active 版本字段展示 +- 草稿编辑 + 校验 + 影响预览 + 发布流程 +- 灰度规则配置 + +使用 Vben 内置表单组件 + 动态表单渲染。 + +### Task 2.3-2.5: 其余管理页面 + +按优先级依次实现: + +**Task 2.3: 系统治理** — 用户管理(复用 Yudao system 模块)、角色权限配置、审计日志查看 + +**Task 2.4: AI 配置** — Prompt 模板管理、质量门控维度配置、保护节点注册管理、Tool Grant 配置 + +**Task 2.5: 市场治理 + 全局知识管理** — 资产审核(上架/驳回)、下架/召回、申诉处理、全局知识库维护、知识来源管理 + +每个页面遵循相同模式:API service → 组件实现 → 测试 → 提交。 + +--- + +## Step 3: Mock API 配置 + +- [ ] **Step 1: 配置 MSW 或 Vben 内置 Mock** + +为每个 `/admin-api/muse/**` 接口提供 mock 数据,使管理端可独立开发。 + +--- + +## 完成标准 + +- [ ] 5 个功能域管理页面可交互 +- [ ] 组件单元测试覆盖率 ≥70%(Vitest + Vue Test Utils) +- [ ] E2E 测试覆盖核心管理流程(Playwright) +- [ ] ESLint + Prettier + TypeScript 类型检查通过 +- [ ] Vite build 成功 \ No newline at end of file diff --git a/docs/superpowers/plans/2026-05-24-P4-集成切换.md b/docs/superpowers/plans/2026-05-24-P4-集成切换.md new file mode 100644 index 00000000..bdffec25 --- /dev/null +++ b/docs/superpowers/plans/2026-05-24-P4-集成切换.md @@ -0,0 +1,206 @@ +# P4: 集成切换 + 质量加固 — 执行计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. + +**目标:** 按模块将前端从 Mock API 切换到真实后端 API,完成端到端联调,修复契约缺陷,实现全量质量加固(测试/CI/安全)。 + +**架构:** 按依赖顺序逐个模块切换:content+account → meta → ai+knowledge → market。每模块切换后更新 OpenAPI 契约。最后进行全量质量加固。 + +**涉及仓库:** muse-cloud, muse-studio, muse-admin, muse-design-docs + +--- + +## Phase 1: 集成切换 + +### Switch S1: content + account 集成切换 + +**依赖:** muse-cloud content + account API 自测通过 + +- [ ] **Step 1: 后端契约测试通过** + +```bash +cd muse-cloud && mvn test -pl muse-module-content,muse-module-account +``` + +验证: Content (50 接口) + Account (33 接口) 全部集成测试通过。 + +- [ ] **Step 2: 移除前端 Content MSW handlers** + +```typescript +// muse-studio/src/test/mocks/browser.ts +// 注释掉 contentHandlers,保留其他模块的 Mock +export const worker = setupWorker( + // ...contentHandlers, // ← 切换真实 API + ...aiHandlers, + ...knowledgeHandlers, + ...marketHandlers, + ...accountHandlers +); +``` + +- [ ] **Step 3: 前端联调验证** + +```bash +cd muse-studio && pnpm dev +``` + +测试流程: +1. 作品列表加载正常 +2. 创建新作品 → 自动跳转 +3. 章节列表 + 创建章节 +4. Block 编辑器加载 → 自动保存 → 验证 IndexedDB 安全网 + +- [ ] **Step 4: 修正接口不一致** + +联调中发现的契约缺陷: +1. 记录具体差异(字段名/类型/状态码不一致) +2. 决定修后端还是修 OpenAPI +3. 更新 OpenAPI 契约文件 +4. 重新生成 TS 类型包 +5. 前端更新类型引用 + +- [ ] **Step 5: 冒烟测试(写作流程)** + +Playwright E2E: +```typescript +// muse-studio/e2e/writing-flow.spec.ts +import { test, expect } from '@playwright/test'; + +test('完整写作流程', async ({ page }) => { + await page.goto('http://localhost:5173'); + + // 1. 查看作品列表 + await expect(page.locator('h1')).toContainText('我的作品'); + + // 2. 创建新作品 + await page.click('button:has-text("新建作品")'); + await page.fill('input[name="title"]', 'E2E 测试作品'); + await page.click('button:has-text("创建")'); + + // 3. 进入编辑器 + await expect(page.locator('.ProseMirror')).toBeVisible(); + + // 4. 输入文本 + await page.locator('.ProseMirror').fill('第一段测试文字'); + + // 5. 等待自动保存 + await page.waitForTimeout(3000); + await expect(page.locator('[data-testid="save-indicator"]')).toContainText('已保存'); +}); +``` + +- [ ] **Step 6: Account API 切换(同时)** + +类似流程:移除 account MSW handlers → 联调验证个人中心/用量/配额 → 修正不一致 → 更新契约 + +- [ ] **Step 7: 提交契约更新** + +```bash +git add docs/api-contracts/ +git commit -m "fix(api): content+account 联调后契约修正" +``` + +### Switch S2: meta API 集成切换 + +**依赖:** muse-cloud meta API 自测通过 + +1. MetaSchema 管理页面切换到真实 API +2. 验证 CRUD + 草稿/发布/版本管理流程 +3. 修正不一致,更新 OpenAPI + +### Switch S3: ai + knowledge API 集成切换 + +**依赖:** muse-cloud ai + knowledge API 自测通过 + +1. AI 生成 SSE 流验证(断线重连、超时) +2. 候选接受/拒绝流程验证 +3. 知识库文档上传/解析/确认流程 +4. 知识绑定/解绑跨模块测试 +5. 修正不一致,更新 OpenAPI + +### Switch S4: market API 集成切换 + +**依赖:** 前面所有模块已切换 + +1. 市场浏览/推荐验证 +2. Handoff 跨空间跳转测试 +3. 发布/审核流程验证 +4. 修正不一致,更新 OpenAPI + +--- + +## Phase 2: 质量加固 + +### Quality G1: 后端全量测试 + +- [ ] **单元测试补充** + - Domain 层 edge case 覆盖 + - Service 层 mock 测试(JUnit 5 + Mockito) + - 目标: Domain ≥90%, Application ≥80% + +- [ ] **集成测试补充** + - Testcontainers + PostgreSQL + - 跨模块事务边界测试(内容保存 → 知识提取 → AI 候选) + - 并发冲突测试(乐观锁) + +- [ ] **契约测试** + - Spring Cloud Contract 或 Pact + - 验证 API 响应与 OpenAPI 定义一致 + +### Quality G2: 前端全量测试 + +- [ ] **muse-studio 单元测试补充** + - 组件测试 (Testing Library) ≥75% + - Hook 测试 ≥80% + +- [ ] **muse-admin 单元测试补充** + - 组件测试 (Vue Test Utils) ≥70% + - Composable 测试 ≥75% + +- [ ] **E2E 测试** + - Playwright 覆盖核心流程 + - studio: 写作流程、作品管理、知识确认 + - admin: MetaSchema 管理、资产审核 + +### Quality G3: CI/CD + +- [ ] **后端 CI 流水线** (GitHub Actions / Jenkins) + ```yaml + # .github/workflows/muse-cloud.yml + steps: + - compile (Maven) + - unit-test (JUnit + Mockito) + - integration-test (Testcontainers) + - lint (Checkstyle + SpotBugs) + - build-image (Docker) + ``` + +- [ ] **前端 CI 流水线** + ```yaml + # .github/workflows/muse-studio.yml + steps: + - typecheck (tsc --noEmit) + - lint (ESLint + Prettier) + - test (Vitest) + - build (Vite) + ``` + +### Quality G4: 安全加固 + +- [ ] **依赖漏洞扫描** (npm audit, OWASP Dependency Check) +- [ ] **XSS 防护 + CSP 头配置** +- [ ] **敏感数据脱敏**(日志中 token/密码脱敏) +- [ ] **SSRF 防护**(Knowledge linkUrl allowlist + 私网拦截) +- [ ] **AI 安全**(Prompt injection 防护、输出合规检查) + +--- + +## 完成标准 + +- [ ] 6 个模块全部切换真实 API +- [ ] 全量端到端冒烟测试通过 +- [ ] 后端测试覆盖率达标: Domain ≥90%, Application ≥80% +- [ ] 前端测试覆盖率达标: Studio ≥75%, Admin ≥70% +- [ ] CI 流水线全部通过 +- [ ] OpenAPI 契约与实现一致 +- [ ] 安全扫描无 Critical/High 新发现 \ No newline at end of file