Commit Graph
66 Commits
Author SHA1 Message Date
chengma 546d9ef7e2 fix: 允许已采集 PDD 商品重新采集 (#121) 2026-08-10 19:38:40 +08:00
chengma 10bb892e6c feat: 移除 Client 真实采购手工授权 (#114) 2026-08-10 18:37:35 +08:00
chengma 13458038e0 fix: 隐藏未选采购规格行 (#113) 2026-08-10 18:17:41 +08:00
chengma a26219091a fix: 简化真实采购创建并恢复客户端选择 (#112) 2026-08-10 18:15:28 +08:00
chengma 66565d50cc feat: 采购价格仅展示选定规格 (#113) 2026-08-10 17:56:03 +08:00
chengma e1b32ea024 feat: 固定创建真实采购任务 (#112) 2026-08-10 17:52:48 +08:00
chengma 7106f6b024 feat: 顺运宝下一步支持单条建采购任务 (#106) 2026-08-10 16:34:55 +08:00
chengma fb2e4c4dab feat: 区分顺运宝标题与图片交互 (#105) 2026-08-10 16:33:42 +08:00
chengma 188725ff64 feat: 支持批量导入 PDD 商品链接 (#104) 2026-08-10 16:14:24 +08:00
chengma 3aaa40cfe0 feat: 增加真实采购任务安全模式 (#98) 2026-08-10 15:11:38 +08:00
chengma 775e38894e feat: 顺运宝支持输入 PDD 商品 ID (#95) 2026-08-10 14:26:03 +08:00
chengma 5a77bde534 feat: 增加规格匹配建议与决策审计 (#90) 2026-08-10 12:41:17 +08:00
chengma d1165b9e17 feat: 重构顺运宝规格主链路 (#88) 2026-08-10 12:24:23 +08:00
chengma f0715ee273 feat: 支持 MySQL 公网 TLS CA 校验 (#86) 2026-08-10 09:59:09 +08:00
chengma ddce0f6679 docs: 记录 MySQL 公网白名单规则 (#85) 2026-08-10 09:44:24 +08:00
chengma 04a777dc8e feat: 支持从 config.yaml 读取 MySQL 配置 (#83) 2026-08-10 09:24:04 +08:00
chengma 8cc8574e8a feat: 准备 Admin 线上 HTTPS 部署 (#81) 2026-08-10 02:46:59 +08:00
chengma c296feaf74 feat: 实现 SQLite 到 MySQL 单向迁移 (#80) 2026-08-10 02:40:14 +08:00
chengma 13cfced80d feat: 迁移 Admin Repository 到 MySQL 8 (#79) 2026-08-10 02:21:31 +08:00
chengma 7e74bf210d feat: 建立 MySQL 8 数据库基础 (#78) 2026-08-10 01:22:22 +08:00
chengma d99c81310d feat: PDD 批量采集支持可选客户端 (#74) 2026-08-10 00:49:51 +08:00
chengma 4433e3af37 feat: 完成规格映射与采购任务创建 (#69) 2026-08-09 22:55:14 +08:00
chengma 518e6df349 feat: 统一蝦皮与顺运宝 PDD 关联入口 (#68) 2026-08-09 22:40:58 +08:00
chengma 57a6484fd5 docs: 澄清顺运宝规格状态语义 (#67) 2026-08-09 22:34:22 +08:00
chengma c6ef54dce1 feat: 建立顺运宝采购处理状态 (#67) 2026-08-09 22:32:19 +08:00
chengma e9a190eed7 style: 右对齐 Admin 底栏提示 (#65) 2026-08-09 22:05:11 +08:00
chengma 155dd3b4a2 feat: 统一 Admin 主列表分页 (#64) 2026-08-09 21:51:20 +08:00
chengma d8a24247df fix: 同步记录弹窗改为局部刷新 (#62) 2026-08-09 21:02:28 +08:00
chengma 3fd863da64 feat: 优化顺运宝同步反馈与记录刷新 (#61) 2026-08-09 20:41:24 +08:00
chengma 919522ff79 fix: 修复顺运宝分页总数误判 (#60) 2026-08-09 19:01:04 +08:00
chengma 05a54b044d feat: 统一顺运宝日期同步并记录历史 (#59) 2026-08-09 18:22:58 +08:00
chengma 4b5cd13807 fix: 增强顺运宝同步完整性保护 (#58) 2026-08-09 17:17:42 +08:00
chengma 008bb87620 feat: 支持管理员自助修改密码 (#57) 2026-08-09 16:49:28 +08:00
chengma ff75b271ee fix: 防止拖选内容误关闭弹窗 (#56) 2026-08-09 16:31:41 +08:00
chengma 2cce9e3888 fix: 用户密码最小长度调整为六位 (#55) 2026-08-09 16:14:58 +08:00
chengma e0c0dac7a0 feat: 增加客户端采购员归属管理 (#54) 2026-08-09 16:02:25 +08:00
chengma 9959fac31a test: 验证网页登录不影响 Client 四接口 (#52) 2026-08-09 13:56:43 +08:00
chengma eea0d65ef8 feat: 实现 Admin 采购员账号管理 (#51) 2026-08-09 13:50:55 +08:00
chengma 0a7db404a1 feat: 实现 Admin 首次初始化与网页登录 (#50) 2026-08-09 13:36:41 +08:00
chengma a18d4e67ae feat: 顺运宝支持指定日期同步 (#53) 2026-08-09 13:24:02 +08:00
chengmaandClaude Opus 5 6e6e99a5d7 fix: 进入顺运宝页面不再自动弹验证码框 (#48)
配了 ocr_url 也会在进入页面时被拦一次输验证码。根因是 OCR 自动登录
只接在「点同步」那条路径上,而进入页面走的是另一条:
SybList → 本地会话过期 → NeedLogin=true → 模板直接弹手工输入框,
压根不调 OCR。

改成 NeedLogin 只由 login_reason 驱动——也就是操作员主动点过同步、
且自动登录确实失败时才弹。这时弹框是他预期的。

打开页面现在纯粹是看数据,不触发任何对外部系统的动作;真正会登录的
只有「同步」一个按钮。这与 Client 侧「『获取任务』是唯一会产生外部
后果的命令」是同一条原则。

会话无效时改成顶部一行提示,按 ocr_url 配没配分两种文案:配了却提示
"需要手工输入"会让人以为配置没生效;没配却提示"会自动登录",点下去
弹出验证码框会让人莫名其妙。用 .hint 不用 .missing——这是状态说明
不是错误,红色留给「PDD 链接未填写」那种要立刻处理的。

EnsureSybSession 未改动:它只读 SQLite 缓存、本地判过期,不发外部
请求,页面渲染不会因此变慢。审查时我一度说它每次渲染都探测顺运宝,
那是错的,已核实。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 12:48:21 +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
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
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
chengmaandClaude Opus 5 ccc146cfbe style: 蝦皮商品名称列再收窄到 18% (#41)
30% → 18%(初版的六成)。蝦皮标题普遍很长,实测最长 60+ 字且全是
关键词堆砌,不收窄的话这一列会把后面的颜色/尺码/SKU/待补几个数字
挤出屏幕——而那几个数字才是这个页面要看的东西。

数值只写在 app.css 一处,模板注释不再重复具体百分比,
免得下次改了数值忘了同步注释。title 属性保留,悬停看完整标题。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 11:16:44 +08:00
chengmaandClaude Opus 5 64d5e74d0d feat: 蝦皮数据页改为商品级列表与规格弹窗 (#41)
原来是 SKU 级一行,问题不在行数(6092→5195 只少 15%),在语义错了:
pdd_goods_url / pdd_goods_id 在 shopee_products 上,是商品级字段。
商品 24420648774 有 62 个 SKU,列表里 62 行显示同一个 PDD 链接,
将来双击任意一行编辑的也是同一个字段,操作员会以为改的是这一行。

加了两列,都是防止聚合后丢信息:

「SKU 数」—— 只显示颜色数和尺码数会严重误导。实测 602 个商品(11.6%)
的颜色数×尺码数大于实际 SKU 数,最悬殊的 26886533818 是 15×5=75 但
实际只有 28 个。蝦皮报表只含有销售成绩的 SKU,数据本来就不全。

「待补」—— 原来解析失败的行整行标黄,聚合成数量后这个信号会丢。
23 条失败分布在 6 个商品里,其中 4 个是「部分失败」:数字看着正常,
坏数据被吃掉,永远没人去补。现在待补>0 整行标黄。

颜色数/尺码数只统计 parse_ok=1,否则空值会被算进 DISTINCT 多出
一个「空颜色」。

弹窗只读,待补的行显示 spec_raw 原文——不显示的话人工不知道该填什么,
保留原文这个设计就白做了。复用 #18 的弹窗机制,app.js 未改动。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 11:12:25 +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
chengmaandClaude Opus 5 e29d6683ad feat: 蝦皮 Excel 报表导入 (#38)
蝦皮数据模块此前整个是骨架,四个入口全返回 501,excelize 连依赖都没装。
本工单做导入和列表,Save/Delete/Collect 保持 501。

真实样本实测:5195 个商品 / 6092 个 SKU,23 行解析失败。

upsert 白名单式,pdd_goods_url / pdd_goods_id 既不在 INSERT 列清单里
也不在 DO UPDATE SET 里。报表没有这两列,写进去就是写空值——操作员
攒几周的 PDD 链接会被一次导入洗光,而且不报错,等到建采购任务才发现。
有独立测试守着:往 DO UPDATE SET 里加回这一行,测试立刻变红。

按 sheet 名字取「最佳表現商品」,不用第 0 个。工作簿有 5 个 sheet,
另外 4 个是广告报表,连 商品規格ID 列都没有;蝦皮调顺序时按下标
会静默导入一张完全不相干的表。

按列名找索引。40 列里 32 列是统计指标,蝦皮加一列所有列号就错位,
而且不报错——会把「點擊率」当成「商品規格」存进去。

规格解析三种格式:含【】53.4%、逗号+空格 26.4%、只有逗号 20.2%,
只认【】会漏掉 46.6%。括号不配对(21 行)和右半整个被【】包住(2 行)
一律判整体失败,不逐段硬凑——错法不统一,针对每种错法写规则等于在猜,
而且没有人工核对过的正确答案可验证。失败时 color/size/advice 必须全空,
原文存进 spec_raw 交人工补。

不写 DELETE + INSERT 的全量替换,将来手动新增的 SKU 会被删掉。

excelize v2.9.1 的 go 指令正好是 1.23.0,与项目固定版本一致。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 09:57:12 +08:00
chengma c59b4b0d59 fix: 修复 PDD 页面误判与失败回执 (#35) 2026-08-07 17:58:30 +08:00