Commit Graph
90 Commits
Author SHA1 Message Date
chengma cbc01f8b39 docs: 归档任务 #51 2026-08-09 13:51:34 +08:00
chengma 771e2ee616 docs: 归档任务 #50 2026-08-09 13:37:23 +08:00
chengma 0a7db404a1 feat: 实现 Admin 首次初始化与网页登录 (#50) 2026-08-09 13:36:41 +08:00
chengma 1064ffed59 docs: 归档任务 #53 2026-08-09 13:24:36 +08:00
chengma a18d4e67ae feat: 顺运宝支持指定日期同步 (#53) 2026-08-09 13:24:02 +08:00
chengma 24b6c742c1 docs: 归档任务 #49 2026-08-09 13:12:58 +08:00
chengma fc03388ed4 docs: 定义 Admin 登录与账号管理基线 (#49) 2026-08-09 13:12:03 +08:00
chengmaandClaude Opus 5 2e686b17a4 feat: 顺运宝登录接入验证码自动识别 (#47)
#46 的登录只有手工输验证码一条路,而会话 24 小时就过期——每天第一次
同步都得有人在场,将来也做不了定时同步。

docs/admin/08 §8 当时写死"不引入 OCR 服务",理由是"多一个必须先启动的
东西"。那条判断基于示例脚本里的 http://127.0.0.1:8000/ocr(本机服务)。
用户提供了托管地址后前提不成立,本工单推翻它——文档里改写并保留原文,
让后来人知道这个决定变过、为什么变。

OCR 优先、手工兜底:识别成功直接登录,失败或服务不可达降级到 #46 已有的
手工弹窗,并在弹窗里说明是"已尝试 N 次"还是"服务不可用"。手工路径不删,
外部服务挂了不该让整个同步功能不可用。

识别失败也是 code:200。实测拿无文字图片探测 https://ocr.ilapage.cn/ocr
返回 {"code":200,"message":"Success","data":""}——不是错误码。所以
Recognize 只负责"这次 HTTP 调用有没有问题",空 data 照常返回 (", nil),
业务校验交给调用方;空 data 和长度不对收敛到同一个 len(code) != 4,
一条规则覆盖两种情况。

不合格的验证码不拿去登录:白费一次尝试,且频繁错误登录可能触发风控。
审查时变异测试发现这条没有测试守着——原测试只断言"重新取图了"和
"最终登录成功",禁用长度校验后依然成立。已补 loginRecorder 记录每次
提交到 /am/auth/login 的 code,断言登录只被调用一次且提交的是合格的那个。

每次重试重新取图(同一张图再识别结果一样,且可能已被上次失败的登录作废);
OCR 用独立 HTTP 客户端不带顺运宝 Cookie;验证码图片只在内存里传,不落盘。

OCR 不可达立即降级、不占用重试次数——对着连不上的地址重试 5 次,
操作员要等 50 秒才看到手工输入框,结果注定一样。

测试全部用 httptest,不打真实的 ocr.ilapage.cn 和 shunyunbaoerp.com。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 12:35:02 +08:00
chengmaandClaude Opus 5 5e426cacf6 feat: 顺运宝货运单同步 (#46)
顺运宝模块此前是骨架,「同步」点了提示"待接入"。5195 个蝦皮商品已经
进系统,但货运单(真实订单)一条都没有,后面的规格匹配无从谈起。

按接口契约(docs/admin/08,从 4 份 HAR 还原)实现:配置、登录(界面
手工输验证码)、会话缓存到 SQLite、按日期范围增量同步、落 syb_orders。

shopee_sku_id 绝不被同步覆盖。它是规格匹配的结果,顺运宝那边根本没有
这个值(只给 11 位商品ID,蝦皮規格ID 是 12 位)。同步写进去就是写空,
把人工攒的匹配成果洗掉且不报错。它只出现在 INSERT 列清单里,不在
DO UPDATE SET 里;repository 层和 service 端到端各有一个测试守着。

增量从「上次同步日期当天」重拉,不是第二天。created 筛选粒度是日期而
last_synced_at 精确到秒,从第二天拉会漏掉当天晚些时候创建的单且不报错。
宁可重复拉(upsert 幂等)也不能漏。中途失败不更新 last_synced_at,
否则下次跳过这段区间,漏的单永远补不回来。

日期运算用 UTC+8,不是 UTC。审查时从 HAR 确认 created 是当地时间:
抓包于 2026-07-28T03:31:45Z(= 11:31 UTC+8),同一响应里 created 是
"2026-07-28 10:37:59";若它是 UTC 则等于 18:37 UTC+8,比抓包晚 7 小时,
订单创建于未来,不成立。用 UTC 算会在本地 00:00-08:00 把"今天"算成昨天,
当天早晨的单这轮拉不到。用 time.FixedZone 写死,不用 LoadLocation——
那要读系统 tzdata,Windows 默认没有,打包成 exe 会失败。

金额一律取 detail/listByStock 的值:08 §5.1 实测同一响应里 amtOrder
在列表接口是分、escrowAmount 却不是,单位不统一,取错差 100 倍。

迁移 v5 纯追加(syb_session、syb_sync_state、syb_orders.product_spec),
v1-v4 逐字未动,CheckSchema 覆盖新表新列。

会话有效性判断把「网络故障」和「明确未登录」的分类集中在 Client.do()
一处——网络抖一下就判定登出的话,验证码会弹个不停,还会丢掉有效会话。

测试全部用 httptest 假服务端,不打真实站点。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 11:49:13 +08:00
chengma 7fb137f685 docs: 归档任务 #45 2026-08-09 11:12:51 +08:00
chengmaandClaude Opus 5 512ccf34d6 docs: 新增顺运宝接口契约;config.yaml 加入 gitignore
从 4 份 HAR 抓包和示例脚本还原 docs/admin/08-顺运宝接口.md。
全部结论标了出处,只有单一样本支撑的都标了 [待定]。

抓包验出三件和现有假设不符的事:

1. 登录响应的 JWT 从不参与请求,认证全靠 Cookie。示例脚本里
   self.token 只用于算缓存有效期,没进过任何请求头。Go 侧存 Cookie
   即可,token 都不用存。

2. _capture_refreshed_token 是死代码——它从响应头 X-Requested-With
   读刷新后的 JWT,而 4 份 HAR 共 18 个响应里带该头的是 0 个。
   会话就是 24 小时硬上限,没有滚动续期,不要移植这段逻辑。

3. 金额单位在同一个响应里不统一:amtOrder 在列表接口是分(61200
   对应 612.0),escrowAmount 却不是(505 对应 505.0)。不能假设
   "列表接口的金额都是分",逐字段确认。这条只有一个样本,已标 [待定]。

还推翻了「货运单规格能直接对上蝦皮商品規格ID」这个前提:顺运宝给的
productId 是 11 位商品ID,蝦皮規格ID 是 12 位。但 productSpec 的格式
与蝦皮报表完全一致,可直接复用 #38 的 ParseSpec,匹配走
"productId 定位商品 → 解析规格 → 在该商品的 SKU 里比对"。

admin/config.yaml 含明文密码且此前没有任何 gitignore 规则挡它,
一次目录级 git add 就会进历史。已加规则,并补 config.example.yaml
作为模板(不含真实凭据,进 git)。

已确认历史提交中从未出现过该凭据。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 10:59:00 +08:00
chengma 5fd71981e0 feat: 支持持续自动获取任务 (#45) 2026-08-09 10:54:18 +08:00
chengmaandClaude Opus 5 4022feee43 docs: 记录 #31 #32 验收通过
两份归档都由实现方写好,本次只更新状态。

#31 遵守了 #20 立的「迁移只追加」规则:v4 用 ALTER TABLE 加 shop_name,
v1/v2 原文未动,CheckSchema 也覆盖了新列。

#32 的 skus 是颜色×尺码的笛卡尔积并带 price_observed_at,
与 #31 的 price_granularity 对得上——两侧独立得出同一结论。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 10:19:11 +08:00
chengma cbb03faac8 docs: 归档任务 #44 2026-08-09 10:12:56 +08:00
chengmaandClaude Opus 5 6d40c1e986 docs: 归档 #41 #43,记录 #38 #41 #43 验收通过
#41 归档记录了两条防信息丢失的设计(SKU 数列、待补列),以及最危险的
「部分失败」场景实测:28431952912 颜色4/尺码5 数字看着正常,
里面有 4 个 SKU 解析失败,靠整行标黄才藏不住。

#43 归档记录了实现踩到的 html/template URL 上下文转义坑:
夹在字面量 & 中间的动态内容会被整体当成一个参数值转义,
?/= 变成 %3F/%3D 让链接失效,只有真跑起来看 HTML 才发现。

也记了架构角色第二个变异第一次打偏、显示「跳过」的事——
探针无效和代码有问题是两回事,#38 刚犯过一次。

三份都如实列了「未验证到的部分」,主要是浏览器实机交互和
列宽百分比在 table-cell 上的实际行为。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 10:06:42 +08:00
chengmaandClaude Opus 5 edfe39cc35 feat: 蝦皮数据页分页与状态筛选 (#43)
导入真实样本后 /shopee 一次吐 3.4MB / 5195 行。但只加分页会把问题从
「5195 行糊在一起」变成「260 页里藏着 6 个」——那 6 个待补规格的商品
仍然找不到。所以分页和状态筛选一起做。

HTML 3.4MB → 16.7KB。

状态条显示全量而不是本页:「共 5195 个商品 · 第 1/260 页」。
显示「共 20 个商品」会让操作员以为总共就 20 个。筛选后显示筛选结果
总数:「待补规格:6 个商品」。

列表查询和 COUNT 共用同一套筛选条件拼装。分开写两份 WHERE,迟早
有天忘了给 COUNT 也加条件,页码算错而且没人发现(#19 踩过一次)。

page 越界兜到最后一页而不是显示空表格——空表格会让操作员以为数据没了。
总数为 0 时显示「第 1/1 页」,不出现「第 1/0 页」。

「待补规格」用 EXISTS 不用 JOIN+DISTINCT:一个商品有多个失败 SKU 时
JOIN 会出重复行,DISTINCT 又让 LIMIT/OFFSET 的行为难推理。

分页控件是 <a href> 纯 GET,浏览器前进后退和书签都正常。首末页用
<span class="disabled"> 禁用,语义上不再是链接,不只靠颜色区分。

这是全项目第一个分页页面,通用逻辑单独放 service/pagination.go 供
后面四页复用,规则写进 05 §3.2 而不是蝦皮页那一节(#34 踩过这个错)。
05 §3 的每页条数从「建议 50」改为「统一 20」并写明理由。

实现踩到 html/template 的 URL 上下文转义:夹在字面量 & 中间的动态内容
会被整体当成一个参数值转义,?/= 变成 %3F/%3D 让链接失效。改为在 Go 里
把整段 URL 拼好,模板作为单个 pipeline 输出,并加了回归测试。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 11:40:07 +08:00
chengma 033bef0614 docs: 归档任务 #42 2026-08-08 11:25:41 +08:00
chengma 5aaf0a937e fix: 允许颜色缺价后继续采集 (#42) 2026-08-08 11:25:06 +08:00
chengma d199867101 docs: 归档任务 #40 2026-08-08 10:30:49 +08:00
chengma d8505a4b2b docs: 更新任务 #39 验收修正 2026-08-08 10:30:49 +08:00
chengma 9dadac2bff feat: 滚动采集评价数量和店铺名 (#40) 2026-08-08 10:30:04 +08:00
chengma 2f83be5e40 fix: 中文显示任务当前步骤 (#39) 2026-08-08 10:29:59 +08:00
chengmaandClaude Opus 5 10b724f62a docs: 归档 #38;改正六份归档里未经验证的 Go 版本说法
#38 归档记录了打回一次的经过(ParseSpec 两个失败分支无测试覆盖,
其中 size=="" 就是真实数据里 2 行走的分支),以及架构角色第一轮
变异打在死分支上、差点误报「测试没牙」的过程。

改正:/usr/local/go 从 2026-07-02 起一直是 1.26.5,而六份归档都写着
「架构角色亲自执行(Go 1.23.0)」——那是照项目固定版本抄的,
没有实际验证过工具链,违反 CLAUDE.md §9「说验证过必须真的跑过」。
措辞改为不宣称具体版本。已用 GOTOOLCHAIN=go1.23.0 补验,结论不变。

admin/AGENTS.md 加两条 [必须]:
- 交付前至少跑一次带 GOTOOLCHAIN=go1.23.0 的验证。开发机装的可能更新,
  Go 会默默用它编译,测过的不是要交付的那个版本。新加依赖时尤其要跑。
- 改数据库时不要只测全新库,先列出现实中存在哪些 schema 状态再一个个验(来自 #20)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 09:59:34 +08:00
chengma 18770f25e1 docs: 归档任务 #39 2026-08-08 09:47:49 +08:00
chengma cb323d37ba feat: 实现 PDD 任务详情窗口 (#39) 2026-08-08 09:46:58 +08:00
chengma 6c12e058c3 docs: 归档任务 #37 2026-08-08 09:26:21 +08:00
chengma 9562da9aad fix: 蛇形遍历颜色并逐色采价 (#37) 2026-08-08 09:25:40 +08:00
chengma e08fd89c91 docs: 归档任务 #36 2026-08-07 18:14:28 +08:00
chengma e65cc2afda fix: 商品页就绪后立即进入规格面板 (#36) 2026-08-07 18:13:10 +08:00
chengma 09b27cec42 docs: 归档任务 #35 2026-08-07 17:59:20 +08:00
chengma c59b4b0d59 fix: 修复 PDD 页面误判与失败回执 (#35) 2026-08-07 17:58:30 +08:00
chengma 51c2292be1 docs: 归档任务 #32 2026-08-07 17:39:16 +08:00
chengma 2d1fdbea3f feat: 执行并提交 PDD 采集任务 (#32) 2026-08-07 17:38:29 +08:00
chengmaandClaude Opus 5 34b4b54b63 docs: 归档任务 #34,记录验收通过
记录两处主动判断及依据:缩短 /tasks 的 placeholder(工单 [注意] 里
预先写明"真装不下就缩文案,不要把宽度调回去");把搜索框宽度规则从
§5.1 提到 §3.1 通用规则(原来写在 PDD 页那一节,另外两页看不到)。

未验证:浏览器实机目视、主题与缩放。用户已实机确认。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:23:49 +08:00
chengmaandClaude Opus 5 3fdab7b2a8 docs: 记录 #16 #17 #19 #20 #24 验收通过
补上 #20 和 #24 的归档(此前只提交了代码),并把 #16 #17 #19
的状态改为验收通过。

#20 归档记录了工单中途被修改的经过:原工单写死「user_version=2 ⇒ 老结构」,
用户实测炸出 table already exists 后才发现 #16 的原地改写让这个版本号
对应两种结构,路径本来就是两条。按 CLAUDE.md §7.5 改工单而不是让实现硬凑。
也记录了变异测试第一轮漏掉两个的原因——架构角色的设计让收敛测试变成了
近乎同义反复。

#24 归档记录了四个变异全部被抓到,其中「破坏原子性判断」那个最关键:
加超时分支最容易顺手把原子抢占破坏掉。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:18:32 +08:00
chengma 61d8d1b263 docs: 归档任务 #31 2026-08-07 17:14:12 +08:00
chengma 93beef8b36 feat: 保存并展示 PDD 店铺与价格采样信息 (#31) 2026-08-07 17:13:25 +08:00
chengma a2fc1501c4 docs: 归档任务 #30 2026-08-07 16:43:49 +08:00
chengma 51062f4c7b feat: 领取 Admin 采集任务并保存本地 (#30) 2026-08-07 16:42:31 +08:00
chengma 107366f1f0 docs: 补充任务 #28 实现提交 2026-08-07 16:14:59 +08:00
chengma 77ec0d997b docs: 补充任务 #28 链接校验记录 2026-08-07 16:14:44 +08:00
chengma 0044442f5d docs: 归档任务 #28 2026-08-07 16:13:48 +08:00
chengma d2decc7a29 docs: 记录任务 #29 验收结果 2026-08-07 15:57:57 +08:00
chengma 52f11eecdc docs: 归档任务 #29 2026-08-07 15:55:18 +08:00
chengma a6e3fe0417 docs: 完成 Android 设备工单验收 (#21 #22 #25 #26 #27) 2026-08-07 15:20:48 +08:00
chengmaandClaude Opus 5 fbfa2fce3d docs: 归档任务 #19
如实记录打回两次的过程,以及两次都是架构角色的检查指令给窄了:
第一次给的 grep 只覆盖「四个模块」,漏掉「四个页面」这个变体;
第二次改成直接给 6 处确切行号,并点明哪两处的「四个」是对的不许改。

未验证四项:浏览器实机交互、主题与缩放、采购任务真实创建路径
(入口尚未实现,验证用的是手工插库)、目标列规格顺序与工单示意图不同。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 15:17:32 +08:00
chengmaandClaude Opus 5 f0d7da37cf feat: 采集采购页,采集与采购统一展示 (#19)
原来的采购任务页是骨架:var rows []gin.H 从不查库,页面永远为空;
TODO 写的还是只查 task_type = 'purchase'。所以 #18 建出来的采集任务
在界面上哪儿都看不到——用户点完「创建采集任务」只能盯着
collect_status 猜。

模块名定为「采集采购」(用户指定),直接点出这页装的是哪两类任务,
比泛称「任务」更能让人一眼知道点进去看什么。路由 /tasks 不变,
改路由会让已有书签和文档链接全失效,没有收益。

列不按类型并列——两种任务字段完全不同,并列会让采集任务行一半是空列。
改成固定列 + 一列「目标」把业务信息概括成一句话:
  采集  PDD 737116531267
  采购  SO-001 · M/黑色 · 2件 · ≤¥42.00
拼接逻辑在 service 层,模板只负责显示。

统计和列表共用同一个筛选条件拼装函数。分开写的话总有一天会忘了
给统计也加条件,数字和表格对不上,操作员会以为页面坏了。

无主任务的客户端列显示「—」。#17 之后采集任务默认无主,这列会大量为空。

详情弹窗只读,采购专有字段(数量、价格上限、目标规格)在采集任务里
整段不出现,不显示空行。复用 #18 的弹窗机制,app.js 无需改动。

顺带清掉 PDD 页加进来之后一直没跟上的模块计数:多处「四个模块/四个页面」
改成五个。其中 06-quality-security.md 那两处是验证清单,
照着做的人只会测四个页面,PDD 页永远不在回归范围里。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 15:16:05 +08:00
chengma c07fe56ac3 docs: 归档任务 #27 2026-08-07 15:13:07 +08:00
chengma ffaeb55a25 docs: 归档任务 #26 2026-08-07 14:53:57 +08:00
chengmaandClaude Opus 5 e40ce5a13c fix: 商品卡在「采集中」无法恢复 (#24)
MarkCollecting 原本只在 pending/failed 时成功,而全库只有客户端提交结果
才会把 collecting 改走。客户端离线、崩溃、任务被删都是常态——一旦发生,
这个商品就永久报废,界面上没有任何入口能救,只能改数据库。

改成 collecting 超过 15 分钟视为已超时,允许重新创建采集任务。

判定在读取那一刻现算,仍是同一条原子 UPDATE,不加后台清理协程:
后台扫要处理"扫到一半客户端正好提交了"的竞态,本项目已经在并发上
栽过两次,能不引入并发就不引入。

15 分钟写成有名字的常量并注明理由:采集本身几十秒到两分钟,加上排队
等客户端来领。宁可短也不要长——采集是只读的,多采一次没有副作用,
而卡死的代价是商品永久报废。

已知取舍:超时后老任务还在队列里,客户端上线可能两个都领走、采两次。
可接受(只读,后一次覆盖前一次),已写进代码注释和 03-data-model.md,
免得后来人当成 bug 去"修"成加锁或加租约——租约和心跳是被明确移除的设计。

时间比较用字符串,依赖 NowISO 产出定宽 UTC。已在 NowISO 上加注释:
改成带时区偏移的本地时间会让这个比较静默失效,不报错但判断全错。

界面两处:超时的显示「采集中(超时)」,否则操作员盯着「采集中」
不知道它已经死了;跳过原因按正在采集/已采集/已删除分类计数,
一个都没建成时告诉操作员还要等多久。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 14:42:54 +08:00