# 云原生开发规范

> **文档版本：** V1.0  
> **创建日期：** 2025-11-16  
> **适用对象：** 所有开发人员  
> **强制性：** ✅ 必须遵守  
> **维护者：** 架构团队

---

## 📋 文档说明

本文档定义云原生环境（Serverless SAE + RDS + OSS）下的**代码规范**，所有业务模块（ASL、AIA、PKB等）必须遵守。

**阅读时间**：10 分钟  
**检查频率**：每次代码提交前

---

## 🌟 核心原则：复用平台能力

> **⭐ 重要提示（2025-11-16 更新）**：平台已提供完整的基础设施服务  
> **详细文档**：[平台基础设施规划](../09-架构实施/04-平台基础设施规划.md)

### 平台已提供的服务

**业务模块（ASL/AIA/PKB/DC等）应该复用以下平台能力，禁止重复实现：**

| 服务 | 导入方式 | 用途 | 文档 |
|------|---------|------|------|
| **存储服务** | `import { storage } from '@/common/storage'` | 文件上传下载 | ✅ 平台级 |
| **日志系统** | `import { logger } from '@/common/logging'` | 标准化日志 | ✅ 平台级 |
| **异步任务** | `import { jobQueue } from '@/common/jobs'` | 长时间任务 | ✅ 平台级 |
| **缓存服务** | `import { cache } from '@/common/cache'` | 分布式缓存 | ✅ 平台级 |
| **数据库** | `import { prisma } from '@/config/database'` | 数据库操作 | ✅ 平台级 |
| **LLM能力** | `import { LLMFactory } from '@/common/llm'` | LLM调用 | ✅ 平台级 |

### 示例：正确使用平台服务

```typescript
// ✅ 正确：直接导入平台服务
import { storage } from '@/common/storage'
import { logger } from '@/common/logging'
import { jobQueue } from '@/common/jobs'
import { prisma } from '@/config/database'

export class LiteratureService {
  async uploadPDF(projectId: string, pdfBuffer: Buffer) {
    // 1. 使用平台存储服务
    const key = `asl/projects/${projectId}/pdfs/${Date.now()}.pdf`
    const url = await storage.upload(key, pdfBuffer)
    
    // 2. 使用平台日志系统
    logger.info('PDF uploaded', { projectId, url })
    
    // 3. 使用平台数据库
    const literature = await prisma.aslLiterature.create({
      data: { projectId, pdfUrl: url }
    })
    
    return literature
  }
}
```

### ❌ 错误：重复实现平台能力

```typescript
// ❌ 错误：在业务模块中自己实现存储
// backend/src/modules/asl/storage/LocalStorage.ts  ← 不应该存在！
export class LocalStorage {
  async upload(file: Buffer) {
    await fs.writeFile('./uploads/file.pdf', file)  // ❌ 重复实现
  }
}

// ❌ 错误：在业务模块中自己实现日志
// backend/src/modules/asl/logger/logger.ts  ← 不应该存在！
export const logger = winston.createLogger({...})  // ❌ 重复实现
```

**原因**：
- ❌ 重复代码，难以维护
- ❌ 不同模块实现不一致
- ❌ 无法统一切换环境（本地/云端）
- ❌ 浪费开发时间

---

## ✅ 推荐做法（DO）

### 1. 文件存储 ✅

```typescript
// ✅ 正确：使用存储抽象层
import { storage } from '@/common/storage/StorageFactory'

export async function uploadFile(file: Buffer, filename: string) {
  const key = `asl/pdfs/${Date.now()}-${filename}`
  const url = await storage.upload(key, file)
  return url
}

// ✅ 正确：Excel 直接从内存解析
import * as xlsx from 'xlsx'

export async function importExcel(buffer: Buffer) {
  const workbook = xlsx.read(buffer, { type: 'buffer' })  // 内存解析
  const data = xlsx.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]])
  return data
}
```

**理由**：
- 容器重启不会丢失文件
- 本地开发和生产环境代码一致
- 自动根据环境变量切换存储方式

---

### 2. 数据库连接 ✅

