Files
hoelee 414bf31ba0 feat(n8n): W5 footer purchase-check webhook (公开只读) + docs
- 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 + 导入步骤
2026-08-28 18:24:42 +08:00

100 lines
6.1 KiB
Markdown
Raw Permalink 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 |
| **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)