# 云原生部署架构指南

> **文档版本：** V1.0  
> **创建日期：** 2025-11-16  
> **适用对象：** 后端开发、架构师、运维  
> **维护者：** 架构团队  
> **状态：** ✅ 已完成

---

## 📋 文档说明

本文档提供 **AI临床研究平台** 部署到阿里云 Serverless 架构的完整指南。

> **⭐ 重要更新（2025-11-16）**：  
> 平台基础设施的详细实施计划和代码实现已迁移到：  
> **[平台基础设施规划](./04-平台基础设施规划.md)**  
> 
> 本文档聚焦于：
> - 云原生架构总体设计
> - 阿里云服务选型和配置
> - Docker容器化和部署流程
> - 成本估算和监控告警

**文档定位**：
- 本文档（03）：**云原生部署架构总览** - 侧重云服务和部署流程
- 04文档：**平台基础设施规划** - 侧重代码实现和开发指南

**阅读时间**：20 分钟  
**实施时间**：参见 [平台基础设施规划](./04-平台基础设施规划.md) 的2.5天实施计划

---

## 🏗️ 架构详解

### 1. Serverless 应用引擎 (SAE)

#### **产品特性**

| 特性 | 说明 | 优势 |
|------|------|------|
| **自动扩缩容** | 根据流量自动调整实例数（0-100） | 高峰期不宕机，低谷期省成本 |
| **按需付费** | ¥0.000110592/请求次 + 实例费 | 初期月费约 ¥200-500 |
| **容器化部署** | 支持 Docker 镜像 | 环境一致性，快速回滚 |
| **内置负载均衡** | 自动分配流量 | 无需单独购买 SLB |
| **健康检查** | 自动重启异常实例 | 提高可用性 |

#### **实例规格选择**

| 阶段 | 规格 | vCPU | 内存 | 适用场景 |
|------|------|------|------|---------|
| **开发/测试** | 0.5C1G | 0.5核 | 1GB | 日请求 < 1000 |
| **初期** | 1C2G | 1核 | 2GB | 日请求 1000-5000 |
| **成长期** | 2C4G | 2核 | 4GB | 日请求 5000-20000 |
| **成熟期** | 4C8G | 4核 | 8GB | 日请求 > 20000 |

**建议配置**：
```yaml
# SAE 应用配置
实例规格: 1C2G
最小实例数: 1  # 避免冷启动
最大实例数: 10
CPU 触发扩容阈值: 70%
内存触发扩容阈值: 80%
```

---

### 2. 云数据库 RDS (PostgreSQL 15)

#### **规格选型**

| 阶段 | 规格 | vCPU | 内存 | 最大连接数 | 月费 |
|------|------|------|------|-----------|------|
| **开发/测试** | 基础版 1C1G | 1核 | 1GB | 100 | ¥120 |
| **初期** | 通用版 2C4G | 2核 | 4GB | 400 | ¥300 |
| **成长期** | 通用版 4C8G | 4核 | 8GB | 800 | ¥600 |
| **成熟期** | 独享版 8C16G | 8核 | 16GB | 1600 | ¥1200 |

#### **关键配置**

```sql
-- 查看当前最大连接数
SHOW max_connections;

-- 查看当前活跃连接
SELECT count(*) FROM pg_stat_activity;

-- 按数据库分组统计连接
SELECT datname, count(*) 
FROM pg_stat_activity 
GROUP BY datname;
```

**连接池计算公式**：
```
每实例连接数 = RDS最大连接数 / SAE最大实例数 × 0.8（安全系数）

示例：
RDS: 400连接
SAE: 最多10实例
每实例: 400 / 10 × 0.8 = 32连接
```

---

### 3. 对象存储 OSS

#### **Bucket 配置**

```yaml
Bucket名称: aiclinical-prod
区域: 华东1（杭州）oss-cn-hangzhou
存储类型: 标准存储
访问权限: 私有（Private）
版本控制: 开启
跨域设置: 允许前端域名
```

#### **目录结构规划**

```
aiclinical-prod/
├── asl/
│   ├── pdfs/               # PDF文件
│   ├── excel/              # Excel文件
│   └── exports/            # 导出文件
├── avatars/                # 用户头像
├── documents/              # 知识库文档
└── temp/                   # 临时文件（1天后自动删除）
```

