merge: T-204 routed task evidence details
This commit is contained in:
@@ -246,6 +246,27 @@ CREATE TABLE order_submissions (
|
||||
UNIQUE (authorization_id),
|
||||
UNIQUE (attempt_id)
|
||||
);
|
||||
|
||||
-- INTERNAL_RAW 原始截图;原文件名和客户端路径不进入数据库
|
||||
CREATE TABLE evidence_assets (
|
||||
id TEXT PRIMARY KEY,
|
||||
upload_key TEXT NOT NULL,
|
||||
task_id TEXT NOT NULL,
|
||||
attempt_id TEXT NOT NULL,
|
||||
kind TEXT NOT NULL, -- T-204 仅 SKU_PANEL_GATE_1
|
||||
privacy_tier TEXT NOT NULL, -- 仅 INTERNAL_RAW
|
||||
sha256 TEXT NOT NULL, -- 64 位小写十六进制
|
||||
byte_size INTEGER NOT NULL,
|
||||
content_type TEXT NOT NULL, -- 仅 image/png
|
||||
width_px INTEGER NOT NULL,
|
||||
height_px INTEGER NOT NULL,
|
||||
storage_key TEXT NOT NULL, -- 由 SHA-256 唯一派生
|
||||
uploaded_by_device_id TEXT NOT NULL,
|
||||
captured_at TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
UNIQUE (uploaded_by_device_id, upload_key),
|
||||
FOREIGN KEY (task_id, attempt_id) REFERENCES purchase_attempts(task_id, id)
|
||||
);
|
||||
```
|
||||
|
||||
MVP 不再用 `spec_trials` 作为审批记录,也不存在 `authorized_unit_price`。实际读价属于
|
||||
@@ -306,6 +327,19 @@ DRAFT / PENDING / NEEDS_MANUAL ─管理员取消(围栏前)→ CANCELED
|
||||
截图上传器只能接收调用方显式指定的截图,不能枚举证据目录或顺带上传 XML/manifest。证据响应
|
||||
使用 `Cache-Control: no-store`,不能暴露为免登录静态目录。
|
||||
|
||||
内部截图存储采用以下固定边界:
|
||||
|
||||
- 单个 PNG 最大 10 MiB、单边最大 8192 px、总像素最大 16,777,216;同时验证 multipart MIME、
|
||||
PNG 魔数、完整解码、字节数、尺寸和调用方声明的 SHA-256。
|
||||
- 上传 handler 必须先通过设备认证,再解析 Content-Type 或读取 body。T-301 前生产认证器固定拒绝,
|
||||
不创建临时 token,也不把管理员 session 当设备身份。
|
||||
- 文件写入显式配置的私有证据根目录:同目录随机临时文件 → 流式 hash → 校验 → `fsync` → 原子
|
||||
rename 到 SHA-256 内容地址 → 最后事务写数据库。数据库永远不指向半文件或缺失文件。
|
||||
- SQLite 与文件系统不能组成跨资源事务;极端故障最多留下不可达孤儿文件。不得为清理孤儿而删除
|
||||
可能被其他资产记录并发复用的内容文件,自动保留/删除策略留给部署任务。
|
||||
- SHA-256 只用于物理内容寻址,不是业务资产唯一键;不同合法证据可以引用相同内容。同设备主体与
|
||||
`upload_key` 同载荷重放原资产,任一规范字段变化即冲突。
|
||||
|
||||
## 六、关键技术难点
|
||||
|
||||
| 难点 | 风险 | 应对 |
|
||||
|
||||
+25
-4
@@ -53,6 +53,14 @@
|
||||
| `POST` | `/tasks/{id}/mark-paid` | 人工确认已付款并完成核对 |
|
||||
| `GET` | `/evidence/{asset_id}` | 登录后读取内部截图;`Cache-Control: no-store` |
|
||||
|
||||
`GET /tasks/{id}` 的完整页与列表抽屉共享同一服务端数据模型和详情模板。列表只可用同源请求携带
|
||||
`X-CMBuyer-View: drawer` 获取 HTML fragment;其他非空 view、跨站 fragment 请求或不接受
|
||||
`text/html` 的 fragment 请求均拒绝。直接导航同一 URL 始终返回完整页。
|
||||
|
||||
`GET /evidence/{asset_id}` 不经静态目录:未登录先返回 `401`,不查询和泄露资产是否存在;登录后
|
||||
缺失或畸形 id 返回空 `404`。成功只返回存储的 PNG,包含 `Content-Length`、固定安全文件名、
|
||||
`Cache-Control: no-store` 与 `X-Content-Type-Options: nosniff`,不返回原文件名或服务端路径。
|
||||
|
||||
### `POST /tasks`
|
||||
|
||||
核心字段:
|
||||
@@ -190,17 +198,30 @@
|
||||
|
||||
```json
|
||||
{
|
||||
"attempt_id": "018f-attempt",
|
||||
"upload_key": "43c9f507-7473-4fa6-8d71-8786c34c6301",
|
||||
"attempt_id": "33c9f507-7473-4fa6-8d71-8786c34c6301",
|
||||
"kind": "SKU_PANEL_GATE_1",
|
||||
"privacy_tier": "INTERNAL_RAW",
|
||||
"sha256": "64-lowercase-hex",
|
||||
"sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
"captured_at": "2026-08-04T09:01:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
- 允许规格面板和确认页截图保留页面已显示的地址/手机号;不要求遮罩或裁剪。
|
||||
- 不接受 XML、目录、manifest、本机绝对路径、外部支付页截图或支付凭据。
|
||||
- MIME、尺寸、字节数和 SHA-256 必须校验;资产只经管理员鉴权端点读取。
|
||||
- T-204 只开放 `kind=SKU_PANEL_GATE_1`;后续 kind 必须由对应真机证据任务收紧扩展。
|
||||
- `privacy_tier` 只能是 `INTERNAL_RAW`;时间必须是以 `Z` 结尾的 UTC RFC 3339。
|
||||
- URL 中的 task id、`upload_key` 与 `attempt_id` 都必须是规范的小写 UUIDv4;`sha256` 必须是
|
||||
恰好 64 位小写十六进制字符。
|
||||
- 恰好一个带 `Content-Type: image/png` 的显式文件;除上述六个元数据字段外,未知或重复字段均拒绝。
|
||||
- 单文件最多 10 MiB、单边最多 8192 px、总像素最多 16,777,216;服务端校验 PNG 魔数、完整解码、
|
||||
字节数、尺寸与调用方声明的 64 位小写 SHA-256。
|
||||
- `attempt_id` 必须由数据库复合外键证明属于 URL 中的 task。认证必须先于 Content-Type 解析和请求体读取。
|
||||
- 同一设备主体和 `upload_key` 的同载荷重放返回原资产;任务、attempt、截图或元数据变化返回 `409`。
|
||||
- 首次成功返回 `201`,幂等重放返回 `200`。响应只含资产 id、关联 id、kind/tier、hash、字节数、
|
||||
MIME、宽高和采集时间,不含设备 token、原文件名或存储路径。
|
||||
- T-301 接入真实设备 Bearer 身份前,生产 `DeviceAuthenticator` 固定拒绝全部上传;不得使用管理员
|
||||
session、临时 token 或共享密钥代替设备身份。
|
||||
|
||||
### `POST /api/v1/purchase-attempts/{aid}/submission-fence`
|
||||
|
||||
@@ -310,6 +331,6 @@ T-103 只实现隔离的 `SkuSelectionFlow`:前四项加安全退出。它的
|
||||
## 四、实现前仍需定值
|
||||
|
||||
- 授权有效期、领取租约时长、心跳/轮询间隔和连续失败停止阈值;
|
||||
- 截图大小上限和内部保留期限;
|
||||
- 内部截图保留期限;截图大小上限已固定为 10 MiB / 8192 px 单边 / 16,777,216 像素;
|
||||
- 可配置单任务数量与最高总价系统上限;
|
||||
- 首次真实提交真机任务的人工授权和待付款订单处置步骤。
|
||||
|
||||
+11
-1
@@ -47,7 +47,9 @@
|
||||
| 创建时间 | 本地时区显示,数据按 UTC 保存 |
|
||||
|
||||
没有操作列。双击非控件区域或键盘 Enter 打开 `/tasks/{id}` 路由化详情抽屉;新 tab 直接访问同 URL
|
||||
则显示完整详情页。关闭抽屉或浏览器返回恢复筛选、滚动和触发行焦点。
|
||||
则显示完整详情页。标题下方同时提供可见“查看详情”按钮,双击不是唯一入口。商品外链、checkbox、
|
||||
输入和按钮本身不触发行双击。抽屉成功加载后才把 URL 推进 `/tasks/{id}`;关闭、Esc 或浏览器返回
|
||||
恢复筛选、滚动和触发行焦点,浏览器前进重新打开同一详情且不重复写 history。
|
||||
|
||||
### 批量开始采购
|
||||
|
||||
@@ -91,6 +93,14 @@
|
||||
|
||||
详情中不出现 `WAITING_CONFIRMATION`、“确认机器选对了吗”、“签发第二趟授权”或“重新试选”。
|
||||
|
||||
完整页与抽屉执行同一 `task-detail-content` 模板和只读查询。直接导航返回完整 SSR 文档;列表 JS 对
|
||||
同一 URL 发出同源 `X-CMBuyer-View: drawer` 请求,只取得 HTML fragment。加载失败时抽屉提供重试和
|
||||
“在完整页打开”,不会把失败请求伪装成已打开详情。
|
||||
|
||||
T-204 只显示数据库中当前实际存在的任务、授权、attempt、submission 和内部截图,缺少事实就显示
|
||||
明确空态;它不创建 attempt/event,不计算闸门,也不提供重置、调和、标记付款或任何设备动作。
|
||||
截图以服务端记录的宽高预留布局并延迟加载,alt 只描述证据种类和采集时间,不转录截图中的地址或手机号。
|
||||
|
||||
## 四、采购工具界面结构
|
||||
|
||||
应用名:**采购工具**。顶部固定 tab:
|
||||
|
||||
Reference in New Issue
Block a user