17 KiB
任务模块问题分析报告(核对版)
日期: 2026-06-16 范围: 任务模块全栈(Rust 后端 + Tauri IPC + Vue 3 前端) 审查文件:
task.rs/tasks.ts/Tasks.vue/TaskDetail.vue/crud.rs/models.rs/types.rs/project.ts/state.rs/migrations.rs/router/index.ts核对方法: Explore 代理并行验源码,逐项file:line取证 核对结论: 原清单 18 项中 真 bug 8 项(P0×2 P1×6)、增强 5 项、假/部分假 3 项、与现有 todo 去重 3 项
〇、核对结论速览
| # | 原清单结论 | 核实 | 处置 |
|---|---|---|---|
| 1 | 状态枚举前后端不一致(5 vs 7) | ✅ 真 | 三方分裂,记 B-260616-12 |
| 2 | DDL priority 默认(1) vs 代码(2) | ✅ 真 | 潜在非现患,记 B-260616-14 |
| 3 | delete_task 硬删除无恢复 | ✅ 真 | 记 B-260616-13 |
| 4 | priority 无值域校验 | ✅ 真 | 记 B-260616-15 |
| 5 | id/created_at/project_id 可篡改 | ✅ 真 | 记 B-260616-16 |
| 6 | updateTask 无 try/catch | ✅ 真 | 记 B-260616-17 |
| 7 | TaskDetail 绕过 store | ✅ 真 | 记 B-260616-18 |
| 8 | /tasks/:id 路由未注册 | ✅ 真 | 🔄 去重 = B-260616-09 已存在 |
| 9 | 无分页全量加载 | 🔄 去重 | = F-260615-03 已立 |
| 10 | watch 重复 IPC | 🔄 去重 | = B-260615-29 设计决策 |
| 11 | 列表组件重复 | ❌ 假 | 两文件模板结构不同(.task-item vs .task-card),排除 |
| 12 | project_id 无存在性校验 | ⚠️ 部分假 | db.rs:22 PRAGMA foreign_keys=ON 兜底拦截,降级 |
| 13 | description 无长度限制 | 🟡 增强 | 待产品决策 |
| 14 | branch_name 无格式校验 | 🟡 增强 | 待产品决策 |
| 15 | TaskDetail 只读无编辑 | 🟡 增强 | 待产品决策 |
| 16 | 无批量操作 | 🟡 增强 | 待产品决策 |
| 17 | 无排序选项 | 🟡 增强 | 待产品决策 |
| 18 | 无搜索能力 | 🔄 去重 | ≈ F-260615-07 思路 |
一、模块架构概览
┌─────────────────────────────────────────────────────────┐
│ 前端 (Vue 3 + TS) │
│ Views │ Tasks.vue (列表) → TaskDetail.vue │
│ Store │ stores/project/tasks.ts │
│ API │ api/task.ts │
│ Types │ api/types.ts → TaskRecord │
│ Constants │ constants/project.ts (状态/优先级映射) │
├─────────────────────────────────────────────────────────┤
│ IPC 层 (Tauri) │
│ Commands │ commands/task.rs (5 个命令) │
│ State │ state.rs → TaskRepo │
├─────────────────────────────────────────────────────────┤
│ 后端 (Rust Crates) │
│ df-core │ types.rs → TaskStatus 枚举 │
│ df-storage │ models.rs → TaskRecord 结构体 │
│ │ crud.rs → TaskRepo (CRUD 实现) │
│ │ migrations.rs → tasks 表 DDL │
└─────────────────────────────────────────────────────────┘
IPC 命令(5 个)
| 命令 | 签名 | 说明 |
|---|---|---|
list_tasks |
(project_id?) → Vec<TaskRecord> |
列出任务,可按项目筛选 |
get_task_by_id |
(id) → TaskRecord |
按 ID 查单个任务 |
create_task |
(input) → TaskRecord |
创建任务,默认 status=todo, priority=2 |
update_task |
(id, field, value) → bool |
更新单字段,status 值有枚举校验 |
delete_task |
(id) → bool |
删除任务(硬删除) |
TaskStatus 枚举(后端 7 种)
| 状态值 | 说明 |
|---|---|
todo |
待开始 |
in_progress |
进行中 |
in_review |
代码审查中 |
testing |
测试中 |
done |
已完成 |
blocked |
已阻塞 |
cancelled |
已取消 |
优先级映射
| 值 | 标签 | 样式 |
|---|---|---|
| 0 | P0 | critical(紧急) |
| 1 | P1 | high(高) |
| 2 | P2 | medium(中,默认) |
| 3 | P3 | low(低) |
二、问题清单(含核对标注)
🔴 P0 — 数据一致性 / 功能断裂
1. 前后端状态枚举完全不一致 【✅ 真 · 核对确认】
| 层 | 状态值集合 | 证据 |
|---|---|---|
后端枚举 TaskStatus |
todo, in_progress, in_review, testing, done, blocked, cancelled(7) |
crates/df-core/src/types.rs:131-146 |
| 前端类型注释 | 同后端 7 种 | src/api/types.ts:88 |
前端常量 TASK_STATUS_LABELS |
todo, in_progress, review_ready, merged, abandoned(5) |
src/constants/project.ts:56-62 |
前端 taskStatusClass |
同常量 5 种,未定义回退 status-todo |
src/constants/project.ts:76-78 |
前端 i18n tasks.ts |
同常量 5 种 | src/i18n/{en,zh-CN}/tasks.ts:47-54 |
影响链:
- 后端
update_task的TaskStatus::is_valid()(task.rs:96)只接受 7 种;前端筛选器的review_ready/merged/abandoned后端全部拒绝 - AI 工具或后端写入
in_review/testing/blocked时,前端TASK_STATUS_LABELS查不到 → 回退显示原始 key(用户见in_review而非中文) taskStatusClass查不到 → 回退status-todo,视觉无法区分- Dashboard 统计
activeTasks仅查in_progress,testing/blocked不计入
根因:前端状态集是早期 Git 工作流导向(review_ready/merged/abandoned),后端枚举后来规范化为通用状态机,两者从未对齐。types.ts 注释随了后端,但常量/i18n/样式仍停在旧集。
人定决策点:前端对齐到后端 7 种纯状态机,还是保留 Git 工作流 5 种语义做映射?
2. DDL 默认值与代码默认值矛盾 【✅ 真 · 潜在非现患】
| 来源 | priority 默认值 |
证据 |
|---|---|---|
| DDL | DEFAULT 1(high) |
crates/df-storage/src/migrations.rs:304 |
Rust default_priority() |
2(medium) |
src-tauri/src/commands/task.rs:26-28 |
前端 Tasks.vue |
2(medium) |
src/views/Tasks.vue:62 |
当前 create_task 显式传 priority(走代码默认 2),DDL 默认值不生效。但未来若有直连 SQL 写入路径或手动改库,priority=1 与应用层 2 不一致。低危,可并入下次 migration 对齐。
3. delete_task 硬删除 — 无恢复机制 【✅ 真 · 核对确认】
// src-tauri/src/commands/task.rs:113
state.tasks.delete(&id).await.map_err(err_str) // 物理删
// crates/df-storage/src/crud.rs:194 — DELETE FROM tasks WHERE id=?
TaskRecord(crates/df-storage/src/models.rs:53-66)无 deleted_at 字段。对比 ProjectRecord 有完整软删除(deleted_at + list_deleted + restore)。
风险:误删永久丢失;关联 branches.task_id 外键变悬空(无 ON DELETE CASCADE/SET NULL)。
人定决策点:任务是否需要软删除 + 恢复(对标 projects),还是物理删除即可(任务粒度小、误删可重建)?
🟠 P1 — 安全 / 竞态 / 架构
4. update_task 缺少 priority 值域校验 【✅ 真 · 核对确认】
// src-tauri/src/commands/task.rs:96-102 — 仅 status 校验
if field == "status" && !TaskStatus::is_valid(&value) { ... }
// priority 直接透传 update_field,无范围检查
update_task(id, "priority", "999") 或 "abc" 静默落库。前端 PRIORITY_LABELS(constants/project.ts:82,key 0-3)查不到 → 回退 P999 + 样式 priority-low(project.ts:95-97)。
修复:field == "priority" 时校验 value.parse::<i32>() ∈ 0..=3。
5. update_task 缺少 id / created_at 等不可变字段保护 【✅ 真 · 核对确认】
// crates/df-storage/src/crud.rs:324-327
"tasks" => &["id", "project_id", "title", "description", "status", "priority",
"branch_name", "assignee", "workflow_def_id", "base_branch",
"created_at", "updated_at"],
白名单含 id / created_at / project_id → AI 工具或恶意调用可改主键、篡改创建时间、把任务移到别的项目。前端未暴露,但 IPC 层无防护。
修复:tasks 白名单移除 id/created_at;project_id 若允许跨项目移动则保留但加目标项目存在性校验。
6. 前端 updateTask store 无错误处理 【✅ 真 · 核对确认】
// src/stores/project/tasks.ts:29-35 — 无 try/catch
async function updateTask(id, field, value) {
await taskApi.update(id, field, value)
const idx = state.tasks.findIndex(t => t.id === id)
if (idx >= 0) (state.tasks[idx] as any)[field] = value
}
对比同文件 loadTasks(10-16) / createTask(18-27) / deleteTask(37-44) 都有 try/catch。IPC 失败(如非法 status 被后端拒)→ Promise reject 冒泡,错误不写 state.error,用户无提示。
修复:补 try/catch,失败写 state.error + toast。
7. TaskDetail.vue 绕过 store 直接调 API 【✅ 真 · 核对确认】
// src/views/TaskDetail.vue:101 import { taskApi, projectApi } from '@/api'
// src/views/TaskDetail.vue:137-141 直接 taskApi.get() / projectApi.list()
其他视图走 useProjectStore(),TaskDetail 直调 API:
- 不享受 AR-11
df-data-changed联动刷新(其他窗口改任务,本页不自动刷新) projectApi.list()全量拉项目列表仅为解析project_id → name
修复:改走 store.loadTasks/store.projects,或单独监听 df-data-changed 重载当前 task。
8. /tasks/:id 路由未注册 【✅ 真 · 导航断裂 · 🔄 去重 = B-260616-09 已存在】
// src/router/index.ts:45-49 — 只有 /tasks
{ path: '/tasks', name: 'Tasks', component: () => import('../views/Tasks.vue') }
// src/views/Tasks.vue:57 — 任务卡点击跳转
router.push('/tasks/${task.id}') // 路由表无此路径 → 落空
对比 /ideas/:id、/projects/:id 均已注册,唯独 /tasks/:id 遗漏。点击任务卡片匹配不到路由(落入 catch-all 或空白页)。1 行改动速赢。
🟡 P2 — 性能 / 体验 / 代码质量
9. 无分页 — 全量加载所有任务 【🔄 去重 = F-260615-03】
// src-tauri/src/commands/task.rs:32-41
None => state.tasks.list_all().await, // 无 limit/offset,ORDER BY created_at DESC
已有任务 F-260615-03 覆盖,不重复立项。
10. Tasks.vue 筛选切换导致重复全量请求 【🔄 去重 = B-260615-29】
// src/views/Tasks.vue:229-233
watch(activeProject, (key) => { store.loadTasks(key === 'all' ? undefined : key) })
// src/views/Tasks.vue:165-192 filteredGroups computed 已做纯前端 filter
这是 B-260615-29 的设计决策(避免跨项目视图不同步),代价是每次切换 IPC 往返。不重复立项。
11. Tasks.vue 和 ProjectDetail.vue 任务列表样式/逻辑重复 【❌ 假 · 排除】
Tasks.vue 和 ProjectDetail.vue 任务列表样式/逻辑重复核对:Tasks.vue:57-73 用 .task-item 布局,ProjectDetail.vue:137-151 用 .task-card 布局,模板结构不同。两者引用相同常量函数(状态/优先级映射),但模板本身非重复。排除。
12. create_task 无 project_id 存在性校验 【⚠️ 部分假 · 降级】
// src-tauri/src/commands/task.rs:59-84 — create_task 不校验 project_id
project_id: input.project_id, // 直接使用
但 crates/df-storage/src/db.rs:22 已开 PRAGMA foreign_keys=ON,DB 层外键约束会拦截指向不存在 project 的 insert。代码层无显式校验,但风险被 DB 兜住。降级为非漏洞,仅留注释说明依赖外键。
13. description 字段无长度限制 【🟡 增强】
TaskRecord.description 是 String,DDL 为 TEXT NOT NULL DEFAULT ''。无前后端长度校验,超大文本影响 Markdown 渲染性能。待产品决策加上限。
14. branch_name / base_branch 无 Git 分支名格式校验 【🟡 增强】
允许任意字符串(空格/特殊字符/中文),可能与实际 Git 分支不兼容。待产品决策加格式校验。
🔵 P3 — 增强建议(均待产品决策)
| # | 项 | 说明 |
|---|---|---|
| 15 | TaskDetail 纯只读 | 无编辑/改状态/改优先级 UI;列表页也无内联编辑 → 当前无任何 UI 改任务状态,只能靠 AI 工具/API |
| 16 | 无批量操作 | 无法批量改状态/删除/分配 |
| 17 | 无排序选项 | 固定 ORDER BY created_at DESC,无法按优先级/状态/更新时间排 |
| 18 | 无搜索能力 | ≈ F-260615-07 思路 |
三、问题汇总矩阵(含核对标注)
| # | 严重度 | 类型 | 问题 | 影响 | 核实 |
|---|---|---|---|---|---|
| 1 | P0 | 数据一致性 | 前后端状态枚举不一致(5 vs 7 种) | 全局 | ✅ 真 |
| 2 | P0 | 数据一致性 | DDL priority 默认值(1) vs 代码(2) | 数据层 | ✅ 真(潜在) |
| 3 | P0 | 数据安全 | delete_task 硬删除无恢复 | 数据丢失 | ✅ 真 |
| 4 | P1 | 安全校验 | priority 无值域校验 | 数据质量 | ✅ 真 |
| 5 | P1 | 安全校验 | id/created_at/project_id 可被篡改 | 数据完整性 | ✅ 真 |
| 6 | P1 | 健壮性 | updateTask store 无 try/catch | 用户体验 | ✅ 真 |
| 7 | P1 | 架构 | TaskDetail 绕过 store | 数据同步 | ✅ 真 |
| 8 | P1 | 功能缺陷 | /tasks/:id 路由未注册 | 导航断裂 | ✅ 真 |
| 9 | P2 | 性能 | 无分页全量加载 | 扩展性 | 🔄 F-260615-03 |
| 10 | P2 | 性能 | 筛选切换重复 IPC | 响应速度 | 🔄 B-260615-29 |
| 11 | P2 | 代码质量 | 任务列表组件重复 | 可维护性 | ❌ 假 |
| 12 | P2 | 安全校验 | project_id 无存在性校验 | 数据完整性 | ⚠️ 部分假(外键兜底) |
| 13 | P2 | 安全校验 | description 无长度限制 | 性能 | 🟡 增强 |
| 14 | P2 | 安全校验 | branch_name 无格式校验 | 数据质量 | 🟡 增强 |
| 15 | P3 | 功能缺失 | TaskDetail 无编辑能力 | 用户体验 | 🟡 增强 |
| 16 | P3 | 功能缺失 | 无批量操作 | 效率 | 🟡 增强 |
| 17 | P3 | 功能缺失 | 无排序选项 | 用户体验 | 🟡 增强 |
| 18 | P3 | 功能缺失 | 无搜索能力 | 用户体验 | 🔄 ≈F-260615-07 |
四、todo 编号映射
记入 docs/todo.md(B-260615-58 起,沿用 6.15 编号保连续):
| todo 号 | 原清单 # | 等级 | 摘要 |
|---|---|---|---|
| B-260616-12 | 1 | P0 | 状态枚举前后端分裂(7 vs 5),需人定:Git 工作流 vs 纯状态机 |
| B-260616-13 | 3 | P0 | delete_task 硬删除无恢复,需人定:是否要任务软删除 |
| B-260616-14 | 2 | P1 | DDL priority DEFAULT 1 vs 代码 2,migration 对齐 |
| B-260616-15 | 4 | P1 | update_task priority 无值域校验(0..=3) |
| B-260616-16 | 5 | P1 | allowed_columns 含 id/created_at/project_id 可篡改 |
| B-260616-17 | 6 | P1 | updateTask store 无 try/catch |
| B-260616-18 | 7 | P1 | TaskDetail 绕 store,不享受联动刷新 |
| B-260616-09 | 8 | P1 | /tasks/:id 路由未注册 — 已存在去重(todo 排查会话区块) |
编号沿用 B-260616 批次(与 B-260616-08~11 同批,今日 06-16 发现)。 增强 #13-17 归 F- 类,待产品决策后立项。
五、建议修复顺序
- #8 路由注册
/tasks/:id(1 行速赢) - #6 updateTask store 加 try/catch
- #4 priority 值域校验(0..=3)
- #5 不可变字段保护(id/created_at 移出白名单)
- #1 状态枚举对齐(需人定决策)
- #3 软删除支持(需人定决策)
- #2 DDL 默认值修正
- #7 TaskDetail 接入 store
- 其余 P2/P3 按需排期
六、人定决策点(需用户拍板,非大模型推断)
- 状态枚举方向(#1):前端对齐到后端 7 种纯状态机,还是保留 Git 工作流 5 种语义(review_ready/merged/abandoned)做映射?影响筛选器/i18n/统计全链。
- 任务软删除(#3):任务是否需要软删除 + 恢复(对标 projects),还是物理删除即可(任务粒度小、误删可重建)?
这两项是产品/架构取舍,与模型能力无关,决策后记入功能决策记录。