#### **生命周期管理**

```json
{
  "Rules": [
    {
      "ID": "delete-temp-files",
      "Prefix": "temp/",
      "Status": "Enabled",
      "Expiration": {
        "Days": 1
      }
    },
    {
      "ID": "archive-old-pdfs",
      "Prefix": "asl/pdfs/",
      "Status": "Enabled",
      "Transitions": [
        {
          "Days": 90,
          "StorageClass": "IA"  // 转为低频访问
        }
      ]
    }
  ]
}
```

---

## 💻 存储抽象层设计（核心）

### 接口定义

**文件**：`backend/src/common/storage/StorageAdapter.ts`

```typescript
/**
 * 存储抽象层接口
 * 
 * @description
 * - 支持本地文件系统 + 阿里云 OSS 无缝切换
 * - 通过环境变量控制实现类
 * 
 * @example
 * const storage = StorageFactory.create()
 * const url = await storage.upload('files/doc.pdf', buffer)
 */
export interface StorageAdapter {
  /**
   * 上传文件
   * @param key 存储键（路径），如 'asl/pdfs/xxx.pdf'
   * @param buffer 文件内容
   * @returns 访问URL
   */
  upload(key: string, buffer: Buffer): Promise<string>
  
  /**
   * 下载文件
   * @param key 存储键
   * @returns 文件内容
   */
  download(key: string): Promise<Buffer>
  
  /**
   * 删除文件
   * @param key 存储键
   */
  delete(key: string): Promise<void>
  
  /**
   * 获取访问URL
   * @param key 存储键
   * @returns 完整访问URL
   */
  getUrl(key: string): string
  
  /**
   * 批量上传
   * @param files 文件列表
   * @returns URL列表
   */
  uploadMany(files: Array<{ key: string; buffer: Buffer }>): Promise<string[]>
}
```

---

### LocalAdapter 实现（本地开发）

**文件**：`backend/src/common/storage/LocalAdapter.ts`

```typescript
import fs from 'fs/promises'
import path from 'path'
import { StorageAdapter } from './StorageAdapter.js'

/**
 * 本地文件系统存储适配器
 * 
 * @description
 * - 用于本地开发环境
 * - 文件存储在 ./uploads 目录
 * - 通过 HTTP 访问：http://localhost:3001/uploads/xxx
 */
export class LocalAdapter implements StorageAdapter {
  private uploadDir: string
  private baseUrl: string
  
  constructor() {
    this.uploadDir = path.resolve(process.cwd(), 'uploads')
    this.baseUrl = process.env.BASE_URL || 'http://localhost:3001'
    
    // 确保目录存在
    this.ensureUploadDir()
  }
  
  private async ensureUploadDir() {
    try {
      await fs.mkdir(this.uploadDir, { recursive: true })
    } catch (error) {
      console.error('创建上传目录失败:', error)
    }
  }
  
  async upload(key: string, buffer: Buffer): Promise<string> {
    const filePath = path.join(this.uploadDir, key)
    
    // 确保父目录存在
    await fs.mkdir(path.dirname(filePath), { recursive: true })
    
    // 写入文件
    await fs.writeFile(filePath, buffer)
    
    // 返回访问URL
    return this.getUrl(key)
  }
  
  async download(key: string): Promise<Buffer> {
    const filePath = path.join(this.uploadDir, key)
    return await fs.readFile(filePath)
  }
  
  async delete(key: string): Promise<void> {
    const filePath = path.join(this.uploadDir, key)
    try {
      await fs.unlink(filePath)
    } catch (error) {
      // 文件不存在时忽略错误
      if ((error as any).code !== 'ENOENT') {
        throw error
      }
    }
  }
  
  getUrl(key: string): string {
    return `${this.baseUrl}/uploads/${key}`
  }
  
  async uploadMany(files: Array<{ key: string; buffer: Buffer }>): Promise<string[]> {
    const urls = await Promise.all(
      files.map(file => this.upload(file.key, file.buffer))
    )
    return urls
  }
}
```

