diff --git a/deploy-server.sh b/deploy-server.sh new file mode 100644 index 0000000..f721fcd --- /dev/null +++ b/deploy-server.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# ============================================================ +# 部署后端到 dm.all8ai.top 服务器 +# 用法: bash deploy-server.sh +# ============================================================ + +set -e + +SERVER_IP="152.136.182.184" +SERVER_USER="ubuntu" +REMOTE_DIR="/opt/sbrain-server" + +PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" +SERVER_DIR="$PROJECT_DIR/server" + +echo "==========================================" +echo " 部署后端到 dm.all8ai.top" +echo "==========================================" +echo "" + +# 1. 创建远程目录 +echo "[1/5] 准备远程目录..." +ssh "$SERVER_USER@$SERVER_IP" "mkdir -p $REMOTE_DIR" +echo "✓ 目录就绪" +echo "" + +# 2. 同步代码(排除 node_modules、.env、dist) +echo "[2/5] 同步代码..." +rsync -avz --delete \ + --exclude='node_modules' \ + --exclude='.env' \ + --exclude='dist' \ + --exclude='.git' \ + "$SERVER_DIR/" "$SERVER_USER@$SERVER_IP:$REMOTE_DIR/" +echo "✓ 代码同步完成" +echo "" + +# 3. 安装依赖 +echo "[3/5] 安装依赖..." +ssh "$SERVER_USER@$SERVER_IP" "cd $REMOTE_DIR && npm install --production" +echo "✓ 依赖安装完成" +echo "" + +# 4. 创建 .env(服务器专用配置) +echo "[4/5] 写入服务器配置..." +ssh "$SERVER_USER@$SERVER_IP" "cat > $REMOTE_DIR/.env << 'ENVEOF' +DB_HOST=127.0.0.1 +DB_PORT=15432 +DB_NAME=bill_query +DB_USER=freedak +DB_PASSWORD= +DB_POOL_MAX=10 + +PORT=9333 +JWT_SECRET=malan-ops-2026-secret-key +CLIENT_URL=https://dm.all8ai.top +ZHIPU_API_KEY=sk-c0c5174892c44ff48d587cd040fbdd40 +ENVEOF" +echo "✓ 配置写入完成" +echo "" + +# 5. 用 pm2 重启后端 +echo "[5/5] 重启后端服务..." +ssh "$SERVER_USER@$SERVER_IP" "cd $REMOTE_DIR && pm2 delete sbrain-server 2>/dev/null; pm2 start 'npx tsx src/index.ts' --name sbrain-server && pm2 save" +echo "✓ 后端已启动" +echo "" + +# 验证 +echo "==========================================" +echo " 部署成功!" +echo " 后端运行在服务器 $SERVER_IP:9333" +echo " 日志: ssh $SERVER_USER@$SERVER_IP 'pm2 logs sbrain-server'" +echo "==========================================" diff --git a/docs/20260802-SaaS+本地数据库.md b/docs/20260802-SaaS+本地数据库.md new file mode 100644 index 0000000..2bc9c55 --- /dev/null +++ b/docs/20260802-SaaS+本地数据库.md @@ -0,0 +1,296 @@ +# 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` 需确认配置是否满足。 diff --git a/run.md b/run.md index ed3f1c8..96659b9 100644 --- a/run.md +++ b/run.md @@ -9,44 +9,87 @@ | 潘家园店长 | 123 | store | | 商品部 | 123 | dept | -## 本地开发启动 +## 架构(2026-08-02 更新:SaaS + 本地数据库) -后端服务和 frp 隧道已配置为 macOS launchd 系统级服务,开机自动启动,崩溃自动重启,与 IDE 无关。 +``` +浏览器 → https://dm.all8ai.top + → Nginx(服务器) → /api/ → 127.0.0.1:9333 (服务器后端, pm2管理) + → frp隧道 127.0.0.1:15432 → 本机PostgreSQL:5432 +``` -### 系统级服务(launchd) +- **前端**:服务器 Nginx 静态文件 `/var/www/dm.all8ai.top` +- **后端**:服务器 `/opt/sbrain-server`,pm2 管理,端口 9333 +- **数据库**:本机 PostgreSQL 15,端口 5432,数据库 bill_query +- **frp 隧道**:暴露 PostgreSQL(5432→15432),服务器后端通过 15432 连接本机数据库 -已创建两个 LaunchAgent: +## 部署流程 + +### 前端部署 + +```bash +bash deploy.sh +``` + +构建前端 + scp 上传到服务器 `/var/www/dm.all8ai.top`。 + +### 后端部署 + +```bash +bash deploy-server.sh +``` + +rsync 同步代码到服务器 `/opt/sbrain-server` + npm install + pm2 重启。 + +### 本机 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 | 日志 | |------|-------|------| -| 后端 (localhost:3333) | `~/Library/LaunchAgents/com.sbrain.backend.plist` | `~/Library/Logs/sbrain-backend.log` | -| frp 隧道 (13333→3333) | `~/Library/LaunchAgents/com.frp.client.plist` | `~/Library/Logs/frpc.log` | - -两个服务均配置了 `KeepAlive: true`,崩溃后自动重启。开机/登录后自动启动,关闭 IDE 和终端不影响运行。 +| frp 隧道 (5432→15432) | `~/Library/LaunchAgents/com.frp.client.plist` | `~/Library/Logs/frpc.log` | #### 管理命令 ```bash -# 停止服务 -launchctl unload ~/Library/LaunchAgents/com.sbrain.backend.plist -launchctl unload ~/Library/LaunchAgents/com.frp.client.plist +# 停止 frpc +launchctl bootout gui/$(id -u)/com.frp.client -# 启动服务 -launchctl load ~/Library/LaunchAgents/com.sbrain.backend.plist +# 启动 frpc launchctl load ~/Library/LaunchAgents/com.frp.client.plist # 查看日志 -tail -f ~/Library/Logs/sbrain-backend.log tail -f ~/Library/Logs/frpc.log # 检查状态 -curl -s http://localhost:3333/api/health pgrep -l frpc +curl -s https://dm.all8ai.top/api/health ``` -### 手动启动(备用,launchd 未运行时) +#### 手动启动 frpc(备用,launchd 未运行时) -#### 1. 启动后端服务 +```bash +cd /Users/freedak/frp && ./frpc -c frpc.toml & +``` + +## 本地开发 + +### 后端开发 ```bash cd server && npm run dev @@ -54,35 +97,38 @@ cd server && npm run dev 后端运行在 `http://localhost:3333`,连接本地 PostgreSQL(`localhost:5432`,数据库 `bill_query`)。 -#### 2. 启动 frp 隧道 - -线上 Nginx 将 `/api/` 代理到远程 `127.0.0.1:13333`,通过 frp tcp 代理转发到本地后端 3333 端口: - -frpc 配置(`/Users/freedak/frp/frpc.toml`)中已包含 dm-api 代理: - -```toml -[[proxies]] -name = "dm-api" -type = "tcp" -localIP = "127.0.0.1" -localPort = 3333 -remotePort = 13333 -``` - -重启 frpc: +### 前端开发 ```bash -kill -9 $(pgrep frpc); cd /Users/freedak/frp && ./frpc -c frpc.toml & +cd client && npm run dev ``` -### 访问 +访问 `http://localhost:5173`(Vite proxy 自动转发 `/api` 到 `localhost:3333`)。 -- **线上前端**:`https://dm.all8ai.top`(API请求通过 frp 隧道转发到本地后端) -- **本地前端开发**:`cd client && npm run dev`,访问 `http://localhost:5173`(Vite proxy 自动转发 `/api` 到 `localhost:3333`) +## 服务器后端管理 -### 注意事项 +```bash +# 查看后端状态 +ssh ubuntu@152.136.182.184 'pm2 status sbrain-server' -- 本地 PostgreSQL 端口 5432 被 Homebrew PostgreSQL 占用,数据库 `bill_query` 在本地 -- frp 隧道将远程 13333 映射到本地 3333,服务器 Nginx 配置 `/api/` → `127.0.0.1:13333` -- launchd 服务开机自启,关闭 IDE/终端不影响;仅注销/关机时停止,下次登录/开机自动恢复 -- `server/.env` 中 `DB_PORT=5432` 连接本地数据库,不要改为 5433 \ No newline at end of file +# 查看后端日志 +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' +``` + +## 注意事项 + +- 本机 PostgreSQL 端口 5432 被 Homebrew PostgreSQL 占用,数据库 `bill_query` 在本地 +- frp 隧道将远程 15432 映射到本地 5432,服务器后端通过 15432 连接本机数据库 +- 本机只需运行 frpc(launchd 自启),后端已部署到服务器 +- 修改后端代码后执行 `bash deploy-server.sh` 部署到服务器 +- 修改前端代码后执行 `bash deploy.sh` 部署到服务器 +- `server/.env`(本机开发用)中 `DB_PORT=5432` 连接本地数据库,不要改为 5433 +- 服务器 `/opt/sbrain-server/.env` 中 `DB_PORT=15432` 连接 frp 隧道端口 +- frpc 断开后线上前端 API 会失效,需重新启动 frpc +- launchd 服务开机自启,关闭 IDE/终端不影响;仅注销/关机时停止,下次登录/开机自动恢复 \ No newline at end of file