T48 SYB 同步记录与导入异步化 #50

Closed
opened 2026-08-20 10:37:52 +08:00 by ila · 6 comments
Owner

背景

目前 POST /syb-products/import 同步执行、跑完才响应。关掉页面后导入会继续跑完(各层都绑定了脱离请求的上下文),但结果报告彻底丢失:导了多少、成功还是中途失败,都看不到。30 分钟超时被切断时同样无声无息。

依赖 #49 的店铺跳过统计字段。

改为异步 + 运行记录

POST /syb-products/import   →  立即返回 runId
GET  /syb-products/sync-runs/{runId}  →  轮询进度与结果
GET  /syb-products/sync-runs          →  历史列表(分页)

关掉页面再回来能看到进度和结果;服务端重启后残留的 running 记录标记为 interrupted。

数据模型

syb_sync_run:

字段 说明
date_from / date_to 同步范围
status running / succeeded / failed / interrupted
order_count / detail_count 货运单数、明细数
accepted_count / shop_skipped 通过店铺过滤的、被跳过的
created / updated 新增、覆盖
shop_filter_hash 启用店铺集合的哈希
shop_breakdown_json 每个店铺的接受/跳过条数
error_message 失败原因摘要
operator_id / operator_name 操作人
started_at / finished_at

[必须] shop_filter_hash 的作用:两次同步结果不同时,能分清是数据变了还是过滤规则变了。

[必须] shop_breakdown_json 要按店铺列出接受/跳过条数,不能只存一个总数——否则新开的店铺永远不会被注意到。

[必须] 只保存统计和错误摘要,不保存 Cookie、验证码、原始响应或账号信息。

行为约定

  • [必须] 启动时把残留的 running 记录标为 interrupted——进程重启后它们不可能再推进。
  • [必须] 单飞:已有 running 记录时拒绝新的导入,提示正在执行的范围。现有的内存锁保留(同进程内更快),运行记录负责跨重启的可见性。
  • [必须] 中途失败时已写入的数据保留(按 (order_code, detail_id) 幂等,重跑覆盖),记录里如实写明失败原因和已处理量。
  • 进度字段(已处理天数/货运单数)随同步推进更新,让轮询有意义。

权限