---

### OSSAdapter 实现（生产环境）

**文件**：`backend/src/common/storage/OSSAdapter.ts`

```typescript
import OSS from 'ali-oss'
import { StorageAdapter } from './StorageAdapter.js'

/**
 * 阿里云 OSS 存储适配器
 * 
 * @description
 * - 用于生产环境
 * - 文件存储在阿里云 OSS
 * - 支持内网/外网访问
 */
export class OSSAdapter implements StorageAdapter {
  private client: OSS
  private bucket: string
  private region: string
  
  constructor() {
    this.region = process.env.OSS_REGION!
    this.bucket = process.env.OSS_BUCKET!
    
    if (!this.region || !this.bucket) {
      throw new Error('OSS配置缺失：OSS_REGION 或 OSS_BUCKET 未设置')
    }
    
    this.client = new OSS({
      region: this.region,
      accessKeyId: process.env.OSS_ACCESS_KEY_ID!,
      accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET!,
      bucket: this.bucket,
      // 使用内网endpoint（SAE访问OSS免流量费）
      internal: process.env.NODE_ENV === 'production',
    })
  }
  
  async upload(key: string, buffer: Buffer): Promise<string> {
    try {
      const result = await this.client.put(key, buffer)
      return result.url
    } catch (error) {
      console.error('OSS上传失败:', error)
      throw new Error(`OSS上传失败: ${key}`)
    }
  }
  
  async download(key: string): Promise<Buffer> {
    try {
      const result = await this.client.get(key)
      return result.content as Buffer
    } catch (error) {
      console.error('OSS下载失败:', error)
      throw new Error(`OSS下载失败: ${key}`)
    }
  }
  
  async delete(key: string): Promise<void> {
    try {
      await this.client.delete(key)
    } catch (error) {
      console.error('OSS删除失败:', error)
      // 文件不存在时不抛出错误
    }
  }
  
  getUrl(key: string): string {
    // 返回外网访问URL
    return `https://${this.bucket}.${this.region}.aliyuncs.com/${key}`
  }
  
  async uploadMany(files: Array<{ key: string; buffer: Buffer }>): Promise<string[]> {
    // 并行上传（最多10个并发）
    const chunks = []
    for (let i = 0; i < files.length; i += 10) {
      chunks.push(files.slice(i, i + 10))
    }
    
    const urls: string[] = []
    for (const chunk of chunks) {
      const chunkUrls = await Promise.all(
        chunk.map(file => this.upload(file.key, file.buffer))
      )
      urls.push(...chunkUrls)
    }
    
    return urls
  }
  
  /**
   * 生成签名URL（临时访问）
   * @param key 存储键
   * @param expires 过期时间（秒），默认1小时
   */
  async getSignedUrl(key: string, expires: number = 3600): Promise<string> {
    return this.client.signatureUrl(key, { expires })
  }
}
```

---

### StorageFactory 工厂类

**文件**：`backend/src/common/storage/StorageFactory.ts`

```typescript
import { StorageAdapter } from './StorageAdapter.js'
import { LocalAdapter } from './LocalAdapter.js'
import { OSSAdapter } from './OSSAdapter.js'

/**
 * 存储工厂类
 * 
 * @description
 * - 根据环境变量自动选择存储实现
 * - STORAGE_TYPE=local → LocalAdapter
 * - STORAGE_TYPE=oss → OSSAdapter
 */
export class StorageFactory {
  private static instance: StorageAdapter | null = null
  
  /**
   * 创建存储实例（单例模式）
   */
  static create(): StorageAdapter {
    if (this.instance) {
      return this.instance
    }
    
    const storageType = process.env.STORAGE_TYPE || 'local'
    
    switch (storageType) {
      case 'oss':
        console.log('📦 使用阿里云 OSS 存储')
        this.instance = new OSSAdapter()
        break
      
      case 'local':
        console.log('📁 使用本地文件存储')
        this.instance = new LocalAdapter()
        break
      
      default:
        throw new Error(`未知的存储类型: ${storageType}`)
    }
    
    return this.instance
  }
  
  /**
   * 重置实例（用于测试）
   */
  static reset() {
    this.instance = null
  }
}

