# CloudBase 鉴权迁移实战（2026-07-25）

## 背景

将本地 `127.0.0.1:8765` 销售工作台迁移到腾讯 CloudBase 静态托管，实现全球 CDN 加速、自动 HTTPS、飞书/钉钉可嵌入、鉴权不再硬编码。

## 架构

```
CloudBase 匿名登录 → cbDb.collection('user_roles').where({name}).get()
  → 验证 password 字段 → 返回 {name, role, dept, tabs, email}
  → state.user = {name, role, dept, tabs, email}
  → localStorage 持久化登录态
```

## user_roles 集合结构（23人）

```json
{"name":"刘新元","dept":"诊断原料销售部","role":"admin","password":"liuxinyuan","status":"在职",
 "tabs":{"dashboard":1,"customers":1,"visitPrep":1,"oppSandbox":1,"complaints":1,"orders":1,"myActivities":1,"todos":1},
 "pwd":"liuxinyuan",
```

## 角色权限模型

| role | 数据范围 | Tab可见 |
|------|---------|---------|
| admin | 全公司 | 全部 |
| manager | 本部门（dept字段过滤） | 全部（含看板） |
| sales | 本人名下（salesperson字段过滤） | 隐藏看板（tabs.dashboard=0） |

## 数据过滤逻辑

```javascript
function getFilteredCustomers() {
  var role = state.user.role, name = state.user.name, dept = state.user.dept;
  if (role === 'admin') return custs;
  if (role === 'manager') return custs.filter(function(c) { return c.dept === dept; });
  return custs.filter(function(c) { return c.salesperson === name; });
}
```

## 9 大实战陷阱（2026-07-25 验证全流程）

### 陷阱 0: 🔴 CloudBase 自动隐藏 `password` 字段（最隐蔽）
**现象**: 登录永远\"密码错误\"，数据库查询显示 `password: ***`。
**原因**: CloudBase 数据库安全规则将字段名含 `password` 的值自动替换为 `***`。查询成功返回数据，但 `user.password` 的值为 `"***"`（三个星号字符串），永远不等于实际密码。
**诊断**: 浏览器 console 直接查：`cbDb.collection('user_roles').where({name:'刘新元'}).get()` → 看 password 字段
**修复**: 
1. 数据库字段重命名为 `pwd`：`"$rename": {"password": "pwd"}`
2. 代码同步：`if (user.pwd !== pwd)`
3. 所有涉及创建新用户的代码都用 `pwd` 而非 `password`

### 陷阱 1: loadUserList 必须在匿名登录后调用
**现象**: 下拉框为空白（0 选项）
**原因**: CloudBase SDK 数据库操作需要认证上下文。匿名登录完成前 `cbDb.collection().get()` 静默返回空数组。
**修复**: `loadUserList()` 必须在 `auth.anonymousAuthProvider().signIn().then()` 块内调用。

### 陷阱 2: 空数据 ≠ .catch()
**现象**: 下拉仍空，无 fallback
**原因**: `res.data = []` 时 `.then()` 执行但 `users.length === 0`，不会触发 `.catch()`。
**修复**: `.then()` 内显式判断 `if (users.length === 0) { showFallbackUsers(); return; }`

### 陷阱 3: user_roles 集合权限必须"所有用户可读"
**现象**: 匿名用户查询 `user_roles` 得到空数据（已知集合有 23 条文档）
**原因**: CloudBase nosql 默认权限为"仅创建者可读写"。tcb CLI 创建的文档属于 CLI 用户，匿名用户无读权限。
**修复**: 控制台 → 数据库 → user_roles → 权限设置 → 「所有用户可读，仅创建者可写」

### 陷阱 4: AUTH 引用全局替换（最常见崩溃原因）
**现象**: `state.error = "Cannot read properties of undefined (reading 'name')"`
**检查命令**: `grep -n 'AUTH\[' xxx.html` — 任何残留引用都会导致崩溃
**必须替换的模式**:
| 旧 | 新 |
|---|---|
| `AUTH[state.user].name` | `(state.user||{}).name` |
| `AUTH[state.user].label` | `(state.user||{}).name` |
| `AUTH[role].name` (getKPI) | `state.user.name` |
| `AUTH[k].admin` | `window._allUsers[i].role !== 'admin'` |
| `state.user === 'gm'` | `state.user.role === 'admin'` |
| `localStorage.setItem(ROLE_KEY, role)` | `localStorage.setItem(ROLE_KEY, JSON.stringify(state.user))` |

### 陷阱 5: CloudBase HTTP 触发器无法 CLI 创建
**现象**: `tcb fn trigger create` 报"目前仅支持定时触发器（timer）"
**workaround**: Cloudflare Tunnel 暴露本地服务器作为公网 OAuth 端点：
```bash
cloudflared tunnel --url http://localhost:8765
# → https://xxx.trycloudflare.com 作为公网 URL
# ⚠️ 临时 URL 重启变化，正式环境用命名隧道或 Tailscale Funnel
```

### 陷阱 6: 飞书应用 URL 不能含中文
**现象**: 飞书开放平台校验"请填写有效的 HTTP/HTTPS URL"
**原因**: CloudBase 静态托管路径 `/工具/index.html` 含中文
**修复**: 部署根目录版本 — `tcb hosting deploy xxx.html index.html -e {env}`

### 陷阱 7: 体验版用户配额限制
**现象**: `tcb user create` 第三个用户起报"超过配额限制"
**修复**: 用匿名登录 + user_roles 数据库验证，不占用授权用户数。所有 23 人通过数据库管理。

### 陷阱 8: JSON 配置写入工具
**现象**: `tcb db nosql execute --command` 多层嵌套 JSON 引号转义困难
**修复**: 用 Python 生成 JSON 命令 → 写临时文件 → `"$(cat file.json)"` 传参。示例：
```python
cmd = [{"TableName":"user_roles","CommandType":"INSERT","Command":json.dumps({...}, ensure_ascii=False)}]
with open('cmd.json','w') as f: json.dump(cmd, f)
```

## 权限管理页面工作流

1. 部署 `admin.html`（独立页面，密码 `biorigm`）
2. 管理员调整角色 + Tab 权限 → 保存 → JSON 复制
3. Agent 接收 JSON → Python 批量生成 MgoCommands → tcb db nosql execute 写入
4. 用户刷新工作台 → loadUserList() 拉取最新权限

## 飞书 OAuth 免登流程（Step 5）

```
飞书 App → redirect with ?code=xxx
  → feishuAutoLogin(code)
  → POST {TUNNEL_URL}/api/feishu/auth {code}
  → Python: app_access_token → code 换 name
  → 前端查 user_roles → finishLogin(user)
```

- Python 端点: `脚本/销售工作台服务.py` 的 `/api/feishu/auth` 路由
- 前端 OAuth 检测: init() 中 `new URLSearchParams(window.location.search).get('code')`