[必须] 同步记录只读,所有角色可查看;发起导入仅管理员(与 #49 一致)。

页面

  • SYB 商品页的导入弹窗改为提交后立即返回并轮询,可关闭
  • 新增「同步记录」页面:列表 + 详情(含按店铺的接受/跳过明细)

门禁

  • Stage A 原型:同步记录列表与详情、导入弹窗的异步改造
  • Stage B:模型+迁移 → 服务端 API → 异步化改造 → Admin 页面 → 文档
## 背景 目前 `POST /syb-products/import` 同步执行、跑完才响应。关掉页面后导入**会继续跑完**(各层都绑定了脱离请求的上下文),但**结果报告彻底丢失**:导了多少、成功还是中途失败,都看不到。30 分钟超时被切断时同样无声无息。 依赖 [#49](https://git.ilapage.cn/OPC/goauto/issues/49) 的店铺跳过统计字段。 ## 改为异步 + 运行记录 ``` POST /syb-products/import → 立即返回 runId GET /syb-products/sync-runs/{runId} → 轮询进度与结果 GET /syb-products/sync-runs → 历史列表(分页) ``` 关掉页面再回来能看到进度和结果;服务端重启后残留的 `running` 记录标记为 `interrupted`。 ## 数据模型 `syb_sync_run`: | 字段 | 说明 | |---|---| | `date_from` / `date_to` | 同步范围 | | `status` | `running` / `succeeded` / `failed` / `interrupted` | | `order_count` / `detail_count` | 货运单数、明细数 | | `accepted_count` / `shop_skipped` | 通过店铺过滤的、被跳过的 | | `created` / `updated` | 新增、覆盖 | | `shop_filter_hash` | **启用店铺集合的哈希** | | `shop_breakdown_json` | 每个店铺的接受/跳过条数 | | `error_message` | 失败原因摘要 | | `operator_id` / `operator_name` | 操作人 | | `started_at` / `finished_at` | | `[必须]` `shop_filter_hash` 的作用:两次同步结果不同时,能分清是**数据变了**还是**过滤规则变了**。 `[必须]` `shop_breakdown_json` 要按店铺列出接受/跳过条数,不能只存一个总数——否则新开的店铺永远不会被注意到。 `[必须]` 只保存统计和错误摘要,**不保存 Cookie、验证码、原始响应或账号信息**。 ## 行为约定 - `[必须]` 启动时把残留的 `running` 记录标为 `interrupted`——进程重启后它们不可能再推进。 - `[必须]` 单飞:已有 `running` 记录时拒绝新的导入,提示正在执行的范围。现有的内存锁保留(同进程内更快),运行记录负责跨重启的可见性。 - `[必须]` 中途失败时已写入的数据保留(按 `(order_code, detail_id)` 幂等,重跑覆盖),记录里如实写明失败原因和已处理量。 - 进度字段(已处理天数/货运单数)随同步推进更新,让轮询有意义。 ## 权限 `[必须]` 同步记录**只读**,所有角色可查看;发起导入仅管理员(与 #49 一致)。 ## 页面 - SYB 商品页的导入弹窗改为提交后立即返回并轮询,可关闭 - 新增「同步记录」页面:列表 + 详情(含按店铺的接受/跳过明细) ## 门禁 - Stage A 原型:同步记录列表与详情、导入弹窗的异步改造 - Stage B:模型+迁移 → 服务端 API → 异步化改造 → Admin 页面 → 文档
Author
Owner

Stage A 原型已完成,等待用户确认

  • QuantUX App ID:6a869fc2191a826306a7efb1
  • 版本:v1(草稿,未确认)
  • 在线原型:https://qux.ilapage.cn/#/apps/6a869fc2191a826306a7efb1.html
  • 画布:1440 × 900
  • 覆盖:6 个页面状态、226 个组件、14 条点击连线
  • 导出一致性验证:PASS,0 个运行时错误
  • 确认人 / 确认时间:等待用户审核

覆盖范围

  1. SYB 商品页的导入入口。
  2. 异步导入日期确认弹窗:说明提交后后台执行、页面可关闭。
  3. 导入运行中:进度条、已处理天数、货运单和明细数,可进入同步记录。
  4. 同步记录列表:执行中、成功、失败、服务中断四种状态。
  5. 成功详情:同步范围、操作人、过滤版本哈希、新增/覆盖以及按店铺接受/跳过统计。
  6. 失败详情:可读错误、恢复建议、已处理数据保留和失败前店铺统计。

权限与交互边界

  • 发起导入入口按工单保持管理员权限。
  • 同步记录和详情为所有角色只读。
  • 原型没有取消后台任务或删除同步记录入口。
  • 失败后的“重新导入”只返回 SYB 商品页,不直接跳过日期确认创建新任务。

生产代码尚未开始;按双门禁规则,等待用户确认原型和覆盖范围后再进入 Stage B。

## Stage A 原型已完成,等待用户确认 - QuantUX App ID:`6a869fc2191a826306a7efb1` - 版本:`v1`(草稿,未确认) - 在线原型:https://qux.ilapage.cn/#/apps/6a869fc2191a826306a7efb1.html - 画布:1440 × 900 - 覆盖:6 个页面状态、226 个组件、14 条点击连线 - 导出一致性验证:PASS,0 个运行时错误 - 确认人 / 确认时间:等待用户审核 ### 覆盖范围 1. SYB 商品页的导入入口。 2. 异步导入日期确认弹窗:说明提交后后台执行、页面可关闭。 3. 导入运行中:进度条、已处理天数、货运单和明细数,可进入同步记录。 4. 同步记录列表:执行中、成功、失败、服务中断四种状态。 5. 成功详情:同步范围、操作人、过滤版本哈希、新增/覆盖以及按店铺接受/跳过统计。 6. 失败详情:可读错误、恢复建议、已处理数据保留和失败前店铺统计。 ### 权限与交互边界 - 发起导入入口按工单保持管理员权限。 - 同步记录和详情为所有角色只读。 - 原型没有取消后台任务或删除同步记录入口。 - 失败后的“重新导入”只返回 SYB 商品页,不直接跳过日期确认创建新任务。 生产代码尚未开始;按双门禁规则,等待用户确认原型和覆盖范围后再进入 Stage B。
Author
Owner

Stage A 原型 v2:修复在线查看不完整

原因

v1 使用固定 1440 × 900 画布。QuantUX 在线查看器自身还会占用浏览器区域,在常见桌面视口中导致画布右侧或底部被裁切;组件没有丢失,是画布超出可用显示区。

修复

  • 同一 App 的 6 个页面统一调整为 1200 × 720。

  • 226 个组件的位置、宽高、字号和间距按比例调整。

  • 每页重新执行边界检查:越界组件均为 0。

  • 14 条点击连线全部保留。

  • QuantUX 导出验证:PASS,运行时错误 0。

  • App ID:6a869fc2191a826306a7efb1

  • 版本:v2(草稿,等待用户确认)

  • 在线原型:https://qux.ilapage.cn/#/apps/6a869fc2191a826306a7efb1.html

  • 确认人 / 确认时间:等待用户复核

生产代码仍未开始,等待用户确认 v2 原型。

## Stage A 原型 v2:修复在线查看不完整 ### 原因 v1 使用固定 `1440 × 900` 画布。QuantUX 在线查看器自身还会占用浏览器区域,在常见桌面视口中导致画布右侧或底部被裁切;组件没有丢失,是画布超出可用显示区。 ### 修复 - 同一 App 的 6 个页面统一调整为 `1200 × 720`。 - 226 个组件的位置、宽高、字号和间距按比例调整。 - 每页重新执行边界检查:越界组件均为 `0`。 - 14 条点击连线全部保留。 - QuantUX 导出验证:`PASS`,运行时错误 `0`。 - App ID:`6a869fc2191a826306a7efb1` - 版本:`v2`(草稿,等待用户确认) - 在线原型:https://qux.ilapage.cn/#/apps/6a869fc2191a826306a7efb1.html - 确认人 / 确认时间:等待用户复核 生产代码仍未开始,等待用户确认 v2 原型。
Author
Owner

Stage A 原型 v2:修正审核入口

再次复查确认,先前提供的 qux.ilapage.cn 地址是需要该域名登录会话的私有编辑器入口,无登录浏览器只会看到 Login;这才是“不能查看”的直接原因,不是画布内容丢失。

已处理:

  • App 类型由误设的 web 修正为与项目既有原型一致的 prototype。
  • QuantUX 登录用户应使用项目此前一直使用的 IP 来源链接:http://124.222.27.183:8082/#/apps/6a869fc2191a826306a7efb1.html
  • 另导出无需 QuantUX 登录的离线审核文件:prototypes/50/v2/index.html。
  • 本地以 1366×768 视口真实渲染检查,页面完整显示。
  • MCP 校验 PASS,0 个运行时错误;导出文件不含 API Key。
  • Git 提交:39d38e1。

v2 仍为草稿,等待用户确认后才进入 Stage B。

## Stage A 原型 v2:修正审核入口 再次复查确认,先前提供的 `qux.ilapage.cn` 地址是需要该域名登录会话的私有编辑器入口,无登录浏览器只会看到 Login;这才是“不能查看”的直接原因,不是画布内容丢失。 已处理: - App 类型由误设的 `web` 修正为与项目既有原型一致的 `prototype`。 - QuantUX 登录用户应使用项目此前一直使用的 IP 来源链接:http://124.222.27.183:8082/#/apps/6a869fc2191a826306a7efb1.html - 另导出无需 QuantUX 登录的离线审核文件:`prototypes/50/v2/index.html`。 - 本地以 1366×768 视口真实渲染检查,页面完整显示。 - MCP 校验 `PASS`,0 个运行时错误;导出文件不含 API Key。 - Git 提交:`39d38e1`。 v2 仍为草稿,等待用户确认后才进入 Stage B。
Author
Owner

Stage A 原型已通过用户验收

  • 确认人:用户
  • 确认时间:2026-08-20(Asia/Shanghai)
  • 确认版本:QuantUX v2
  • App ID:6a869fc2191a826306a7efb1
  • 本地审核快照:prototypes/50/v2/index.html
  • 覆盖范围:异步导入弹窗、运行进度、同步记录列表、成功/失败/中断状态、详情店铺统计及权限边界。

原型双门禁已满足,开始 Stage B 实施;页面结构和主要流程不得脱离本确认版本。

## Stage A 原型已通过用户验收 - 确认人:用户 - 确认时间:2026-08-20(Asia/Shanghai) - 确认版本:QuantUX v2 - App ID:`6a869fc2191a826306a7efb1` - 本地审核快照:`prototypes/50/v2/index.html` - 覆盖范围:异步导入弹窗、运行进度、同步记录列表、成功/失败/中断状态、详情店铺统计及权限边界。 原型双门禁已满足,开始 Stage B 实施;页面结构和主要流程不得脱离本确认版本。
Author
Owner

#50 实施完成,等待验收

原型已由用户确认,Stage B 已实现并推送。

实现

  • POST /api/admin/v1/syb-products/import 改为创建持久化后台任务并立即返回 202 + runId;仅管理员可发起。
  • 新增同步记录分页列表和详情接口;所有已登录角色只读可见。
  • 新增 syb_sync_run 与版本迁移 1786701000000_syb_sync_run.go,保存状态、处理进度、统计、店铺快照哈希、店铺接受/跳过明细、错误摘要和操作人。
  • 保留内存锁,并用唯一 active_slot 约束跨进程单任务执行;服务启动时将遗留 running 标记为 interrupted。
  • 中途失败保留已写入数据,最终记录部分进度和可读错误;不保存 Cookie、验证码、原始响应或账号凭据。
  • SYB 商品页改为异步提交和轮询,可离开页面;新增“同步记录”列表与详情页。
  • 已按 Wiki-first 更新长期文档并同步核心 docs/ 镜像。

验证

  • go test ./...:通过。
  • go build:通过。
  • pnpm run lint:0 error(仓库既有 30 warnings)。
  • pnpm run build:prod:通过(仓库既有 CSS/chunk warnings)。
  • Playwright syb-sync-run.spec.ts:3/3 通过,覆盖异步返回与轮询完成、失败详情/店铺统计、采购员只读权限。
  • DevHarness check --strict:通过;5 份改动镜像与 Wiki 正文逐行一致。

提交与文档

  • 代码:badb78f feat(#50): run SYB imports asynchronously
  • 已确认原型:39d38e1 design(#50): add reviewable async import prototype
  • Wiki:c94df94 docs(#50): document asynchronous SYB imports

未执行

  • 未对本地真实 MySQL 执行新增迁移(数据库迁移属于高风险动作,需另行人工确认)。
  • 未调用真实 SYB 运行长耗时导入,也未做真实进程重启/多进程竞争验证;当前由 SQLite 服务测试、完整 Go 测试和模拟 API 的浏览器测试覆盖。

工单保持开启,等待用户验收。

## #50 实施完成,等待验收 原型已由用户确认,Stage B 已实现并推送。 ### 实现 - `POST /api/admin/v1/syb-products/import` 改为创建持久化后台任务并立即返回 `202 + runId`;仅管理员可发起。 - 新增同步记录分页列表和详情接口;所有已登录角色只读可见。 - 新增 `syb_sync_run` 与版本迁移 `1786701000000_syb_sync_run.go`,保存状态、处理进度、统计、店铺快照哈希、店铺接受/跳过明细、错误摘要和操作人。 - 保留内存锁,并用唯一 `active_slot` 约束跨进程单任务执行;服务启动时将遗留 `running` 标记为 `interrupted`。 - 中途失败保留已写入数据,最终记录部分进度和可读错误;不保存 Cookie、验证码、原始响应或账号凭据。 - SYB 商品页改为异步提交和轮询,可离开页面;新增“同步记录”列表与详情页。 - 已按 Wiki-first 更新长期文档并同步核心 `docs/` 镜像。 ### 验证 - `go test ./...`:通过。 - `go build`:通过。 - `pnpm run lint`:0 error(仓库既有 30 warnings)。 - `pnpm run build:prod`:通过(仓库既有 CSS/chunk warnings)。 - Playwright `syb-sync-run.spec.ts`:3/3 通过,覆盖异步返回与轮询完成、失败详情/店铺统计、采购员只读权限。 - DevHarness `check --strict`:通过;5 份改动镜像与 Wiki 正文逐行一致。 ### 提交与文档 - 代码:`badb78f feat(#50): run SYB imports asynchronously` - 已确认原型:`39d38e1 design(#50): add reviewable async import prototype` - Wiki:`c94df94 docs(#50): document asynchronous SYB imports` ### 未执行 - 未对本地真实 MySQL 执行新增迁移(数据库迁移属于高风险动作,需另行人工确认)。 - 未调用真实 SYB 运行长耗时导入,也未做真实进程重启/多进程竞争验证;当前由 SQLite 服务测试、完整 Go 测试和模拟 API 的浏览器测试覆盖。 工单保持开启,等待用户验收。
Author
Owner

??? 2026-08-20 ???????????:Task-50-SYB-async-sync;Wiki revision:a627417c412e44fbc6b0816f15395e131760522f??????

??? 2026-08-20 ???????????:Task-50-SYB-async-sync;Wiki revision:a627417c412e44fbc6b0816f15395e131760522f??????
ila closed this issue 2026-08-20 16:34:18 +08:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OPC/goauto#50