当前数据库是本机Homebrew PostgreSQL 15，数据库名为 bill_query，端口5432，仅监听 localhost；当前用户 freedak 是超级用户，本机认证方式为 trust。这适合本机开发，但不适合直接照搬到服务器生产环境。前端浏览器也不应直接连接PostgreSQL，必须通过后端API访问。

## 登录账号

### 平台管理员

| 用户名 | 密码 | 角色 | 说明 |
|--------|------|------|------|
| 平台管理员 | 123 | platform_admin | 管理所有租户，登录后进入平台管理后台 |

### 租户用户（存储在服务器 sbrain_admin 数据库）

| 用户名 | 密码 | 角色 | 租户 |
|--------|------|------|------|
| 总部管理员 | 123 | hq | tenant_a（西部马华） |

> 租户管理员（hq）登录后可在系统内创建区域经理、店长、商品部等角色用户。
> 密码使用 bcrypt 加密存储。

## 架构（2026-08-02 更新：SaaS 多租户）

```
浏览器 → https://dm.all8ai.top
       → Nginx(服务器) → /api/ → 127.0.0.1:9333 (服务器后端, pm2管理)
       → 服务器本地 PostgreSQL:5432 → sbrain_admin（平台库：租户配置、用户认证）
       → frp隧道 127.0.0.1:15432 → 本机PostgreSQL:5432 → bill_query（租户业务库）
```

### 数据库连接（重要）

后端连接两个不同的数据库，走不同端口：

| 连接池 | 目标数据库 | 连接地址 | 认证方式 | 配置来源 |
|--------|-----------|---------|---------|---------|
| `adminPool` | `sbrain_admin`（平台库） | `127.0.0.1:5432`（服务器本地 PG） | scram-sha-256（用户 `sbrain_admin`，密码 `sbrain2026`） | `.env` 中 `ADMIN_DB_*` |
| `tenantPool` | `bill_query`（业务库） | `127.0.0.1:15432`（FRP 隧道 → 本机 PG） | trust（用户 `freedak`，无需密码） | `tenant_configs.db_port=15432` |
| `pool` | `bill_query`（业务库） | `127.0.0.1:15432`（FRP 隧道 → 本机 PG） | trust（用户 `freedak`，无需密码） | `.env` 中 `DB_*` |

> ⚠️ `tenant_configs` 表中 `db_port` 必须为 `15432`（FRP 隧道），`db_password` 为 NULL（trust 认证）。
> ⚠️ `adminPool` 连接服务器本地 5432，需要 `ADMIN_DB_PASSWORD=sbrain2026`。

### 数据库分布

| 数据库 | 位置 | 存储内容 |
|--------|------|---------|
| `sbrain_admin` | 服务器本地 PostgreSQL（5432） | 平台管理员、租户配置（tenant_configs）、所有租户用户（tenant_users） |
| `bill_query` | 本机 PostgreSQL（通过 frp 暴露为 15432） | 租户业务数据（销售、任务、成本分析等） |

### 多租户数据隔离

- **认证流程**：用户登录 → 查 `sbrain_admin.tenant_users` → bcrypt 验证 → JWT 含 `tenantId`
- **数据隔离**：业务请求 → `tenantDbMiddleware` 根据 JWT 中的 `tenantId` 查 `tenant_configs` 获取该租户数据库连接信息 → `AsyncLocalStorage` 设置租户连接池 → 所有 `query()` 自动走对应租户数据库
- **租户用户管理**：平台管理员为租户创建管理员（hq），租户管理员在系统内创建区域经理/店长/商品部等用户，均存储在 `sbrain_admin.tenant_users` 表中，通过 `tenant_id` 字段区分

### 关键文件

| 文件 | 职责 |
|------|------|
| `server/src/config/tenant-db.ts` | 租户连接池缓存 + AsyncLocalStorage |
| `server/src/config/admin-pool.ts` | 平台库连接池（通过 `ADMIN_DB_*` 环境变量配置） |
| `server/src/config/database.ts` | 主业务库连接池 + 全局 `query()` 函数（自动注入数据权限） |
| `server/src/middleware/tenant-db.ts` | 根据 JWT tenantId 动态切换数据库 + 注入用户 scope |
| `server/src/middleware/data-scope.ts` | 数据权限工具函数（`getDataScope` / `scopeStoreFilter`） |
| `server/src/routes/auth.ts` | 租户用户登录（查 tenant_users + bcrypt） |
| `server/src/routes/admin.ts` | 平台管理 API + 租户内用户管理 API |
| `server/sql/11_tenant_management.sql` | 平台数据库建表 SQL |

## 部署流程

### 一键部署（前端+后端）

