- 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
81 lines
4.4 KiB
Markdown
81 lines
4.4 KiB
Markdown
# 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)
|