- 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
123 lines
9.7 KiB
Markdown
123 lines
9.7 KiB
Markdown
# Project State (Handoff) — 2026-08-22
|
||
|
||
> 交接文档:压缩/新会话后,按此文档续做。细节见各 doc。
|
||
|
||
## 🔑 连接凭据速查(新会话直接取用)
|
||
|
||
> ⚠️ **安全警告:本块含生产真实凭据(密码/API token/key)。只留本地用于交接——推 Gitea 前必须整块删除或脱敏,否则凭据随 git 历史永久泄露。**
|
||
|
||
| 系统 | 地址 / 凭据 |
|
||
|---|---|
|
||
| **SSH DSM** | `192.168.1.1:9998` user `hoelee` / `<redacted>`;sudo 免密;docker 用 `/usr/local/bin/docker` |
|
||
| **AList** | `http://alist-serve-resource:5244`(容器内)/ `localhost:5244`(DSM host)admin `hoelee` / `<redacted>`(2FA 关) |
|
||
| **NocoDB** | `https://nocodb.hoelee.com`;API token: `<redacted>`;base AlistAccess = `p3lzkresswyowqg` |
|
||
| **n8n** | `https://auto.hoelee.com`;API key: `<redacted><redacted>` |
|
||
| **NocoDB 表 ID** | Customers `maqb9zgvza2te3k` · Products `molyb3pxv81l75g` · Resources `m6cm7tkxzd8m18c` · CustomerProducts `mvyqbv8ao7u6bct` · AccessGrants `mxdx3yhfcyej3ao` · AccessLogs `ml12zcyzlehgi21` · Purchases `mwp12z9lcohhsuk`;M2M link col `c4ko4x8kwi16swg` |
|
||
| **n8n workflow ID** | W1=Cust Provision `gU2ee9Eg5w1KzB5p` · W2=CP Sync `D9OWAI7JUjKnRdOn` |
|
||
|
||
## 已完成 ✅
|
||
|
||
### 1. AList stock 部署(Phase 1)— 运行中
|
||
- Container `alist-serve-resource` (xhofe/alist v3.63.0),DSM port **5244**,网络 `bridge_hoelee`
|
||
- 挂载 `/volume1/Class` `/volume1/Book` `/volume1/Comic` `/volume1/Template`(只读 → /mnt/Class 等)
|
||
- 旧 alist (v3.54.0) 仍运行在 5243,**暂不处理**
|
||
- Admin: username `hoelee`,2FA 已关闭;compose 在项目根 `docker-compose.yml`
|
||
- DSM 上从 host shell 访问内网容器要用 `localhost:5244`;n8n 容器内用 `http://alist-serve-resource:5244`
|
||
|
||
### 2. NocoDB schema(Phase 2 重建)— 7 表最终模型
|
||
Base: **`AlistAccess`**(`p3lzkresswyowqg`),与旧 `accessDB` 完全隔离。表 + model_id + 关键点:
|
||
| 表 | model_id | 备注 |
|
||
|---|---|---|
|
||
| Customers | maqb9zgvza2te3k | plaintext password + **password_updated_at** |
|
||
| Products | molyb3pxv81l75g | default_duration_days;M2M→Resources |
|
||
| Resources | m6cm7tkxzd8m18c | alist_path |
|
||
| CustomerProducts | mvyqbv8ao7u6bct | 核心表(customer+product+expiry) |
|
||
| AccessGrants | mxdx3yhfcyej3ao | **物化状态表,不是 trigger**(禁止 loop) |
|
||
| AccessLogs | ml12zcyzlehgi21 | 审计 |
|
||
| Purchases | mwp12z9lcohhsuk | 纯记录 |
|
||
|
||
- 建表脚本:`scripts/create_nocodb_schema.py`(幂等)
|
||
- 表/字段/链接列 ID 全表:`docs/DATABASE_SCHEMA.md`(已更新为 7 表)
|
||
- NocoDB API token(workspace 级,来自 nc_api_tokens 表 n8n 那行):在 `docs/API_INTEGRATION.md` / SETUP.md 有记(或查 NocoDB UI)
|
||
|
||
### 2. W1 客户开通 workflow — **已实测跑通** ✅
|
||
- 文件:`n8n/W1-customer-provision.json`
|
||
- 流程:Webhook → Extract customerId → **Load Env(step 2, 用 $env)** → Fetch 记录 → 判断 create/update → Create Role → Find Role Id → Create User(role:[roleId]) → Find User Id → Write Back
|
||
- 关键:AList user/create 传 **空 role 数组会被拒**(自动填 guest→报 "admin or guest user can not be created")。必须先 Create Role 拿自增 id,再 Create User 带 `role:[id]`
|
||
- **base_path 已硬编码 `'/'`**(不是 `/Class`,否则 JoinPath 双重前缀 → 403):commit 1ac1f17
|
||
- **Loop guard**:新增 "Skip if already synced" IF 节点(sync_status=synced 时短路,不写回 NocoDB → 打破 webhook 循环);同时去掉 last_sync 写入(时间戳每次必变会破坏 guard)
|
||
- Load Env 已同步为 `$env` 版本(process.env 在 Code 节点不可用):commit 58c2f57
|
||
|
||
### 3. W2 CustomerProduct 权限同步 — **已改好,需重新 import 测试** ⏳
|
||
- 文件:`n8n/W2-customerproduct-sync.json`
|
||
- 流程:Webhook → Extract customerProductId → Load Env → **Compute Effective Grants**(登录 AList→读 customer→读全部 active CP→读 Product 的 M2M Resources→**per-Resource MAX(expires_at)** 合并→读 AList role 现状→diff→算 newScopes) → Skip if no change → AList Update Role Scopes → Write Back CP Sync Status
|
||
- **已修 bug(commit 36a6b3b)**:查询 active CP 的 where 语法从 `(customer,eq,${id})` → `(customer.Id,eq,${id})`。NocoDB link 字段用 display name 直接 eq 返回 0 条;必须用 `customer.Id` 子字段或外键列 `nc_jitu___Customers_id`
|
||
- **已修 bug**:link API 返回 `{Id,name}` 不含 `alist_path` → 新增 resById 反查(先加载全量 Resources 按 Id 索引,再按 link 结果的 Id 反查 alist_path)
|
||
- **Loop guard**:新增 "Skip if no change" IF + No-op 死端(scopes 无 diff 且已 synced 时短路);去掉 last_sync
|
||
- ⚠️ 待用户重新 import + 测试
|
||
|
||
### 4. W3 Expiry Sync(全量对账)— **已实现,待 import + dry-run 测试** 🆕
|
||
- 文件:`n8n/W3-expiry-sync.json`(Schedule `*/5` → 单 Code 节点"Reconcile & Apply")
|
||
- 设计:`n8n/W3-DESIGN.md`;参考实现 `n8n/w3-compute.js`(自包含,read+write 都在 Code 内)
|
||
- **核心逻辑**:对每个有 `alist_role_id` 的 customer → desired = 有效 CP 派生路径 ∪ 有效 AccessGrants 路径(**并集,防止整写 scopes 清掉 manual 授权**)→ 过期排除 → diff → `role/update` 整体重写 → 选项 disable
|
||
- **Env 开关**:`ALIST_AUTO_DISABLE_ON_EXPIRE`(默认 false=只观察)、`W3_DRY_RUN`(true=只打印计划不写)
|
||
- **disable 护栏**:仅当 `nowEmpty && status=active && currentPaths>0`(原本有 scope 现在空)才禁用;空 role 的新客户不动(否则刚开通没买产品就被禁,W2 不会重新启用)
|
||
- **已本地验证**:`n8n/test_w3.js` mock 数据跑通 OFF/ON 两模式,断言全过(manual 授权保留、到期清除、disable 正确、空 role 客户不动)
|
||
- **loop 防护**:只写 AList user disabled + role scopes;**不**翻 NocoDB Customers.status(Customers update webhook→W1 可能 re-provision/re-enable=loop)
|
||
|
||
## 🔴 下一步(按序)
|
||
1. **用户测试 W2**(import 覆盖 → 用例 2、3 → AList 前端实测客户登录看目录)→ 报错贴 execution 错误
|
||
1.5. **W3 测试**:import `W3-expiry-sync.json` → 设 `W3_DRY_RUN=true` 跑一次看计划 → 造过期 CP 观察 scope 移除 → 开关 auto-disable 做 M3(详见 W3-DESIGN 里程碑)
|
||
2. 验证通过后:**expiry scheduler**(每 5 分钟对账:全量 diff NocoDB↔AList,到期的移除 scope)—— 规格 §26/§37
|
||
3. 其余 workflow(W3-W6):Product/Resource 变更 → 重算受影响 customer;手动 sync;日志;通知
|
||
4. 本地 git 留档 → 验证稳定后推 Gitea(git.hoelee.com)
|
||
|
||
## 🐛 已沉淀的坑(勿再踩)
|
||
- **AList**:
|
||
- `user/create` 空 role 数组 → 400 "admin or guest"(源码 `req.Role==0 → 默认 guest` + `IsGuest()` 检查);先建 role 拿 id
|
||
- `role/create`/`update` 返回 `data:null`;create 后要 **list 按 name 定位 id**
|
||
- delete 用 `?id=` query param,不是 body
|
||
- role/list 返回 `data.content[]` + `data.total`(PageResp)
|
||
- GUEST=1 / ADMIN=2 / GENERAL=0;用户级 bit14=16384 = Check path limit
|
||
- **多文件夹授权正确做法(实测确认)**:`base_path="/"` + role scope 用**绝对路径**。base_path 设成
|
||
`/Class` 会导致 scope 绝对路径被 JoinPath 双重前缀 → 全部 403。见 API_INTEGRATION §9.8。
|
||
- 源码位置:`internal/op/user.go` CreateUser / `server/handles/role.go` / `internal/model/roles.go`
|
||
- **NocoDB**(`nocodb-selfhosted` skill 已更新):
|
||
- SingleSelect 不能建表内联选项;要先建空列再 PATCH `{"colOptions":{options},"dtxp":"a,b,c"}`
|
||
- Link 字段用 `type:"bt"`(belongs-to)建,`hm` 会把列名搞乱
|
||
- **Link 字段 where 查询**:`(linkField,eq,id)` 匹配的是 display name,永远返回 0 条。必须用 `(linkField.Id,eq,id)` 子字段语法或外键列 `nc_jitu___*_id`。已验证:`(customer,eq,5)`→0 条;`(customer.Id,eq,5)`→对。
|
||
- **Link API 返回结构**:`/links/{col}/records/{id}` 只返回 `{Id,name}`(display 列),不含业务字段。如需 alist_path 等字段,要按 Id 反查全表
|
||
- base/table/column ID 全在 DATABASE_SCHEMA.md
|
||
- **n8n env 访问(v1.123.72)**(memory 已记):
|
||
- Code 节点内 `$env` ✅(**用户实测可用**,如 `$env.NOCODB_URL`)
|
||
- HTTP 节点表达式字段里 `$env` / `process.env` ❌ 都不行
|
||
- 模式:step 2 放 "Load Env" Code 节点输出全部 env **并透传 payload 字段**,下游 HTTP 用 `$('Load Env').item.json.VAR`
|
||
- step 2 已验证模板:
|
||
```js
|
||
const prev = $input.item.json;
|
||
return { json: {
|
||
customerId: prev.customerId,
|
||
eventType: prev.eventType,
|
||
NOCODB_URL: $env.NOCODB_URL,
|
||
NOCODB_TOKEN: $env.NOCODB_TOKEN,
|
||
T_CUSTOMERS: $env.NOCODB_TABLE_CUSTOMERS,
|
||
ALIST_URL: $env.ALIST_URL,
|
||
ALIST_ADMIN_USERNAME: $env.ALIST_ADMIN_USERNAME,
|
||
ALIST_ADMIN_PASSWORD: $env.ALIST_ADMIN_PASSWORD,
|
||
ALIST_ADMIN_OTP: $env.ALIST_ADMIN_OTP || '',
|
||
} };
|
||
```
|
||
|
||
## 📁 项目结构
|
||
```
|
||
alist-serve-resource/
|
||
├── docker-compose.yml / .env.example / .gitignore
|
||
├── n8n/ W1-customer-provision.json · W2-customerproduct-sync.json · SETUP.md · W2-DESIGN.md · w2-compute.js
|
||
├── scripts/ create_nocodb_schema.py · healthcheck.sh
|
||
└── docs/ API_INTEGRATION.md · DATABASE_SCHEMA.md · INSTALL.md · CONFIGURATION.md · SECURITY.md · BACKUP_RESTORE.md · UPGRADE.md · TROUBLESHOOTING.md · PROJECT_STATE.md
|
||
```
|
||
|
||
## 待确认的设计点
|
||
1. W2 当前不写 AccessGrants 表 —— 是否在验证后补物化写入?(避免 AccessGrant→n8n loop 的前提下,由 W2 单向写)
|
||
2. 密码:客户可自改 AList 密码 → NocoDB plaintext 会失真;当前不强制回写(用户已选方案 C)
|
||
3. 对账/到期只靠 n8n 定时扫描(AList 无 expiry 概念)—— 已确认 |