```bash
bash deploy.sh          # 全量部署（后端+前端）
bash deploy.sh server   # 只部署后端
bash deploy.sh front    # 只部署前端
```

- 后端：rsync 同步到服务器 `/opt/sbrain-server` + npm install + pm2 重启
- 前端：构建 + scp 上传到服务器 `/var/www/dm.all8ai.top`

### 本机 frp 隧道（launchd 系统级服务）

frpc 配置（`/Users/freedak/frp/frpc.toml`）：

```toml
serverAddr = "152.136.182.184"
serverPort = 7000
loginFailExit = false
auth.token = "frp_gitea_2026"

[[proxies]]
name = "dm-db"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5432
remotePort = 15432
```

已配置为 macOS launchd 系统级服务，开机自动启动，崩溃自动重启，与 IDE 无关。

| 服务 | plist | 日志 |
|------|-------|------|
| frp 隧道 (5432→15432) | `~/Library/LaunchAgents/com.frp.client.plist` | `~/Library/Logs/frpc.log` |

#### 管理命令

```bash
# 停止 frpc
launchctl bootout gui/$(id -u)/com.frp.client

# 启动 frpc
launchctl load ~/Library/LaunchAgents/com.frp.client.plist

# 查看日志
tail -f ~/Library/Logs/frpc.log

# 检查状态
pgrep -l frpc
curl -s https://dm.all8ai.top/api/health
```

#### 手动启动 frpc（备用，launchd 未运行时）

```bash
cd /Users/freedak/frp && ./frpc -c frpc.toml &
```

## 本地开发

### 后端开发

```bash
cd server && npm run dev
```

后端运行在 `http://localhost:3333`，连接本地 PostgreSQL（`localhost:5432`，数据库 `bill_query`）。

### 前端开发

```bash
cd client && npm run dev
```

访问 `http://localhost:5173`（Vite proxy 自动转发 `/api` 到 `localhost:3333`）。

## 服务器后端管理

```bash
# 查看后端状态
ssh ubuntu@152.136.182.184 'pm2 status sbrain-server'

# 查看后端日志
ssh ubuntu@152.136.182.184 'pm2 logs sbrain-server --lines 50'

# 重启后端
ssh ubuntu@152.136.182.184 'pm2 restart sbrain-server'

# 查看后端 .env
ssh ubuntu@152.136.182.184 'cat /opt/sbrain-server/.env'
```

## 注意事项

- **平台库 `sbrain_admin` 在服务器本地 PostgreSQL（5432）**，业务库 `bill_query` 在本机 PostgreSQL（通过 frp 暴露为 15432）
- `adminPool` 连服务器本地 5432 的 `sbrain_admin`（SCRAM 认证，用户 `sbrain_admin`，密码 `sbrain2026`）
- `tenantPool` / `pool` 连 FRP 隧道 15432 的 `bill_query`（trust 认证，用户 `freedak`，无需密码）
- `tenant_configs` 表中 `db_port` 必须为 `15432`，`db_password` 为 NULL（trust 认证）
- `pg` 库在 `password` 为空字符串时会触发 SCRAM 认证失败，因此 `admin-pool.ts`、`database.ts`、`tenant-db.ts` 中空密码时不传 `password` 参数
- `deploy.sh` 每次部署会覆盖服务器 `.env`，其中包含 `ADMIN_DB_*` 配置（`ADMIN_DB_PORT=5432`，`ADMIN_DB_PASSWORD=sbrain2026`），修改 `.env` 配置必须同步修改 `deploy.sh`
- 租户用户统一存储在 `sbrain_admin.tenant_users` 表，通过 `tenant_id` 区分租户
- `tenant_users.region` 字段为 `VARCHAR(500)`，区域经理的 `region` 存储逗号分隔的 `store_code` 列表（如 `0010,0011,0014`）
- 密码使用 bcrypt 加密，平台管理员密码目前仍为明文（后续可改）
- 本机只需运行 frpc（launchd 自启），后端已部署到服务器
- 修改后端代码后执行 `bash deploy.sh server` 部署到服务器
- 修改前端代码后执行 `bash deploy.sh front` 部署到服务器
- 全量部署执行 `bash deploy.sh`（后端+前端）
- `server/.env`（本机开发用）中 `DB_PORT=5432` 连接本地数据库，不要改为 5433
- 服务器 `/opt/sbrain-server/.env` 中 `DB_PORT=15432` 连接 frp 隧道端口
- frpc 断开后线上前端 API 会失效，需重新启动 frpc
- launchd 服务开机自启，关闭 IDE/终端不影响；仅注销/关机时停止，下次登录/开机自动恢复