```typescript
// ✅ 正确：使用全局 Prisma Client
import { prisma } from '@/config/database'

export async function createProject(data: any) {
  return await prisma.aslScreeningProject.create({ data })
}

// ✅ 正确：批量操作使用事务
export async function importLiteratures(literatures: any[]) {
  return await prisma.$transaction(async (tx) => {
    return await tx.aslLiterature.createMany({ data: literatures })
  })
}
```

**理由**：
- 全局实例复用连接
- 避免连接数耗尽
- 事务保证数据一致性

---

### 3. 环境变量配置 ✅

```typescript
// ✅ 正确：统一配置管理
// backend/src/config/env.ts
export const config = {
  llm: {
    apiKey: process.env.LLM_API_KEY!,
    baseUrl: process.env.LLM_BASE_URL!,
  },
  oss: {
    region: process.env.OSS_REGION!,
    bucket: process.env.OSS_BUCKET!,
  },
  database: {
    url: process.env.DATABASE_URL!,
  }
}

// ✅ 正确：使用配置对象
import { config } from '@/config/env'
const apiKey = config.llm.apiKey
```

**理由**：
- 配置集中管理
- 类型安全
- 便于切换环境

---

### 4. 长时间任务处理 ✅

```typescript
// ✅ 正确：异步任务 + 进度轮询
export async function startScreening(req, res) {
  // 1. 创建任务记录
  const task = await prisma.aslScreeningTask.create({
    data: {
      projectId: req.params.projectId,
      status: 'pending',
      totalItems: 100,
    }
  })
  
  // 2. 立即返回任务ID
  res.send({ success: true, taskId: task.id })
  
  // 3. 后台异步执行（不阻塞请求）
  processScreeningAsync(task.id).catch(err => {
    console.error('筛选任务失败:', err)
  })
}

// 前端轮询进度
export async function getTaskProgress(req, res) {
  const task = await prisma.aslScreeningTask.findUnique({
    where: { id: req.params.taskId }
  })
  
  res.send({
    status: task.status,
    progress: Math.round((task.completedItems / task.totalItems) * 100)
  })
}
```

**理由**：
- 避免请求超时（SAE默认30秒）
- 用户体验更好
- 支持批量任务

---

### 5. 日志输出 ✅

```typescript
// ✅ 正确：使用 logger 输出到 stdout
import pino from 'pino'

const logger = pino({
  level: process.env.LOG_LEVEL || 'info'
})

export async function uploadFile(req, res) {
  logger.info({ 
    userId: req.userId, 
    filename: req.file.filename 
  }, 'File uploaded')
  
  // ... 上传逻辑
}

// ✅ 正确：结构化日志
logger.error({ 
  error: err.message, 
  stack: err.stack,
  userId: req.userId 
}, 'Upload failed')
```

**理由**：
- SAE 自动采集 stdout 日志
- 结构化便于查询分析
- 集中查看，不会丢失

---

### 6. 错误处理 ✅

```typescript
// ✅ 正确：统一错误处理
export async function uploadPdf(req, res) {
  try {
    const file = await req.file()
    const buffer = await file.toBuffer()
    
    const url = await storage.upload(key, buffer)
    
    res.send({ success: true, url })
  } catch (error) {
    logger.error({ error }, 'PDF upload failed')
    
    res.status(500).send({
      success: false,
      error: {
        code: 'UPLOAD_FAILED',
        message: '文件上传失败，请重试'
      }
    })
  }
}
```

**理由**：
- 用户看到友好错误信息
- 日志记录详细错误
- 不暴露内部实现

---

### 7. 临时文件处理 ✅

```typescript
// ✅ 正确：/tmp 目录用完立即删除
import fs from 'fs/promises'
import path from 'path'

export async function extractPdfText(ossKey: string): Promise<string> {
  const tmpPath = path.join('/tmp', `${Date.now()}.pdf`)
  
  try {
    // 1. 从 OSS 下载到 /tmp
    await storage.download(ossKey, tmpPath)
    
    // 2. 提取文本
    const text = await extractWithNougat(tmpPath)
    
    return text
  } finally {
    // 3. 立即删除临时文件（无论成功失败）
    try {
      await fs.unlink(tmpPath)
    } catch (err) {
      logger.warn({ tmpPath }, 'Failed to delete temp file')
    }
  }
}
```

