feat(mcp): 新增 MCP 服务基础设施,重构规则文件序号,新增 v1.3.0 工单文档

- 规则重组:全局/ 下 8 个规则合并为 6 个(01+02→01,05+06→04),序号顺延

- 新增项目规则 05-多入口功能同步规范(UI/语音入口覆盖检查)

- 新增 MCP 服务基础设施:Mcp/ 目录(DI 注册、端点扩展、动态工具描述符)、单元测试

- v1.3.0 工单文档:03 系列(会议任务拆分)、04(富文本描述与附件管理)

- MCP 接口与前端集成指南:docs/manual/08、09
This commit is contained in:
ShaoHua
2026-06-16 01:15:40 +08:00
parent aacc56e952
commit 9223ceca50
30 changed files with 3907 additions and 346 deletions
@@ -0,0 +1,241 @@
# 研发工单 v1.3.0 - 03-01 会议数据模型与 API
> 父工单:[03-会议任务拆分](./03-会议任务拆分.md)
---
## 一、目标
为"会议任务拆分"功能建立数据模型:扩展 `TaskEntity` 新增 `TaskType` 枚举和 `MeetingNotes`/`AudioDuration` 字段,定义完整的会议相关 API 端点与 DTO,完成数据库迁移。
## 二、范围
**包含**
- 新增 `TaskType` 枚举(Normal / Meeting
- `TaskEntity` 新增字段:`TaskType``MeetingNotes``AudioDuration`
- EF Core 数据库迁移
- DTO 扩展:`CreateTaskDto``UpdateTaskDto``TaskDto` 新增对应字段
- 会议 API 端点骨架:`MeetingController` + `MeetingService`
- API 路径:`/api/meeting/{taskId}/transcribe``/api/meeting/{taskId}/notes`
- 会议相关 DTO 定义:`TranscribeRequest``TranscribeResponse``MeetingNotesRequest`
**不包含**
- AI 拆分逻辑(由 03-03 完成)
- STT 转写实现(由 03-02 完成)
- 前端组件(由 03-04 完成)
## 三、前置条件
| 条件 | 说明 |
|---|---|
| Hua.Todo v1.2.0 | 基础 Task CRUD 能力、父子任务、DynamicApi 中间件 |
| ABP 基类迁移(可选) | 若工单 09v1.2.0)已完成,TaskEntity 已转为 ABP 基类,字段新增方式不同 |
## 四、详细规格
### 4.1 TaskType 枚举
```csharp
/// <summary>待办项类型,区分普通待办项与会议</summary>
public enum TaskType
{
/// <summary>普通待办项(默认)</summary>
Normal = 0,
/// <summary>会议:包含录音/纪要,支持 AI 任务拆分</summary>
Meeting = 1
}
```
文件位置:`src/Hua.Todo.Core/Entities/TaskType.cs`
### 4.2 TaskEntity 新增字段
```csharp
/// <summary>待办项类型</summary>
public TaskType TaskType { get; set; } = TaskType.Normal;
/// <summary>会议纪要/转写文字(仅 Meeting 类型有值)</summary>
[MaxLength(20000)]
public string? MeetingNotes { get; set; }
/// <summary>录音时长(秒),仅 Meeting 类型有值</summary>
public double? AudioDuration { get; set; }
```
### 4.3 EF Core 配置(TodoDbContext.cs
```csharp
builder.Entity<TaskEntity>(b =>
{
// ... 现有配置 ...
b.Property(x => x.TaskType)
.HasDefaultValue(TaskType.Normal)
.HasConversion<int>(); // 枚举存为整数
b.Property(x => x.MeetingNotes)
.HasMaxLength(20000); // 最多约 20000 字符
b.Property(x => x.AudioDuration)
.IsRequired(false);
});
```
### 4.4 数据库迁移
```
dotnet ef migrations add AddMeetingFieldsToTasks
```
迁移应在 `Hua.Todo.Application/Migrations/` 目录生成。
### 4.5 DTO 扩展
**CreateTaskDto** 新增:
```csharp
/// <summary>待办项类型(0=Normal, 1=Meeting),默认 Normal</summary>
public TaskType TaskType { get; set; } = TaskType.Normal;
```
**TaskDto** 新增:
```csharp
/// <summary>待办项类型</summary>
public TaskType TaskType { get; set; }
/// <summary>会议纪要/转写文字</summary>
public string? MeetingNotes { get; set; }
/// <summary>录音时长(秒)</summary>
public double? AudioDuration { get; set; }
```
### 4.6 会议 DTO
```csharp
/// <summary>转写请求</summary>
public class TranscribeRequest
{
public IFormFile Audio { get; set; } = null!;
public string? Format { get; set; }
}
/// <summary>转写响应</summary>
public class TranscribeResponse
{
public int TaskId { get; set; }
public string Transcript { get; set; } = string.Empty;
public double AudioDuration { get; set; }
}
/// <summary>保存会议纪要请求</summary>
public class MeetingNotesRequest
{
public string Notes { get; set; } = string.Empty;
}
/// <summary>会议纪要响应</summary>
public class MeetingNotesResponse
{
public int TaskId { get; set; }
public string MeetingNotes { get; set; } = string.Empty;
}
```
### 4.7 API 端点
#### POST /api/meeting/{taskId}/transcribe
- 接收:multipart/form-data(音频文件)
- 返回:`TranscribeResponse`
- 业务:音频上传后异步转写,结果存回 `TaskEntity.MeetingNotes`
- 当前骨架:返回占位文字,具体转写逻辑由 03-02 实现
#### POST /api/meeting/{taskId}/notes
- 接收:`MeetingNotesRequest`
- 返回:`MeetingNotesResponse`
- 业务:保存/更新会议纪要
### 4.8 MeetingService 骨架
```csharp
/// <summary>会议业务服务</summary>
public class MeetingService
{
private readonly ITaskRepository _taskRepo;
public MeetingService(ITaskRepository taskRepo)
{
_taskRepo = taskRepo;
}
/// <summary>验证 taskId 对应的待办项存在且为 Meeting 类型</summary>
public async Task<TaskEntity> GetMeetingTaskOrThrow(int taskId)
{
var task = await _taskRepo.GetAsync(taskId);
if (task == null)
throw new NotFoundException($"任务 {taskId} 不存在");
if (task.TaskType != TaskType.Meeting)
throw new BusinessException($"任务 {taskId} 不是会议类型");
return task;
}
/// <summary>保存会议纪要</summary>
public async Task<TaskEntity> SaveNotes(int taskId, string notes)
{
var task = await GetMeetingTaskOrThrow(taskId);
task.MeetingNotes = notes;
task.UpdatedAt = DateTime.UtcNow;
await _taskRepo.UpdateAsync(task);
return task;
}
/// <summary>保存转写结果与音频时长</summary>
public async Task<TaskEntity> SaveTranscript(int taskId, string transcript, double audioDuration)
{
var task = await GetMeetingTaskOrThrow(taskId);
task.MeetingNotes = transcript;
task.AudioDuration = audioDuration;
task.UpdatedAt = DateTime.UtcNow;
await _taskRepo.UpdateAsync(task);
return task;
}
}
```
## 五、验收标准
| 验收项 | 验证方法 | 预期结果 |
|---|---|---|
| TaskType 枚举可用 | 编译通过 | `TaskType.Normal` / `TaskType.Meeting` 可正常赋值 |
| DB 迁移可执行 | `dotnet ef database update` | `Tasks` 表新增 `TaskType`/`MeetingNotes`/`AudioDuration` 列 |
| 创建 Meeting 类型任务 | `POST /api/task``taskType:1` | 返回的 TaskDto 中 `taskType=1` |
| 保存会议纪要 | `POST /api/meeting/{id}/notes` | `meetingNotes` 字段更新成功 |
| 非 Meeting 类型调用会议 API | 普通任务调 `/api/meeting/{id}/notes` | 返回业务异常 |
| 现有代码兼容 | 运行所有已有测试 | 不破坏现有业务 |
## 六、风险
| 风险 | 影响 | 应对 |
|---|---|---|
| TaskEntity 已重构为 ABP 基类 | 字段新增方式不同 | 通过 ABP 的 `ExtraProperties` 或标准字段新增,迁移方式略有调整 |
| 字段长度限制 | 超长会议纪要截断 | `MeetingNotes``MaxLength(20000)`,前端也做长度限制 |
## 七、Touch List
| 文件路径 | 修改类型 | 是否共享 |
|---|---|---|
| `src/Hua.Todo.Core/Entities/TaskType.cs` | 新增 | 否 |
| `src/Hua.Todo.Core/Entities/TaskEntity.cs` | 修改 | 是 |
| `src/Hua.Todo.Application/Data/TodoDbContext.cs` | 修改 | 是 |
| `src/Hua.Todo.Application/Models/TaskModels.cs` | 修改 | 是 |
| `src/Hua.Todo.Application/Meeting/MeetingController.cs` | 新增 | 否 |
| `src/Hua.Todo.Application/Meeting/MeetingService.cs` | 新增 | 否 |
| `src/Hua.Todo.Application/Meeting/Models/MeetingDtos.cs` | 新增 | 否 |
| `src/Hua.Todo.Web/src/types/task.ts` | 修改 | 是 |
| `Migrations/AddMeetingFieldsToTasks.cs` | 新增 | 是 |
---
**工单编号**03-01
**标题**:会议数据模型与 API
**版本**v1.3.0
**创建日期**2026-06-16