// 导出单例
export const storage = StorageFactory.create()
```

---

### 使用示例

```typescript
// backend/src/modules/asl/controllers/literatureController.ts

import { storage } from '../../../common/storage/StorageFactory.js'
import { prisma } from '../../../config/database.js'

/**
 * 上传PDF文件
 */
export async function uploadPdf(req, res) {
  try {
    const { literatureId } = req.params
    const file = await req.file()
    
    if (!file) {
      return res.status(400).send({ error: 'No file uploaded' })
    }
    
    // 读取文件内容
    const buffer = await file.toBuffer()
    
    // 生成存储键
    const key = `asl/pdfs/${Date.now()}-${file.filename}`
    
    // ✅ 上传到存储（自动根据环境选择Local或OSS）
    const url = await storage.upload(key, buffer)
    
    // 保存到数据库
    await prisma.aslLiterature.update({
      where: { id: literatureId },
      data: {
        pdfUrl: url,
        pdfOssKey: key,
        pdfFileSize: buffer.length,
      }
    })
    
    res.send({
      success: true,
      url,
      size: buffer.length
    })
  } catch (error) {
    console.error('PDF上传失败:', error)
    res.status(500).send({ error: 'Upload failed' })
  }
}

/**
 * Excel上传（不需要存储，直接解析）
 */
export async function importExcel(req, res) {
  const file = await req.file()
  const buffer = await file.toBuffer()
  
  // ✅ 直接从内存解析，不落盘
  const workbook = xlsx.read(buffer, { type: 'buffer' })
  const data = xlsx.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]])
  
  // 批量入库
  await prisma.aslLiterature.createMany({ data })
  
  res.send({ success: true, count: data.length })
}
```

---

## 🔧 数据库连接池配置

### Prisma配置

**文件**：`backend/src/config/database.ts`

```typescript
import { PrismaClient } from '@prisma/client'

// 环境判断
const isProduction = process.env.NODE_ENV === 'production'

// 计算连接池大小
// 生产环境：RDS 400连接 / SAE 10实例 × 0.8 = 32连接/实例
// 开发环境：本地 PostgreSQL，5连接足够
const connectionLimit = isProduction ? 32 : 5

/**
 * Prisma 客户端配置
 */
export const prisma = new PrismaClient({
  log: isProduction 
    ? ['error', 'warn']  // 生产环境只记录错误和警告
    : ['query', 'error', 'warn'],  // 开发环境记录所有查询
  
  datasources: {
    db: {
      url: process.env.DATABASE_URL
    }
  },
  
  // 关键配置：连接池
  ...(isProduction && {
    // 仅在生产环境配置连接池限制
    datasources: {
      db: {
        url: process.env.DATABASE_URL,
        // 连接池配置
        pool: {
          timeout: 5,           // 获取连接超时（秒）
          maxsize: connectionLimit,  // 最大连接数
          min: 2,               // 最小保持连接
        }
      }
    }
  })
})

// 优雅关闭
process.on('SIGINT', async () => {
  console.log('📦 正在关闭数据库连接...')
  await prisma.$disconnect()
  process.exit(0)
})

process.on('SIGTERM', async () => {
  console.log('📦 收到SIGTERM，正在关闭数据库连接...')
  await prisma.$disconnect()
  process.exit(0)
})

// 连接数监控（仅生产环境）
if (isProduction) {
  setInterval(async () => {
    try {
      const result = await prisma.$queryRaw<Array<{ count: bigint }>>`
        SELECT count(*) as count 
        FROM pg_stat_activity 
        WHERE datname = current_database()
      `
      
      const connectionCount = Number(result[0].count)
      console.log(`📊 当前数据库连接数: ${connectionCount}`)
      
      // 告警阈值：80%
      const maxConnections = 400  // 根据RDS规格设置
      if (connectionCount > maxConnections * 0.8) {
        console.error(`⚠️ 数据库连接数过高: ${connectionCount}/${maxConnections}`)
      }
    } catch (error) {
      console.error('连接数监控失败:', error)
    }
  }, 60000)  // 每60秒检查一次
}
```

---

## 🌍 环境变量管理

### 本地开发环境

**文件**：`backend/.env.development`

```bash
# 环境
NODE_ENV=development

