Files
alist-serve-resource/docs/N8N_WORKFLOWS.md
T
hoelee 4693c9d684 feat(W4): daily full reconciliation — user/role drift repair + ReconcileLogs audit
- w4-body.js: ONE-DIRECTIONAL sync (NocoDB status = SOT, never reverse);
  drift matrix: missing user/role (provision w/ username lookup to avoid
  duplicates), dangling refs (adopt/recreate), disabled<->status, canonical
  base_path '/' + permission 16384, scopes vs calculateEffectiveGrants,
  username drift detect+log_only; verify-after-repair on every change;
  password never pushed on repair (方案 C); dry-run via ?dryRun=true
- gen_w4.js: daily 03:00 cron + webhook alist-w4-reconcile; API-import-safe
  (UUID node ids, no pinData, webhookId)
- ReconcileLogs table mktsb7le6fqr2u1 (add_reconcilelogs_table.py, idempotent,
  v2 API); FK column nc_jitu___Customers_id for bt-link writes (v2 ignores
  link column itself)
- test_w4_reconcile.js: 34 assertions / 12 mock worlds, ALL PASSED
- live-verified against production: dry-run (3 drifts + 1 error found),
  real repair (3 repaired, verify all passed, 4 log rows), idempotent rerun
- NocoDB 2026.08.x pitfalls documented (v1 col endpoints gone, row delete
  via body [{Id}]); n8n API import/activation pitfalls (activeVersionId)
- docs: W4-DESIGN.md new; DATABASE_SCHEMA 8 tables; N8N_WORKFLOWS, SETUP,
  PROJECT_STATE updated
2026-08-23 09:34:11 +08:00

81 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# n8n Workflow 说明(已实施)
> n8n 运行在现有 auto.hoelee.com(`bridge_hoelee` 网络)。它是**同步器,不是 SOT**。
> 更新日期 2026-08-23。
## 已实现的三个 Workflow
| Workflow | ID | 触发 | 职责 |
|---|---|---|---|
| **W1** Customer Provision & Status Lifecycle | `gU2ee9Eg5w1KzB5p` | NocoDB Customers webhook | provision + 整户 status lifecycle |
| **W2** CustomerProduct Sync | `D9OWAI7JUjKnRdOn` | NocoDB CustomerProducts webhook | 重算该客有效 grants 并应用 |
| **W3** Expiry Sync | `zlEfjj5eK5IfILtk` | schedule `*/5` | 定时 | 全量对账 + drift 修复(scopes 层) |
| **W4** Daily Full Reconciliation | `pl8j9lpg5gjrd0dB` | 每日 03:00 + webhook `alist-w4-reconcile` | 全量对账 + 用户/角色级 drift 修复 + ReconcileLogs |
三个 workflow 共享同一套算法 `calculateEffectiveGrants`(`n8n/shared/effective-grants.js`,
构建时经 `gen_w1/w2/w3.js` 内联进各 Code 节点)。
## W1 — Customer Provision & Status Lifecycle
- 触发:Customers 表 after-insert / after-update(webhook `alist-customer-provision`)
- 单一 Code 节点,处理 5 状态:
- `active`(无 user)→ 建 role + 建 user + 重算 grants + enable
- `active`(有 user)→ re-enable + 重算 grants(不恢复旧值)+ 应用
- `pending`/`suspended`/`expired`/`deleted`(有 user)→ disable + 清空 scopes
- `pending`/`suspended`/`expired`/`deleted`(无 user)→ no-op
- 回填 `alist_user_id` / `alist_role_id`,写 `sync_status=synced`
## W2 — CustomerProduct Sync
- 触发:CustomerProducts 表 after-insert / after-update(webhook `alist-customerproduct-sync`)
- 读 CP → 定位 customer → 重算该客 grants → diff AList role → 有变化才 `role/update` 整体重写
- `customer` link 为空时优雅跳过(不回写)
## W3 — Expiry Sync(全量对账)
- 触发:每 5 分钟 schedule
- 逐客调 `calculateEffectiveGrants` → diff → 应用
- Env 开关:`W3_DRY_RUN=true`(只打印计划)、`ALIST_AUTO_DISABLE_ON_EXPIRE=true`(scopes 变空时 disable 用户)
## W4 — Daily Full Reconciliation(每日全量对账)
- 触发:每日 03:00 cron + 手动 webhook(`/webhook/alist-w4-reconcile`,`?dryRun=true` 出计划)
- 单向同步:NocoDB status 是 SOT,W4 只修 AList,**永不反向改 NocoDB status**
- 覆盖 W3 不管的 drift:缺 user/role → 按 username 反查防重再 provision;悬空引用 → adopt/recreate;
非 active → disable + 清 scopes;base_path/permission 修回 canonical(`/` + 16384);
username drift → detect + log_only;修复后逐个 **verify 回读**,失败记 `verify_failed`
- 写 ReconcileLogs(表 `mktsb7le6fqr2u1`),只记 drift/repair/error 行;customer 链接用 FK 列
- 密码随 repair 不推送(方案 C)
- 详见 `docs/W4-DESIGN.md`
## 共用 env 变量
n8n Code 节点内用 `$env.VAR`(`process.env` 不可靠)。下表是本项目需要的 env:
| 变量 | 说明 |
|---|---|
| `NOCODB_URL` / `NOCODB_TOKEN` | NocoDB 地址 + `xc-token` |
| `NOCODB_TABLE_CUSTOMERS` / `..._CUSTOMERPRODUCTS` / `..._PRODUCTS` / `..._RESOURCES` / `..._ACCESSGRANTS` / `..._RECONCILELOGS` | 各表 model_id(ReconcileLogs = `mktsb7le6fqr2u1`) |
| `NOCODB_FK_RECONCILELOG_CUSTOMER` | ReconcileLogs.customer 的 FK 列名 `nc_jitu___Customers_id`(W4 写链接必须用 FK 列) |
| `NOCODB_LINK_PROD_RESOURCES` | Product↔Resource M2M 列 id(`c4ko4x8kwi16swg`)|
| `ALIST_URL` | `http://alist-serve-resource:5244`(n8n 容器内)|
| `ALIST_ADMIN_USERNAME` / `ALIST_ADMIN_PASSWORD` | AList admin 登录 |
| `W3_DRY_RUN` / `ALIST_AUTO_DISABLE_ON_EXPIRE` | W3 开关 |
## 幂等规则
所有操作 = get current → compare → apply diff。重跑不产生重复 user/role/scope。
更新 scope 一律「整体重写该 role 的 `permission_scopes`」。
## Loop 防护(重要)
- n8n 只写回 bookkeeping 字段(`alist_user_id`/`alist_role_id`/`sync_status`/`sync_required`/`last_sync`/`last_sync_error`)。
- W1 用 **old/new field comparison**(NocoDB `previous_rows` vs `rows`)区分「业务变更」vs「echo」;echo 直接短路。
- `writeBack()` 值比对守卫:字段值不变就不发 PATCH,掐断 echo loop。
## 依赖的 AList API(源码级验证,见 API_INTEGRATION.md)
- 登录:`POST /api/auth/login`
- User:`/api/admin/user/{list,get,create,update,delete}`
- Role:`/api/admin/role/{list,get,create,update,delete}`(delete 用 `?id=` query param)