**理由**：
- /tmp 容量有限（512MB）
- 容器重启会清空
- 避免磁盘占满

---

## ❌ 禁止做法（DON'T）

### 1. 本地文件存储 ❌

```typescript
// ❌ 禁止：本地文件系统存储
import fs from 'fs'

export async function uploadFile(req, res) {
  const file = await req.file()
  const buffer = await file.toBuffer()
  
  // ❌ 错误：容器重启丢失
  fs.writeFileSync('./uploads/file.pdf', buffer)
  
  res.send({ url: '/uploads/file.pdf' })
}

// ❌ 禁止：依赖本地路径
const uploadDir = '/var/app/uploads'
const filePath = path.join(uploadDir, filename)
```

**问题**：
- 容器重启或扩容后文件丢失
- 多实例间无法共享文件
- 磁盘空间有限

**正确做法**：使用 `storage.upload()` 上传到 OSS

---

### 2. 内存缓存 ❌

```typescript
// ❌ 禁止：内存缓存（多实例不共享）
const cache = new Map<string, any>()

export async function getProject(id: string) {
  // ❌ 错误：扩容后其他实例读不到缓存
  if (cache.has(id)) {
    return cache.get(id)
  }
  
  const project = await prisma.aslScreeningProject.findUnique({ where: { id } })
  cache.set(id, project)
  return project
}

// ❌ 禁止：全局变量存储状态
let taskStatus = {}  // 多实例不同步
```

**问题**：
- 多实例间数据不同步
- 扩容后缓存失效
- 内存占用不可控

**正确做法**：使用 Redis 或数据库

---

### 3. 硬编码配置 ❌

```typescript
// ❌ 禁止：硬编码
const LLM_API_KEY = 'sk-xxx'
const DB_HOST = '192.168.1.100'
const OSS_BUCKET = 'my-bucket'

// ❌ 禁止：写死端口
app.listen(3001)

// ❌ 禁止：写死域名
const baseUrl = 'https://api.example.com'
```

**问题**：
- 无法切换环境
- 安全风险（密钥泄露）
- 部署困难

**正确做法**：
```typescript
// ✅ 使用环境变量
const apiKey = process.env.LLM_API_KEY
const port = process.env.PORT || 3001
app.listen(port)
```

---

### 4. 同步长任务 ❌

```typescript
// ❌ 禁止：同步处理长任务
export async function screenLiteratures(req, res) {
  const literatures = await prisma.aslLiterature.findMany({...})
  
  // ❌ 错误：100篇可能超过30秒
  for (const lit of literatures) {
    await llmScreening(lit)  // 每篇2-3秒
  }
  
  res.send({ success: true })  // 可能已经超时
}

// ❌ 禁止：没有超时保护
const result = await axios.get(url)  // 可能永久等待
```

**问题**：
- SAE 请求超时 30 秒
- 前端等待时间过长
- 无法显示进度

**正确做法**：异步任务 + 进度轮询（见 DO 第4条）

---

### 5. 本地日志文件 ❌

```typescript
// ❌ 禁止：写入本地文件
import fs from 'fs'

export function logError(error: Error) {
  // ❌ 错误：容器重启丢失，无法集中查看
  fs.appendFileSync('/var/log/app.log', error.message + '\n')
}

// ❌ 禁止：使用 console.log 而不用 logger
console.log('User logged in')  // 无结构化，难以查询
```

**问题**：
- 容器重启日志丢失
- 多实例日志分散
- 无法集中分析

**正确做法**：
```typescript
// ✅ 输出到 stdout，使用 logger
logger.info({ userId, action: 'login' }, 'User logged in')
```

---

### 6. 新建数据库连接 ❌

```typescript
// ❌ 禁止：每次请求新建连接
import { PrismaClient } from '@prisma/client'

export async function getProjects(req, res) {
  // ❌ 错误：每次新建，连接数暴增
  const prisma = new PrismaClient()
  const projects = await prisma.aslScreeningProject.findMany()
  await prisma.$disconnect()
  
  res.send(projects)
}

// ❌ 禁止：直接使用 pg 或其他驱动
import { Pool } from 'pg'
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
```