# 存储配置
STORAGE_TYPE=local
BASE_URL=http://localhost:3001

# 数据库
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/aiclinical_dev

# LLM配置
LLM_API_KEY=sk-xxx
LLM_BASE_URL=https://api.deepseek.com

# 其他服务（本地无需配置）
OSS_REGION=
OSS_ACCESS_KEY_ID=
OSS_ACCESS_KEY_SECRET=
OSS_BUCKET=
```

---

### 生产环境配置

**在SAE控制台配置，不要写入文件**：

```bash
# 环境
NODE_ENV=production

# 存储配置
STORAGE_TYPE=oss
OSS_REGION=oss-cn-hangzhou
OSS_ACCESS_KEY_ID=LTAI5t***（从RAM用户获取）
OSS_ACCESS_KEY_SECRET=***
OSS_BUCKET=aiclinical-prod

# 数据库
DATABASE_URL=postgresql://aiclinical:***@rm-xxx.mysql.rds.aliyuncs.com:5432/aiclinical_prod

# LLM配置
LLM_API_KEY=sk-***
LLM_BASE_URL=https://api.deepseek.com

# 日志级别
LOG_LEVEL=info
```

---

## 🐳 Docker 配置

### Dockerfile

**文件**：`backend/Dockerfile`

```dockerfile
# ==================== 构建阶段 ====================
FROM node:20-alpine AS builder

WORKDIR /app

# 复制依赖文件
COPY package*.json ./
COPY prisma ./prisma/

# 安装依赖（包括dev依赖，用于构建）
RUN npm ci

# 复制源代码
COPY . .

# 生成 Prisma Client
RUN npx prisma generate

# 构建 TypeScript
RUN npm run build

# ==================== 生产阶段 ====================
FROM node:20-alpine

WORKDIR /app

# 复制依赖文件
COPY package*.json ./
COPY prisma ./prisma/

# 仅安装生产依赖
RUN npm ci --only=production

# 生成 Prisma Client
RUN npx prisma generate

# 从构建阶段复制编译后的代码
COPY --from=builder /app/dist ./dist

# 复制静态文件（如prompts）
COPY prompts ./prompts
COPY config ./config

# 创建非root用户
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001

# 切换到非root用户
USER nodejs

# 暴露端口
EXPOSE 3001

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
  CMD node -e "require('http').get('http://localhost:3001/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"

# 启动应用
CMD ["node", "dist/index.js"]
```

---

### docker-compose.yml（本地测试）

**文件**：`docker-compose.yml`

```yaml
version: '3.8'

services:
  # PostgreSQL数据库
  postgres:
    image: postgres:15-alpine
    container_name: aiclinical-postgres
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: aiclinical_dev
    ports:
      - "5432:5432"
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Redis（可选）
  redis:
    image: redis:7-alpine
    container_name: aiclinical-redis
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data

  # 后端应用
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    container_name: aiclinical-backend
    ports:
      - "3001:3001"
    environment:
      NODE_ENV: development
      STORAGE_TYPE: local
      DATABASE_URL: postgresql://postgres:postgres@postgres:5432/aiclinical_dev
    depends_on:
      postgres:
        condition: service_healthy
    volumes:
      - ./backend/uploads:/app/uploads  # 本地存储目录

volumes:
  postgres-data:
  redis-data:
```

**使用方式**：
```bash
# 启动所有服务
docker-compose up -d

# 查看日志
docker-compose logs -f backend

