# Node.js 后端 - SAE 容器部署完全指南 **文档版本**: v1.1 (修复 Prisma 构建和数据库同步问题) **创建时间**: 2025-12-13 **最后修订**: 2025-12-13 **适用范围**: AIclinicalresearch 平台 - Node.js 后端服务 **目标读者**: 运维工程师、后端开发工程师 **部署目标**: 阿里云 SAE(Serverless 应用引擎)容器部署 **v1.1 更新日志**: - 🛑 修复:Prisma 目录构建上下文问题(增加预处理步骤) - 🛑 修复:生产环境缺少 Prisma CLI 导致迁移失败 - 🔥 新增:针对开发历史不规范的"反向同步"流程(关键) - ✅ 优化:数据库迁移策略(pg_dump 导入后不执行 migrate) - ✅ 简化:移除 Init Container 方案,改用启动命令 --- ## 📋 目录 1. [为什么选择 SAE 容器部署](#1-为什么选择-sae-容器部署) 2. [部署前准备](#2-部署前准备) 3. [后端服务分析](#3-后端服务分析) 4. [🔥 Prisma 反向同步(必读)](#4-prisma-反向同步必读) 5. [构建 Docker 镜像](#5-构建-docker-镜像) 6. [本地测试验证](#6-本地测试验证) 7. [推送到 ACR](#7-推送到-acr) 8. [SAE 应用配置](#8-sae-应用配置) 9. [数据库部署策略](#9-数据库部署策略) 10. [端到端测试](#10-端到端测试) 11. [监控与维护](#11-监控与维护) 12. [故障排查](#12-故障排查) 13. [注意事项与禁忌](#13-注意事项与禁忌) --- ## 1. 为什么选择 SAE 容器部署 ### ✅ 核心优势 | 优势 | 说明 | 对您的价值 | |------|------|----------| | **环境一致性** | Docker 镜像保证本地开发(Node 22 + Prisma)与线上环境完全一致 | 杜绝"我本地明明能跑"的问题 | | **弹性伸缩** | SAE 根据 CPU/内存使用率自动增减实例数量 | 大量用户 AI 对话时自动扩容 | | **免运维** | 无需管理服务器 OS、安全补丁,SAE 全托管 | 1-2 人团队节省运维精力 | | **内网互通** | 与 SAE 的 Python 服务和 ECS 的 Dify 通过 VPC 内网高速通信 | 毫秒级延迟,无公网流量费 | | **私有化就绪** | Docker 镜像可直接交付给医院,部署到内网环境 | 满足医疗数据合规要求 | | **统一架构** | 前端(Nginx)、后端(Node.js)、Python 微服务都用 Docker | 一套部署流程,降低学习成本 | ### 🎯 为什么不用 Code Package 部署? | 对比项 | Code Package | 容器部署(推荐)| |-------|--------------|----------------| | 环境一致性 | ⚠️ 云端 Node 版本可能不同 | ✅ 镜像锁定版本 | | 私有化交付 | ❌ 无法直接交付 | ✅ 打包即可交付 | | 系统依赖 | ⚠️ 无法自定义 | ✅ Dockerfile 完全控制 | | 构建速度 | ✅ 快(上传即可) | ⚠️ 慢(需构建镜像)| | 适用场景 | 快速原型验证 | 生产环境、私有化 | **结论**:对于需要私有化部署的医疗科研产品,容器部署是唯一选择。 --- ## 2. 部署前准备 ### ✅ 前置条件检查清单 #### 本地开发环境 - [ ] Docker Desktop 已安装并运行(版本 20.10+) - [ ] Node.js 已安装(版本 22+) - [ ] 后端代码已拉取到本地 - [ ] 后端在本地能正常启动(`npm run dev`) 验证命令: ```bash # 检查 Docker docker --version # 输出示例:Docker version 24.0.6 # 检查 Node.js node --version # 输出示例:v22.11.0 # 检查 npm npm --version # 输出示例:10.9.0 ``` #### 阿里云资源 - [ ] **RDS PostgreSQL 15** 实例已创建并运行 - 数据库名称:`ai_clinical`(或自定义) - 用户名和密码已准备 - 内网地址已获取(如 `pgm-2zex1m2y3r23hdn5.pg.rds.aliyuncs.com:5432`) - 白名单已配置(允许 SAE VPC 访问) - [ ] **阿里云容器镜像服务 ACR** 已开通 - 命名空间已创建(如 `clinical-research`) - 登录密码已设置 - [ ] **SAE 应用** 已创建(或准备创建) - VPC 和交换机已选择(与 RDS 在同一 VPC) - [ ] **依赖服务的内网地址已获取**: - Python 微服务(SAE):`http://172.17.x.x:8000` - Dify 服务(ECS):`http://172.17.x.x:80` #### 敏感信息准备 准备以下配置信息(稍后配置到 SAE 环境变量): ```bash # 数据库 DATABASE_URL=postgresql://username:password@pgm-2zex1m2y3r23hdn5.pg.rds.aliyuncs.com:5432/ai_clinical?connection_limit=18&pool_timeout=10 # LLM API Keys(至少配置一个) DEEPSEEK_API_KEY=sk-xxxxx DASHSCOPE_API_KEY=sk-xxxxx CLOSEAI_API_KEY=sk-xxxxx # Dify DIFY_API_KEY=app-xxxxx DIFY_API_URL=http://172.17.x.x:80/v1 # 阿里云 OSS OSS_REGION=oss-cn-beijing OSS_BUCKET=clinical-research-files OSS_ACCESS_KEY_ID=LTAI5t... OSS_ACCESS_KEY_SECRET=xxx... # JWT 安全密钥(生产环境必须修改) JWT_SECRET=your-strong-random-secret-min-32-chars ``` --- ## 3. 后端服务分析 ### 📦 技术栈 | 技术 | 版本 | 用途 | |------|------|------| | **Node.js** | 22+ | 运行时环境 | | **Fastify** | 5.6+ | Web 框架(高性能) | | **TypeScript** | 5.9+ | 类型安全 | | **Prisma** | 6.17+ | ORM(数据库访问) | | **pg-boss** | 12.5+ | 队列(Postgres-Only) | | **winston** | 3.18+ | 日志系统 | ### 📊 依赖服务 ``` Node.js 后端(SAE) │ ├──→ RDS PostgreSQL 15(数据库) │ ├──→ Python 微服务(SAE) - 文档提取 │ └─ http://172.17.x.x:8000 │ ├──→ Dify 服务(ECS) - RAG 知识库 │ └─ http://172.17.x.x:80/v1 │ └──→ 阿里云 OSS - 文件存储 └─ clinical-research-files ``` ### 🔧 核心功能模块 | 模块 | 路径 | 功能 | 依赖服务 | |------|------|------|---------| | **AIA** | `/api/chat` | AI 智能助理 | DeepSeek/千问/Claude | | **PKB** | `/api/knowledge-bases` | 个人知识库 | Dify + OSS | | **ASL** | `/api/asl/*` | 智能文献 | Python 微服务 + OSS | | **DC** | `/api/dc/*` | 数据清洗 | Python 微服务 + OSS | | **RVW** | `/api/review` | 稿件审查 | DeepSeek/千问 | | **Health** | `/health` | 健康检查 | RDS | ### 📝 启动流程 ```bash # 1. 安装依赖 npm install # 2. 生成 Prisma Client npm run prisma:generate # 3. 数据库迁移(仅首次或更新时) npx prisma migrate deploy # 4. 编译 TypeScript npm run build # 5. 启动应用 npm start ``` --- ## 4. 🔥 Prisma 反向同步(必读) ### ⚠️ 重要警告:为什么需要这一步? **您的开发历史**: - ✅ 使用 `pg_dump` 导出了本地 PostgreSQL 数据库(包含表结构和数据) - ✅ 已经导入到阿里云 RDS PostgreSQL - ⚠️ **但是**:开发过程中,经常直接用 SQL 或 Navicat 修改数据库,**没有同步更新 `schema.prisma` 文件** **后果**: ```typescript // schema.prisma 中定义: model User { id String email String name String // ❌ 缺少 phone 字段 } // 但数据库里实际有: CREATE TABLE "User" ( id VARCHAR(255), email VARCHAR(255), name VARCHAR(255), phone VARCHAR(50) -- ✅ 这个字段存在,但 Prisma 不知道 ); // 代码里尝试访问: const user = await prisma.user.findUnique({ where: { id: '123' } }); console.log(user.phone); // ❌ TypeScript 报错:Property 'phone' does not exist on type 'User' ``` **更严重的后果**: - Prisma Client 生成的类型与数据库不一致 - 运行时可能读不到数据或报错 - 如果强行运行 `prisma migrate deploy`,可能因为表已存在而失败 ### ✅ 解决方案:反向同步(Introspection) #### 步骤 1:连接到 RDS 数据库 在本地开发环境,临时连接到 RDS: ```bash # 1. 备份当前的 .env 文件 cp backend/.env backend/.env.backup # 2. 创建临时 RDS 连接配置 cat > backend/.env.rds < { process.exit(res.statusCode === 200 ? 0 : 1); })" # 暴露端口 EXPOSE 3001 # 🔥 启动命令(仅启动应用,不执行数据库迁移) # 解释:因为数据库已通过 pg_dump 导入,结构已就绪,无需 migrate CMD ["node", "dist/index.js"] ``` ### 📝 Dockerfile 关键修改说明 #### 修改 1:全局安装 Prisma CLI ```dockerfile # 🔥 在阶段 2 新增: RUN npm install -g prisma@6.17.0 ``` **原因**: - `npm ci` + `npm prune --production` 会删除 devDependencies 中的 `prisma` 包 - 但生产环境可能需要执行 `npx prisma db pull` 或排查问题 - 全局安装确保 `prisma` 命令始终可用 **代价**: - 镜像体积增加约 50MB - 但这是值得的(避免"命令找不到"的问题) #### 修改 2:不执行数据库迁移 ```dockerfile # ❌ 错误做法(会导致"表已存在"错误): CMD ["sh", "-c", "npx prisma migrate deploy && node dist/index.js"] # ✅ 正确做法(仅启动应用): CMD ["node", "dist/index.js"] ``` **原因**: - 您的数据库是通过 `pg_dump` 导入的,表结构已经存在 - 如果执行 `prisma migrate deploy`,可能因为迁移记录对不上而报错 - 正确的做法:启动前确保 `schema.prisma` 与数据库一致(第 4 节已处理) #### 修改 3:移除 dumb-init(可选优化) ```dockerfile # 移除: # RUN apk add --no-cache dumb-init # ENTRYPOINT ["dumb-init", "--"] # Node.js 22 在处理信号时已经足够稳定 # 如果你发现下载 dumb-init 很慢或报错,可以去掉这个优化 ``` ### 📝 创建 .dockerignore 在 `backend/` 目录下创建 `.dockerignore`: ``` # Node.js node_modules npm-debug.log yarn-error.log # 开发文件 .env .env.* *.local # 构建产物(已在 Dockerfile 中生成) dist # 测试文件 test tests *.test.ts *.spec.ts coverage # 文档和临时文件 docs *.md .vscode .idea .DS_Store Thumbs.db # 上传文件(运行时生成) uploads/* # Git .git .gitignore # 日志 *.log logs # 临时文件 temp tmp *.swp *.swo *~ # 数据库文件(SQLite,如果有) *.db *.sqlite # 脚本文件(仅开发使用) scripts/*.ts *.bat *.ps1 ``` --- ## 6. 本地测试验证 ### 🛑 前置步骤:复制 Prisma 文件(必须执行) ```bash # 1. 回到项目根目录 cd AIclinicalresearch # 2. 复制 prisma 到 backend 目录(确保构建时能找到) cp -r prisma backend/prisma # 3. 验证复制成功 ls backend/prisma/schema.prisma # 应该输出:backend/prisma/schema.prisma ``` ### 步骤 1:构建镜像 ```bash # 在 backend 目录下执行 cd backend # 🔥 确保已执行上面的复制步骤! # 构建镜像(需要 5-10 分钟) docker build -t backend-service:v1.0.0 . # 查看镜像大小 docker images backend-service:v1.0.0 # 预期大小:~300-500MB(Alpine 基础镜像 + Node.js + 依赖) ``` **如果构建失败**: ```bash # 常见问题 1:Prisma 文件找不到 # 错误信息:COPY failed: file not found in build context or excluded by .dockerignore: stat prisma: file does not exist # 解决:确保执行了复制步骤:cp -r ../prisma ./prisma(在 backend 目录外执行) # 常见问题 2:网络超时(npm install 慢) # 解决:使用国内镜像源 # 在 Dockerfile 的 npm ci 之前添加: RUN npm config set registry https://registry.npmmirror.com # 常见问题 3:Prisma 生成失败 # 解决:检查 backend/prisma/schema.prisma 是否存在且格式正确 ``` ### 步骤 2:本地运行测试 ```bash # 创建测试环境变量文件 cat > .env.docker.test </dev/null || echo "✅ prisma 目录已清理" # 注意:根目录的 prisma/ 文件夹保留,这是源文件 ``` --- ## 7. 推送到 ACR ### 步骤 1:登录 ACR ```bash # 获取 ACR 登录地址(阿里云控制台 → 容器镜像服务 → 访问凭证) # 示例:registry.cn-hangzhou.aliyuncs.com # 登录(使用 ACR 密码,不是阿里云账号密码) docker login --username=your-aliyun-account registry.cn-hangzhou.aliyuncs.com # 输入密码后看到: # Login Succeeded ``` ### 步骤 2:标记镜像 ```bash # 格式:registry地址/命名空间/仓库名:版本号 docker tag backend-service:v1.0.0 \ registry.cn-hangzhou.aliyuncs.com/clinical-research/backend-service:v1.0.0 # 同时打一个 latest 标签(方便测试) docker tag backend-service:v1.0.0 \ registry.cn-hangzhou.aliyuncs.com/clinical-research/backend-service:latest ``` ### 步骤 3:推送镜像 ```bash # 推送指定版本 docker push registry.cn-hangzhou.aliyuncs.com/clinical-research/backend-service:v1.0.0 # 推送 latest docker push registry.cn-hangzhou.aliyuncs.com/clinical-research/backend-service:latest # 推送过程需要 5-10 分钟(视网络速度) ``` ### 步骤 4:验证推送成功 登录阿里云控制台 → 容器镜像服务 → 镜像仓库 → `backend-service` - 应该看到版本:`v1.0.0` 和 `latest` - 镜像大小:~300-500MB - 推送时间:刚才的时间 --- ## 8. SAE 应用配置 ### 步骤 1:创建应用 **阿里云控制台** → **SAE** → **应用列表** → **创建应用** | 配置项 | 值 | 说明 | |-------|-----|------| | **应用名称** | `backend-service` | 后端服务 | | **命名空间** | 选择已创建的命名空间 | 与 Python 服务同一命名空间 | | **VPC** | 选择 RDS 所在 VPC | 必须与 RDS 在同一 VPC | | **交换机** | 选择可用区 | 建议多可用区 | | **应用实例规格** | 2核4G | 初始规格 | | **实例数量** | 2 | 最小 2 个实例(高可用) | ### 步骤 2:配置镜像 | 配置项 | 值 | |-------|-----| | **镜像类型** | 容器镜像服务企业版实例 | | **镜像仓库** | `registry.cn-hangzhou.aliyuncs.com/clinical-research/backend-service` | | **镜像版本** | `v1.0.0` | | **镜像拉取策略** | 总是拉取镜像 | ### 步骤 3:配置端口 | 配置项 | 值 | |-------|-----| | **容器端口** | `3001` | | **协议** | TCP | ### 步骤 4:配置环境变量(关键步骤) **⚠️ 重要:请仔细配置以下环境变量** #### 基础配置 ```bash NODE_ENV=production PORT=3001 HOST=0.0.0.0 LOG_LEVEL=info SERVICE_NAME=backend-service ``` #### 数据库配置(必需) ```bash # ⚠️ 使用 RDS 内网地址 DATABASE_URL=postgresql://username:password@pgm-2zex1m2y3r23hdn5.pg.rds.aliyuncs.com:5432/ai_clinical?connection_limit=18&pool_timeout=10 # 连接池配置(根据 RDS 规格调整) DB_MAX_CONNECTIONS=400 MAX_INSTANCES=20 ``` **连接池计算说明**: - 假设 RDS 最大连接数:400 - SAE 最大实例数:20 - 每实例连接数 = (400 / 20) - 预留 = 18 - 预留 10 个连接给管理任务和其他服务 #### 存储配置(必需) ```bash # 使用阿里云 OSS STORAGE_TYPE=oss OSS_REGION=oss-cn-beijing OSS_BUCKET=clinical-research-files OSS_ACCESS_KEY_ID=LTAI5t... OSS_ACCESS_KEY_SECRET=xxx... ``` #### 缓存和队列配置(Postgres-Only) ```bash # 使用 PostgreSQL 作为缓存(不需要 Redis) CACHE_TYPE=postgres # 使用 pg-boss 作为队列(不需要 Redis) QUEUE_TYPE=pgboss ``` #### LLM API 配置(至少配置一个) ```bash # DeepSeek(推荐,性价比高) DEEPSEEK_API_KEY=sk-xxxxx DEEPSEEK_BASE_URL=https://api.deepseek.com # 通义千问(备选) DASHSCOPE_API_KEY=sk-xxxxx # CloseAI(OpenAI/Claude 代理) CLOSEAI_API_KEY=sk-xxxxx CLOSEAI_OPENAI_BASE_URL=https://api.openai-proxy.org/v1 CLOSEAI_CLAUDE_BASE_URL=https://api.openai-proxy.org/anthropic ``` #### Dify 配置(必需) ```bash # ⚠️ 使用 ECS 内网 IP(不要使用公网域名) DIFY_API_URL=http://172.17.x.x:80/v1 DIFY_API_KEY=app-xxxxx ``` **如何获取 Dify 内网 IP**: 1. 登录 ECS 控制台 2. 找到 Dify 所在的 ECS 实例 3. 查看"私有 IP 地址"(如 `172.16.0.20`) #### 安全配置(必需) ```bash # ⚠️ 生产环境必须修改为强密码(至少 32 位随机字符串) JWT_SECRET=your-strong-random-secret-min-32-chars-change-in-production JWT_EXPIRES_IN=7d ``` **生成强密码**: ```bash # Linux/Mac openssl rand -base64 32 # Windows PowerShell -join ((65..90) + (97..122) + (48..57) | Get-Random -Count 32 | ForEach-Object {[char]$_}) ``` #### CORS 配置(可选) ```bash # 如果前端使用自定义域名,配置允许的源 # 注意:如果前端使用 Nginx 反向代理,则不需要配置(Nginx 已处理) CORS_ORIGIN=https://your-frontend-domain.com ``` ### 步骤 5:配置健康检查 **SAE 控制台** → **应用配置** → **健康检查** | 配置项 | 值 | 说明 | |-------|-----|------| | **检查方式** | HTTP 请求 | | | **检查路径** | `/health` | 后端健康检查端点 | | **检查端口** | 3001 | 与容器端口一致 | | **检查协议** | HTTP | | | **初始延迟** | 60 秒 | 给 Prisma 初始化足够时间 | | **检查间隔** | 10 秒 | | | **超时时间** | 3 秒 | | | **不健康阈值** | 3 次 | 连续失败 3 次标记为不健康 | | **健康阈值** | 2 次 | 连续成功 2 次标记为健康 | ### 步骤 6:配置弹性伸缩 **SAE 控制台** → **应用配置** → **弹性伸缩** | 配置项 | 值 | 说明 | |-------|-----|------| | **最小实例数** | 2 | 高可用保证 | | **最大实例数** | 10 | 根据预期负载调整 | | **扩容条件** | CPU > 70% 持续 3 分钟 | | | **缩容条件** | CPU < 30% 持续 5 分钟 | | ### 步骤 7:配置日志 **SAE 控制台** → **应用配置** → **日志配置** | 配置项 | 值 | |-------|-----| | **日志类型** | 标准输出(stdout) | | **日志存储** | 开启(保存 7 天) | ### 步骤 8:部署应用 点击"部署"按钮,SAE 将: 1. 从 ACR 拉取镜像(~2 分钟) 2. 启动容器实例(~1 分钟) 3. 执行健康检查(~1 分钟) 4. 流量切换(~30 秒) **总耗时**:约 5 分钟 --- ## 9. 数据库部署策略 ### 🎯 您的实际情况(非常重要) 根据您的开发历史,数据库部署策略与标准流程**完全不同**: **标准流程(不适合您)**: ```mermaid graph LR A[代码] --> B[Prisma Migrations] B --> C[空数据库] C --> D[自动创建表结构] ``` **您的实际流程**: ```mermaid graph LR A[本地 PostgreSQL
包含数据] --> B[pg_dump 导出] B --> C[RDS 导入] C --> D[表结构已存在] D --> E[Prisma 反向同步] ``` ### ✅ 正确的部署策略 #### 策略总览 | 步骤 | 操作 | 何时执行 | 目的 | |------|------|---------|------| | **1. 导出本地数据库** | `pg_dump` | 首次部署前 | 备份现有数据和结构 | | **2. 导入到 RDS** | `psql` 或 DMS | 首次部署前 | 迁移到云端 | | **3. Prisma 反向同步** | `prisma db pull` | 首次部署前(第 4 节) | 同步 Schema | | **4. 启动应用** | `node dist/index.js` | SAE 部署时 | 正常运行 | | **5. ~~数据库迁移~~** | ~~`prisma migrate deploy`~~ | ❌ **不执行** | 表已存在,无需迁移 | ### 方案 1:使用 pg_dump 导入(您的情况,推荐) #### 步骤 1:导出本地数据库(如果还没做) ```bash # 在本地开发环境执行 docker exec ai-clinical-postgres pg_dump -U postgres -d ai_clinical \ --no-owner --no-acl --clean --if-exists \ > ai_clinical_backup_$(date +%Y%m%d).sql # 查看导出文件 ls -lh ai_clinical_backup_*.sql # 应该看到文件大小(如 5.2MB) ``` #### 步骤 2:导入到 RDS(如果还没做) **方法 A:使用 psql 命令行(推荐)** ```bash # 连接到 RDS 并导入 psql "postgresql://username:password@pgm-2zex1m2y3r23hdn5.pg.rds.aliyuncs.com:5432/ai_clinical" \ < ai_clinical_backup_20251213.sql # 输出示例: # DROP TABLE # CREATE TABLE # INSERT 0 123 # ... # ✅ 导入完成 ``` **方法 B:使用 DMS(阿里云数据管理)** 1. 登录阿里云控制台 → 数据管理 DMS 2. 连接到 RDS 实例 3. 数据方案 → SQL 窗口 → 粘贴 SQL 文件内容 4. 执行 #### 步骤 3:验证导入成功 ```bash # 连接到 RDS psql "postgresql://username:password@pgm-2zex1m2y3r23hdn5.pg.rds.aliyuncs.com:5432/ai_clinical" # 查看所有 Schema \dn # 应该看到: # aia_schema # asl_schema # common_schema # dc_schema # pkb_schema # platform_schema # rvw_schema # ssa_schema # st_schema # public # 查看用户表 SELECT count(*) FROM "User"; # 应该返回正确的用户数量 # 退出 \q ``` #### 步骤 4:确认已执行 Prisma 反向同步 **⚠️ 这一步非常关键!** ```bash # 确保第 4 节的步骤已完成: # 1. 已执行 npx prisma db pull # 2. 已执行 npx prisma generate # 3. 已提交 schema.prisma 到 Git # 验证:检查 schema.prisma 的修改时间 ls -l prisma/schema.prisma # 修改时间应该是最近(今天) # 如果还没做,立即返回第 4 节执行! ``` ### 方案 2:标准 Prisma Migrations(仅适用于新项目) **⚠️ 如果您的数据库是通过 `pg_dump` 导入的,请跳过这一节!**
点击展开:标准迁移流程(仅供参考) #### 适用场景 - 全新项目,RDS 数据库是空的 - 从未手动修改过数据库结构 - 所有表结构都通过 Prisma Migrations 管理 #### 执行步骤 ```bash # 1. 连接到 RDS export DATABASE_URL="postgresql://username:password@pgm-2zex1m2y3r23hdn5.pg.rds.aliyuncs.com:5432/ai_clinical" # 2. 执行迁移 cd backend npx prisma migrate deploy # 3. 验证 npx prisma db execute --stdin <<< "SELECT schemaname FROM pg_tables WHERE schemaname NOT IN ('pg_catalog', 'information_schema') GROUP BY schemaname;" ```
### 🚨 常见错误与修正 #### 错误 1:启动时执行 `prisma migrate deploy` 导致失败 **错误信息**: ``` Error: P3005: The database schema is not empty. Read more about how to baseline an existing production database: https://pris.ly/d/migrate-baseline ``` **原因**:表已通过 `pg_dump` 导入,再执行 migrate 会冲突。 **解决方法**: ```bash # 方法 1:移除启动命令中的 migrate(推荐) # SAE 控制台 → 应用配置 → 启动命令 # 确保启动命令是: node dist/index.js # 不要写: # sh -c "npx prisma migrate deploy && node dist/index.js" # ❌ ``` #### 错误 2:应用启动后,访问数据库报错 **错误信息**: ```typescript PrismaClientKnownRequestError: Invalid `prisma.user.findMany()` invocation: column "phone" does not exist ``` **原因**:`schema.prisma` 与数据库不一致。 **解决方法**: 返回第 4 节,执行 `npx prisma db pull` 反向同步。 ### ✅ 部署检查清单 在启动 SAE 应用前,确认以下步骤已完成: - [ ] 本地数据库已通过 `pg_dump` 导出 - [ ] SQL 文件已导入到 RDS(表结构和数据都存在) - [ ] 已执行 `npx prisma db pull` 同步 Schema - [ ] 已执行 `npx prisma generate` 生成客户端 - [ ] 已提交 `schema.prisma` 到 Git - [ ] Docker 镜像的启动命令**不包含** `prisma migrate deploy` - [ ] SAE 环境变量中的 `DATABASE_URL` 指向 RDS **如果以上都确认,可以放心部署!** ✅ --- ## 10. 端到端测试 ### 步骤 1:获取应用访问地址 **SAE 控制台** → **应用详情** → **应用访问配置** 复制以下地址: ``` # 公网访问地址(用于前端调用) https://backend-service-xxxxx.cn-hangzhou.sae.aliyuncs.com # VPC 内网访问地址(用于服务间调用) http://172.16.0.30:3001 ``` ### 步骤 2:测试健康检查 ```bash # 使用公网地址测试 curl https://backend-service-xxxxx.cn-hangzhou.sae.aliyuncs.com/health # 预期返回: { "status": "healthy", "timestamp": "2025-12-13T10:30:00.000Z", "uptime": 12345.678, "database": { "status": "connected", "connections": 8 }, "version": "1.0.0" } ``` ### 步骤 3:测试用户注册 ```bash curl -X POST https://backend-service-xxxxx.cn-hangzhou.sae.aliyuncs.com/api/auth/register \ -H "Content-Type: application/json" \ -d '{ "email": "sae-test@example.com", "password": "Test123456", "name": "SAE测试用户" }' # 预期返回: { "success": true, "user": { "id": "...", "email": "sae-test@example.com", "name": "SAE测试用户" }, "token": "eyJhbGciOiJIUzI1NiIs..." } ``` ### 步骤 4:测试文件上传(PKB 模块) ```bash # 获取 Token(从步骤 3) TOKEN="eyJhbGciOiJIUzI1NiIs..." # 上传 PDF 文档 curl -X POST https://backend-service-xxxxx.cn-hangzhou.sae.aliyuncs.com/api/knowledge-bases/kb-xxx/documents \ -H "Authorization: Bearer $TOKEN" \ -F "file=@test.pdf" \ -F "name=测试文档" # 预期返回: { "success": true, "document": { "id": "...", "name": "测试文档", "status": "processing" } } ``` ### 步骤 5:测试 Python 微服务调用 ```bash # 测试 ASL 模块的 PDF 提取功能 curl -X POST https://backend-service-xxxxx.cn-hangzhou.sae.aliyuncs.com/api/asl/extract \ -H "Authorization: Bearer $TOKEN" \ -F "file=@paper.pdf" # 查看后端日志(SAE 控制台 → 应用详情 → 日志) # 应该看到: # [INFO] Calling extraction service: http://172.17.x.x:8000/extract/pdf # [INFO] Extraction completed in 3.2s ``` ### 步骤 6:测试 Dify 服务调用 ```bash # 测试 PKB 模块的知识库对话 curl -X POST https://backend-service-xxxxx.cn-hangzhou.sae.aliyuncs.com/api/knowledge-bases/kb-xxx/chat \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "query": "这篇文献的研究方法是什么?", "conversationId": "conv-xxx" }' # 查看后端日志 # 应该看到: # [INFO] Calling Dify API: http://172.17.x.x:80/v1/chat-messages # [INFO] Dify response received in 2.5s ``` ### 步骤 7:测试 OSS 文件上传 ```bash # 上传大文件(测试 OSS) curl -X POST https://backend-service-xxxxx.cn-hangzhou.sae.aliyuncs.com/api/asl/upload \ -H "Authorization: Bearer $TOKEN" \ -F "file=@large-paper.pdf" # 查看日志,应该看到 OSS 上传成功: # [INFO] File uploaded to OSS: clinical-research-files/asl/xxx.pdf ``` --- ## 11. 监控与维护 ### 📊 SAE 自带监控 #### 1. 实时监控 **SAE 控制台** → **应用详情** → **监控** **关键指标**: | 指标 | 健康阈值 | 告警阈值 | 说明 | |------|---------|---------|------| | **CPU 使用率** | < 60% | > 80% | LLM 调用是 CPU 密集型 | | **内存使用率** | < 70% | > 85% | 监控内存泄漏 | | **请求 QPS** | - | - | 了解负载 | | **平均响应时间** | < 500ms | > 2000ms | AI 对话除外(可能 10s+) | | **错误率** | < 0.5% | > 2% | 监控服务稳定性 | | **实例数量** | 2+ | - | 确保高可用 | #### 2. 日志查看 **SAE 控制台** → **应用详情** → **日志** → **实时日志** **关键日志示例**: ```bash # ✅ 正常启动 [Config] Environment validation passed [Database] ✅ 数据库连接成功! [Fastify] Server listening on http://0.0.0.0:3001 # ✅ 正常请求 [INFO] POST /api/chat 200 2345ms # ⚠️ 警告日志(需关注) [WARN] Database connection pool near limit: 16/18 connections # ❌ 错误日志(需立即处理) [ERROR] Failed to connect to Python service: ECONNREFUSED [ERROR] Prisma timeout: Database connection pool exhausted [ERROR] Dify API error: 502 Bad Gateway ``` #### 3. 数据库连接监控 ```bash # 在 SAE Webshell 中执行(或使用 RDS 控制台) psql $DATABASE_URL -c " SELECT count(*) as total_connections, count(*) FILTER (WHERE state = 'active') as active_connections, count(*) FILTER (WHERE state = 'idle') as idle_connections FROM pg_stat_activity WHERE datname = 'ai_clinical'; " # 输出示例: # total_connections | active_connections | idle_connections # ------------------+--------------------+----------------- # 35 | 5 | 30 # 告警条件:total_connections > (MAX_INSTANCES * connection_limit * 0.8) # 示例:20 实例 * 18 连接/实例 * 0.8 = 288 连接 ``` ### 🔧 日常维护任务 #### 每日检查 ```bash # 1. 检查应用健康状态 # SAE 控制台 → 应用列表 → 查看运行状态(绿色为正常) # 2. 查看错误日志 # SAE 控制台 → 应用详情 → 日志 → 筛选 ERROR 级别 # 3. 检查数据库连接数 # RDS 控制台 → 实例监控 → 连接数(< 80% 为健康) ``` #### 每周任务 ```bash # 1. 查看性能指标趋势 # SAE 控制台 → 应用详情 → 监控 → 选择"最近 7 天" # 关注: # - CPU/内存是否有持续上涨(内存泄漏?) # - 响应时间是否变慢 # - 错误率是否增加 # 2. 清理日志(如果日志存储空间紧张) # SAE 控制台 → 应用详情 → 日志配置 → 清理旧日志 ``` #### 每月任务 ```bash # 1. 更新依赖(安全补丁) # 在本地开发环境执行: npm outdated npm update npm audit fix # 重新测试后部署 # 2. 重建镜像(包含系统安全更新) docker build -t backend-service:v1.0.1 . docker push registry.cn-hangzhou.aliyuncs.com/clinical-research/backend-service:v1.0.1 # 3. 在 SAE 中灰度更新 # SAE 控制台 → 应用详情 → 部署 # 选择新镜像版本:v1.0.1 # 灰度发布:先更新 1 个实例,观察 10 分钟后全量发布 # 4. 数据库备份(RDS 自动备份,仅需验证) # RDS 控制台 → 备份恢复 → 查看最近备份时间 ``` ### 🚨 告警配置 **云监控** → **应用监控** → **创建告警规则** **推荐告警规则**: | 告警项 | 阈值 | 通知方式 | |-------|------|---------| | CPU 使用率 > 80% 持续 5 分钟 | 告警 | 钉钉/邮件 | | 内存使用率 > 85% 持续 5 分钟 | 告警 | 钉钉/邮件 | | 错误率 > 2% 持续 3 分钟 | 紧急 | 短信+钉钉 | | 实例健康检查失败 > 3 次 | 紧急 | 短信+钉钉 | | RDS 连接数 > 80% | 警告 | 钉钉/邮件 | --- ## 12. 故障排查 ### 问题 1:应用启动失败 **症状**: ``` SAE 控制台显示:实例启动中 → 健康检查失败 → 实例停止 ``` **排查步骤**: ```bash # 1. 查看启动日志 # SAE 控制台 → 应用详情 → 日志 → 筛选最近 5 分钟 # 2. 常见错误原因: # 错误 A:环境变量验证失败 # 日志:❌ [Config] Environment validation failed: DATABASE_URL is required # 解决:检查 SAE 环境变量配置,补充缺失的变量 # 错误 B:数据库连接失败 # 日志:❌ 数据库连接失败: getaddrinfo ENOTFOUND pgm-2zex1m2y3r23hdn5.pg.rds.aliyuncs.com # 解决: # - 检查 DATABASE_URL 是否正确 # - 检查 RDS 白名单是否允许 SAE VPC 访问 # - 检查 RDS 内网地址是否可达 # 错误 C:Prisma 迁移未执行 # 日志:Error: P1001: Can't reach database server at `pgm-2zex1m2y3r23hdn5.pg.rds.aliyuncs.com` # 解决:先执行数据库迁移(参见第 8 节) # 错误 D:端口冲突 # 日志:Error: listen EADDRINUSE: address already in use :::3001 # 解决:检查 PORT 环境变量是否为 3001(与 Dockerfile EXPOSE 一致) ``` ### 问题 2:数据库连接池耗尽 **症状**: ``` [ERROR] Prisma timeout: Database connection pool exhausted [ERROR] P2024: Timed out fetching a new connection from the pool ``` **根本原因**: - SAE 实例数 * 每实例连接数 > RDS 最大连接数 - 连接未正确释放(代码 Bug) **排查步骤**: ```bash # 1. 检查当前连接数 psql $DATABASE_URL -c " SELECT count(*) as current_connections FROM pg_stat_activity WHERE datname = 'ai_clinical'; " # 2. 检查 SAE 实例数 # SAE 控制台 → 应用详情 → 实例列表 # 假设有 10 个实例 # 3. 计算连接数 # 每实例连接数 = 18(connection_limit) # 总连接数 = 10 * 18 = 180 # 如果实际连接数远超 180,说明连接未释放 # 4. 查找连接泄漏 psql $DATABASE_URL -c " SELECT pid, state, wait_event, query_start, state_change, query FROM pg_stat_activity WHERE datname = 'ai_clinical' AND state_change < now() - interval '5 minutes' ORDER BY state_change; " # 5. 紧急处理:杀死长时间空闲连接 psql $DATABASE_URL -c " SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'ai_clinical' AND state = 'idle' AND state_change < now() - interval '10 minutes'; " ``` **解决方法**: ```bash # 方法 1:调整连接池配置(临时) # 在 DATABASE_URL 中降低 connection_limit DATABASE_URL=postgresql://...?connection_limit=10&pool_timeout=10 # 方法 2:限制 SAE 实例数(临时) # SAE 控制台 → 应用配置 → 弹性伸缩 # 将最大实例数从 20 降低到 10 # 方法 3:升级 RDS 规格(长期) # RDS 控制台 → 变更配置 → 升级到更大规格(增加最大连接数) # 方法 4:修复代码(如果是连接泄漏) # 检查代码中是否有未释放的连接: # - 未使用 Prisma 的事务 API # - 手动创建的数据库连接未关闭 # - 长时间运行的查询 ``` ### 问题 3:无法连接 Python 微服务 **症状**: ``` [ERROR] Failed to connect to Python service: ECONNREFUSED 或 [ERROR] connect ETIMEDOUT 172.17.x.x:8000 ``` **排查步骤**: ```bash # 1. 确认 Python 服务是否运行 # SAE 控制台 → 应用列表 → 查看 extraction-service 状态 # 2. 确认内网地址是否正确 # SAE 控制台 → extraction-service 应用 → 应用访问配置 # 复制"VPC 内网访问地址",更新后端环境变量 # 3. 测试内网连通性 # 在后端应用的 Webshell 中执行: curl -v http://172.17.x.x:8000/health # 4. 检查安全组规则 # SAE 控制台 → extraction-service 应用 → 网络配置 # 确认入站规则允许 VPC 内访问 8000 端口 ``` **解决方法**: ```bash # 如果内网地址错误,更新环境变量: # SAE 控制台 → backend-service 应用 → 应用配置 → 环境变量 # 修改或添加: EXTRACTION_SERVICE_URL=http://<正确的内网IP>:8000 # 重启应用使环境变量生效 # SAE 控制台 → backend-service 应用 → 重启 ``` ### 问题 4:无法连接 Dify 服务 **症状**: ``` [ERROR] Dify API error: ECONNREFUSED 或 [ERROR] Dify API error: 502 Bad Gateway ``` **排查步骤**: ```bash # 1. 确认 Dify 服务是否运行 # ECS 控制台 → 实例列表 → 找到 Dify 实例 → 远程连接 # 在 ECS 中执行: docker ps | grep dify # 应该看到 dify-api, dify-worker, dify-web, redis, weaviate 等容器运行中 # 2. 测试 Dify API curl http://localhost/v1/info # 3. 从 SAE 测试连通性 # 在后端应用的 Webshell 中执行: curl -v http://172.17.x.x:80/v1/info ``` **解决方法**: ```bash # 如果 Dify 未启动,在 ECS 中重启: cd /path/to/dify docker-compose up -d # 如果 API Key 错误,更新环境变量: # SAE 控制台 → backend-service 应用 → 环境变量 DIFY_API_KEY=app-<正确的Key> # 如果内网地址错误,更新环境变量: DIFY_API_URL=http://:80/v1 ``` ### 问题 5:OSS 上传失败 **症状**: ``` [ERROR] OSS upload failed: AccessDenied 或 [ERROR] OSS upload failed: InvalidAccessKeyId ``` **排查步骤**: ```bash # 1. 检查环境变量 # SAE 控制台 → 应用详情 → 环境变量 # 确认以下变量正确: OSS_REGION=oss-cn-beijing OSS_BUCKET=clinical-research-files OSS_ACCESS_KEY_ID=LTAI5t... OSS_ACCESS_KEY_SECRET=xxx... # 2. 测试 OSS 访问 # 在后端应用的 Webshell 中执行: node -e " const OSS = require('ali-oss'); const client = new OSS({ region: process.env.OSS_REGION, accessKeyId: process.env.OSS_ACCESS_KEY_ID, accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET, bucket: process.env.OSS_BUCKET }); client.list().then(result => console.log('✅ OSS connection successful')).catch(err => console.error('❌ OSS error:', err.message)); " ``` **解决方法**: ```bash # 如果 AccessKey 错误,重新生成: # 阿里云控制台 → AccessKey 管理 → 创建 AccessKey # 更新 SAE 环境变量 # 如果 Bucket 权限错误: # OSS 控制台 → Bucket 列表 → clinical-research-files → 访问控制 # 确认 Bucket 为"私有",且 RAM 用户有读写权限 ``` ### 问题 6:内存泄漏 **症状**: ``` SAE 监控显示:内存使用率持续上涨,最终导致 OOM(Out of Memory) 日志:JavaScript heap out of memory ``` **排查步骤**: ```bash # 1. 查看内存趋势 # SAE 控制台 → 应用详情 → 监控 → 选择"最近 24 小时" # 如果内存曲线持续上涨,不下降,说明有内存泄漏 # 2. 查看实例内存详情 # 在 Webshell 中执行: node -e "console.log(process.memoryUsage())" # 输出示例: # { # rss: 123456789, // 总内存(字节) # heapTotal: 45678910, // V8 堆总大小 # heapUsed: 34567890, // V8 堆已使用 # external: 1234567, // 外部内存(Buffer等) # arrayBuffers: 123456 // ArrayBuffer # } # 3. 启用内存快照(本地调试) # 在本地开发环境添加 --inspect 参数: node --inspect dist/index.js # 使用 Chrome DevTools 查看内存快照 ``` **常见内存泄漏原因**: 1. **全局变量累积**(如缓存无限增长) 2. **事件监听器未移除** 3. **定时器未清理** 4. **闭包持有大对象** 5. **Prisma 查询结果未释放** **解决方法**: ```typescript // 错误示例:全局缓存无限增长 const cache = {}; // ❌ 永不清理 // 正确示例:使用 LRU 缓存 import LRUCache from 'lru-cache'; const cache = new LRUCache({ max: 1000, ttl: 1000 * 60 * 10 }); // ✅ 最多 1000 项,10 分钟过期 // 错误示例:事件监听器泄漏 emitter.on('event', handler); // ❌ 从不移除 // 正确示例:及时移除监听器 emitter.once('event', handler); // ✅ 自动移除 // 或 emitter.on('event', handler); // 使用后: emitter.off('event', handler); // ✅ 手动移除 // 错误示例:定时器泄漏 setInterval(() => { ... }, 1000); // ❌ 永不清理 // 正确示例:清理定时器 const timer = setInterval(() => { ... }, 1000); process.on('SIGTERM', () => clearInterval(timer)); // ✅ 优雅关闭时清理 ``` ### 问题 7:Prisma Schema 与数据库不一致 **症状**: ```typescript // 代码中访问字段: const user = await prisma.user.findUnique({ where: { id: '123' } }); console.log(user.phone); // ❌ TypeScript 报错:Property 'phone' does not exist // 或运行时报错: PrismaClientKnownRequestError: column "phone" does not exist ``` **原因**: - 开发过程中直接修改了数据库(加了 `phone` 字段) - 但没有更新 `schema.prisma` - Prisma Client 基于旧的 Schema 生成,不知道新字段 **解决方法**: ```bash # 1. 在本地开发环境,连接到 RDS export DATABASE_URL="postgresql://username:password@pgm-2zex1m2y3r23hdn5.pg.rds.aliyuncs.com:5432/ai_clinical" # 2. 反向同步 Schema npx prisma db pull # 3. 重新生成 Client npx prisma generate # 4. 提交代码 git add prisma/schema.prisma git commit -m "fix: sync Prisma schema with database" git push # 5. 重新构建镜像并部署 cp -r prisma backend/prisma cd backend docker build -t backend-service:v1.0.1 . docker push registry.cn-hangzhou.aliyuncs.com/clinical-research/backend-service:v1.0.1 # 6. 在 SAE 中更新镜像版本 # SAE 控制台 → 应用详情 → 部署 → 选择新版本 v1.0.1 ``` --- ## 13. 注意事项与禁忌 ### ✅ 最佳实践 #### 1. **环境变量管理** ```bash # ✅ 正确做法:使用 SAE 环境变量 # SAE 控制台 → 应用配置 → 环境变量 # ❌ 错误做法:在代码或 Dockerfile 中硬编码 # Dockerfile ENV DATABASE_URL="postgresql://user:pass@host/db" # ❌ 泄露敏感信息 ``` #### 2. **连接池配置** ```bash # ✅ 正确做法:根据 RDS 和 SAE 配置动态计算 DATABASE_URL=postgresql://...?connection_limit=18&pool_timeout=10 # 计算公式: # connection_limit = (RDS_MAX_CONNECTIONS / MAX_INSTANCES) - 预留 # 示例:(400 / 20) - 2 = 18 # ❌ 错误做法:使用默认值 DATABASE_URL=postgresql://... # ❌ 默认无限制,导致连接耗尽 ``` #### 3. **文件存储** ```typescript // ✅ 正确做法:使用 OSS 存储 import { StorageFactory } from './common/storage'; const storage = StorageFactory.create(); // 根据 STORAGE_TYPE 自动选择 await storage.upload('uploads/file.pdf', buffer); // ❌ 错误做法:存储到容器文件系统 import fs from 'fs'; fs.writeFileSync('/app/uploads/file.pdf', buffer); // ❌ 容器重启后丢失 ``` #### 4. **日志输出** ```typescript // ✅ 正确做法:使用 stdout(SAE 自动收集) console.log('[INFO] User logged in:', userId); logger.info('User logged in', { userId }); // ❌ 错误做法:写入文件 import fs from 'fs'; fs.appendFileSync('/var/log/app.log', message); // ❌ 容器重启后丢失 ``` #### 5. **优雅关闭** ```typescript // ✅ 正确做法:监听 SIGTERM 信号 process.on('SIGTERM', async () => { console.log('[Server] Received SIGTERM, shutting down gracefully...'); await fastify.close(); // 关闭 HTTP 服务器 await prisma.$disconnect(); // 关闭数据库连接 process.exit(0); }); // ❌ 错误做法:直接退出 process.on('SIGTERM', () => process.exit(0)); // ❌ 连接未释放 ``` ### ❌ 绝对禁止 #### 1. **禁止直接修改数据库而不同步 Prisma Schema(致命)** ```bash # ❌ 错误做法: # 1. 在 Navicat 中直接添加字段 ALTER TABLE "User" ADD COLUMN phone VARCHAR(50); # 2. 然后直接在代码中使用 const user = await prisma.user.create({ data: { email: 'test@example.com', phone: '13800138000' // ❌ TypeScript 报错:phone 不存在 } }); # ✅ 正确做法: # 1. 先修改 schema.prisma model User { email String phone String? @db.VarChar(50) // 添加字段定义 } # 2. 生成迁移(仅开发环境) npx prisma migrate dev --name add_user_phone # 3. 生成客户端 npx prisma generate # 4. 在代码中使用 const user = await prisma.user.create({ data: { email: 'test@example.com', phone: '13800138000' // ✅ TypeScript 识别 } }); # 或者,如果数据库已手动修改: # 1. 反向同步 npx prisma db pull # 2. 生成客户端 npx prisma generate ``` **原因**: - Prisma Client 基于 Schema 生成 - Schema 与数据库不一致会导致运行时错误或类型不匹配 - 这是导致生产环境崩溃的最常见原因之一 #### 2. **禁止在启动命令中无脑执行 `prisma migrate deploy`** ```bash # ❌ 错误做法(如果数据库是通过 pg_dump 导入的): CMD ["sh", "-c", "npx prisma migrate deploy && node dist/index.js"] # 错误原因: # - 表结构已存在(通过 pg_dump 导入) # - migrate deploy 会尝试重新创建表 # - 导致错误:Table already exists # ✅ 正确做法(针对您的情况): CMD ["node", "dist/index.js"] # 解释: # - 数据库结构已通过 pg_dump 导入 # - Schema 已通过 prisma db pull 同步 # - 无需在启动时执行迁移 ``` #### 3. **禁止忽略 Prisma 反向同步步骤** ```bash # ❌ 错误流程: pg_dump → 导入 RDS → 直接构建镜像 → 部署 # ❌ 问题:Schema 与数据库不一致 # ✅ 正确流程: pg_dump → 导入 RDS → prisma db pull(同步)→ 构建镜像 → 部署 # ✅ 关键:prisma db pull 确保一致性 ``` #### 4. **禁止在代码中硬编码敏感信息** ```typescript // ❌ 错误示例 const dbUrl = 'postgresql://admin:P@ssw0rd@pgm-2zex1m2y3r23hdn5.pg.rds.aliyuncs.com:5432/ai_clinical'; // ✅ 正确做法 const dbUrl = process.env.DATABASE_URL; ``` #### 5. **禁止在 backend/ 目录构建前不复制 Prisma 文件** ```bash # ❌ 错误做法: cd backend docker build -t backend-service:v1.0.0 . # ❌ 错误:COPY prisma ./prisma 会失败(找不到文件) # ✅ 正确做法: cd AIclinicalresearch # 项目根目录 cp -r prisma backend/prisma # 先复制 cd backend docker build -t backend-service:v1.0.0 . # 再构建 ``` #### 6. **禁止使用内存作为缓存(多实例环境)** ```typescript // ❌ 错误示例:内存缓存不共享 const cache = new Map(); // 实例 1 的缓存,实例 2 看不到 // ✅ 正确做法:使用 PostgreSQL 缓存(Postgres-Only) import { CacheFactory } from './common/cache'; const cache = CacheFactory.create(); // 根据 CACHE_TYPE 选择 ``` #### 7. **禁止使用本地文件作为队列** ```typescript // ❌ 错误示例:文件队列不共享 import fs from 'fs'; fs.writeFileSync('/tmp/queue.json', JSON.stringify(tasks)); // 实例间不同步 // ✅ 正确做法:使用 pg-boss(Postgres-Only) import { jobQueue } from './common/jobs'; await jobQueue.send('pdf-extraction', { fileId: '123' }); ``` #### 8. **禁止使用同步阻塞操作** ```typescript // ❌ 错误示例:阻塞事件循环 import fs from 'fs'; const data = fs.readFileSync('/large-file.pdf'); // 阻塞其他请求 // ✅ 正确做法:使用异步 API const data = await fs.promises.readFile('/large-file.pdf'); ``` #### 9. **禁止跳过健康检查** ```typescript // ❌ 错误示例:健康检查总是返回 200 app.get('/health', async (req, reply) => { return { status: 'ok' }; // 即使数据库断开也返回 ok }); // ✅ 正确做法:真正检查依赖服务 app.get('/health', async (req, reply) => { const dbHealthy = await testDatabaseConnection(); if (!dbHealthy) { reply.code(503); return { status: 'unhealthy', database: 'disconnected' }; } return { status: 'healthy' }; }); ``` #### 10. **禁止忽略错误处理** ```typescript // ❌ 错误示例:吞掉错误 try { await riskyOperation(); } catch (error) { // 什么都不做 } // ✅ 正确做法:记录错误并返回适当的响应 try { await riskyOperation(); } catch (error) { logger.error('Risky operation failed', { error, stack: error.stack }); reply.code(500).send({ error: 'Internal server error' }); } ``` #### 11. **禁止在生产环境使用 development 模式** ```bash # ❌ 错误配置 NODE_ENV=development # 会打印大量调试日志,暴露敏感信息 # ✅ 正确配置 NODE_ENV=production LOG_LEVEL=info ``` #### 12. **禁止使用弱 JWT 密钥** ```bash # ❌ 错误配置 JWT_SECRET=secret # 太弱,容易被破解 # ✅ 正确配置(至少 32 位随机字符串) JWT_SECRET=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 # 使用 openssl rand -base64 32 生成 ``` #### 13. **禁止直接暴露 Prisma 错误到前端** ```typescript // ❌ 错误示例:泄露数据库结构 try { await prisma.user.create({ data: { email: 'test@example.com' } }); } catch (error) { reply.code(500).send({ error: error.message }); // 可能暴露表名、字段名 } // ✅ 正确做法:返回通用错误 try { await prisma.user.create({ data: { email: 'test@example.com' } }); } catch (error) { logger.error('Failed to create user', { error }); reply.code(500).send({ error: 'Failed to create user' }); } ``` #### 14. **禁止忽略 Docker 镜像优化** ```dockerfile # ❌ 错误示例:单阶段构建,镜像臃肿 FROM node:22 COPY . . RUN npm install # 包含 devDependencies CMD ["node", "dist/index.js"] # ✅ 正确做法:多阶段构建,减小镜像体积 FROM node:22-alpine AS builder # ... 构建阶段 ... FROM node:22-alpine COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules # 仅生产依赖 ``` --- ## 📚 相关文档 - [03-Dify-ECS部署完全指南.md](./03-Dify-ECS部署完全指南.md) - [04-Python微服务-SAE容器部署指南.md](./04-Python微服务-SAE容器部署指南.md) - [06-前端Nginx-SAE容器部署指南.md](./06-前端Nginx-SAE容器部署指南.md)(待创建) --- ## 🆘 获取帮助 **遇到问题?** 1. 查看本文档的"故障排查"章节 2. 查看 SAE 控制台的实时日志 3. 查看 RDS 控制台的性能监控 4. 联系团队技术支持 --- **部署愉快!🚀**