# SaaS + 本地数据库架构方案

> 日期：2026-08-02
> 目标：应用 SaaS 化部署在服务器，数据库留在客户本机，保证用户对数据安全性的要求

## 一、设计原则

- **可维护性**：一套应用代码，统一部署在服务器，统一升级
- **数据安全性**：业务数据始终在客户本机，客户完全掌控，服务器不存业务数据
- **多租户**：支持多客户接入，每客户独立数据库，互不可见

## 二、目标架构

```
租户A用户 ─┐
租户B用户 ─┤→ Nginx(服务器) → 后端(服务器:3333) → 识别租户 → 连对应frp端口 → 客户本机PostgreSQL
租户C用户 ─┘
```

### 数据流向

```
浏览器 → https://dm.all8ai.top
       → Nginx 反向代理 /api/ → 127.0.0.1:3333 (服务器后端)
       → 后端解析 tenant_id
       → 查 tenant_configs 表获取该租户的 frp 端口
       → 连接 127.0.0.1:16xxx (frp 隧道)
       → frp 转发到客户本机 127.0.0.1:5432 (PostgreSQL)
       → 返回数据
```

## 三、组件部署位置

| 组件 | 位置 | 说明 |
|------|------|------|
| 前端(静态文件) | 服务器 | Nginx 托管，统一部署 |
| 后端(Node.js) | 服务器 | 统一维护，一套代码服务所有租户 |
| 服务器主库(PostgreSQL) | 服务器 | 仅存租户配置、用户账号、frp端口映射，不存业务数据 |
| 业务数据库(PostgreSQL) | 客户本机 | 每个租户自己的数据库，数据完全在客户侧 |
| frps(frp服务端) | 服务器 | 接收各租户 frp 客户端连接 |
| frpc(frp客户端) | 客户本机 | 每个租户运行自己的 frpc，暴露本机 PostgreSQL |

## 四、多租户路由机制

### 4.1 服务器主库表结构

```sql
-- 服务器主库：仅存租户配置，不存业务数据
CREATE TABLE tenant_configs (
  tenant_id      VARCHAR(50) PRIMARY KEY,
  tenant_name    VARCHAR(100) NOT NULL,
  db_host        VARCHAR(100) NOT NULL DEFAULT '127.0.0.1',
  db_port        INTEGER NOT NULL,           -- frp 分配的端口，如 16001
  db_name        VARCHAR(100) NOT NULL DEFAULT 'bill_query',
  db_user        VARCHAR(50) DEFAULT 'postgres',
  db_password    VARCHAR(100) DEFAULT '',
  frp_port       INTEGER NOT NULL,           -- frp 远程端口
  status         VARCHAR(20) DEFAULT 'inactive',  -- active/inactive
  created_at     TIMESTAMP DEFAULT NOW(),
  updated_at     TIMESTAMP DEFAULT NOW()
);

CREATE TABLE tenant_users (
  id         SERIAL PRIMARY KEY,
  tenant_id  VARCHAR(50) REFERENCES tenant_configs(tenant_id),
  username   VARCHAR(50) NOT NULL,
  password   VARCHAR(200) NOT NULL,
  role       VARCHAR(20) NOT NULL,           -- hq/regional/store/dept
  name       VARCHAR(50),
  UNIQUE(tenant_id, username)
);
```

### 4.2 后端中间件：租户识别 + 动态DB连接

```typescript
// tenant.middleware.ts
// 从 JWT token 中解析 tenant_id
// 根据 tenant_id 查 tenant_configs 获取 DB 连接信息
// 动态创建/复用 pg.Pool 连接到对应租户的数据库

interface TenantContext {
  tenantId: string
  dbPool: pg.Pool
}

// 请求进来 → 解析 token → 获取 tenant_id → 查配置 → 连对应DB
// 不同租户的 DB 连接完全隔离，端口不同
```

### 4.3 frp 多租户配置

服务器 frps 配置（`/etc/frp/frps.toml`）：

```toml
bindAddr = "0.0.0.0"
bindPort = 7000
auth.token = "frp_sbrain_2026"
```

租户A本机 frpc 配置：

```toml
serverAddr = "dm.all8ai.top"
serverPort = 7000
auth.token = "frp_sbrain_2026"

[[proxies]]
name = "tenant-a-db"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5432
remotePort = 16001
```

租户B本机 frpc 配置：

```toml
serverAddr = "dm.all8ai.top"
serverPort = 7000
auth.token = "frp_sbrain_2026"

[[proxies]]
name = "tenant-b-db"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5432
remotePort = 16002
```

每个租户分配独立的远程端口（16001、16002、16003...）。

## 五、数据安全保证

1. **数据在客户侧**：业务数据始终在客户本机 PostgreSQL，不出局域网
2. **传输加密**：frp 隧道支持 TLS 加密，数据传输安全
3. **物理隔离**：不同租户的数据库连接端口不同，进程隔离，一个租户的查询不可能泄漏到另一个
4. **客户可控**：客户可随时关闭 frpc 断开连接，服务器立即无法访问该租户数据
5. **服务器零业务数据**：服务器只存应用代码 + 租户路由配置，不存任何业务数据
6. **审计可追溯**：所有 DB 操作通过后端 API，可记录审计日志

## 六、客户接入流程

```
1. 管理员在服务器后台创建租户
   → 分配 tenant_id（如 tenant_a）
   → 分配 frp 远程端口（如 16001）
   → 生成 frpc 配置文件

2. 发送 frpc 配置给客户

3. 客户本机准备
   → 安装 PostgreSQL
   → 创建数据库 bill_query
   → 导入初始数据（pg_restore）
   → 安装 frpc 并启动

4. 服务器后端检测到 frp 连接
   → tenant_configs.status 更新为 active
   → 该租户用户可登录使用

5. 客户用户访问
   → 浏览器打开 https://dm.all8ai.top
   → 登录（绑定 tenant_id）
   → API 请求自动路由到该租户的本机数据库
```

