# NocoDB 数据结构(Phase 2b — 重建,8 表最终模型) > Source of Truth = NocoDB。AList 只做 enforcement,不存业务授权数据。 > 本 schema 按《Hermes 调整指导》最终业务模型重建(2026-08-22)。 > 通过 NocoDB API 自动建表,无需手动点 UI。ReconcileLogs 于 2026-08-23 W4 新增。 - **Base**: `AlistAccess`(id `p3lzkresswyowqg`),独立新 base,与旧 `accessDB` 隔离。 - **MySQL source**: mysql-server 上的 `alistaccess` schema(由 NocoDB 自动管理)。 - **API token**: workspace token(`nc_api_tokens` 表内 `n8n` token),`xc-token` header。 ## 表 model_id(n8n v2 API 硬编码用) | 表 | model_id | |---|---| | Customers | `maqb9zgvza2te3k` | | Products | `molyb3pxv81l75g` | | Resources | `m6cm7tkxzd8m18c` | | CustomerProducts | `mvyqbv8ao7u6bct` | | AccessGrants | `mxdx3yhfcyej3ao` | | AccessLogs | `ml12zcyzlehgi21` | | Purchases | `mwp12z9lcohhsuk` | | ReconcileLogs | `mktsb7le6fqr2u1`(FK 列 `nc_jitu___Customers_id` = `coa6ynwcdlune6p`) | > ⚠ ID 是本次重建生成的。若再次删库重建,ID 会变,需同步更新 n8n 节点 + 本文档。 ## 关系(LinkToAnotherRecord) | 子表 | 字段 | 方向 | 指向 | 说明 | |---|---|---|---|---| | CustomerProducts | `customer` | bt | Customers | | | CustomerProducts | `product` | bt | Products | | | Products | `Resources` | **mm** | Resources | M2M link,列 id `c4ko4x8kwi16swg` | | Resources | `Products` | mm (reverse) | Products | 列 id `cfxi44n9ixkimqc` | | AccessGrants | `customer` | bt | Customers | | | AccessGrants | `resource` | bt | Resources | | | AccessLogs | `customer` | bt | Customers | | | Purchases | `customer` | bt | Customers | | | Purchases | `product` | bt | Products | | | ReconcileLogs | `customer` | bt | Customers | ⚠ 写入时用 FK 列 `nc_jitu___Customers_id`(v2 忽略 link 列本身) | > M2M 读写方式(已实测):`GET/POST /api/v2/tables/{products}/links/c4ko4x8kwi16swg/records/{productId}`, > POST body `{"Id": }`。反向用 Resources 的 `cfxi44n9ixkimqc`。 ## 表结构 ### 1. Customers | 字段 | 类型 | 说明 | |---|---|---| | Id | number (PK) | | | username | SingleLineText | AList 登录名(唯一) | | password | SingleLineText | **plaintext**(规格 §2 已确认,勿 hash) | | display_name | SingleLineText | | | email | Email | | | notes | LongText | | | alist_user_id | Number | AList 用户 ID(n8n 回填) | | alist_role_id | Number | AList role ID(n8n 回填) | | status | SingleSelect | pending/active/suspended/expired/deleted(account-level 整户开关,见下) | | sync_status | SingleSelect | pending/syncing/synced/error/unknown | | sync_required | Checkbox | 系统维护,管理员勿手改 | | last_sync | DateTime | | | last_sync_error | LongText | | **Customers.status 语义(account-level 整户开关,不替代 CustomerProduct 到期):** | 值 | 含义 | AList user | scopes | |---|---|---|---| | `pending` | 已建但未正式开通 | 若无则**不建**;有则 disabled | empty | | `active` | 正常使用 | enabled | 按当前有效 CP 重算 | | `suspended` | 管理员暂时封禁 | disabled | empty | | `expired` | 整户生命周期结束 | disabled | empty | | `deleted` | 软删除(保留账号+历史)| disabled | empty | - 任意 `X → active` 恢复时,重算当前有效 grants(忽略已过期产品),不恢复旧 scopes。 - status 变化**不自动改** CustomerProducts(不批量改 status/expires_at、不删除)。 ### 2. Products | 字段 | 类型 | 说明 | |---|---|---| | Id | number (PK) | | | name | SingleLineText | 如 "Python Basic" | | description | LongText | | | default_duration_days | Number | 默认有效期天数 | | status | SingleSelect | active/inactive | | Resources | link (mm) | 该产品包含的 Resources | ### 3. Resources | 字段 | 类型 | 说明 | |---|---|---| | Id | number (PK) | | | name | SingleLineText | 如 "Python 01" | | alist_path | SingleLineText | 真实 AList path,如 "/Class/Python/01" | | description | LongText | | | status | SingleSelect | active/inactive | | Products | link (mm, reverse) | | ### 4. CustomerProducts(核心新表) | 字段 | 类型 | 说明 | |---|---|---| | Id | number (PK) | | | customer | link bt → Customers | | | product | link bt → Products | | | start_at | Date | | | expires_at | Date | 实际到期(可 override default_duration) | | status | SingleSelect | scheduled/active/expired/revoked/suspended | | sync_status | SingleSelect | pending/syncing/synced/error/unknown | | sync_required | Checkbox | | | last_sync | DateTime | | | last_sync_error | LongText | | ### 5. AccessGrants(最终实际权限,n8n 计算维护) | 字段 | 类型 | 说明 | |---|---|---| | Id | number (PK) | | | customer | link bt → Customers | | | resource | link bt → Resources | | | permission | SingleSelect | read/readwrite | | start_at | Date | | | expires_at | Date | effective expiry(多来源取 max) | | status | SingleSelect | pending/active/revoked/expired | | source | SingleSelect | product/manual | | sync_status | SingleSelect | pending/syncing/synced/error/unknown | | sync_required | Checkbox | | | last_sync | DateTime | | | last_sync_error | LongText | | | revoked_at | DateTime | | | revoke_reason | LongText | | ### 6. AccessLogs(审计) | 字段 | 类型 | |---|---| | Id | number (PK) | | customer | link bt → Customers | | username | SingleLineText | | resource_path | SingleLineText | | file_path | SingleLineText | | action | SingleSelect (login/view/download/preview/logout) | | ip_address | SingleLineText | | user_agent | LongText | | result | SingleSelect (success/denied) | | bytes | Number | | timestamp | DateTime | ### 7. ReconcileLogs(W4 对账审计,n8n W4 写入) | 字段 | 类型 | 说明 | |---|---|---| | Id | number (PK) | | | run_id | SingleLineText | 本次对账 run(`recon-`) | | customer | link bt → Customers | FK 列 `nc_jitu___Customers_id`(v2 写链接必须写 FK 列) | | drift_type | SingleSelect | user_missing / role_missing / dangling_user_ref / dangling_role_ref / user_disabled_drift / scopes_drift / base_path_drift / permission_drift / role_binding_drift / username_drift / verify_failed / reconcile_error | | details | LongText | 前后值 JSON | | action | SingleLineText | 做了什么(provisioned / adopted / fixed / rewritten / disabled / log_only / verify …) | | result | SingleSelect | repaired / log_only / failed / skipped | | timestamp | DateTime | | > 只记 drift / repair / error 行,健康客户不产生行。W4 每日全量或 webhook 触发时写入。 ### 8. Purchases(纯记录,不参与权限) | 字段 | 类型 | |---|---| | Id | number (PK) | | customer | link bt → Customers | | product | link bt → Products | | purchase_date | Date | | amount | Decimal | | payment_method | SingleLineText | | order_id | SingleLineText | | notes | LongText | ## 建表 API recipe(实测 2026-08-22,复用见 nocodb-selfhosted skill) - 建 base:`POST /api/v2/meta/bases/ {"title"}` - 建表(内联非 select 列):`POST /api/v2/meta/bases/{baseId}/tables {"title","table_name","type":"table","columns":[{"title","uidt"}]}` - 可用 uidt:`SingleLineText`,`LongText`,`Number`,`Decimal`,`Date`,`DateTime`,`Email`,`Checkbox` - SingleSelect 两段式: 1. 建空列 `POST /api/v1/db/meta/tables/{tid}/columns {"title","uidt":"SingleSelect"}` 2. 设选项 **必须同时传 `dtxp`**(逗号枚举串): `PATCH /api/v1/db/meta/columns/{colid} {"colOptions":{"options":[{"title","order","color":null}]},"dtxp":"'opt1','opt2'"}` ⚠ 只传 `colOptions` 不传 `dtxp` 会静默失败(options 空)。 - Link:`POST /api/v1/db/meta/tables/{childTid}/columns {"title","uidt":"LinkToAnotherRecord","parentId","childId","type":"bt"}` - M2M:`type":"mm"`,然后走 `/api/v2/tables/{t}/links/{colId}/records/{rowId}` 读写。 - 删记录:`DELETE /api/v2/tables/{tid}/records` body `[{"Id":N},...]`(不是 `/records/{id}`)。