Files
SBrainCO/docs/20260802-SaaS+本地数据库.md
freedakgmail c3fbe4ab81 feat: SaaS架构改造 - 后端部署到服务器, 数据库留本机用frp隧道连接
- 新增 deploy-server.sh: 后端部署脚本(rsync + npm install + pm2)
- frp 隧道从暴露API(3333→13333)改为暴露PostgreSQL(5432→15432)
- 后端部署到服务器 /opt/sbrain-server, pm2管理, 端口9333
- Nginx /api/ 从 127.0.0.1:13333 改为 127.0.0.1:9333
- 服务器 .env: DB_PORT=15432 连接frp隧道
- run.md 全面更新为新架构说明
- 新增 docs/20260802-SaaS+本地数据库.md 架构方案文档
- 移除本机后端 launchd 服务(后端已上服务器)
2026-08-02 18:12:15 +08:00

9.3 KiB
Raw Permalink Blame History

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 服务器主库表结构

-- 服务器主库:仅存租户配置,不存业务数据
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连接

// 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):

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

租户A本机 frpc 配置:

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 配置:

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

#!/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,保持不变)

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

9.3 客户端 frpc 安装脚本(给新租户)

#!/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 需确认配置是否满足。