feat(plan): 添加 P1-P4 四仓详细执行计划

This commit is contained in:
zizi 2026-05-24 18:13:39 +08:00
parent e0834cf4f8
commit 4c02ab99fe
4 changed files with 2204 additions and 0 deletions

View File

@ -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 业务模块,实现全量 API229 接口),提供可调用的后端服务。
**架构:** 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'
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JavaCodeStyleSettings>
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
<value />
</option>
</JavaCodeStyleSettings>
</code_scheme>
</component>
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
<!-- muse-cloud/muse-module-content/pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.muse</groupId>
<artifactId>muse</artifactId>
<version>${revision}</version>
</parent>
<artifactId>muse-module-content</artifactId>
<packaging>pom</packaging>
<name>Muse Module Content</name>
<description>作品、章节、Block、导出模块</description>
<modules>
<module>muse-module-content-api</module>
<module>muse-module-content-server</module>
</modules>
</project>
```
- [ ] **Step 2: 创建 api 子模块 pom.xml**
```xml
<!-- muse-cloud/muse-module-content/muse-module-content-api/pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.muse</groupId>
<artifactId>muse-module-content</artifactId>
<version>${revision}</version>
</parent>
<artifactId>muse-module-content-api</artifactId>
<packaging>jar</packaging>
<name>Muse Module Content API</name>
<description>Content 模块 API 接口定义Feign 接口 + DTO</description>
<dependencies>
<dependency>
<groupId>com.muse</groupId>
<artifactId>muse-common</artifactId>
</dependency>
</dependencies>
</project>
```
- [ ] **Step 3: 创建 server 子模块 pom.xml**
```xml
<!-- muse-cloud/muse-module-content/muse-module-content-server/pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.muse</groupId>
<artifactId>muse-module-content</artifactId>
<version>${revision}</version>
</parent>
<artifactId>muse-module-content-server</artifactId>
<packaging>jar</packaging>
<name>Muse Module Content Server</name>
<description>Content 模块服务实现</description>
<dependencies>
<dependency>
<groupId>com.muse</groupId>
<artifactId>muse-module-content-api</artifactId>
</dependency>
<dependency>
<groupId>com.muse</groupId>
<artifactId>muse-module-system-api</artifactId>
</dependency>
<!-- Spring Boot Starter Web, MyBatis, PostgreSQL, Testcontainers -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
```
- [ ] **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<PageResult<WorkSummaryDTO>> listWorks(
@RequestParam("pageNo") Integer pageNo,
@RequestParam("pageSize") Integer pageSize,
@RequestParam(value = "status", required = false) String status
);
@PostMapping("/app-api/muse/works")
CommonResult<WorkDTO> createWork(@RequestBody CreateWorkRequest request);
@GetMapping("/app-api/muse/works/{workId}")
CommonResult<WorkDTO> getWork(@PathVariable("workId") Long workId);
@PutMapping("/app-api/muse/works/{workId}")
CommonResult<Void> updateWork(@PathVariable("workId") Long workId, @RequestBody UpdateWorkRequest request);
@DeleteMapping("/app-api/muse/works/{workId}")
CommonResult<Void> deleteWork(@PathVariable("workId") Long workId);
// ===== Chapter =====
@GetMapping("/app-api/muse/works/{workId}/chapters")
CommonResult<List<ChapterDTO>> listChapters(@PathVariable("workId") Long workId);
@PostMapping("/app-api/muse/works/{workId}/chapters")
CommonResult<ChapterDTO> createChapter(@PathVariable("workId") Long workId, @RequestBody CreateChapterRequest request);
@GetMapping("/app-api/muse/chapters/{chapterId}")
CommonResult<ChapterDetailDTO> getChapter(@PathVariable("chapterId") Long chapterId);
@PutMapping("/app-api/muse/chapters/{chapterId}")
CommonResult<Void> updateChapter(@PathVariable("chapterId") Long chapterId, @RequestBody UpdateChapterRequest request);
@DeleteMapping("/app-api/muse/chapters/{chapterId}")
CommonResult<Void> deleteChapter(@PathVariable("chapterId") Long chapterId);
// ===== Block =====
@GetMapping("/app-api/muse/chapters/{chapterId}/blocks")
CommonResult<List<BlockDTO>> listBlocks(@PathVariable("chapterId") Long chapterId);
@PostMapping("/app-api/muse/chapters/{chapterId}/blocks")
CommonResult<BlockDTO> createBlock(@PathVariable("chapterId") Long chapterId, @RequestBody CreateBlockRequest request);
@PutMapping("/app-api/muse/blocks/{blockId}")
CommonResult<SaveBlockResultDTO> saveBlock(@PathVariable("blockId") Long blockId, @RequestBody SaveBlockRequest request);
@DeleteMapping("/app-api/muse/blocks/{blockId}")
CommonResult<Void> 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 的 <modules> 中添加
# <module>muse-module-content</module>
```
- [ ] **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<WorkSummaryDTO> 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<WorkSummaryDTO> 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<WorkDO> {
}
```
- [ ] **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<PageResult<WorkSummaryDTO>> 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<WorkDTO> 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<CommonResult> 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 成功(含所有测试)

View File

@ -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 SPATanStack Query 数据层Zustand 状态管理Tiptap/ProseMirror 编辑器MSW Mock APISSE 双通道实时通信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: <AppLayout />,
children: [
{ index: true, element: <WorkspacePage /> },
{ path: 'works/:workId', element: <WorkspacePage /> },
{ path: 'works/:workId/editor/:chapterId', element: <EditorPage /> },
{ path: 'knowledge', element: <KnowledgePage /> },
{ path: 'knowledge/:workId', element: <KnowledgePage /> },
{ path: 'agents', element: <AgentPage /> },
{ path: 'market', element: <MarketPage /> },
{ path: 'account', element: <AccountPage /> },
],
},
]);
```
- [ ] **Step 2: 编写布局组件**
```tsx
// src/components/layout/AppLayout.tsx
import { Outlet } from 'react-router-dom';
import Sidebar from './Sidebar';
export default function AppLayout() {
return (
<div className="flex h-screen bg-gray-50">
<Sidebar />
<main className="flex-1 overflow-auto">
<Outlet />
</main>
</div>
);
}
```
- [ ] **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 (
<nav className="w-16 bg-white border-r flex flex-col items-center py-4 gap-2">
{navItems.map(item => (
<NavLink key={item.to} to={item.to}
className={({ isActive }) =>
`p-2 rounded text-xs ${isActive ? 'bg-blue-100' : 'hover:bg-gray-100'}`
}>
<div className="text-xl">{item.icon}</div>
<div>{item.label}</div>
</NavLink>
))}
</nav>
);
}
```
- [ ] **Step 4: 创建占位页面**
```tsx
// src/pages/WorkspacePage.tsx
export default function WorkspacePage() {
return <div className="p-6"><h1 className="text-2xl font-bold">我的作品</h1></div>;
}
```
- [ ] **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(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
</React.StrictMode>
);
```
- [ ] **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<T>(
path: string,
options: RequestInit = {}
): Promise<T> {
const response = await fetch(`${BASE_URL}${path}`, {
headers: {
'Content-Type': 'application/json',
'X-API-Version': '1',
...options.headers,
},
...options,
});
const body: CommonResult<T> = 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: <T>(path: string) => request<T>(path),
post: <T>(path: string, data?: unknown) =>
request<T>(path, {
method: 'POST',
body: data ? JSON.stringify(data) : undefined,
}),
put: <T>(path: string, data?: unknown) =>
request<T>(path, {
method: 'PUT',
body: data ? JSON.stringify(data) : undefined,
}),
delete: <T>(path: string) => request<T>(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 APIContent 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<IDBDatabase> {
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<string, unknown>): Promise<void> {
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<Record<string, unknown> | 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<void> {
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<EditorState>((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<UIState>((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();
// 自动保存到 IndexedDBdebounce 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<string, unknown>);
// 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 (
<div className="prose max-w-3xl mx-auto min-h-screen py-8">
<EditorContent editor={editor} />
</div>
);
}
function debounce<T extends (...args: any[]) => any>(fn: T, ms: number): T {
let timer: ReturnType<typeof setTimeout>;
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 成功

View File

@ -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 + TypeScriptComposition API + SFC script setupdefHttp 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: <T>(url: string, params?: Record<string, unknown>) =>
requestClient.get<T>(`${BASE}${url}`, { params }),
post: <T>(url: string, data?: unknown) =>
requestClient.post<T>(`${BASE}${url}`, data),
put: <T>(url: string, data?: unknown) =>
requestClient.put<T>(`${BASE}${url}`, data),
delete: <T>(url: string) =>
requestClient.delete<T>(`${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<any>(`/governance/meta-schemas/${schemaKey}`);
}
export function createMetaSchemaDraft(schemaKey: string, data: any) {
return museAdminApi.post<any>(`/governance/meta-schemas/${schemaKey}/drafts`, data);
}
export function publishMetaSchemaDraft(schemaKey: string, draftVersion: number, data: any) {
return museAdminApi.post<any>(
`/governance/meta-schemas/${schemaKey}/drafts/${draftVersion}/publish`,
data
);
}
```
- [ ] **Step 2: 编写列表页**
```vue
<!-- apps/web-antd/src/views/governance/MetaSchemaList.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import { BasicTable, useTable, BasicColumn } from '@vben/common-ui';
import { listMetaSchemas, type MetaSchemaSummary } from '#/api/muse/governance/meta-schema';
import { useRouter } from 'vue-router';
const router = useRouter();
const [registerTable, { reload }] = useTable({
api: async ({ page, size, scope }) => {
const res = await listMetaSchemas({ pageNo: page, pageSize: size, scope });
return { items: res.list, total: res.total };
},
columns: [
{ title: 'Schema Key', dataIndex: 'schemaKey', width: 200 },
{ title: '显示名称', dataIndex: 'displayName', width: 200 },
{ title: '字段类型', dataIndex: 'fieldType', width: 100 },
{ title: '作用域', dataIndex: 'scope', width: 100 },
{ title: '活跃版本', dataIndex: 'activeVersion', width: 100 },
{ title: '状态', dataIndex: 'status', width: 100 },
] as BasicColumn[],
});
function handleDetail(record: MetaSchemaSummary) {
router.push(`/governance/meta-schemas/${record.schemaKey}`);
}
</script>
<template>
<div class="p-4">
<BasicTable @register="registerTable" @row-click="handleDetail">
<template #toolbar>
<a-button type="primary">新建 MetaSchema</a-button>
</template>
</BasicTable>
</div>
</template>
```
- [ ] **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 成功

View File

@ -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 新发现