# 停止服务
docker-compose down
```

---

## 🚀 部署流程

### Step 1: 准备阿里云服务

#### 1.1 开通云数据库 RDS

```bash
# 1. 登录阿里云控制台
# 2. 搜索"云数据库 RDS"
# 3. 创建实例
#    - 数据库类型：PostgreSQL 15
#    - 规格：2核4GB（通用型）
#    - 存储空间：20GB（SSD）
#    - 网络：VPC（与SAE同区域）
# 4. 设置白名单（添加 SAE 应用的 VPC网段）
# 5. 创建数据库用户
# 6. 创建数据库：aiclinical_prod
```

#### 1.2 开通对象存储 OSS

```bash
# 1. 搜索"对象存储 OSS"
# 2. 创建 Bucket
#    - Bucket名称：aiclinical-prod
#    - 区域：华东1（杭州）
#    - 存储类型：标准存储
#    - 访问权限：私有
# 3. 创建 RAM 用户（用于API访问）
#    - 权限：AliyunOSSFullAccess
#    - 获取 AccessKeyId 和 AccessKeySecret
```

#### 1.3 开通容器镜像服务

```bash
# 1. 搜索"容器镜像服务 ACR"
# 2. 创建命名空间：aiclinical
# 3. 创建镜像仓库：backend
#    - 类型：私有
#    - 地域：华东1（杭州）
```

---

### Step 2: 构建和推送镜像

```bash
# 1. 登录阿里云容器镜像服务
docker login --username=<你的阿里云账号> registry.cn-hangzhou.aliyuncs.com

# 2. 构建镜像
cd backend
docker build -t aiclinical-backend:v1.0.0 .

# 3. 打标签
docker tag aiclinical-backend:v1.0.0 \
  registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:v1.0.0

# 4. 推送到阿里云
docker push registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:v1.0.0
```

**脚本自动化**：

**文件**：`backend/scripts/build-and-push.sh`

```bash
#!/bin/bash

VERSION=$1

if [ -z "$VERSION" ]; then
  echo "Usage: ./build-and-push.sh <version>"
  echo "Example: ./build-and-push.sh v1.0.0"
  exit 1
fi

echo "🔨 构建镜像: $VERSION"
docker build -t aiclinical-backend:$VERSION .

echo "🏷️ 打标签"
docker tag aiclinical-backend:$VERSION \
  registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:$VERSION

echo "📤 推送到阿里云"
docker push registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:$VERSION

echo "✅ 完成！"
echo "镜像地址: registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:$VERSION"
```

---

### Step 3: 创建 SAE 应用

#### 3.1 基本配置

```yaml
应用名称: aiclinical-backend
应用类型: 容器镜像
镜像地址: registry.cn-hangzhou.aliyuncs.com/aiclinical/backend:v1.0.0
端口: 3001
健康检查路径: /health
```

#### 3.2 实例配置

```yaml
实例规格: 1C2G
最小实例数: 1
最大实例数: 10
CPU触发扩容: 70%
内存触发扩容: 80%
```

#### 3.3 环境变量配置

在 SAE 控制台配置环境变量（**重要！**）：

```bash
NODE_ENV=production
STORAGE_TYPE=oss
DATABASE_URL=postgresql://aiclinical:***@rm-xxx.mysql.rds.aliyuncs.com:5432/aiclinical_prod
OSS_REGION=oss-cn-hangzhou
OSS_ACCESS_KEY_ID=LTAI5t***
OSS_ACCESS_KEY_SECRET=***
OSS_BUCKET=aiclinical-prod
LLM_API_KEY=sk-***
LOG_LEVEL=info
```

#### 3.4 VPC网络配置

```yaml
VPC: 选择与RDS相同的VPC
安全组: 允许3001端口入站
公网访问: 开启（分配公网SLB）
```

---

### Step 4: 部署应用

```bash
# 1. 在 SAE 控制台点击"部署应用"
# 2. 选择镜像版本
# 3. 确认配置无误
# 4. 点击"确定"开始部署

# 等待3-5分钟，查看部署日志
```

**部署成功标志**：
- ✅ 实例状态：运行中
- ✅ 健康检查：通过
- ✅ 访问 `http://<SAE公网地址>/health` 返回 200

---

### Step 5: 验证部署

```bash
# 1. 健康检查
curl http://<SAE公网地址>/health

# 预期响应：
{
  "status": "ok",
  "database": "connected",
  "timestamp": "2025-11-16T10:30:00.000Z"
}

# 2. API测试
curl http://<SAE公网地址>/api/v1/health

# 3. 查看日志
# 在 SAE 控制台 → 应用详情 → 实时日志
```

---

## 📊 成本估算

### 初期阶段（100用户，日活20）

