--- id: T-203 title: 实现任务创建 API 与管理 Web phase: 2 deps: - T-201 - T-202 status: DONE created: 2026-07-26 context_ref: 2b265c9 work_branch: main write_paths: - backend-api/** - init.ps1 - init.sh - docs/00-ai-start-here.md - docs/02-requirements.md - docs/03-tech-stack.md - docs/04-architecture.md - docs/05-coding-rules.md - docs/07-user-stories.md - docs/08-interaction-checklist.md - docs/api.md - docs/current-state.md - docs/design/admin-task-create.html - docs/design/admin-task-detail.html - docs/design/android-task-preview.html - docs/design/android-candidate-confirm.html - docs/tasks/T-203.md - progress.md --- ## 问题 / 背景 T-201 只有健康检查、SQLite 和 migration 骨架,T-202 只确认了页面信息结构。当前还 不能从管理端安全上传参考图、创建 `PENDING` 任务、查看真实列表/详情或取消待领取 任务,因此 T-205 的原子领取没有可消费的数据。 审计还发现正式合约存在四个会阻断后续 App 接入的问题: - 现有 Android `ProbeTask` 强制标题和 SKU 非空,但任务创建 API/数据表遗漏 SKU。 - 已确认 Web 使用“最高总预算”,部分 Android 原型误写成每件预算。 - API/原型允许 JPEG/PNG/WebP,但 Android 需求提取当前只接受 JPEG。 - 管理会话属于 T-204,T-203 不能伪造登录或永久开放匿名管理接口。 ## 关联需求与交互 - 功能:F-001、F-002,以及 F-007 的最小可见错误。 - 用户故事:US-001、US-002。 - 交互:IX-002、IX-003;登录 IX-001 留给 T-204。 - 页面:`/tasks`、`/tasks/new`、`/tasks/{id}`。 - API:资产上传、任务创建/列表/详情、待领取任务取消。 ## 已定合约 1. 标题和 SKU 必填;描述可空。标题最多 120 个 Unicode 字符且不超过 2048 个 UTF-8 字节,SKU 不超过 512 个 UTF-8 字节,描述不超过 8192 个 UTF-8 字节。 2. `max_budget` 表示当前任务全部数量的最高商品总预算,币种 CNY,不含无法确认的 运费或优惠;数据库用整数分保存,API 固定输出两位小数字符串。 3. 参考图上传接受可解码 JPEG/PNG/WebP,原始请求最多 20 MiB、最长边最多 10000 px、总像素最多 25 MP;后端统一缩放到最长边不超过 2048 px、白底合成并 编码为匿名 JPEG。响应的媒体类型、大小和 SHA-256 都指向规范化结果。 4. SKU、数量和总预算属于不可变原始事实,任何派生结果都不能覆盖。 5. 列表增加 `q`,匹配任务 ID、来源引用、标题或 SKU;分页固定按 `created_at DESC, id DESC`,cursor 同时包含两个字段。 6. T-203 只提前实现事务化 `PENDING -> CANCELED`;其他状态取消返回冲突,完整取消 请求和状态机属于 T-205。 7. T-204 前,管理 Web/API 业务路由只接受 loopback 来源;Web 写操作还必须携带 SameSite Cookie 与表单字段匹配的随机双提交 CSRF token,API 创建/上传必须使用 JSON 或 multipart 与自定义幂等头。`/login` 不提供假实现,局域网部署仍被明确 阻塞。 8. 创建、上传均按调用主体、操作和 `Idempotency-Key` 幂等;同 key 同请求返回原 资源,同 key 不同请求返回 `409`。 ## 方案 1. 增加 assets、purchase_tasks、task_events 和 idempotency_records migration, 包含外键、CHECK、唯一约束和 rollback。 2. 建立 domain/usecase/repository/sqlite 分层;handler 和模板不得直接执行 SQL。 3. 建立受控文件存储与图片规范化器,随机相对路径、同目录临时文件原子改名,失败时 清理;数据库和响应永不暴露绝对路径。 4. 实现 JSON/multipart API 和稳定错误映射,保持无默认 CORS、request ID 与 recovery 安全约束。 5. 用 `html/template`、`embed`、原生 CSS/JS 实现列表、新建和详情;真实数据为空时 显示空状态,不把原型假数据写入数据库。 6. Web 表单无 JS 也可完成,使用 PRG `303`;JS 只负责图片预览、防重复、离开提醒 和取消确认。 ## 验收要点 - [x] migration 可 up/down/up,约束和外键有效,启动前 migration 门禁明确。 - [x] JPEG/PNG/WebP 被真实解码并规范化为 JPEG;伪 MIME、损坏、超限和像素炸弹拒绝。 - [x] 合法任务创建为唯一 `PENDING`,原始 title/SKU/quantity/budget/image 不可变。 - [x] 上传和创建幂等;同 key 不同请求冲突,并发重试不产生重复资源。 - [x] 列表筛选、搜索、稳定 cursor 和详情返回真实数据,404 不泄露内部信息。 - [x] 只有 `PENDING` 可立即取消;终态或不支持状态返回稳定冲突。 - [x] Web 覆盖空列表、创建字段错误、图片错误、成功跳转、详情和取消二次确认。 - [x] 非 loopback 业务请求、缺失/错误 CSRF、错误 Content-Type 被拒绝。 - [x] HTML 自动转义,日志/响应不含 Cookie、文件绝对路径、私有样本或请求正文。 - [x] Go test/race/vet/gofmt、迁移 smoke、真实 HTTP/Web smoke 和 Playwright 通过。 ## 边界 - 不实现管理登录、Cookie 会话、用户表/密码哈希或设备鉴权;属于 T-204。 - 不实现 claim、租约、执行状态机、候选、执行证据或 App HTTP TaskSource。 - 不开放非 loopback 管理使用;T-204 完成前不能部署到局域网供其他用户访问。 - 不实现对象存储、ORM、SPA、WebSocket、推送或自动下单。 - 不提交运行时数据库、上传文件、密钥或真实订单/店铺/商品数据。 ## 执行记录 ### 2026-07-26:任务开始 - 基于提交 `2b265c9` 开始,工作区干净。 - codebase-memory MCP 本轮未暴露 graph 工具,按项目规则回退到 `rg` 和定点文件读取。 - 已审计 T-201 现有 config/database/migration/httpapi/cmd 骨架、T-202 原型以及 requirements/API/architecture/IX,先固定上述跨层合约再编码。 ### 2026-07-26:合约和实现 - 修正正式需求和原型中的 SKU、最高总预算、图片格式与验收编号;总预算统一指当前 任务全部数量的商品预算,模型不得覆盖 SKU、数量或预算。 - 新增 `00002_tasks_and_assets.sql`,建立 assets、purchase_tasks、task_events 和 idempotency_records;字段长度、UTF-8 字节、外键、状态、金额和唯一性均有数据库 约束。 - 建立 domain/usecase/repository/sqlite 分层,上传和创建事务化幂等,列表按 `created_at DESC, id DESC` 稳定分页,待领取取消使用状态与 version 条件更新。 - 文件层真实解码 JPEG/PNG/WebP,限制 20 MiB、10000 px 和 25 MP,白底缩放至最长 边 2048 px 后以质量 90 编码为随机相对键 JPEG;绝对路径不进入数据库外部响应。 - Gin API 实现上传/读取资产、创建/列表/详情/取消任务;业务路由在 T-204 前只接受 loopback,错误使用稳定 code、request ID 和 no-store 响应。 - 管理 Web 使用嵌入模板/CSS/JS,实现真实空列表、搜索筛选、创建、图片预览与复用、 详情参考图和取消确认;使用双提交 CSRF、PRG 303、HTML 自动转义和固定 CSP。 - API 启动前检查全部 migration;存在 pending 版本时拒绝启动并明确提示执行 `migrate up`,服务进程不自动修改数据库。 ### 2026-07-26:自动验证 - `GOTOOLCHAIN=local go test -count=1 ./...`:99 个测试通过;覆盖领域校验、金额、 图片限制、路径防护、迁移约束、事务幂等、并发、分页、HTTP 生命周期、CSRF 和 模板安全。 - `go test -race -count=1 ./...`、`go vet ./...`、`gofmt -l cmd internal migrations` 和 API/migration Windows 构建全部通过。 - 真实 migration CLI 完成 `status -> up -> up -> down -> API gate -> up`:首次 `applied=2`、重复为 0,回滚 v2 后 API 以退出码 1 拒绝旧库,恢复后 v1/v2 均 `applied`。 - `httptest` 使用临时真实 SQLite/asset 目录跑通:上传及幂等重放、创建及重放、SKU 搜索、详情、JPEG 内容读取、取消和重复取消;非 loopback 和缺失幂等键被拒绝。 - Playwright 连接真实 Gin/SQLite 进程完成创建任务和详情读取;1440x900、 390x844、360x800 均无横向溢出,可见操作控件最小 44 px,规范化参考图 `naturalWidth=64`、`naturalHeight=48` 且成功加载,只有同源 HTML/CSS/JS/资产请求, 新页面 console 为 0 error、0 warning;取消对话框默认聚焦“保留任务”。 - 根 `init.ps1` 通过:Android `test assembleDebug` 共 76 个 Gradle task 成功, 现有 166 次测试保持通过;随后 Go 测试、vet、格式检查和两个入口构建再次通过。 ### 2026-07-26:隐私与收尾 - 测试只使用代码生成的脱敏小图、标题和 SKU;未读取、复制或提交本地蝦皮订单、 店铺名、真实商品文字或图片。 - Playwright、数据库、资产和 exe smoke 产物只保存在被忽略的 `.local/`、`bin/` 或 `var/`,未纳入 Git;业务响应和管理页面不显示客户端文件名或服务端存储路径。 - T-203 验收全部满足,状态改为 `DONE`;T-204 可开始实现正式管理会话和角色门禁。