- W5 gets username ?u= → Customers→CustomerProducts → {username,known,purchases}
- 无 HMAC(密钥会下发客户端): 改用 CORS 限 drive.digikedai.com + 最小数据 + CF WAF 建议
- 复用现有 NOCODB_* env, 零新增 env
- 2 节点: webhook(GET, lastNode, CORS 头) + Code(w5-body.js)
- footer v2.6 动态行数据源; 待手动导入 n8n
- docs: N8N_WORKFLOWS + PROJECT_STATE 记录 W5 + 导入步骤
100 lines
6.1 KiB
Markdown
100 lines
6.1 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 |
|
||
| **W5** Footer Purchase Check | (导入后由 n8n 分配) | GET webhook `dk-purchase-check` | footer 动态行数据源(公开只读) |
|
||
|
||
W1–W4 共享同一套算法 `calculateEffectiveGrants`(`n8n/shared/effective-grants.js`,
|
||
构建时经 `gen_w1/w2/w3/w4.js` 内联进各 Code 节点)。W5 是独立的**公开只读** webhook(不属同步器)。
|
||
|
||
## 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`
|
||
|
||
## W5 — Footer Purchase Check(公开只读,不属同步器)
|
||
|
||
- 触发:`GET /webhook/dk-purchase-check?u=<username>`(公网经 `auto.hoelee.com`)
|
||
- 职责:footer v2.6 动态行数据源——按 username 查 Customers → 拉该客 CustomerProducts →
|
||
返回 `{ username, known, purchases:[{product, expires_at, display_state}] }`
|
||
- 安全模型(**无 HMAC**——key 会随 footer JS 下发到客户端=空话):
|
||
- CORS:webhook `options.responseHeaders` 设 `Access-Control-Allow-Origin: https://drive.digikedai.com`
|
||
→ 仅 AList 门户 origin 能读响应
|
||
- 最小数据:只回 product 名 + 到期 + display_state,**不含内部 Id**
|
||
- 服务器枚举靠 CF WAF 在 auto.hoelee.com 上做限流 + Origin/Referer 规则(见交付指南)
|
||
- 零 AList 认证请求:username 由 footer 本地解码用户自己的 JWT(`localStorage['token']`)取出 →
|
||
无「空客户端设备驱逐」风险(v2.3 事故机制完全不涉及)
|
||
- 复用现有 env(`NOCODB_URL`/`NOCODB_TOKEN`/`NOCODB_TABLE_CUSTOMERS`/`NOCODB_TABLE_CUSTOMERPRODUCTS`),**零新增 env**
|
||
- 生成:`node n8n/gen_w5.js` → `W5-purchase-check.json`(2 节点:webhook[GET, lastNode, CORS 头] + Code「Lookup Purchases」)
|
||
- 导入(手动):n8n → Import from File → `W5-purchase-check.json` → 记下 Webhook URL(应为
|
||
`https://auto.hoelee.com/webhook/dk-purchase-check`)→ 激活。测试:
|
||
`curl 'https://auto.hoelee.com/webhook/dk-purchase-check?u=tester'` 应回 JSON 含 `known:true` + 2 条 purchases
|
||
|
||
## 共用 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)
|