| 服务 | 规格 | 月费 |
|------|------|------|
| SAE | 1C2G × 1实例 | ¥200 |
| RDS | 2C4G 通用版 | ¥300 |
| OSS | 100GB标准存储 + 10GB流量 | ¥15 |
| ACR | 私有仓库 | ¥0（免费额度） |
| **合计** | | **¥515/月** |

### 成长期（1000用户，日活200）

| 服务 | 规格 | 月费 |
|------|------|------|
| SAE | 2C4G × 平均3实例 | ¥600 |
| RDS | 4C8G 通用版 | ¥600 |
| OSS | 500GB标准存储 + 50GB流量 | ¥70 |
| CDN | 100GB流量（可选） | ¥20 |
| **合计** | | **¥1290/月** |

### 成熟期（10000用户，日活2000）

| 服务 | 规格 | 月费 |
|------|------|------|
| SAE | 4C8G × 平均10实例 | ¥2000 |
| RDS | 8C16G 独享版 + 读写分离 | ¥2000 |
| OSS | 2TB标准存储 + 500GB流量 | ¥300 |
| CDN | 1TB流量 | ¥200 |
| Redis | 2GB标准版 | ¥200 |
| **合计** | | **¥4700/月** |

---

## 🔍 监控与告警

### 应用性能监控（ARMS）

```yaml
# 在 SAE 控制台开通 ARMS
监控指标:
  - RT（响应时间）
  - QPS（每秒请求数）
  - 错误率
  - JVM内存
  
告警规则:
  - RT > 3秒
  - 错误率 > 5%
  - 可用率 < 99%
```

### 数据库监控

```yaml
# RDS 控制台 → 监控与报警
监控指标:
  - CPU利用率
  - 内存利用率
  - 连接数
  - IOPS
  
告警规则:
  - CPU > 80%
  - 连接数 > 320（80%）
  - 磁盘使用率 > 80%
```

### 成本告警

```bash
# 费用中心 → 费用预警
设置预算:
  - 每月预算: ¥1000
  - 80%预警: ¥800
  - 通知方式: 邮件 + 短信
```

---

## 📚 故障排查

### 常见问题

#### 问题1：数据库连接失败

**症状**：应用启动失败，日志显示 `Connection refused`

**解决方案**：
```bash
# 1. 检查 RDS 白名单
#    确保添加了 SAE 应用的 VPC网段

# 2. 检查 DATABASE_URL 格式
#    正确格式: postgresql://user:pass@host:port/db

# 3. 检查 RDS 实例状态
#    确保实例运行中
```

---

#### 问题2：OSS 上传失败

**症状**：文件上传返回 403 Forbidden

**解决方案**：
```bash
# 1. 检查 RAM 用户权限
#    确保有 AliyunOSSFullAccess

# 2. 检查 Bucket 权限
#    确保应用有写入权限

# 3. 检查环境变量
#    OSS_ACCESS_KEY_ID 和 OSS_ACCESS_KEY_SECRET 是否正确
```

---

#### 问题3：连接数耗尽

**症状**：`Connection pool exhausted`

**解决方案**：
```typescript
// 1. 检查当前连接数
const result = await prisma.$queryRaw`
  SELECT count(*) FROM pg_stat_activity
`

// 2. 调整连接池配置
// 减少每实例连接数，或增加 RDS 规格

// 3. 检查是否有连接泄漏
// 确保所有查询都正确关闭
```

---

## 📝 更新日志

| 日期 | 版本 | 变更内容 | 维护者 |
|------|------|---------|--------|
| 2025-11-16 | V1.0 | 创建文档，定义云原生部署架构 | 架构团队 |

---

## 📚 相关文档

- [前后端模块化架构设计-V2](../00-系统总体设计/前后端模块化架构设计-V2.md) - 架构总纲
- [云原生开发规范](../04-开发规范/08-云原生开发规范.md) - DO/DON'T 检查清单
- [Schema隔离架构设计](./01-Schema隔离架构设计（10个）.md)
- [数据库连接配置](./02-数据库连接配置.md)

---

**文档维护者：** 架构团队  
**最后更新：** 2025-11-16  
**文档状态：** ✅ 已完成

