ParseSpec
顺运宝数据模块是骨架,「同步」按钮点了提示"同步功能待接入"。 5195 个蝦皮商品已经进系统了,但货运单(也就是真实订单)一条都没有, 后面的规格匹配和采购任务无从谈起。
做:
admin/config.yaml
syb_orders
不做(各自独立工单):
shopee_sku_id
SybCreateTask
[必须] 新增 v5,v1–v4 一个字节都不许改(#20 的规则,admin/AGENTS.md 已写死)。
[必须]
admin/AGENTS.md
-- ① 会话缓存。存 Cookie 就够——08 §3.1 已确认 JWT 不参与认证。 CREATE TABLE syb_session ( username TEXT PRIMARY KEY, cookies TEXT NOT NULL, -- JSON 数组 expires_at TEXT NOT NULL, -- min(JWT exp, 24h) updated_at TEXT NOT NULL ); -- ② 上次同步到哪 CREATE TABLE syb_sync_state ( id INTEGER PRIMARY KEY CHECK (id = 1), -- 只允许一行 last_synced_at TEXT, updated_at TEXT NOT NULL ); -- ③ 规格原文。匹配要用它,放列里才能查、才能在界面显示。 -- 对应 shopee_skus.spec_raw,两边同一个概念。 ALTER TABLE syb_orders ADD COLUMN product_spec TEXT;
[必须] CheckSchema 要覆盖新表和新列。
CheckSchema
[必须] #20 的三起点收敛测试要把 v5 覆盖进去。
[必须] created 的筛选粒度是日期(YYYY-MM-DD), 而 last_synced_at 是精确到秒的。
created
YYYY-MM-DD
last_synced_at
下次同步必须从「上次同步日期当天」重新拉,不是从第二天。
上次同步 2026-08-09 14:30 ✗ 下次从 2026-08-10 拉 → 漏掉 8-09 14:30 之后创建的单,永远补不回来 ✓ 下次从 2026-08-09 拉 → 重复拉当天已有的,靠 upsert 幂等
[必须] 宁可重复拉也不能漏。漏单意味着有订单永远不会被采购, 而且不会报错,没人会发现。
[必须] 首次同步用 config.yaml 的 sync_from。
config.yaml
sync_from
[必须] 结束日期用今天,不要用未来日期。
[必须] shopee_sku_id 绝不能被同步覆盖。
它是规格匹配的结果(人工确认或自动匹配产生),顺运宝那边根本没有这个值 (08 §6.2:顺运宝只给 11 位商品ID,蝦皮規格ID 是 12 位)。 同步时写进去就是写 NULL,把人工攒的匹配成果洗掉,而且不报错。
这和 #38 的 pdd_goods_url 是同一类问题,#38 那次有独立测试守着,这次也要有。
pdd_goods_url
[必须] 可以覆盖的:order_no / title / product_spec / quantity / price_twd_cent / image_url / syb_data / shopee_goods_id / updated_at。
order_no
title
product_spec
quantity
price_twd_cent
image_url
syb_data
shopee_goods_id
updated_at
[必须] shopee_goods_id 可以覆盖——它就是 details[].productId, 来自顺运宝,不是人工填的。
details[].productId
一行 syb_orders = 一个商品明细(details[] 的一项),不是一张货运单。
details[]
syb_id
details[].id
code
orderCode
details[].productTitle
details[].productSpec
details[].productQty
CHECK (quantity > 0)
details[].productPrice
{base_url}/api/p/file?id={productThumb}
productThumb
[必须] 金额取 detail/listByStock 的值,不要用列表接口的。 08 §5.1 实测:同一张单 amtOrder 在列表是 61200(分),在明细是 612.0(元), 但 escrowAmount 在列表是 505、明细也是 505.0——同一响应里两个金额字段单位不同。 明细接口的单位是统一的元,自己 ×100 转分。
detail/listByStock
amtOrder
61200
612.0
escrowAmount
505
505.0
[必须] productPrice 是浮点,×100 转整数时先四舍五入再转, 不要直接截断(239.0 * 100 在浮点下可能是 23899.999...)。
productPrice
239.0 * 100
23899.999...
[必须] quantity <= 0 的明细跳过并计数报告,不要硬塞——表上有 CHECK 会直接报错。
quantity <= 0
[建议] 收件人姓名/电话/地址(receiver / receiverTel / receiverAddr) 不入库。做采购决策用不到,属于个人信息(08 §5)。 syb_data 里如果带着,落库前剔掉。
[建议]
receiver
receiverTel
receiverAddr
[必须] Admin 要持有一个顺运宝 HTTP 客户端(含 Cookie Jar)。 验证码和登录必须走同一个 Cookie Jar,换客户端拿到的验证码对不上(08 §3.2)。
界面流程:
顺运宝数据页 → 点「同步」 ├─ 会话有效 ────────────────→ 直接开始同步 └─ 会话无效/过期 ──→ 弹出登录框 账号 <config.yaml 带出,只读> 验证码 [图片] [换一张] [____] [登录并同步]
[必须] 密码从 config.yaml 读,不在界面上显示,也不回显到 HTML。
[必须] 判断会话是否有效必须区分「明确未登录」和「网络故障」(08 §3.5): 超时/5xx/格式错要抛错,不能当成未登录。否则网络抖一下就弹验证码, 而且会把本来有效的会话丢掉。
[必须] 缓存有效期取 min(JWT 剩余, 24h)。08 §3.4 已确认没有滚动续期, 不要移植示例脚本里的 _capture_refreshed_token——那是死代码 (4 份 HAR 共 18 个响应,带那个头的 0 个)。
min(JWT 剩余, 24h)
_capture_refreshed_token
[必须] 缓存写失败不能让已登录的会话失效。缓存就是缓存。
① 算日期范围(上次同步日 → 今天) ② POST /am/stock/listTotal → 总数 ③ 总数 > config.max_matches → 报错,提示缩小范围,不要硬拉 ④ 按 page_size 翻页 POST /am/stock/list → 收集 stock 行和 id ⑤ 按 100 个一批 POST /am/stock/detail/listByStock ⑥ 展开 details[],逐条 upsert ⑦ 全部成功后才更新 last_synced_at
[必须] 第 ⑦ 步:中途失败不更新 last_synced_at。 更新了的话下次同步 会跳过这段区间,漏掉的单永远补不回来。
[必须] 整个同步不要放在一个大事务里——几千条明细的事务会长时间持锁。 按货运单为单位提交,失败了已成功的部分保留(下次重拉会 upsert 覆盖,幂等)。
[必须] 同步是长任务,不能阻塞 HTTP 请求线程直到结束。 [建议] 最简做法:同步接口立即返回,结果写进 syb_sync_state 或状态条, 页面刷新后显示。不要为此引入后台协程池——一次只允许一个同步在跑, 用一个互斥标志挡住重复点击即可。
syb_sync_state
[必须] 同步完成后状态条显示:
同步完成:日期范围 2026-08-09 ~ 2026-08-09,货运单 12 张,商品明细 27 条 (新增 20,更新 7,跳过 0)
[必须] 有跳过或失败时列出来,不要只给个数字。
[必须] 失败时说清下一步该干嘛:会话过期→重新登录;超过上限→缩小日期范围; 网络错→稍后重试。
[必须] 任何日志、错误信息、界面提示都不得出现密码。 日志可能被贴进工单排查问题。
[建议] 配置结构体的 String() 方法把密码打成 ****,从源头防住。
String()
****
admin/config/config.go
admin/config/config_test.go
admin/repository/db.go
admin/repository/migrate_test.go
admin/repository/syb.go
admin/syb/client.go
admin/syb/client_test.go
httptest
admin/service/syb.go
admin/service/syb_test.go
admin/handler/web/others.go
SybList
SybSync
admin/handler/web/web.go
admin/templates/syb/list.html
admin/go.mod
github.com/goccy/go-yaml
docs/admin/03-data-model.md
docs/admin/05-ui-specification.md
docs/admin/00-getting-started.md
[必须] 测试绝不能打真实的 shunyunbaoerp.com。用 httptest 起假服务端, 响应体照 08 文档里的形状造。打真站会污染对方数据、可能触发风控。
shunyunbaoerp.com
配置
迁移
syb_session
syb_orders.product_spec
会话
同步
max_matches
productPrice × 100
upsert
其他
SybMatch
GOTOOLCHAIN=go1.23.0
go vet
gofmt -l .
go test ./...
cd D:\chengma\cmautobuy\admin $env:GOTOOLCHAIN="go1.23.0" go vet ./...; gofmt -l .; go test ./... -count=1 Remove-Item Env:GOTOOLCHAIN
真实同步(需要真实账号,admin/config.yaml 已配好):
go run .
/syb
UPDATE syb_orders SET shopee_sku_id='TEST-SKU' WHERE syb_id='<任选>'
SELECT
TEST-SKU
[必须] 第 3 步和第 5 步的实际输出要贴出来。第 5 步是这张工单唯一 「错了要几周后才发现」的点——匹配成果被洗掉不会报错,等到建采购任务才发现。
回退:git revert。已迁到 v5 的库回退后会因「版本高于程序支持」拒绝启动, 这是 Migrate 已有的正确行为。同步进来的数据可以清表重来。
git revert
Migrate
用户已于 2026-08-09 明确通过验收,本工单验收完成。
5e426ca
d062d38
72821ea
docs/task/46-顺运宝货运单同步.md
go build ./...
go test ./... -count=1
go vet ./...
现关闭工单,并同步 #15、#14 的任务清单。
No dependencies set.
The note is not visible to the blocked user.
基本信息
ParseSpec可复用)、#20(迁移只追加)、#43(分页模式)要解决什么
顺运宝数据模块是骨架,「同步」按钮点了提示"同步功能待接入"。
5195 个蝦皮商品已经进系统了,但货运单(也就是真实订单)一条都没有,
后面的规格匹配和采购任务无从谈起。
做什么 / 不做什么
做:
admin/config.yaml读顺运宝配置syb_orders不做(各自独立工单):
shopee_sku_id留空)SybCreateTask保持 501)怎么做
迁移 v5(三条,只追加)
[必须]新增 v5,v1–v4 一个字节都不许改(#20 的规则,admin/AGENTS.md已写死)。[必须]CheckSchema要覆盖新表和新列。[必须]#20 的三起点收敛测试要把 v5 覆盖进去。增量边界 —— 本工单最容易写错的地方
[必须]created的筛选粒度是日期(YYYY-MM-DD),而
last_synced_at是精确到秒的。下次同步必须从「上次同步日期当天」重新拉,不是从第二天。
[必须]宁可重复拉也不能漏。漏单意味着有订单永远不会被采购,而且不会报错,没人会发现。
[必须]首次同步用config.yaml的sync_from。[必须]结束日期用今天,不要用未来日期。upsert 白名单 —— 第二个最容易出事的地方
[必须]shopee_sku_id绝不能被同步覆盖。它是规格匹配的结果(人工确认或自动匹配产生),顺运宝那边根本没有这个值
(08 §6.2:顺运宝只给 11 位商品ID,蝦皮規格ID 是 12 位)。
同步时写进去就是写 NULL,把人工攒的匹配成果洗掉,而且不报错。
这和 #38 的
pdd_goods_url是同一类问题,#38 那次有独立测试守着,这次也要有。[必须]可以覆盖的:order_no/title/product_spec/quantity/price_twd_cent/image_url/syb_data/shopee_goods_id/updated_at。[必须]shopee_goods_id可以覆盖——它就是details[].productId,来自顺运宝,不是人工填的。
字段映射
一行
syb_orders= 一个商品明细(details[]的一项),不是一张货运单。syb_iddetails[].idorder_nocodeorderCode,那个是空的titledetails[].productTitleproduct_specdetails[].productSpecshopee_goods_iddetails[].productIdshopee_sku_idquantitydetails[].productQtyCHECK (quantity > 0)price_twd_centdetails[].productPrice× 100image_url{base_url}/api/p/file?id={productThumb}productThumb是数字 ID 不是 URLsyb_data[必须]金额取detail/listByStock的值,不要用列表接口的。08 §5.1 实测:同一张单
amtOrder在列表是61200(分),在明细是612.0(元),但
escrowAmount在列表是505、明细也是505.0——同一响应里两个金额字段单位不同。明细接口的单位是统一的元,自己 ×100 转分。
[必须]productPrice是浮点,×100 转整数时先四舍五入再转,不要直接截断(
239.0 * 100在浮点下可能是23899.999...)。[必须]quantity <= 0的明细跳过并计数报告,不要硬塞——表上有 CHECK 会直接报错。[建议]收件人姓名/电话/地址(receiver/receiverTel/receiverAddr)不入库。做采购决策用不到,属于个人信息(08 §5)。
syb_data里如果带着,落库前剔掉。登录与会话
[必须]Admin 要持有一个顺运宝 HTTP 客户端(含 Cookie Jar)。验证码和登录必须走同一个 Cookie Jar,换客户端拿到的验证码对不上(08 §3.2)。
界面流程:
[必须]密码从config.yaml读,不在界面上显示,也不回显到 HTML。[必须]判断会话是否有效必须区分「明确未登录」和「网络故障」(08 §3.5):超时/5xx/格式错要抛错,不能当成未登录。否则网络抖一下就弹验证码,
而且会把本来有效的会话丢掉。
[必须]缓存有效期取min(JWT 剩余, 24h)。08 §3.4 已确认没有滚动续期,不要移植示例脚本里的
_capture_refreshed_token——那是死代码(4 份 HAR 共 18 个响应,带那个头的 0 个)。
[必须]缓存写失败不能让已登录的会话失效。缓存就是缓存。同步过程
[必须]第 ⑦ 步:中途失败不更新last_synced_at。 更新了的话下次同步会跳过这段区间,漏掉的单永远补不回来。
[必须]整个同步不要放在一个大事务里——几千条明细的事务会长时间持锁。按货运单为单位提交,失败了已成功的部分保留(下次重拉会 upsert 覆盖,幂等)。
[必须]同步是长任务,不能阻塞 HTTP 请求线程直到结束。[建议]最简做法:同步接口立即返回,结果写进syb_sync_state或状态条,页面刷新后显示。不要为此引入后台协程池——一次只允许一个同步在跑,
用一个互斥标志挡住重复点击即可。
报告要说清楚
[必须]同步完成后状态条显示:[必须]有跳过或失败时列出来,不要只给个数字。[必须]失败时说清下一步该干嘛:会话过期→重新登录;超过上限→缩小日期范围;网络错→稍后重试。
密码不许进日志
[必须]任何日志、错误信息、界面提示都不得出现密码。日志可能被贴进工单排查问题。
[建议]配置结构体的String()方法把密码打成****,从源头防住。预计修改文件
admin/config/config.goconfig.yaml;缺文件时给出「复制 example」的提示admin/config/config_test.goadmin/repository/db.goCheckSchema覆盖新表新列admin/repository/migrate_test.goadmin/repository/syb.gosyb_orders白名单 upsert、列表查询admin/syb/client.goadmin/syb/client_test.gohttptest起假服务端,不打真实站点admin/service/syb.goadmin/service/syb_test.goadmin/handler/web/others.goSybList/SybSync真实实现;新增登录与验证码路由admin/handler/web/web.goadmin/templates/syb/list.htmladmin/go.modgithub.com/goccy/go-yaml从 indirect 转 directdocs/admin/03-data-model.mdsyb_orders字段来源docs/admin/05-ui-specification.mddocs/admin/00-getting-started.md[必须]测试绝不能打真实的shunyunbaoerp.com。用httptest起假服务端,响应体照 08 文档里的形状造。打真站会污染对方数据、可能触发风控。
验收标准
配置
config.yaml时提示「复制 config.example.yaml 并填账号密码」,不是「读取失败」迁移
CheckSchema覆盖syb_session/syb_sync_state/syb_orders.product_spec会话
syb_session同步
sync_from,之后用last_synced_atlast_synced_at(有测试)max_matches→ 报错并提示缩小范围,不硬拉price_twd_cent=productPrice × 100,四舍五入image_url拼成{base_url}/api/p/file?id={productThumb}quantity <= 0的明细被跳过并计入报告upsert
shopee_sku_id同步后仍在(独立测试,这条最要紧)shopee_goods_id会被同步更新其他
httptest)SybCreateTask/SybMatch仍是 501GOTOOLCHAIN=go1.23.0下go vet/gofmt -l ./go test ./...全过怎么验证
真实同步(需要真实账号,
admin/config.yaml已配好):go run .→ 打开/syb→ 点「同步���→ 应弹登录框并显示验证码图片UPDATE syb_orders SET shopee_sku_id='TEST-SKU' WHERE syb_id='<任选>'SELECT出来确认shopee_sku_id还是TEST-SKU[必须]第 3 步和第 5 步的实际输出要贴出来。第 5 步是这张工单唯一「错了要几周后才发现」的点——匹配成果被洗掉不会报错,等到建采购任务才发现。
风险和回退
shopee_sku_idlast_synced_athttptest;已列为验收项回退:
git revert。已迁到 v5 的库回退后会因「版本高于程序支持」拒绝启动,这是
Migrate已有的正确行为。同步进来的数据可以清表重来。用户已于 2026-08-09 明确通过验收,本工单验收完成。
5e426cad062d38;归档格式修正72821eadocs/task/46-顺运宝货运单同步.mdgo build ./...、go test ./... -count=1、go vet ./...全部通过。现关闭工单,并同步 #15、#14 的任务清单。