## 七、与当前架构对比

| 维度 | 当前架构 | 目标架构 |
|------|---------|---------|
| 后端 | 本机 localhost:3333 | 服务器 |
| 前端 | 服务器(静态文件) | 服务器(不变) |
| 业务DB | 本机 PostgreSQL | 客户本机 PostgreSQL(每租户独立) |
| frp 隧道 | 1条(API 3333→13333) | N条(DB 5432→16xxx)，每租户1条 |
| 多租户 | 不支持 | 支持，按租户路由DB |
| 本机依赖 | 必须开着 | 不需要(服务器独立运行) |
| 数据安全 | 无隔离 | 客户数据在客户侧，服务器零业务数据 |
| 后端部署 | 改完即生效(tsx watch) | deploy-server.sh(rsync + 重启) |
| 可维护性 | 仅单租户 | 一套代码服务所有租户，统一升级 |

## 八、实施计划

### 一期：单租户迁移（当前系统 → 服务器后端 + 本机DB）

| 步骤 | 内容 | 说明 |
|------|------|------|
| 1 | 服务器安装 Node.js 24 + npm | 后端运行环境 |
| 2 | 服务器安装 PostgreSQL | 仅作主库，存租户配置 |
| 3 | frp 改为暴露 PostgreSQL | 新增 dm-db 代理(5432→15432) |
| 4 | 后端部署到服务器 | rsync 代码 + npm install + pm2 管理 |
| 5 | server/.env 改 DB_PORT=15432 | 连 frp 暴露的数据库端口 |
| 6 | Nginx 配置调整 | /api/ → 127.0.0.1:3333(不再走13333) |
| 7 | 验证 | 线上前端 → 服务器后端 → frp隧道 → 本机DB |

### 二期：多租户支持

| 步骤 | 内容 | 说明 |
|------|------|------|
| 1 | 创建 tenant_configs / tenant_users 表 | 服务器主库 |
| 2 | 后端加租户识别中间件 | JWT 解析 tenant_id |
| 3 | 后端加动态 DB 连接池 | 按租户连不同端口 |
| 4 | 前端登录绑定租户 | 登录页增加租户标识 |
| 5 | frps 多租户端口管理 | 每租户独立端口 |
| 6 | 租户管理后台 | 创建租户、查看连接状态、分配端口 |

### 三期：商业化运营

| 步骤 | 内容 | 说明 |
|------|------|------|
| 1 | 客户自助注册 | 自动分配 tenant_id + frp 端口 |
| 2 | frpc 一键安装脚本 | 客户本机自动配置 |
| 3 | 按租户计费 | 用量统计、账单 |
| 4 | 审计日志 | 记录所有 DB 操作 |
| 5 | 数据备份方案 | 客户本机自动备份 |

## 九、部署脚本

### 9.1 后端部署脚本（deploy-server.sh）

```bash
#!/bin/bash
# 部署后端到服务器
SERVER="ubuntu@dm.all8ai.top"
REMOTE_DIR="/opt/sbrain-server"

echo "=== 部署后端到服务器 ==="

# 1. 同步代码（排除 node_modules）
rsync -avz --exclude='node_modules' --exclude='.env' \
  server/ $SERVER:$REMOTE_DIR/

# 2. 安装依赖
ssh $SERVER "cd $REMOTE_DIR && npm install --production"

# 3. 重启服务（pm2）
ssh $SERVER "cd $REMOTE_DIR && pm2 restart sbrain-server || pm2 start 'npm run start' --name sbrain-server"

echo "=== 部署完成 ==="
```

### 9.2 前端部署脚本（deploy.sh，保持不变）

```bash
# 前端构建 + scp 上传，与当前流程一致
bash deploy.sh
```

### 9.3 客户端 frpc 安装脚本（给新租户）

```bash
#!/bin/bash
# 客户本机一键安装 frpc
TENANT_ID=$1
FRP_PORT=$2
FRP_TOKEN="frp_sbrain_2026"

cat > /etc/frp/frpc.toml << EOF
serverAddr = "dm.all8ai.top"
serverPort = 7000
auth.token = "$FRP_TOKEN"

[[proxies]]
name = "${TENANT_ID}-db"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5432
remotePort = $FRP_PORT
EOF

# 启动 frpc
frpc -c /etc/frp/frpc.toml &
```

## 十、风险与应对

| 风险 | 影响 | 应对 |
|------|------|------|
| 客户本机休眠/关机 | 该租户 API 不可用 | 前端显示"数据库离线"提示；建议客户保持开机 |
| frp 隧道断开 | 该租户 API 不可用 | frpc 配置 `loginFailExit = false` 自动重连 |
| 服务器后端重启 | 所有租户短暂不可用 | pm2 自动重启；健康检查 |
| 客户本机 PostgreSQL 崩溃 | 该租户数据不可用 | 客户侧责任，提供备份建议文档 |
| 网络延迟 | API 响应慢 | frp TCP 隧道延迟通常 <50ms，可接受 |

## 十一、服务器资源需求

| 资源 | 最低配置 | 推荐配置 |
|------|---------|---------|
| CPU | 2核 | 4核 |
| 内存 | 2GB | 4GB |
| 磁盘 | 10GB | 20GB |
| 带宽 | 5Mbps | 10Mbps |
| Node.js | v24 | v24 |
| PostgreSQL | 15(主库) | 15(主库) |

当前服务器 `152.136.182.184` 需确认配置是否满足。