**问题**：
- 连接数快速耗尽（RDS限制 400 连接）
- 性能低下（连接建立耗时）
- 资源浪费

**正确做法**：
```typescript
// ✅ 使用全局 Prisma Client
import { prisma } from '@/config/database'
const projects = await prisma.aslScreeningProject.findMany()
```

---

### 7. 忽略错误 ❌

```typescript
// ❌ 禁止：空的 catch
try {
  await storage.upload(key, buffer)
} catch (error) {
  // ❌ 错误被吞掉，无法排查
}

// ❌ 禁止：不处理 Promise rejection
processAsync(taskId)  // 没有 .catch()

// ❌ 禁止：返回模糊错误
catch (error) {
  res.status(500).send({ error: 'Something went wrong' })
  // 用户不知道什么错了，如何解决
}
```

**问题**：
- 错误无法追踪
- 用户体验差
- 排查困难

**正确做法**：
```typescript
// ✅ 记录日志 + 友好错误信息
try {
  await storage.upload(key, buffer)
} catch (error) {
  logger.error({ error, key }, 'Upload failed')
  res.status(500).send({
    success: false,
    error: {
      code: 'UPLOAD_FAILED',
      message: '文件上传失败，请检查网络后重试'
    }
  })
}
```

---

## 🔍 代码审查检查清单

在**提交代码前**，请逐项检查：

### 文件存储
- [ ] 是否使用 `storage.upload()` 而非 `fs.writeFile()`？
- [ ] Excel 是否从内存解析，而非保存到本地？
- [ ] PDF 提取后是否立即删除临时文件？

### 数据库
- [ ] 是否使用全局 `prisma` 实例？
- [ ] 是否避免在循环中执行单条查询？（应该批量操作）
- [ ] 批量操作是否使用事务？

### 配置管理
- [ ] 是否所有配置都从 `process.env` 读取？
- [ ] 是否没有硬编码的 IP、域名、密钥？
- [ ] `.env.example` 是否已更新？

### 长时间任务
- [ ] 超过 10 秒的任务是否改为异步？
- [ ] 是否提供了进度查询接口？
- [ ] 前端是否有轮询或 WebSocket 获取进度？

### 日志
- [ ] 是否使用 `logger` 而非 `console.log`？
- [ ] 日志是否结构化（JSON格式）？
- [ ] 是否记录了关键操作（userId、action）？

### 错误处理
- [ ] 所有 async 函数是否有 try-catch？
- [ ] 是否记录了详细错误日志？
- [ ] 是否返回了友好的错误信息？

### 临时文件
- [ ] `/tmp` 目录使用后是否立即删除？
- [ ] 是否在 `finally` 块中清理？
- [ ] 是否避免长期依赖 `/tmp`？

---

## 🎯 快速自检（5分钟）

**运行以下命令，检查代码中是否有违规**：

```bash
# 检查是否有本地文件存储
grep -r "fs.writeFile\|fs.appendFile" backend/src/modules/

# 检查是否有硬编码配置
grep -r "sk-\|http://\|192.168" backend/src/modules/

# 检查是否有新建 Prisma 连接
grep -r "new PrismaClient" backend/src/modules/

# 检查是否有 console.log
grep -r "console.log" backend/src/modules/
```

**预期结果**：所有检查应该返回 **0 个匹配**

---

## 📚 参考文档

- [云原生部署架构指南](../09-架构实施/03-云原生部署架构指南.md) - 包含完整代码示例
- [前后端模块化架构设计-V2](../00-系统总体设计/前后端模块化架构设计-V2.md) - 架构总纲
- [数据库设计规范](./01-数据库设计规范.md)
- [API设计规范](./02-API设计规范.md)
- [代码规范](./05-代码规范.md)
- [Git提交规范](./06-Git提交规范.md)

---

## 📝 更新日志

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

---

**文档维护者：** 架构团队  
**最后更新：** 2025-11-16  
**文档状态：** ✅ 已完成  
**强制执行：** ✅ 所有代码提交前必须检查

