- Single Code-node sweep: desired = CP-active paths UNION active AccessGrants; expiry excluded -> role/update rewrite; optional auto-disable (env-gated, default off) - disable guard: only when had scopes before (never fresh empty-role customer) - loop guard: no NocoDB Customers.status flip (avoids W1 re-provision loop) - test_w3.js mock-verified both AUTO_DISABLE modes; gen_w3.js builds JSON from w3-compute.js
4.0 KiB
4.0 KiB
W3 — Expiry Sync / 全量对账(schedule: every 5 min)
定时任务,不是 webhook。负责「到期移除 scope、恢复客户、至空则禁用」。 与 W1/W2 正交:W1/W2 是事件驱动(NocoDB 变更 → 重算单个 customer); W3 是周期性全量对账(扫所有 customer → desired vs actual → 修 AList / 回写 NocoDB)。 幂等规则 §28:get current → compare → apply diff,重跑不产生重复写。
触发
- Schedule Trigger(
n8n-nodes-base.scheduleTrigger),cron*/5 * * * *。 - 每次起手先 login AList 换新 token(JWT_EXPIRE=2h,不缓存)。
核心逻辑(desired-state)
对每个已有 alist_role_id 的 customer:
- 读该 customer 全部 CustomerProducts(
where=(customer.Id,eq,${cid})&limit=1000)。 - desired paths = 仅取 status=active 且未过期 的 CP 的 Product→Resources
alist_path集合并。- 过期判定:
status==='expired'或expires_at < today,一律排除。 - 有效 CP 无
expires_at→ 视为长期有效,保留。
- 过期判定:
- 读 AList role 当前 scopes(
role/get?id=)。 desired pathsvscurrent paths→ 差异即要改。- 到期移除:current 有、desired 无 → 从 scopes 移除(整体重写 role/update)。
- 新生效加入:desired 有、current 无 → 加入。
- 有变 →
POST /api/admin/role/update整体重写permission_scopes(幂等)。 - disable 政策(开关
ALIST_AUTO_DISABLE_ON_EXPIRE,默认false):- 若
desired 为空且 该 customer NocoDBstatus==='active'→ 回写status='expired',且(开关开时)user/update disabled=true。 - 默认关 = 首轮只观察,不自动禁用任何客户,避免误伤。
- 若
实现形态(与 W2 的差异,需知晓)
- W2(事件驱动):Code 节点算 → 独立 HTTP 节点 apply,单客户,逐步可见。
- W3(后台对账):单个 Code 节点内完成 read + write(
this.helpers.httpRequest), 每客户 try/catch 收集错误进 summary,最终返回一条 summary + 可选通知。 理由:① 无多 item 跨节点表达式陷阱(reconcile 输出 N 条会让$('X').item.json报错); ② 定时无人值守,原子化比逐步可见更稳;③ 幂等,重复跑无副作用。 代价:写操作在代码里,调试靠 per-customererrors[]。已权衡,接受。
Webhook loop 防护(关键)
- NocoDB webhook 触发的是 W1(Customers) 和 W2(CustomerProducts)。
- W3 回写要避开:
- 改 CustomerProducts.sync_status → 会触发 W2 webhook;W2 自带 "skip if already synced" 短路,安全。但 W3 应在确无 diff 时完全不写,减少空触发。
- 改 Customers.status→expired → 会触发 W1 webhook → 可能重新 provision → loop。 规避:W3 只在自己做了「去 scope 至空」时才翻 status;并且只设一个方向 (active→expired),同一同步周期内不回头。首期开关默认关,连 status 翻转也延后。
- 铁律(照 PROJECT_STATE):不盲写
last_sync时间戳;语义没变就不写。
待确认决策点
- disable 政策是否首期启用?(建议否,先观察 1–2 轮)
- 是否顺带物化 AccessGrants(W3 写 product/manual 源之外)?—— 挂在 W2 验证后那项确认。
- reconciliation 结果日志:不动 schema(AccessLogs 是用户行为审计),先只靠 n8n execution + summary。要不要新开
ReconcileLogs表,验证后再说。
里程碑 / 测试
- M1:以
dryRun=true跑一次(只读,不写 AList/NocoDB),输出「将改的 diff」清单。 - M2:造一条
expires_at=昨天的 CP,观察 W3 将其 scope 移除(开关仍关,禁用不生效)。 - M3:开
ALIST_AUTO_DISABLE_ON_EXPIRE=true,造一键全到期 customer,观察 role scopes 清空 + user disabled + status=expired。 - 全部用 n8n execution error + summary JSON 验收。