# 架构说明(Architecture) > 本文解释系统的分层职责与数据流。更新日期 2026-08-23。 ## 三层职责(核心心智模型) | 层 | 载体 | 职责 | 谁改它 | |---|---|---|---| | **Account lifecycle** | `Customers.status` | 账户整体状态开关(active / pending / suspended / expired / deleted)| 管理员手工 | | **Product entitlement** | `CustomerProducts` + `Products` + `Resources` | 「这个客户买了哪些产品 → 能访问哪些路径」+ 每个产品的到期时间 | 管理员手工 | | **Effective permission** | AList Role `permission_scopes` | 最终生效的路径授权(由 n8n 从上面两层计算出来,写到 AList)| **仅 n8n**,管理员不直接碰 | ``` Customers.status (account-level:整户开关) + CustomerProducts (product entitlement:买了什么、何时到期) + Product → Resources (产品包含哪些路径) + CustomerProduct.expires_at ↓ calculateEffectiveGrants (n8n 共享算法,单一事实源) ↓ AList Role.permission_scopes (effective/materialized permission) ``` ## 关键原则 1. **`Customers.status` 不进入权限计算**。它只决定「整户开关」: - `active` → 允许计算并应用 grants - `pending` / `suspended` / `expired` / `deleted` → AList user disabled + scopes 清空(账户级封禁) - 权限的「细粒度」永远来自 CustomerProducts 的到期时间,不由 status 替代。 2. **AccessGrants 是预留的物化表,当前不经 n8n 写入**。将来(W4)可用于 reconciliation 日志/物化审计,但现在不作为 source-of-truth,也不参与计算。 3. **n8n 是同步器,不是 SOT**。Source of Truth = NocoDB;AList 只做 enforcement。 ## 数据流 ``` 管理员在 NocoDB 改 Customers.status 或 CustomerProducts │ (NocoDB webhook:after insert / after update) ▼ n8n W1 (Customer webhook) ── 处理整户 status lifecycle n8n W2 (CustomerProduct webhook) ── 重算该客有效 grants 并应用 n8n W3 (每 5 分钟 schedule) ── 全量对账:重算所有客户,修 drift │ ▼ AList Admin API(role/update 写 permission_scopes;user/update 写 disabled) ``` - **W1** 单一 Code 节点:provision + status lifecycle(5 状态)+ 幂等 write-back。 - **W2** 单一 Code 节点:CustomerProduct 变更 → 重算该客 grants → diff → 应用。 - **W3** 单一 Code 节点:全量 sweep,逐客调共享算法,diff 后应用;支持 dry-run / auto-disable 开关。 三个 workflow 共用同一个 `calculateEffectiveGrants`(`n8n/shared/effective-grants.js`,构建时内联),保证三处算法零复制、零漂移。 ## Status lifecycle(W1) | status | AList user | scopes | 说明 | |---|---|---|---| | `pending`(无 user)| —(不建)| — | 纯标签,customer 未正式开通 | | `pending`/`suspended`/`expired`/`deleted`(有 user)| disabled | 清空 | 账户级封禁,soft,保留历史 | | `active`(无 user)| 新建 + enabled | 计算应用 | 首次开通 | | `active`(有 user)| re-enable | 重算 + 应用 | 恢复/续期,永不恢复旧值 | | 任意 `X → active` | enable | 重算(仅当前有效)| resume,忽略已过期产品 | ## Loop 防护 - n8n 只写回 bookkeeping 字段(`alist_user_id`/`alist_role_id`/`sync_status`/`sync_required`/`last_sync`/`last_sync_error`)。 - W1 用 **old/new field comparison**(NocoDB webhook 的 `previous_rows` vs `rows`)区分「管理员业务变更」vs「自己的 write-back echo」:echo 直接短路,零 PATCH。 - `writeBack()` 有值比对守卫:字段值没变就不发 PATCH,从物理上掐断 echo loop。