feat(backend): implement task creation and admin web
This commit is contained in:
@@ -53,8 +53,9 @@
|
||||
|
||||
当前已完成 Phase 0 和 Phase 1:Android 可运行、设备就绪、workflow、私有样本导入、
|
||||
动态词搜索、最多 5 个候选截图采集、结构化需求提取、候选评估和人工确认停止点均已
|
||||
验证。T-201 后端骨架和 T-202 P0 Web/App 低保真原型均已完成,原型已通过自动验证
|
||||
和人工确认。下一步按编号开始 T-203,实现任务创建 API 与管理 Web。
|
||||
验证。T-201 后端骨架、T-202 P0 原型和 T-203 任务 API/管理 Web 均已完成。下一步
|
||||
按编号开始 T-204,实现正式管理账号、角色、会话和设备身份;在此之前业务路由只
|
||||
允许 loopback,不能部署给局域网用户使用。
|
||||
|
||||
严格按以下顺序推进:
|
||||
|
||||
|
||||
+10
-9
@@ -6,7 +6,7 @@
|
||||
|
||||
| 项 | 当前事实 |
|
||||
| --- | --- |
|
||||
| 任务来源 | 其他管理后台或本项目管理 Web 采集商品标题、描述、图片、数量和预算。 |
|
||||
| 任务来源 | 其他管理后台或本项目管理 Web 采集商品标题、SKU、描述、图片、数量和预算。 |
|
||||
| 第一层样本来源 | 本机目录中的蝦皮订单文本和参考图;二者以蝦皮订单号作为同名文件名。 |
|
||||
| 执行方式 | 采购人员使用 Android App 操作拼多多;需求提取、动态词搜索、最多 5 个候选证据采集、匹配建议和人工确认停止点已可运行。 |
|
||||
| 核心痛点 | 人工把图片和描述转成搜索词、逐条比较商品并记录结果,耗时且不一致。 |
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
| ID | 功能 | 用户结果 | 优先级 | 用户故事 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| F-001 | 创建采购任务 | 管理员提交标题、描述、图片、数量和可选预算,获得任务编号。 | P0 | US-001 |
|
||||
| F-001 | 创建采购任务 | 管理员提交标题、SKU、描述、图片、数量和可选预算,获得任务编号。 | P0 | US-001 |
|
||||
| F-002 | 查看任务状态 | 管理员看到任务阶段、候选摘要、截图和失败原因。 | P0 | US-002 |
|
||||
| F-003 | 手动领取任务 | 空闲且就绪的 App 点击后只领取一条待处理任务。 | P0 | US-003 |
|
||||
| F-004 | 解析采购需求 | 系统从图片和文字提取搜索词、属性及约束,并保留原始输入。 | P0 | US-004 |
|
||||
@@ -46,9 +46,10 @@
|
||||
|
||||
## 五、业务规则
|
||||
|
||||
1. 标题、描述和图片至少满足“标题或描述不为空,且图片存在”。
|
||||
2. 数量必须是正整数;最高预算如填写,必须大于零。
|
||||
3. 数量和最高预算以管理员输入为准,模型不得更改。
|
||||
1. 标题、SKU 和图片必填,描述可为空。
|
||||
2. 数量必须是正整数;最高商品总预算如填写,必须大于零。总预算表示当前任务全部
|
||||
数量的商品金额上限,不包含尚无法可靠确认的运费、优惠或支付金额。
|
||||
3. SKU、数量和最高商品总预算以管理员输入为准,模型不得更改。
|
||||
4. 一台设备同一时间最多有一条 `CLAIMED` 或 `RUNNING` 任务。
|
||||
5. 一条任务同一时间只能被一台设备持有;重复点击不能产生重复领取。
|
||||
6. App 未就绪时不能开始:无障碍未授权、拼多多未安装、设备离线或已有运行任务都
|
||||
@@ -95,11 +96,11 @@ T-004 已固定首版规则:推荐私有目录为被 Git 忽略的 `private-fi
|
||||
|
||||
### 单任务验收
|
||||
|
||||
- F-001/US-001/IX-001:合法输入创建后出现唯一任务编号和 `PENDING` 状态;非法
|
||||
数量、预算或缺失图片时在原表单显示可修复错误。
|
||||
- F-002/US-002/IX-002:状态变化后管理页面能看到最新阶段、时间、设备、候选摘要
|
||||
- F-001/US-001/IX-002:合法输入创建后出现唯一任务编号和 `PENDING` 状态;非法
|
||||
标题、SKU、数量、预算或缺失图片时在原表单显示可修复错误。
|
||||
- F-002/US-002/IX-003:状态变化后管理页面能看到最新阶段、时间、设备、候选摘要
|
||||
或结构化错误;无权限用户不可访问。
|
||||
- F-003/US-003/IX-004:App 点击“获取任务”后原子领取一条任务;重复点击或多请求
|
||||
- F-003/US-003/IX-005:App 点击“获取任务”后原子领取一条任务;重复点击或多请求
|
||||
不得领取第二条或把同一任务分配两次。
|
||||
- F-004/US-004/IX-006:解析结果包含搜索词、识别属性、预算、数量、置信度和警告;
|
||||
原始输入保留,硬约束与输入一致。T-103 已实现版本化 schema、0.75 置信阈值、
|
||||
|
||||
@@ -22,12 +22,12 @@
|
||||
| 数据迁移 | Goose v3.26.0,使用嵌入式 SQL migration | 已验证 | v3.26.0 是已核实仍声明 Go 1.23.0 的最高版本;v3.27.x 要求 Go 1.25。 |
|
||||
| 管理 Web | Gin + `html/template` + `embed` + 少量原生 JS/CSS | MVP 已定 | 不单独引入 SPA 工程,模板和静态资源随服务构建。 |
|
||||
| 数据库 | SQLite | MVP 已定 | 单服务、单设备验证足够;多实例或并发提升前迁移 PostgreSQL。 |
|
||||
| 图片/截图 | 后端受控本地文件目录,数据库存元数据 | MVP 已定 | 禁止把二进制直接塞入日志;生产再评估对象存储。 |
|
||||
| 图片/截图 | 后端受控本地文件目录 + `golang.org/x/image` v0.28.0 | 已验证 | JPEG/PNG/WebP 真解码后白底缩放并编码为 JPEG;数据库只存元数据和随机相对键。 |
|
||||
| 管理鉴权 | 单个种子管理账号 + 服务端会话 Cookie | MVP 已定 | 密码只保存哈希;完整 RBAC 为 V2。 |
|
||||
| App 鉴权 | 采购员登录态 + 设备绑定令牌 | 目标已定,细节待实现 | 人员身份与设备身份分离;令牌只保存哈希。 |
|
||||
| VLM 接入 | 应用内统一适配器,优先兼容 OpenAI 风格多模态接口 | 需求提取与候选评估已实现,供应商待定 | T-103/T-104 使用严格 JSON Schema、单候选单次调用和 2048 px 图片上限;GUI-Owl/MAI-UI 动作模型不具备需求提取能力。 |
|
||||
| 通知 | MVP 不使用推送 | 已定 | 点击“获取任务”调用原子 claim API;V2 再评估厂商推送/WebSocket。 |
|
||||
| 后端测试 | 标准库 `testing` + `httptest` | 骨架已验证 | 当前 25 个测试覆盖配置、SQLite pragma/外键、迁移、健康检查、安全 recovery、错误合约、路由和有界关闭;后续任务再增加状态机、权限与幂等。 |
|
||||
| 后端测试 | 标准库 `testing` + `httptest` | T-203 已验证 | 当前 99 个测试覆盖配置、迁移、图片限制、领域校验、事务幂等、分页、HTTP 生命周期、CSRF、安全响应头和有界关闭。 |
|
||||
| Android 测试 | Gradle `test` + `kotlinx-coroutines-test` 1.7.3 + 真实设备 smoke | Phase 1 探针已验证 | 166 次测试覆盖 runner、动态页面分类、受控证据、VLM schema、人工确认策略与隐私;OnePlus PKG110 上完成私有 fixture + 本机 mock 的需求提取和 5 候选评估 smoke。 |
|
||||
| 部署 | 单机局域网 Go 服务;容器化后置 | MVP 已定 | Android 测试机必须能通过 HTTPS 或受控测试网络访问。 |
|
||||
|
||||
@@ -125,7 +125,8 @@ backend-api/
|
||||
docs/
|
||||
```
|
||||
|
||||
`domain/usecase/repository/sqlite/web` 将在对应业务任务出现真实代码时创建,不提交空包。
|
||||
T-203 已建立 `domain/usecase/repository/sqlite` 和 `transport/webui`,handler 不直接
|
||||
执行 SQL,Web 与 JSON API 复用同一 usecase。
|
||||
|
||||
## 构建与运行命令
|
||||
|
||||
@@ -136,7 +137,7 @@ docs/
|
||||
| Android 测试任务 | `android-buyer\gradlew.bat test --no-daemon` | 已验证;当前全工程 166 次测试通过 |
|
||||
| Android 安装/启动 | `$env:RUN_START_COMMAND="1"; .\init.ps1` | 已在 Android 16 真机验证 |
|
||||
| 后端依赖 | `$env:GOTOOLCHAIN="local"; go mod download`(在 `backend-api/`) | 已验证 |
|
||||
| 后端测试 | `$env:GOTOOLCHAIN="local"; go test ./...; go vet ./...` | 已验证;25 个测试 |
|
||||
| 后端测试 | `$env:GOTOOLCHAIN="local"; go test ./...; go vet ./...` | 已验证;99 个测试 |
|
||||
| 后端构建 | `go build -o bin/cmroubao-api.exe ./cmd/api` 与 `./cmd/migrate` | 已验证 |
|
||||
| 后端启动 | `go run ./cmd/api` | 已完成本机 HTTP smoke |
|
||||
| 数据迁移 | `go run ./cmd/migrate up` | 已完成 `status/up/up/down/up` smoke |
|
||||
|
||||
+12
-3
@@ -69,8 +69,14 @@ Authorization、Cookie 或 panic 内容。
|
||||
SQLite 默认文件为被忽略的 `backend-api/var/cmroubao.db`,连接启用 foreign keys、
|
||||
5 秒 busy timeout、WAL 和 immediate transaction,并限制单连接以匹配单进程 MVP。
|
||||
数据库由 `cmd` 显式打开和关闭,不存在包级单例。Goose SQL 通过 `embed.FS` 加载,
|
||||
`cmd/migrate` 显式执行 `up/down/status`;T-201 只验证迁移链路,业务表由后续任务
|
||||
按 API 和领域模型建立。
|
||||
`cmd/migrate` 显式执行 `up/down/status`。API 启动前检查所有迁移均已应用,不在
|
||||
服务进程内自动改表;T-203 已用 `00002_tasks_and_assets.sql` 建立任务、资产、事件
|
||||
和幂等记录表。
|
||||
|
||||
T-204 管理会话完成前,T-203 的业务路由只允许 loopback 来源,Web 写操作还校验
|
||||
浏览器 Cookie 与表单字段的随机双提交 CSRF token;这不是正式登录,不能据此开放
|
||||
局域网监听。模板/API handler 仍调用同一 usecase,T-204 只替换主体和会话门禁,
|
||||
不重写业务规则。
|
||||
|
||||
### 2.2 Android App
|
||||
|
||||
@@ -350,9 +356,10 @@ IDLE
|
||||
| `id` | PK | 任务 ID |
|
||||
| `title` | NOT NULL | 原始标题 |
|
||||
| `description` | NOT NULL | 原始说明,可为空字符串 |
|
||||
| `sku` | NOT NULL | 原始 SKU,不得由模型改写 |
|
||||
| `image_asset_id` | FK, NOT NULL | 原始参考图 |
|
||||
| `quantity` | `> 0` | 权威数量 |
|
||||
| `max_budget` | `> 0`, nullable | 权威最高预算,币种为 CNY |
|
||||
| `max_budget` | `> 0`, nullable | 全部数量的权威最高商品总预算,币种为 CNY |
|
||||
| `status` | NOT NULL | 任务状态枚举 |
|
||||
| `created_by` | FK | 创建人 |
|
||||
| `claimed_by_device_id` | FK, nullable | 当前设备 |
|
||||
@@ -380,6 +387,8 @@ IDLE
|
||||
- `execution_events` 只追加,记录 step、事件类型、可读消息和时间;不保存密码或 token。
|
||||
- `assets` 保存文件相对路径、媒体类型、大小、哈希、创建者和保留时间。
|
||||
- 原图和截图必须通过鉴权接口读取,文件名不能直接作为公开 URL。
|
||||
- 任务参考图上传可接受 JPEG/PNG/WebP,但后端必须先真实解码、限制字节与像素,再
|
||||
统一规范化为匿名 JPEG;未来 Android 读取的资产不能依赖原始扩展名或声明 MIME。
|
||||
|
||||
## 六、API 和并发边界
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
- 关联页面:管理 Web `/tasks/new`
|
||||
- 前置条件:管理员已登录;参考图片可读取。
|
||||
|
||||
作为采购管理员,我想提交商品标题、描述、参考图、数量和预算,从而让采购设备拿到
|
||||
没有歧义的原始采购要求。
|
||||
作为采购管理员,我想提交商品标题、SKU、描述、参考图、数量和总预算,从而让采购
|
||||
设备拿到没有歧义的原始采购要求。
|
||||
|
||||
**范围**
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
**验收场景**
|
||||
|
||||
1. 合法输入提交后,任务列表出现唯一编号、摘要和 `PENDING` 状态。
|
||||
2. 数量、预算、图片或文本不满足规则时,保留已填内容并指出修复位置。
|
||||
2. 标题、SKU、数量、总预算、图片或描述不满足规则时,保留已填内容并指出修复位置。
|
||||
3. 网络超时后重复提交同一幂等请求,不产生重复任务。
|
||||
|
||||
## US-002 跟踪任务和查看证据
|
||||
|
||||
@@ -52,14 +52,15 @@
|
||||
|
||||
**正常路径**
|
||||
|
||||
1. 输入标题、描述、数量、可选预算并选择一张图片。
|
||||
1. 输入标题、SKU、描述、数量、可选商品总预算并选择一张图片。
|
||||
2. 浏览器展示图片预览、文件名和大小。
|
||||
3. 提交期间锁定重复提交并显示“正在创建”。
|
||||
4. 成功后进入任务详情,显示编号和 `PENDING`。
|
||||
|
||||
**状态与异常**
|
||||
|
||||
- 数量默认 1,只接受正整数;预算为空表示不设置上限。
|
||||
- 标题和 SKU 必填,描述可空;数量默认 1,只接受正整数。
|
||||
- 商品总预算为空表示不设置上限;填写时表示当前任务全部数量的商品金额上限。
|
||||
- 图片类型、大小或解码失败时在图片控件旁提示。
|
||||
- 上传成功但创建失败时重用已上传 asset,不重复上传。
|
||||
- 网络状态不确定时使用幂等键查询结果,不直接再创建。
|
||||
@@ -177,7 +178,8 @@
|
||||
- 进程重启时查询任务和 execution;无法证明可安全恢复则标记失败。
|
||||
- 验证码、风控、登录、支付和未知页面立即停止,不展示“自动继续”。
|
||||
- AI 超时最多按配置重试;输出无效或低置信度转人工。
|
||||
- 任意候选达到预算上限校验失败时不能进入接受状态。
|
||||
- 候选商品价乘以任务数量达到总预算上限校验失败,或运费/优惠导致总价无法确认时,
|
||||
不能进入接受状态。
|
||||
|
||||
**可访问性与反馈**
|
||||
|
||||
@@ -203,7 +205,8 @@
|
||||
**状态与异常**
|
||||
|
||||
- 没有候选时只允许确认“无匹配”或“需人工处理”。
|
||||
- 存在超预算或关键属性未知时禁用“接受候选”并说明原因。
|
||||
- 存在商品总价超预算、最终商品金额无法确认或关键属性未知时禁用“接受候选”并
|
||||
说明原因。
|
||||
- 回传超时:保留选择和原因,查询最终状态后再重试。
|
||||
- 返回键不能恢复自动点击;等待确认一旦到达就是硬停止点。
|
||||
- 所有结果都写入 `order_submitted=false`。
|
||||
|
||||
+29
-4
@@ -13,6 +13,11 @@
|
||||
- 分页使用 `limit` 和不透明 `cursor`;MVP `limit` 最大 100。
|
||||
- 客户端不得根据 HTTP 超时判断操作失败,必须查询资源最终状态。
|
||||
|
||||
T-204 管理会话完成前,T-203 的管理 Web 和管理 API 只接受来源地址为 loopback 的
|
||||
请求,Web 写操作还要求 SameSite Cookie 与表单字段匹配的随机双提交 CSRF token。
|
||||
该门禁只用于阻止业务路由被提前暴露,不是正式身份认证;`/login`、管理会话和
|
||||
局域网访问仍由 T-204 实现。
|
||||
|
||||
通用错误:
|
||||
|
||||
```json
|
||||
@@ -113,7 +118,8 @@ HTTP 语义:
|
||||
|
||||
表单字段:
|
||||
|
||||
- `file`:必填,MVP 允许 JPEG/PNG/WebP;实际大小上限在配置中固定。
|
||||
- `file`:必填,MVP 允许可解码 JPEG/PNG/WebP;请求最多 20 MiB、最长边最多
|
||||
10000 px、总像素最多 25 MP。
|
||||
- `purpose`:`TASK_REFERENCE` 或 `EXECUTION_EVIDENCE`。
|
||||
- `task_id`:证据图片必填;参考图创建时为空。
|
||||
|
||||
@@ -129,6 +135,13 @@ HTTP 语义:
|
||||
}
|
||||
```
|
||||
|
||||
T-203 成功返回 `201`。使用相同 `Idempotency-Key` 和相同图片内容重试时返回同一
|
||||
资产;同 key 不同内容返回 `409`。
|
||||
|
||||
`TASK_REFERENCE` 在后端统一白底合成、缩放到最长边不超过 2048 px,并以质量 90
|
||||
编码为匿名 JPEG。响应的 `media_type`、`size_bytes` 和 `sha256` 都描述规范化结果,
|
||||
不描述原始上传文件。T-203 尚不接受 `EXECUTION_EVIDENCE`。
|
||||
|
||||
### `GET /api/v1/assets/{asset_id}/content`
|
||||
|
||||
受鉴权的文件流。只能读取用户有权查看的任务资产;不返回服务端文件路径。
|
||||
@@ -143,6 +156,7 @@ HTTP 语义:
|
||||
{
|
||||
"source_ref": "external-admin-task-10001",
|
||||
"title": "黑色双肩包",
|
||||
"sku": "BLACK-20L",
|
||||
"description": "容量约20L,外观接近参考图",
|
||||
"image_asset_id": "2bbc1bf2-30f7-497e-a52b-bcb4c61d57c3",
|
||||
"quantity": 2,
|
||||
@@ -152,9 +166,12 @@ HTTP 语义:
|
||||
|
||||
规则:
|
||||
|
||||
- `title` 或 `description` 至少一个非空,`image_asset_id` 必填。
|
||||
- `title`、`sku` 和 `image_asset_id` 必填,`description` 可为空。
|
||||
- `title` 最多 120 个 Unicode 字符且不超过 2048 个 UTF-8 字节;`sku` 不超过
|
||||
512 个 UTF-8 字节;`description` 不超过 8192 个 UTF-8 字节。
|
||||
- `quantity` 是正整数。
|
||||
- `max_budget` 可为空,否则为大于零、最多两位小数的 CNY 金额。
|
||||
- `max_budget` 可为空,否则为大于零、最多两位小数的 CNY 金额,表示当前任务全部
|
||||
数量的最高商品总预算,不含尚无法确认的运费或优惠。
|
||||
- 同一调用方的 `source_ref` 如填写必须唯一。
|
||||
|
||||
成功返回 `201`:
|
||||
@@ -164,6 +181,7 @@ HTTP 语义:
|
||||
"id": "37c9c715-9b51-4ed5-984e-66dad2710c71",
|
||||
"status": "PENDING",
|
||||
"title": "黑色双肩包",
|
||||
"sku": "BLACK-20L",
|
||||
"quantity": 2,
|
||||
"max_budget": "200.00",
|
||||
"created_at": "2026-07-25T08:30:00Z"
|
||||
@@ -172,7 +190,9 @@ HTTP 语义:
|
||||
|
||||
### `GET /api/v1/tasks`
|
||||
|
||||
管理端列表。可选参数:`status`、`created_from`、`created_to`、`limit`、`cursor`。
|
||||
管理端列表。可选参数:`q`、`status`、`created_from`、`created_to`、`limit`、
|
||||
`cursor`。`q` 匹配任务 ID、`source_ref`、标题或 SKU;默认 `limit=20`,最大 100。
|
||||
排序固定为 `created_at DESC, id DESC`,不透明 cursor 同时编码这两个字段。
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -180,6 +200,7 @@ HTTP 语义:
|
||||
{
|
||||
"id": "37c9c715-9b51-4ed5-984e-66dad2710c71",
|
||||
"title": "黑色双肩包",
|
||||
"sku": "BLACK-20L",
|
||||
"status": "RUNNING",
|
||||
"quantity": 2,
|
||||
"device_name": "pdd-phone-01",
|
||||
@@ -212,6 +233,9 @@ HTTP 语义:
|
||||
}
|
||||
```
|
||||
|
||||
T-203 只实现 `PENDING -> CANCELED`;其他状态返回 `409`。执行中设置取消请求及 App
|
||||
安全检查点响应属于 T-205。
|
||||
|
||||
## 设备与领取
|
||||
|
||||
### `POST /api/v1/devices/heartbeat`
|
||||
@@ -249,6 +273,7 @@ App 空闲或运行时上报设备状态;运行时任务续租使用任务专
|
||||
"id": "37c9c715-9b51-4ed5-984e-66dad2710c71",
|
||||
"status": "CLAIMED",
|
||||
"title": "黑色双肩包",
|
||||
"sku": "BLACK-20L",
|
||||
"description": "容量约20L,外观接近参考图",
|
||||
"image_asset_id": "2bbc1bf2-30f7-497e-a52b-bcb4c61d57c3",
|
||||
"quantity": 2,
|
||||
|
||||
+18
-10
@@ -5,23 +5,25 @@
|
||||
## 当前快照
|
||||
|
||||
- 日期:2026-07-26
|
||||
- 阶段:T-202 P0 Web/App 低保真原型已确认完成,准备开始 T-203
|
||||
- Git:当前分支为 `main`;T-001 至 T-004、T-101 至 T-104、T-201、T-202
|
||||
- 阶段:T-203 任务创建 API 与管理 Web 已完成,准备开始 T-204
|
||||
- Git:当前分支为 `main`;T-001 至 T-004、T-101 至 T-104、T-201 至 T-203
|
||||
均已纳入 Git 历史
|
||||
- 生产代码:`android-buyer/` 已接入 Roubao Android 源码
|
||||
- Android:固定 `main@c8a6d7f03422eb01744b01f3ee77bf7757741f7e`;MIT 许可证已保留
|
||||
- 后端:Go 1.23.0 + Gin 1.11.0 + SQLite + Goose 3.26.0 骨架已建立;Go Blueprint
|
||||
v0.10.11 只作为一次性输入,危险默认与演示逻辑已移除
|
||||
- 后端:Go 1.23.0 + Gin 1.11.0 + SQLite + Goose 3.26.0;已实现图片规范化、任务
|
||||
创建/查询/取消 API 和 SSR 管理 Web,Go Blueprint 只作为一次性骨架输入
|
||||
- 本机 Android 工具:JDK 17.0.13、Command-line Tools 22.0、SDK 34、
|
||||
Build Tools 34.0.0、Platform Tools/ADB 37.0.0;用户级 SDK 环境变量已设置
|
||||
- Android Studio:未安装;`winget` 静默安装卡住后已终止,不阻塞命令行构建
|
||||
- 测试:`lintDebug test assembleDebug` 成功;App 两个变体、task contract 和导入器
|
||||
共 26 份报告、166 次测试,0 failure、0 error、0 skipped
|
||||
- 后端测试:`GOTOOLCHAIN=local go test -count=1 ./...` 共 25 个测试通过;
|
||||
`go vet ./...`、API/migration Windows 构建和根 `init.ps1` 均通过
|
||||
- 后端测试:`GOTOOLCHAIN=local go test -count=1 ./...` 共 99 个测试通过;
|
||||
全包 race、`go vet ./...`、API/migration Windows 构建和根 `init.ps1` 均通过
|
||||
- 原型:4 个管理 Web 页面和 7 个 Android 页面均可离线独立打开;Playwright
|
||||
以 1440×900、390×844、360×800 验证 36 个页面/视口组合,无页面横向溢出、
|
||||
脚本错误或外部请求,Android 可见交互控件均不小于 44px
|
||||
- 管理 Web:真实 Gin/SQLite 流程已完成图片上传、任务创建、列表、详情参考图和
|
||||
待领取取消;同三种视口无横向溢出,可见操作控件不小于 44px
|
||||
- Workflow:纯 Kotlin runner 已支持步骤 timeout、最多 3 次 retry、安全阻塞、
|
||||
用户停止和单 runner 并发拒绝;T-102 已接入搜索加有界候选采集五步
|
||||
- TaskSource:严格 CLI 已生成并验证真实私有 ProbeTask;默认 APK 不含私有 fixture
|
||||
@@ -56,20 +58,20 @@
|
||||
| `docs/tasks/T-104.md` | DONE | 动态搜索、候选严格评估、本地建议和人工确认点 |
|
||||
| `docs/tasks/T-201.md` | DONE | Go-Gin、SQLite、Goose migration 和健康检查骨架 |
|
||||
| `docs/tasks/T-202.md` | DONE | 生成并确认 P0 Web/App 低保真原型 |
|
||||
| `docs/tasks/T-203.md` | DONE | 图片/任务 API、SQLite 业务层和 SSR 管理 Web |
|
||||
| `docs/design/` | 已确认 | T-202 原型索引、4 个管理页和 7 个 Android 页面 |
|
||||
| `deepseek总结.txt` | 已有 | 历史讨论摘要,不是正式需求权威 |
|
||||
| `android-buyer/` | 已有 | Roubao `main` 固定 commit 的 Android 基线 |
|
||||
| `android-buyer/task-contract/` | 已有 | Android/CLI 共享 ProbeTask 与 TaskSource |
|
||||
| `android-buyer/tools/shopee-importer/` | 已有 | 开发机私有 fixture 导入 CLI |
|
||||
| `backend-api/` | 已有 | Go-Gin API、SQLite、migration、健康检查和测试骨架 |
|
||||
| `backend-api/` | 已有 | Go-Gin 任务 API、SQLite、图片存储、管理 Web 和 migration |
|
||||
| `init.ps1` / `init.sh` | 已验证/待跨平台 | Windows 同时验证 Android/Go;Unix 入口待 Linux/WSL 复核 |
|
||||
|
||||
## 任务摘要
|
||||
|
||||
- 已完成:T-001 至 T-004、T-101 至 T-104、T-201、T-202。
|
||||
- 已完成:T-001 至 T-004、T-101 至 T-104、T-201 至 T-203。
|
||||
- 正在进行:无。
|
||||
- 下一个可领取任务:T-203 实现任务创建 API 与管理 Web;T-204 也已满足依赖,
|
||||
但按编号顺序在 T-203 之后处理。
|
||||
- 下一个可领取任务:T-204 实现账号、角色、会话和设备身份;完成后 T-205 可开始。
|
||||
|
||||
## 当前可运行内容
|
||||
|
||||
@@ -107,6 +109,12 @@ go run ./cmd/api
|
||||
`http.Server.Shutdown` 由集成测试验证在 1 秒预算内结束。当前 Windows 没有可用 WSL
|
||||
发行版,因此 `init.sh` 未实际运行;Windows 标准入口已验证。
|
||||
|
||||
2026-07-26 完成 T-203:新增两条业务 migration、图片规范化存储、任务领域/用例/
|
||||
SQLite 仓储、管理 API 和 SSR Web。创建/上传幂等,列表稳定分页,详情可读取同源
|
||||
规范化参考图,只有 `PENDING` 可取消。T-204 前业务路由仅限 loopback,Web 写操作
|
||||
使用双提交 CSRF;这不是正式鉴权。真实 HTTP/Playwright 和 migration CLI smoke、
|
||||
99 个 Go 测试、全包 race/vet、Windows 构建与根 `init.ps1` 全部通过。
|
||||
|
||||
## 维护规则
|
||||
|
||||
发生以下变化时覆盖更新本文:
|
||||
|
||||
@@ -210,6 +210,12 @@
|
||||
<input id="title" name="title" maxlength="120" placeholder="例如:演示用桌面收纳盒">
|
||||
<p id="title-error" class="error"></p>
|
||||
</div>
|
||||
<div class="span-2">
|
||||
<label class="required" for="sku">SKU</label>
|
||||
<input id="sku" name="sku" maxlength="120" placeholder="例如:浅灰色 · 中号">
|
||||
<p id="sku-error" class="error"></p>
|
||||
<p class="help">SKU 是不可覆盖的原始采购要求。</p>
|
||||
</div>
|
||||
<div class="span-2">
|
||||
<label for="description">商品描述</label>
|
||||
<textarea id="description" name="description" placeholder="填写规格、颜色、材质等判断条件"></textarea>
|
||||
@@ -221,7 +227,7 @@
|
||||
<p id="quantity-error" class="error"></p>
|
||||
</div>
|
||||
<div>
|
||||
<label for="budget">最高总预算(元)</label>
|
||||
<label for="budget">最高商品总预算(元)</label>
|
||||
<input id="budget" name="budget" type="number" min="0.01" step="0.01" inputmode="decimal" placeholder="不填写则不设上限">
|
||||
<p id="budget-error" class="error"></p>
|
||||
</div>
|
||||
@@ -284,7 +290,7 @@
|
||||
});
|
||||
|
||||
function clearFeedback() {
|
||||
['title', 'quantity', 'budget', 'image'].forEach((name) => {
|
||||
['title', 'sku', 'quantity', 'budget', 'image'].forEach((name) => {
|
||||
document.querySelector(`#${name}-error`).textContent = '';
|
||||
});
|
||||
notice.style.display = 'none';
|
||||
@@ -303,18 +309,23 @@
|
||||
clearFeedback();
|
||||
let firstInvalid = null;
|
||||
const title = document.querySelector('#title');
|
||||
const sku = document.querySelector('#sku');
|
||||
const quantity = document.querySelector('#quantity');
|
||||
const budget = document.querySelector('#budget');
|
||||
if (!title.value.trim()) {
|
||||
document.querySelector('#title-error').textContent = '请输入商品标题。';
|
||||
firstInvalid ||= title;
|
||||
}
|
||||
if (!sku.value.trim()) {
|
||||
document.querySelector('#sku-error').textContent = '请输入 SKU。';
|
||||
firstInvalid ||= sku;
|
||||
}
|
||||
if (!Number.isInteger(Number(quantity.value)) || Number(quantity.value) < 1) {
|
||||
document.querySelector('#quantity-error').textContent = '数量必须是大于 0 的整数。';
|
||||
firstInvalid ||= quantity;
|
||||
}
|
||||
if (budget.value && Number(budget.value) <= 0) {
|
||||
document.querySelector('#budget-error').textContent = '预算必须大于 0,或留空。';
|
||||
document.querySelector('#budget-error').textContent = '商品总预算必须大于 0,或留空。';
|
||||
firstInvalid ||= budget;
|
||||
}
|
||||
if (!image.files.length) {
|
||||
|
||||
@@ -289,8 +289,9 @@
|
||||
<p class="description">桌面用分格收纳盒,浅灰色,塑料材质。
|
||||
需要带可拆分隔板,外观接近参考图。</p>
|
||||
<div class="constraints">
|
||||
<span class="constraint">SKU:浅灰色 · 中号</span>
|
||||
<span class="constraint">数量:2</span>
|
||||
<span class="constraint">最高总预算:60.00 元</span>
|
||||
<span class="constraint">最高商品总预算:60.00 元</span>
|
||||
<span class="constraint">关键属性:浅灰 / 分隔板</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,14 +38,14 @@
|
||||
</section>
|
||||
<section class="panel" id="requirement" aria-labelledby="requirementTitle">
|
||||
<h2 id="requirementTitle">管理员原始要求</h2>
|
||||
<dl class="facts"><dt>商品</dt><dd>透明抽屉式收纳盒</dd><dt>SKU</dt><dd>白色 · 中号</dd><dt>数量</dt><dd>2 件</dd><dt>最高预算</dt><dd>¥ 48.00 / 件</dd></dl>
|
||||
<dl class="facts"><dt>商品</dt><dd>透明抽屉式收纳盒</dd><dt>SKU</dt><dd>白色 · 中号</dd><dt>数量</dt><dd>2 件</dd><dt>最高商品总预算</dt><dd>¥ 96.00</dd></dl>
|
||||
</section>
|
||||
<section class="panel" id="candidate" aria-labelledby="candidateTitle">
|
||||
<h2 id="candidateTitle">候选 1 / 3</h2>
|
||||
<div class="evidence" role="img" aria-label="脱敏的候选商品截图占位">脱敏候选截图<br>证据占位</div>
|
||||
<dl class="facts"><dt>候选标题</dt><dd>透明可叠放抽屉收纳盒</dd><dt>单价</dt><dd class="price" id="price">¥ 39.90</dd></dl>
|
||||
<h3>匹配与风险</h3>
|
||||
<ul class="checks" id="checks"><li><span class="ok">符合</span> 颜色:白色</li><li><span class="ok">符合</span> 规格:中号</li><li><span class="ok">符合</span> 单价未超过预算</li></ul>
|
||||
<ul class="checks" id="checks"><li><span class="ok">符合</span> 颜色:白色</li><li><span class="ok">符合</span> 规格:中号</li><li><span class="ok">符合</span> 商品总价 ¥ 79.80 未超过预算</li></ul>
|
||||
</section>
|
||||
<section class="panel">
|
||||
<h2>人员结论</h2>
|
||||
@@ -65,7 +65,7 @@
|
||||
</dialog>
|
||||
<script>
|
||||
const scenario=document.querySelector("#scenario"),candidate=document.querySelector("#candidate"),accept=document.querySelector("#accept"),reject=document.querySelector("#reject"),noneAction=document.querySelector("#noneAction"),price=document.querySelector("#price"),checks=document.querySelector("#checks"),dialog=document.querySelector("#confirmDialog"),cancel=document.querySelector("#cancel"),completion=document.querySelector("#completion");let pending="";
|
||||
function render(){const v=scenario.value,missing=v==="none";candidate.hidden=missing;noneAction.disabled=!missing;accept.disabled=v!=="valid";reject.disabled=missing;if(v==="over"){price.textContent="¥ 56.00";checks.innerHTML='<li><span class="ok">符合</span> 颜色:白色</li><li><span class="ok">符合</span> 规格:中号</li><li><span class="risk">不符合</span> 单价超过最高预算,不能接受</li>'}else if(v==="unknown"){price.textContent="¥ 41.00";checks.innerHTML='<li><span class="ok">符合</span> 颜色:白色</li><li><span class="risk">未知</span> 规格无法确认,不能接受</li><li><span class="ok">符合</span> 单价未超过预算</li>'}else{price.textContent="¥ 39.90";checks.innerHTML='<li><span class="ok">符合</span> 颜色:白色</li><li><span class="ok">符合</span> 规格:中号</li><li><span class="ok">符合</span> 单价未超过预算</li>'}}
|
||||
function render(){const v=scenario.value,missing=v==="none";candidate.hidden=missing;noneAction.disabled=!missing;accept.disabled=v!=="valid";reject.disabled=missing;if(v==="over"){price.textContent="¥ 56.00";checks.innerHTML='<li><span class="ok">符合</span> 颜色:白色</li><li><span class="ok">符合</span> 规格:中号</li><li><span class="risk">不符合</span> 商品总价 ¥ 112.00 超过总预算,不能接受</li>'}else if(v==="unknown"){price.textContent="¥ 41.00";checks.innerHTML='<li><span class="ok">符合</span> 颜色:白色</li><li><span class="risk">未知</span> 规格无法确认,不能接受</li><li><span class="ok">符合</span> 商品总价 ¥ 82.00 未超过总预算</li>'}else{price.textContent="¥ 39.90";checks.innerHTML='<li><span class="ok">符合</span> 颜色:白色</li><li><span class="ok">符合</span> 规格:中号</li><li><span class="ok">符合</span> 商品总价 ¥ 79.80 未超过总预算</li>'}}
|
||||
document.querySelectorAll("[data-action]").forEach(button=>button.addEventListener("click",()=>{pending=button.dataset.action;document.querySelector("#dialogText").textContent="选择:"+pending;dialog.showModal();setTimeout(()=>cancel.focus(),0)}));
|
||||
scenario.addEventListener("change",render);cancel.addEventListener("click",()=>dialog.close());dialog.addEventListener("cancel",e=>{e.preventDefault();dialog.close()});document.querySelector("#confirm").addEventListener("click",()=>{dialog.close();completion.classList.add("show");document.querySelector("#completionText").textContent="“"+pending+"”已记录,结果正在同步。";completion.scrollIntoView({behavior:"smooth",block:"start"})});render();
|
||||
</script>
|
||||
|
||||
@@ -31,13 +31,13 @@
|
||||
<dt>商品标题</dt><dd>透明抽屉式桌面收纳盒</dd>
|
||||
<dt>SKU</dt><dd>白色 · 中号</dd>
|
||||
<dt>数量</dt><dd class="constraint">2 件</dd>
|
||||
<dt>最高预算</dt><dd class="constraint">¥ 48.00 / 件</dd>
|
||||
<dt>最高商品总预算</dt><dd class="constraint">¥ 96.00</dd>
|
||||
<dt>描述</dt><dd>带把手,可叠放,优先选择相同外观。</dd>
|
||||
</dl>
|
||||
</section>
|
||||
<section class="panel derived" aria-labelledby="derivedTitle">
|
||||
<h2 id="derivedTitle">AI 辅助理解 · 待验证</h2>
|
||||
<p>可能关键词:透明、抽屉式、中号、可叠放。最终数量和预算始终以原始要求为准。</p>
|
||||
<p>可能关键词:透明、抽屉式、中号、可叠放。最终 SKU、数量和商品总预算始终以原始要求为准。</p>
|
||||
</section>
|
||||
<section class="panel">
|
||||
<label for="scenario">原型状态</label>
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
---
|
||||
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 可开始实现正式管理会话和角色门禁。
|
||||
Reference in New Issue
Block a user