配置来源收敛 - 系列平台编码 tm/jd/dy/jd_self 与 9 类飞书地址改为整系列并集去重后挂锚点, 子款编码保留在 series.platform_ids 备查;多表冲突保留最高优先级并告警 - 写表脚本(当日/天猫主图/京东主图)去掉本地 JSON 回退,地址缺失或读取失败 直接 fail closed,避免库与表地址不一致被静默掩盖 - 测试注入 records_provider 时禁用配置缓存读写,防止假数据写入生产快照 采集开关(dim_style.collect_enabled) - 锚点为系列总开关:false 则整系列不采 - 锚点为 true 时,子款自身 false 仅把自己的 ERP 编码排除出聚合,其余子款照常 - 排除结果记录在 series.erp_excluded 并在运行时提示 聚水潭全店铺日报防错 - 列布局 fail closed、行 td 数校验、满页判定 + 总页数兜底、值域交叉校验 - 新增单日总量闸:超近 14 天中位数 8 倍拒绝落库 其他 - 新增电商费用归集配置 ad_cost_rollup.json 与积分兑换周报采集 - 店铺情报、供应链到货同步、通知路由与相关配置/文档同步更新 - 忽略本地 dsh workbench 脚手架(含 node_modules)
408 lines
31 KiB
Markdown
408 lines
31 KiB
Markdown
# GYXX Flow 运维手册
|
||
|
||
## 日常检查
|
||
|
||
```bash
|
||
uv run gyxx doctor --json
|
||
uv run gyxx schedule status
|
||
uv run gyxx list
|
||
lark-cli --profile hermes-analyzer auth status --json --verify
|
||
lark-cli --profile hermes-analyzer auth check --scope "im:message.send_as_user im:message" --json
|
||
```
|
||
|
||
两条 lark-cli 检查必须在实际运行调度服务的系统账户下执行。业务消息统一由该 profile 的
|
||
user 身份投递;只有日报卡片的图片预上传因接口限制使用同 profile 的 bot 身份。不要用
|
||
交互登录账户的授权结果代替 NSSM/systemd 服务账户验证。
|
||
|
||
Linux 服务同时检查:
|
||
|
||
```bash
|
||
systemctl status gyxx-flow.service
|
||
journalctl -u gyxx-flow.service --since today
|
||
```
|
||
|
||
工作流失败时,使用 `workflow_id`、业务日期和 `run_id` 对照 `<GYXX_DATA_ROOT>/logs`、运行记录、节点产物和副作用账本。
|
||
|
||
## 手工执行
|
||
|
||
```bash
|
||
uv run gyxx run <workflow_id> --date 2026-08-01
|
||
uv run gyxx run <workflow_id> --date 2026-08-01 --execute
|
||
uv run gyxx scripts list --json
|
||
uv run gyxx scripts run <command_id> --date 2026-08-01
|
||
uv run gyxx scripts run <command_id> --date 2026-08-01 --execute
|
||
```
|
||
|
||
`gyxx run` 接受 23 个调度工作流和已注册的手动工作流;补采和维护操作使用 `gyxx scripts run`。两类入口都默认 dry-run,只有业务日期、凭据、浏览器登录态、PostgreSQL、lark-cli user 身份,以及工作流确实需要大模型时对应的 Hermes 角色全部确认后才使用 `--execute`。
|
||
|
||
| 手动场景 | 命令 ID | 日期行为 |
|
||
|---|---|---|
|
||
| 内容映射重建 | `content.mapping.rebuild` | 业务日期只用于运行审计 |
|
||
| 内容失败任务重试 | `content.failed.retry` | 业务日期只用于运行审计 |
|
||
| 内容日报重跑 | `gyxx backfill content.metrics.daily` | 使用 `--from/--to` 指定日期范围 |
|
||
| 京东自营品牌单日回采 | `shop.jd_self_operated.collect_brand` | `--date` 自动成为起止日期 |
|
||
| 商品历史补采 | `product.backfill.run` | `--date` 自动成为 `--from/--to` |
|
||
| 商品评价补采 | `product.review.orchestrate` | `--date` 自动成为目标日期 |
|
||
| 天猫旗舰店和箱包店视频上传 | `product.video_upload.run` | 业务日期用于运行审计;默认顺序扫描两店,由 PostgreSQL 附件级账本防重 |
|
||
| 京东旗舰店视频上传 | `product.jd_video_upload.run` | 业务日期只用于运行审计;从固定凌云air记录锚点按飞书视图顺序扫描,日期可为空 |
|
||
| 采购单更新 | `supply.workflow.run` | 自动选择 `mcp-run purchase-order-update` |
|
||
|
||
命令的额外脚本参数使用可重复的 `--arg=<值>` 传入。`supply.workflow.run --execute` 会进入真实 ERP 修改链路,必须在任务文件、目标范围和回滚条件全部复核后执行。
|
||
|
||
### 天猫视频上传工作流
|
||
|
||
天猫视频上传注册为幂等定时工作流 `product.video_upload`,按 `config/schedules.json` 以 2026-09-04 为起始日每 3 天 14:00(Asia/Shanghai)执行;前端仍可手动选择“光影行星旗舰店 / 光影行星鑫华达专卖店:龙虾仔”并立即运行,也可填写天猫话题完整名称或关键词。正式执行只处理所选店铺,话题搜索结果按包含关系选中。默认 dry-run 只生成运行记录并跳过浏览器节点;确认飞书待传记录、对应淘宝光合店铺的登录态和 PostgreSQL 后,再显式正式执行:
|
||
|
||
```bash
|
||
uv run gyxx run product.video_upload --date 2026-08-07
|
||
uv run gyxx run product.video_upload --date 2026-08-07 --execute
|
||
```
|
||
|
||
前端把店铺选择作为受限运行参数传给脚本,只允许 `flagship` 或 `luggage`。脚本先读取飞书并应用所选店铺路由,再写入/读取附件级账本;只有存在可领取的未上传附件时才启动有头 Chrome、登录对应账号并上传。旗舰店只选择“天猫上传”未勾选且有附件的光影行星男/女记录;箱包店只选择女性、全部款式命中箱包店白名单且有附件的记录。两店分别使用 `GUANGHE_USERNAME` / `GUANGHE_PASSWORD` 和 `GUANGHE_LUGGAGE_USERNAME` / `GUANGHE_LUGGAGE_PASSWORD`,箱包店当前账号为 `光影行星鑫华达专卖店:龙虾仔`,密码配置保持不变,并保留独立浏览器 Profile。发生登录或页面错误时保留窗口供人工定位,排查完成后手动关闭。
|
||
|
||
每次正式扫描都会把视频附件写入 PostgreSQL 附件级发布账本,防重身份由视频 `file_token + 目标账号 + 飞书记录` 构成。账本状态为 `published` 的附件在下次执行时自动跳过,因此同一业务日期可以安全重跑并发现新增附件。旗舰店仍只在一条飞书记录的全部附件发布成功后回写“天猫上传”;箱包店不借用该勾选状态,是否已上传完全以自己的目标账号账本为准。
|
||
|
||
首次正式执行会读取 `config/tmall-video-publication-backfill.json`,将 2026-07-26 箱包店旧日志中可按 `record_id + file_token + 文件名` 再次核验的 25 个明确成功附件回填为 `published`。配置与当前飞书任一身份不一致都会停止,不会静默猜测。该回填不依赖部署机保留旧 `var/` 日志文件;证据路径与成功行号已固化在配置中供审计。
|
||
|
||
只有淘宝光合页面出现明确成功回执后才把附件记为 `published`。点击发布后超时、浏览器中断或回执无法确认时,必须记为 `ambiguous` 并停止自动重发;先在对应店铺的作品管理中按账号、附件和飞书记录人工核对,再做审计式对账,不能通过更换业务日期或重复运行绕过。
|
||
|
||
单条续跑或指定附件使用公开命令,并通过可重复的 `--arg` 传参。下面的命令会停在正式发布前,核对无误后才能去掉 `--stop-before-publish`:
|
||
|
||
```bash
|
||
uv run gyxx scripts run product.video_upload.run --date 2026-08-07 --arg=--record-id --arg=RECORD_ID --arg=--stop-before-publish --execute
|
||
```
|
||
|
||
### 京东视频上传工作流
|
||
|
||
京东视频上传注册为幂等定时工作流 `product.jd_video_upload`,按 `config/schedules.json` 以 2026-09-04 为起始日每 3 天 14:00(Asia/Shanghai)执行;前端仍可手动立即运行,同一业务日期可以重复执行。每次都从飞书视图中的固定凌云air记录 `recvrXTRWWFXeJ`(含)扫描到视图末尾,不依赖“日期”字段。真正的防重键是 PostgreSQL 中的 `视频 file_token + 目标账号 + 飞书记录`:
|
||
|
||
```bash
|
||
uv run gyxx run product.jd_video_upload --date 2026-08-07
|
||
uv run gyxx run product.jd_video_upload --date 2026-08-07 --execute
|
||
```
|
||
|
||
框架 dry-run 不启动子进程,不下载附件、不写数据库也不打开京东。前端控制台点击“京东视频上传”会重新扫描锚点之后的视图后缀:新建的记录即使日期为空也能被发现;既有后缀记录后来新增第二个视频附件时,新 `file_token` 也会形成独立任务。已入账的同一 `record_id + file_token` 不会重复发布。锚点丢失或重复时脚本拒绝退化为全表扫描,避免误发历史内容。组合款在下载素材前直接跳过。正式执行按视图顺序逐条处理:有图片时设置飞书封面;没有图片时保留京东从视频生成的默认封面;生成 5-27 字标题、关联同款同色商品、选择相关话题和标签。只有页面出现明确成功回执后才把账本写为 `published`;点击发布后超时或浏览器中断会写为 `ambiguous` 并停止,必须先在京东内容管理中人工核对,不能自动重发。
|
||
|
||
商品关联只按页面商品标题中的款式名做受控模糊匹配,再由直连多模态大模型判断包体颜色;价格和 SPU 不参与查询或勾选。同款同色商品可多选,整个表单最多 10 个。SPU 仅作为可选审计信息;组合款视频当前主动跳过。
|
||
|
||
首次登录或遇到验证码时,使用公开命令限定一条记录并停在发布按钮前:
|
||
|
||
```bash
|
||
uv run gyxx scripts run product.jd_video_upload.run --date 2026-08-07 --arg=--record-id --arg=RECORD_ID --arg=--manual-login --arg=--stop-before-publish --execute
|
||
```
|
||
|
||
首次部署或多京东账号部署必须显式配置稳定且不可变的 `JD_VIDEO_ACCOUNT_KEY`;已有单账号账本时,前端运行会从 JD 渠道唯一账号 cohort 自动解析,数据库不保存登录账号明文。浏览器由 Scrapling 隐身会话启动真实 Chrome,默认使用该命令独立的 Profile、Cookie 和 Storage State,不能改成其他京东采集任务的共享目录。需要复用一个已启动且已验证的京麦 Chrome 会话时,使用运行时绑定注入的 `GYXX_BROWSER_CDP_URL`;脚本只借用唯一的现有 context,并在退出时保留原浏览器及其页面。
|
||
|
||
### 主图周工作流
|
||
|
||
主图调度的唯一工作流 ID 是 `product.main_image.weekly`,每周日 08:30 启动。图同时运行 JD、TM 两个无依赖分支;一个平台失败不会取消、跳过或回滚另一个平台的采集、PG upsert 和飞书插入。两个分支全部结束后再汇总工作流状态:任一平台失败时整体状态为失败,并在错误摘要中标明具体平台和错误,另一平台已经成功的结果继续保留。
|
||
|
||
两个平台默认都在采集后执行飞书主图表插入和本地 PostgreSQL `main_image_creatives` upsert。执行前必须同时确认两套浏览器登录态、飞书身份和本地数据库;排障时分别检查 `jd`、`tmall` 节点以及对应插入脚本日志,不能只以采集文件存在判断双 sink 已完成。
|
||
|
||
### 三平台电商费用日报
|
||
|
||
唯一工作流 ID 是 `product.ecommerce_costs.daily`,每天 10:00 由项目内 Python 调度器运行,
|
||
业务日期为前一天;天猫、京东、抖音三个下载分支并行启动,各自完成下载后再导入。天猫流程先进入万相台商品报表选择昨日,提交下载任务;任务在下载列表显示“生成成功”
|
||
后下载 ZIP、做路径安全校验并解压 CSV,随后将商品级广告指标幂等写入
|
||
`product_daily_metrics.raw_data.tm_ad_metrics`。商品款式先按 `dim_style.tm_spus` 的商品 ID 匹配,
|
||
匹配不到再按商品名称做精确包含匹配,并在节点内保留 `style_name` 与
|
||
`style_match_method`(`product_id`/`product_name`/`ambiguous`/`unmatched`)。它不会覆盖同一商品日期
|
||
已有的访客、成交等经营日报字段;data-hub 产品生命进程从该节点读取广告成交金额和花费并计算投产比、电商费比。
|
||
|
||
正式服务必须注入 `WANXIANG_ACCOUNT`、`WANXIANG_PASSWORD`、`GYXX_DATA_ROOT` 和
|
||
`GYXX_POSTGRES_DSN`。默认浏览器 Profile 是
|
||
`<GYXX_DATA_ROOT>\state\browser-profiles\wanxiang-ads`,登录失效时不能在另一个 Profile 中登录后
|
||
期待服务自动获得状态。
|
||
|
||
手工验证或补采某一天时,先 dry-run,再显式执行完整工作流:
|
||
|
||
```powershell
|
||
uv run gyxx run product.ecommerce_costs.daily --date 2026-08-17
|
||
uv run gyxx run product.ecommerce_costs.daily --date 2026-08-17 --execute
|
||
```
|
||
|
||
如果登录态失效,使用有头的采集命令(不要给它加 `--headless`)完成验证码/滑块,再单独导入:
|
||
|
||
```powershell
|
||
uv run gyxx scripts run product.tmall_wanxiang_ads.collect --date 2026-08-17 --execute
|
||
uv run gyxx scripts run product.import.tmall_ads --date 2026-08-17 --execute
|
||
```
|
||
|
||
原始文件位于 `<GYXX_DATA_ROOT>\data\raw\product_commerce\tmall_wanxiang_ads\<date>\`,其中
|
||
`manifest.json` 记录 ZIP 和解压出的 CSV。定时工作流每次都会进入商品报表点击“下载报表”,在日期范围
|
||
选择“昨日”并点击“确定”,再到下载列表等待“生成成功”。若只需重新解析已下载文件,可以跳过采集步骤
|
||
重跑导入;默认导入以 manifest 列出的本次 CSV 为准,避免同一日期目录中历史 CSV 被重复合并;重复导入同一日期和商品不会产生重复行。采集命令手工重试时若不加 `--force-request`,仍可
|
||
复用下载列表中已有的“生成成功”任务。
|
||
|
||
排障顺序:
|
||
|
||
1. 检查 `gyxx schedule status`、服务日志和对应 `run_id`,确认失败发生在申请、下载、解压还是入库。
|
||
2. 检查同一 Profile 是否仍能打开万相台商品报表;登录失效时在有头命令中人工完成验证。
|
||
3. 检查日期目录中的 `manifest.json`、CSV 表头和 `日期/主体ID/主体名称`;ZIP 存在不代表数据库已导入。
|
||
4. 检查 PostgreSQL 的 `product_daily_metrics` 是否存在 `platform='tm'`、目标日期和商品 ID,且
|
||
`raw_data->'tm_ad_metrics'` 已更新;确认经营日报其他 JSON 节点仍保留。
|
||
5. 只有在外部状态核对清楚后才按同一业务日期重跑,不能用换日期或重复申请掩盖下载结果不明。
|
||
|
||
## 调度操作
|
||
|
||
```bash
|
||
uv run gyxx schedule run --dry-run --once
|
||
uv run gyxx schedule status
|
||
sudo systemctl restart gyxx-flow.service
|
||
```
|
||
|
||
调度服务也只加载显式指定的受控环境文件:
|
||
|
||
```bash
|
||
uv run gyxx schedule run --env-file /etc/gyxx-flow/gyxx-flow.env
|
||
```
|
||
|
||
业务时间只在 `config/schedules.json` 中维护。systemd 负责进程守护,Python scheduler 负责全部业务定时;不要重复注册 cron、systemd timer 或 Windows 任务计划。
|
||
|
||
需要停用单个工作流时,优先修改该工作流的调度启用状态,先 dry-run,再重启服务。其他模块继续运行。
|
||
|
||
### 开机自启(Windows 生产机)
|
||
|
||
重启/关机开机后的自动恢复链路:**Windows 自动登录 → `Startup\gyxx-keepalive.vbs` → `deploy/watchdog/gyxx_keepalive.py` → 控制台(8765) + `gyxx schedule run` 调度器**。keepalive 每 30 秒巡检并负责拉起死掉的控制台/调度器(单实例锁防双跑,冷却 120s、熔断保护),日志在 `var/logs/keepalive/keepalive.log`,验收用 `python deploy/watchdog/gyxx_keepalive.py --once`。
|
||
|
||
采集依赖浏览器 CDP,必须运行在交互式用户会话中,因此不能改成 Windows 服务或 Task Scheduler SYSTEM 任务(也不允许注册 Windows 任务计划)。免登录自启的唯一环节是自动登录,用脚本启用(密码交互输入、写入 `HKLM\...\Winlogon`,仅 Administrators 可读):
|
||
|
||
```powershell
|
||
powershell -ExecutionPolicy Bypass -File D:\gyxx-flow\deploy\watchdog\autologon.ps1 # 启用
|
||
powershell -ExecutionPolicy Bypass -File D:\gyxx-flow\deploy\watchdog\autologon.ps1 -Disable # 撤销
|
||
```
|
||
|
||
启用后重启验证:开机自动进桌面,约 2 分钟内控制台与调度器就位;停机窗内到期任务由调度器 catch-up 自动补跑。
|
||
|
||
## 数据和浏览器状态
|
||
|
||
生产 `GYXX_DATA_ROOT` 必须位于源码目录之外,Linux 推荐 `/var/lib/gyxx-flow`:
|
||
|
||
- `data/raw/<module>`:原始采集结果,不按临时文件清理。
|
||
- `data/normalized`、`data/curated`:清洗、标准化和聚合数据。
|
||
- `data/exports`:Markdown、Excel 等交付文件。
|
||
- `state/browser/<module>/<script>`:独立 Cookie、Profile 和 storage state。
|
||
- `state/accounts/<account>`:账号级登录态主库(同一账号多个脚本共用,`runtime-bindings.json` 中声明 `account` 的脚本每次运行前自动合并同步)。
|
||
- `state/scheduler`、`state/locks`、运行账本:调度和幂等状态。
|
||
- `tmp`:仅存放可重建临时文件。
|
||
|
||
账号级登录态管理:
|
||
|
||
- `gyxx accounts list`:查看账号、登录态有效性、成员脚本。
|
||
- `gyxx accounts login <account_id>`:打开该账号的登录浏览器(独立 Profile),人工扫码/验证码登录后自动保存 `cookies.json`/`storage_state.json` 到账号主库,并同步到全部成员脚本;`--timeout` 控制等待时长,`--force` 强制重登。
|
||
- `gyxx accounts sync [<account_id>]`:把账号主库 cookie 推送到成员脚本(不打开浏览器)。
|
||
- `gyxx accounts seed <account_id> --source-binding <binding_id>`:一次性把已有脚本的登录态按平台域名过滤后导入账号主库;仅用于迁移,不替代后续的账号级登录。
|
||
|
||
同一账号只需登录一次:之后任何成员脚本运行前都会从账号主库合并最新登录态到自己的独立 Cookie 文件,无需逐脚本登录。
|
||
|
||
当前配置按登录身份拆分为以下账号,避免同一平台不同店铺互相覆盖 Cookie:
|
||
|
||
- `douyin-shop`:抖店,600 秒;
|
||
- `jd-shop`、`jd-self-operated`、`jd-market-rank`:京东三个独立登录身份,各 900 秒;
|
||
- `tmall-shop`:天猫/淘宝/万相台光影行星账号,900 秒;
|
||
- `tmall-ozko`:商品经营日报 ozko 账号,900 秒;
|
||
- `xiaohongshu-content`:小红书,900 秒;
|
||
- `douyin-content`:抖音内容侧,900 秒;
|
||
- `chanmama`:蝉妈妈,900 秒(脚本自带账密登录);
|
||
- `bilibili-content`:B 站,900 秒。
|
||
|
||
星图/百应采集(`xingtu_scraper_v2.py`)的登录态挂在 `douyin-shop` 账号下;`xingtu` 账号当前无成员脚本,仅保留 vault。
|
||
|
||
商品经营日报 `product_commerce:taobao_sycm_collect.py` 会在同一次运行中按品牌路由账号:光影行星使用 `tmall-shop`,ozko 使用 `tmall-ozko`,两边各自使用独立 Profile 和账号 vault。`tmall-ozko` 没有静态成员脚本是有意设计,因为它由这个按品牌拆分的包装工作流动态选择;登录态成功后仍会回写 `state/accounts/tmall-ozko/`,由同一套 keepalive 负责续期。
|
||
|
||
账号需要在 `config/runtime-bindings.json` 的 `accounts.<account_id>.keepalive` 中显式开启续期,并配置一个不会产生业务副作用的已登录页面 URL。所有账号由唯一的 `gyxx schedule run` 常驻进程在后台维护;每个账号使用独立 CDP 端口、独立 Profile 和账号级 Cookie vault,启动后按 `initial_delay_seconds` 错峰访问,避免同时打开多个登录页面。访问成功且没有跳转到登录页时,Scrapling 才会把最新 Cookie/storage state 原子写回 `state/accounts/<account_id>/`,并同步给成员脚本;不需要另建 Windows 任务计划、cron 或 timer。
|
||
|
||
Cookie 续期只能延长站点支持滑动续期的会话,不能突破服务端硬过期、主动退出、风控或验证码。keepalive 检测到登录页/无效 Cookie 时会停止发布,不会用失效状态覆盖最后一份有效 vault;此时按 `gyxx accounts list` 检查 `keepalive.status`,必要时重新执行 `gyxx accounts login <account_id>`。
|
||
|
||
### 工作流执行前的 Cookie 健康网关
|
||
|
||
每个正式执行(非 dry-run)的工作流在采集步骤前会自动注入"每账号一个"的 `cookie-gate.<account_id>` 关键步骤,由 `src/gyxx_flow/cookie_gate.py` 实现按账号分三级的状态机:
|
||
|
||
- **blocking**(纯扫码/验证码账号:`douyin-shop`、`xiaohongshu-content`、`douyin-content`、`bilibili-content` 等):先看 keepalive 近期成功状态(默认 1 小时内视为新鲜,可用 `GYXX_COOKIE_GATE_MAX_STATUS_AGE_SECONDS` 调整),新鲜且 vault 静态有效则直接放行;否则用 keepalive 同款无头浏览器真实探活。探活失败即判定过期:gate 步骤失败,依赖它的采集步骤记 `dependency-failed` 跳过、工作流失败退出,并通过飞书通知登录负责人(`config/notification-routing.json` 中手机号为 `+8613663785145` 的人员,默认 30 分钟节流,可用 `GYXX_LOGIN_ALERT_OPEN_IDS` 覆盖收件人)执行 `uv run gyxx accounts login <account_id>` 完成扫码/验证码重登。
|
||
- **recover**(配置了 `login_command_id` 的账密自动重登账号:`erp-jushuitan`):过期时先自动跑账密登录恢复,恢复成功放行;失败才阻断并飞书通知。
|
||
- **defer**(成员脚本自带账号密码登录的账号:京东系、天猫系、`chanmama`):不注入 gate,保持脚本内账密自动登录流程不变。
|
||
|
||
按账号划分的持久状态在 `state/accounts/<id>/cookie_state.json`(`healthy` ↔ `awaiting_login`)。编排器型工作流(入口脚本本身无账号绑定、由子采集脚本使用账号)在 `config/workflows.json` 的 `required_accounts` 字段声明依赖账号。`--rerun-step`/`--resume-from` 切片和 shadow 运行不发送通知;dry-run 完全不注入 gate。设置 `GYXX_COOKIE_GATE=0` 可临时关闭整网关。
|
||
|
||
采集脚本不再内嵌扫码/验证码登录:运行中检测到登录态失效会以 `CookieExpiredError` 快速失败并按退出码 75 记为 cookie 跳过;`self_xhs_scraper.py --login-only` 是唯一保留的人工登录引导(该脚本不在任何账号成员内,自管绑定状态)。
|
||
|
||
### 登录恢复后的自动重跑
|
||
|
||
gate 阻断工作流时会把待重跑清单(工作流 + 业务日期,shadow/验收运行不记录,同工作流同日期去重,上限 20 条)写进 `state/accounts/<id>/cookie_state.json` 的 `pending_reruns`。常驻调度器(`gyxx schedule run`)的维护循环里挂着 `CookieGateRecoveryManager`:
|
||
|
||
- 每个心跳检查有待重跑记录的账号:keepalive 近期成功 + vault 静态有效 → 立即视为恢复;否则每 5 分钟做一次真实探活(mode C 账号会自动账密重登,重登成功同样触发恢复)。
|
||
- 恢复确认后,用调度器同款启动器按**原业务日期**自动重跑全部待重跑工作流(日志在 `data/logs/scheduler/`),清空清单并飞书通知"登录态已恢复 + 已自动重跑清单"。
|
||
- 启动失败的任务保留在清单里下一轮重试;同一账号两次重跑启动之间有 5 分钟冷却;控制台手动脚本类临时运行 id(`script.*`)不会自动重跑。
|
||
- 与正常调度并发安全:工作流级锁 + EffectLedger 幂等保证同一天不会重复产生副作用。
|
||
|
||
项目不会自动搬动或删除已有 `var/`。迁移数据根时应停止调度器,完整备份和复制原目录,修改环境变量后依次运行 doctor、dry-run 和一次指定日期的对账执行。
|
||
|
||
## 清理和保留
|
||
|
||
可以在确认没有任务运行后清理缓存、`tmp`、dry-run 目录和可重建构建产物。以下内容不得作为普通缓存删除:
|
||
|
||
- `data/raw` 原始数据;
|
||
- `state/scheduler` 和资源锁;
|
||
- EffectLedger、运行 journal 和浏览器状态;
|
||
- 尚未核对或尚未导入数据库的 Excel、JSON、Markdown 导出。
|
||
|
||
旧 Windows 任务计划 XML 只属于历史部署证据,确认 Python scheduler 已接管且完成备份后再归档。
|
||
|
||
## 故障处理
|
||
|
||
1. 按 `workflow_id`、业务日期和 `run_id` 定位失败节点。
|
||
2. 检查本机 PostgreSQL、对应 Hermes 角色、飞书身份和脚本专属 CDP 端口。
|
||
3. 检查 Cookie/storage state 是否存在且未失效。
|
||
4. 修复后按同一业务日期重跑,依靠唯一键和 EffectLedger 防止重复写入。
|
||
5. 仍失败时停用该工作流,不停止其他模块。
|
||
|
||
### 副作用账本对账
|
||
|
||
正式节点失败后,EffectLedger 会保留 `ambiguous` 回执,避免不确定状态下重复写库或重复
|
||
发送飞书。不要删除或手工修改回执文件。只有先从 PostgreSQL、飞书消息回执和目标文档
|
||
确认该节点是否已产生外部效果,才可执行审计式对账:
|
||
|
||
```bash
|
||
uv run gyxx effects reconcile <workflow_id> \
|
||
--date 2026-08-04 \
|
||
--step <step_id> \
|
||
--expected-run-id <原失败run_id> \
|
||
--action retry \
|
||
--operator <操作人> \
|
||
--reason <重试原因> \
|
||
--evidence <已核对的证据> \
|
||
--execute
|
||
```
|
||
|
||
`retry` 仅在证明确实没有完成外部写入时允许同日重试;若证据表明外部写入已经完成,使用
|
||
`--action applied`。命令只接受当前仍为 `ambiguous` 且 `run_id` 精确匹配的回执,并保留
|
||
操作人、原因、证据和时间,不能用于绕过正在执行的节点。
|
||
|
||
## 回滚
|
||
|
||
代码回滚时先停止 `gyxx-flow.service`,切换到上一个已验收版本,重新安装锁定依赖并执行 doctor 和调度 dry-run;确认后再启动服务。不得用代码回滚覆盖 `GYXX_DATA_ROOT`。
|
||
|
||
数据撤销必须针对明确表、业务键和 `run_id`,先备份 PostgreSQL,再执行经过复核的 SQL。不得删除整个数据库、数据卷或数据根。
|
||
|
||
浏览器状态异常时,只备份并处理目标脚本的 `state/browser/<module>/<script>`,不要清理其他脚本状态。调度状态异常时先停止唯一调度进程并备份 `state/scheduler`,不要同时启动第二实例。
|
||
|
||
## 商品日报采集开关(product.daily)
|
||
|
||
采集开关是 `dim_style.collect_enabled`(2026-10-29 起),**两层语义**(2026-10-30 起):
|
||
|
||
1. **锚点是系列总开关**:以该系列 `is_series_anchor = TRUE` 那一行为准;系列没有锚点时按款名
|
||
排序第一个。锚点 `FALSE` → **整条系列一款都不采**,同系列其余款式跟随锚点。
|
||
2. **锚点 `TRUE` 时,子款开关只决定自己的 ERP 编码是否参与聚合**:子款 `TRUE` 的 ERP 款式编码
|
||
并入锚点一起去聚水潭取数;子款 `FALSE` 的编码不并入(既不由它自己采,也不让锚点代采)。
|
||
被排除的子款列在 `series.erp_excluded`,运行时打
|
||
`[INFO] 系列「X」子款 Y 已关闭采集,ERP 编码不纳入「Z」聚合`。平台商品编码仍按整系列聚合,
|
||
不受子款开关影响。
|
||
|
||
旧的独立表 `product_daily_collect_scope` 已迁移并删除,备份在
|
||
`var/tmp/collect_scope_before_drop_20261029.json`。
|
||
|
||
默认值是 `TRUE`(采集),因此产品生命进程在 `dim_style` 新增的款式**无需任何操作**就会进入日报;
|
||
配件、包装、耗材、停售款才需要显式关掉。
|
||
|
||
关掉整条系列(改锚点那一行即可,子款自动跟随):
|
||
|
||
```sql
|
||
-- 单系列
|
||
UPDATE dim_style SET collect_enabled = FALSE WHERE style_name = '黑曜石托特包2';
|
||
|
||
-- 批量:把若干系列的所有行一起关(无锚点的系列也覆盖到)
|
||
UPDATE dim_style SET collect_enabled = FALSE
|
||
WHERE COALESCE(NULLIF(btrim(series_name), ''), style_name)
|
||
IN ('飞机盒', '香片', '雨衣', '鞋袋');
|
||
```
|
||
|
||
恢复采集把 `FALSE` 改成 `TRUE`。改完无需重启控制台或调度器:`StyleConfigLoader.load()`
|
||
每次运行都直接读库,下一轮 `product.daily`(每天 08:40,业务日 T-1)即生效。
|
||
|
||
两点注意:
|
||
|
||
- **开关落在锚点行上才控制整条系列。** ERP 编码与三平台商品编码都由
|
||
`_apply_series_sharing()` 按整系列聚合并去重后挂到锚点(子款误填的编码也会并入,但
|
||
`collect_enabled = FALSE` 的子款 ERP 编码除外),锚点是系列唯一的采集单元;子款自己的
|
||
编码保留在 `series.own_erp_codes` 与 `series.platform_ids` 备查。
|
||
- 关掉只影响**商品日报**(tm/jd/dy 平台段 + ERP 段)。天猫/京东主图周采集、聚水潭全店铺日报、
|
||
三平台电商费用**不受此开关影响**。
|
||
|
||
放行不等于一定会采:采集门仍要求「ERP 款式编码 + 各平台商品编码」齐备;飞书目标表地址
|
||
(下一节的 9 类 `bitable_*` 列)只决定能否写飞书,缺地址的款照常采集入库。缺口写在
|
||
`var/logs/product_commerce/<date>_skipped_styles.json` 的 `skipped.reason`。
|
||
|
||
改款式名时要同步改 `series_name`/锚点标记,否则旧开关会变成孤儿行、新名字按默认 TRUE 采集。
|
||
|
||
## 飞书表地址维护(dim_style)
|
||
|
||
2026-10-29 起,9 类飞书多维表格地址**全部维护在 `dim_style`**,而且只写在系列锚点行上。
|
||
`workflow_dynamic_styles` / `workflow_dynamic_style_platforms` 里的同名字段已废弃,
|
||
代码不再读取;旧值备份在 `var/tmp/bitable_urls_before_move_20261029.json`。
|
||
|
||
| 用途 | `dim_style` 列 | 原动态配置字段 |
|
||
|---|---|---|
|
||
| 销量表 | `bitable_weekly_url` | `sales_bitable_url` |
|
||
| 主图表 | `bitable_main_image_url` | `main_image_bitable_url` |
|
||
| 合作达人表 | `bitable_creator_url` | `creator_bitable_url` |
|
||
| 自营合作达人表 | `bitable_self_creator_url` | `self_creator_bitable_url` |
|
||
| 每周/每月笔记分析 | `bitable_weekly_note_url` | `weekly_note_analysis_url` |
|
||
| 平台单品分析 | `bitable_style_analysis_url` | `style_analysis_bitable_url` |
|
||
| 人群画像(天猫) | `bitable_persona_tm_url` | `tm_persona_bitable_url` |
|
||
| 人群画像(京东) | `bitable_persona_jd_url` | `jd_persona_bitable_url` |
|
||
| 人群画像(抖音) | `bitable_persona_dy_url` | `dy_persona_bitable_url` |
|
||
|
||
子款留空即可:两处代码会把整条系列的地址**聚合去重**后统一下发——
|
||
`workflow_config._propagate_series_targets()` 在合并建档数据时按「锚点优先、其余按款名」
|
||
收集整系列的非空地址,去重后写回每一行;`style_config_loader._apply_series_sharing()`
|
||
在聚合配置时再做一次同样的兜底。因此子款误填的地址不会丢、也不会让同系列出现两套表。
|
||
一个表位只能落一张表:同一字段填了多个不同地址时,保留优先级最高的那条,并在日志里打
|
||
`[WARN] 系列「X」<字段> 有 N 个不同表`,提示到锚点手动裁决。
|
||
|
||
```sql
|
||
-- 改某个系列的主图表(只改锚点一行,子款自动跟随)
|
||
UPDATE dim_style
|
||
SET bitable_main_image_url = 'https://xxx.feishu.cn/base/<base>?table=<tbl>'
|
||
WHERE style_name = '逐星'; -- 必须是该系列的锚点行
|
||
|
||
-- 查看哪些锚点还缺地址
|
||
SELECT style_name, series_name,
|
||
bitable_weekly_url, bitable_main_image_url, bitable_creator_url
|
||
FROM dim_style
|
||
WHERE is_series_anchor AND COALESCE(bitable_weekly_url, '') = ''
|
||
ORDER BY style_name;
|
||
```
|
||
|
||
改完不需要重启:`StyleConfigLoader.load()` 每次运行都读库,下一轮工作流即生效。
|
||
若某类地址为空,对应环节会跳过写表但照常采集入库(例如主图缺表地址打 `[NO-TABLE]`)。
|
||
|
||
**地址读取是 fail-closed 的**(2026-10-30):写表脚本拿不到 `dim_style` 地址时会打
|
||
`[FAIL] … 拒绝回退本地 JSON` 并以退出码 1 结束,**不会**改用 `bitable_style_map.json` /
|
||
`bitable_main_image_map.json` 里的旧地址。所以看到这类 `[FAIL]` 先去查库里的地址,
|
||
不要去改本地 JSON(这两个文件已无读取方)。
|
||
|
||
新增系列时,地址写在锚点行。若某款业务上确实是独立一套表(历史上 `逐星GT` 这种情形),
|
||
必须把它拆成独立系列(单独的 `series_name` + 锚点标记)并把地址写在自己那行;只写地址
|
||
而不拆系列的话,聚合会把它并入原系列,只在日志里留一条告警,不会真的生效。
|
||
|
||
## 业务源码同步
|
||
|
||
外部业务源码目录只用于发现更新,GYXX Flow 运行时不导入或挂载这些目录。只读检查:
|
||
|
||
```powershell
|
||
uv run gyxx sources status `
|
||
--source-root content_marketing=<内容源码目录> `
|
||
--source-root product_commerce=<商品源码目录> `
|
||
--source-root shop_intelligence=<店铺源码目录> `
|
||
--source-root supply_chain=<供应链源码目录> `
|
||
--json
|
||
```
|
||
|
||
只有未转换、无冲突的 `source_changed` 可以显式执行:
|
||
|
||
```powershell
|
||
uv run gyxx sources apply ... --execute --json
|
||
```
|
||
|
||
经过统一路径、数据库、Hermes、飞书或浏览器适配的文件必须人工合并并运行回归测试。新增可执行脚本还需登记工作流、调度和独立 CDP 绑定。同步完成后执行测试、doctor、acceptance 和 source status;真实采集不在源码同步过程中自动触发。
|