flnxi-Wu/架构设计与编码规范.md
WuYongJie d1c6efc79d
Some checks failed
CI Check / check (push) Has been cancelled
feat: NAS 部署与 Harbor 推送流程
2025-12-03 14:39:06 +08:00

20 KiB
Raw Blame History

架构设计与编码规范

📋 项目目标

最终目的:让其他公司能够一键部署或一条命令从Docker部署到自己的NAS或服务器上。

核心原则:

  1. 性能开销优先 - 最小化资源消耗,确保在低配置设备上也能稳定运行
  2. 稳定性优先 - 确保长期稳定运行,减少崩溃和错误
  3. 易于部署 - 零配置或最小配置即可运行
  4. 可维护性 - 代码清晰,易于理解和维护
  5. 国内镜像源优先 - 所有依赖、库等外部资源必须使用国内镜像源,确保国内用户快速部署和稳定运行

🏗️ 架构设计原则

1. 资源消耗最小化

1.1 内存管理

  • ✅ 禁止内存泄漏

    • 所有事件监听器必须正确清理
    • 定时器必须清理
    • 数据库连接池大小限制
    • 文件流必须关闭
  • ✅ 避免大对象常驻内存

    • 大文件使用流式处理,不全部加载到内存
    • 图片处理使用流式API
    • 数据库查询使用分页,避免一次性加载大量数据
  • ✅ 缓存策略

    • 使用内存缓存时设置大小限制和TTL
    • 优先使用Redis等外部缓存(如果可用)
    • 避免缓存大对象

1.2 CPU优化

  • ✅ 避免阻塞主线程

    • 所有I/O操作使用异步
    • 图片处理使用Worker线程或子进程
    • 数据库查询使用连接池
  • ✅ 减少不必要的计算

    • 禁用Next.js Image Optimizer(图片已在上传时优化)
    • 避免重复的图片处理
    • 使用数据库索引优化查询

1.3 网络优化

  • ✅ 减少网络请求

    • 合并API请求
    • 使用HTTP/2多路复用
    • 静态资源使用CDN或本地缓存
  • ✅ 超时和重试机制

    • 所有外部API调用设置合理的超时时间
    • 实现指数退避重试机制
    • 避免无限重试导致资源耗尽

2. 稳定性保障

2.1 错误处理

// ✅ 好的做法:完整的错误处理
try {
  const result = await someOperation()
  return NextResponse.json({ success: true, data: result })
} catch (error) {
  // 记录详细错误信息(用于诊断)
  console.error('[Operation] Error:', {
    message: error instanceof Error ? error.message : String(error),
    stack: error instanceof Error ? error.stack : undefined,
    context: { /* 相关上下文信息 */ }
  })
  
  // 返回用户友好的错误信息(不暴露内部细节)
  return NextResponse.json(
    { error: '操作失败,请稍后重试' },
    { status: 500 }
  )
}

// ❌ 不好的做法:忽略错误或暴露内部细节
try {
  await someOperation()
} catch (error) {
  // 错误被忽略,无法诊断问题
}

2.2 资源清理

// ✅ 好的做法:确保资源清理
async function processFile(file: File) {
  let stream: ReadableStream | null = null
  try {
    stream = file.stream()
    // 处理文件
  } finally {
    // 确保流被关闭
    if (stream) {
      await stream.cancel()
    }
  }
}

// ❌ 不好的做法:资源未清理
async function processFile(file: File) {
  const stream = file.stream()
  // 处理文件,但流可能未关闭
}

2.3 数据库连接管理

// ✅ 好的做法:使用连接池,设置合理的限制
const prisma = new PrismaClient({
  log: process.env.NODE_ENV === 'development' ? ['query', 'error'] : ['error'],
  datasources: {
    db: {
      url: process.env.DATABASE_URL,
    },
  },
})

// 设置连接池大小(根据服务器配置调整)
// 在DATABASE_URL中设置:?connection_limit=10&pool_timeout=20

// ❌ 不好的做法:无限制的连接
// 可能导致数据库连接耗尽

2.4 文件描述符管理

// ✅ 好的做法:限制并发操作
const MAX_CONCURRENT_UPLOADS = 5
const uploadSemaphore = new Semaphore(MAX_CONCURRENT_UPLOADS)

async function uploadFile(file: File) {
  await uploadSemaphore.acquire()
  try {
    // 上传文件
  } finally {
    uploadSemaphore.release()
  }
}

// ❌ 不好的做法:无限制并发
// 可能导致文件描述符耗尽

3. 部署友好性

3.1 环境变量管理

// ✅ 好的做法:提供合理的默认值
const config = {
  port: parseInt(process.env.PORT || '3000', 10),
  databaseUrl: process.env.DATABASE_URL || 'postgresql://localhost:5432/app',
  // 所有配置都有默认值,可以零配置运行
}

// ❌ 不好的做法:强制要求环境变量
if (!process.env.DATABASE_URL) {
  throw new Error('DATABASE_URL is required') // 部署时容易出错
}

3.2 健康检查

// ✅ 好的做法:提供健康检查端点
export async function GET() {
  try {
    // 检查数据库连接
    await prisma.$queryRaw`SELECT 1`
    
    // 检查S3连接(如果配置了)
    if (process.env.S3_ENDPOINT) {
      // 简单的连接测试
    }
    
    return NextResponse.json({
      status: 'ok',
      timestamp: new Date().toISOString(),
      version: process.env.APP_VERSION || 'unknown'
    })
  } catch (error) {
    return NextResponse.json(
      { status: 'error', message: 'Health check failed' },
      { status: 503 }
    )
  }
}

3.3 优雅关闭

// ✅ 好的做法:处理关闭信号
process.on('SIGTERM', async () => {
  console.log('SIGTERM received, shutting down gracefully...')
  
  // 停止接受新请求
  server.close(() => {
    console.log('HTTP server closed')
  })
  
  // 关闭数据库连接
  await prisma.$disconnect()
  
  // 清理其他资源
  process.exit(0)
})

4. 监控和诊断

4.1 日志记录

// ✅ 好的做法:结构化日志
function logOperation(operation: string, data: any) {
  console.log(JSON.stringify({
    timestamp: new Date().toISOString(),
    level: 'info',
    operation,
    ...data
  }))
}

// 使用示例
logOperation('upload', {
  userId: user.id,
  fileSize: file.size,
  duration: Date.now() - startTime
})

4.2 性能监控

// ✅ 好的做法:记录操作耗时
async function monitoredOperation() {
  const startTime = Date.now()
  try {
    const result = await operation()
    const duration = Date.now() - startTime
    
    if (duration > 1000) {
      console.warn(`[Performance] Operation took ${duration}ms`)
    }
    
    return result
  } catch (error) {
    const duration = Date.now() - startTime
    console.error(`[Performance] Operation failed after ${duration}ms:`, error)
    throw error
  }
}

📝 编码规范

1. API路由设计

1.1 统一响应格式

// ✅ 好的做法:统一的响应格式
export async function GET() {
  try {
    const data = await fetchData()
    return NextResponse.json({
      success: true,
      data,
      timestamp: new Date().toISOString()
    })
  } catch (error) {
    return NextResponse.json({
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error',
      timestamp: new Date().toISOString()
    }, { status: 500 })
  }
}

1.2 请求验证

// ✅ 好的做法:验证和清理输入
import { z } from 'zod'

const uploadSchema = z.object({
  title: z.string().min(1).max(100),
  file: z.instanceof(File)
})

export async function POST(request: Request) {
  try {
    const formData = await request.formData()
    const data = uploadSchema.parse({
      title: formData.get('title'),
      file: formData.get('file')
    })
    
    // 处理数据
  } catch (error) {
    if (error instanceof z.ZodError) {
      return NextResponse.json(
        { error: 'Invalid input', details: error.errors },
        { status: 400 }
      )
    }
    throw error
  }
}

1.3 权限检查

// ✅ 好的做法:统一的权限检查
async function requireAdmin(session: Session | null) {
  if (!session) {
    throw new Error('Unauthorized')
  }
  
  const isAdmin = session.user.role === 'ADMIN' || (session.user as any).isSuperAdmin
  if (!isAdmin) {
    throw new Error('Forbidden')
  }
  
  return session
}

export async function POST(request: Request) {
  const session = await getServerSession(authOptions)
  await requireAdmin(session)
  
  // 管理员操作
}

2. 数据库操作

2.1 使用事务

// ✅ 好的做法:关键操作使用事务
await prisma.$transaction(async (tx) => {
  // 检查条件
  const count = await tx.user.count()
  
  // 执行操作
  const user = await tx.user.create({
    data: { /* ... */ }
  })
  
  return user
})

2.2 查询优化

// ✅ 好的做法:只查询需要的字段
const users = await prisma.user.findMany({
  select: {
    id: true,
    email: true,
    username: true,
    // 不查询password等大字段
  },
  take: 20, // 分页
  skip: 0
})

// ❌ 不好的做法:查询所有字段
const users = await prisma.user.findMany() // 可能包含大字段

2.3 错误处理

// ✅ 好的做法:处理数据库错误
try {
  await prisma.user.create({ data })
} catch (error) {
  if (error instanceof Prisma.PrismaClientKnownRequestError) {
    if (error.code === 'P2002') {
      // 唯一约束冲突
      return NextResponse.json(
        { error: '用户已存在' },
        { status: 409 }
      )
    }
  }
  throw error
}

3. 文件处理

3.1 流式处理

// ✅ 好的做法:使用流处理大文件
import { pipeline } from 'stream/promises'
import { createReadStream } from 'fs'

async function uploadLargeFile(filePath: string) {
  const fileStream = createReadStream(filePath)
  const uploadStream = createUploadStream()
  
  await pipeline(fileStream, uploadStream)
  // 文件不会全部加载到内存
}

// ❌ 不好的做法:一次性读取整个文件
const fileContent = await fs.readFile(filePath) // 大文件会占用大量内存

3.2 文件大小限制

// ✅ 好的做法:检查文件大小
const MAX_FILE_SIZE = 10 * 1024 * 1024 * 1024 // 10GB

if (file.size > MAX_FILE_SIZE) {
  return NextResponse.json(
    { error: '文件大小超过限制' },
    { status: 413 }
  )
}

4. 前端组件

4.1 性能优化

// ✅ 好的做法:使用React.memo和useMemo
import { memo, useMemo } from 'react'

const MaterialCard = memo(({ material }: { material: Material }) => {
  const previewUrl = useMemo(() => {
    return material.previewUrl || '/placeholder.png'
  }, [material.previewUrl])
  
  return (
    <Image
      src={previewUrl}
      alt={material.title}
      unoptimized // 图片已在上传时优化,不需要Next.js再次优化
      width={300}
      height={200}
    />
  )
})

4.2 错误边界

// ✅ 好的做法:错误边界处理
'use client'

import { ErrorBoundary } from 'react-error-boundary'

function ErrorFallback({ error }: { error: Error }) {
  return (
    <div className="error-container">
      <h2>出现错误</h2>
      <p>{error.message}</p>
      <button onClick={() => window.location.reload()}>
        刷新页面
      </button>
    </div>
  )
}

export default function Page() {
  return (
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      {/* 页面内容 */}
    </ErrorBoundary>
  )
}

🚀 部署优化

1. Docker优化

1.1 多阶段构建

# ✅ 好的做法:多阶段构建,最小化镜像大小
FROM node:18-alpine AS builder
# 构建阶段

FROM node:18-alpine AS runner
# 运行阶段,只包含必要的文件
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

1.2 层缓存优化

# ✅ 好的做法:依赖文件放在前面,利用Docker缓存
COPY package.json package-lock.json ./
RUN npm ci --production

COPY . .
RUN npm run build

2. 环境配置

2.1 配置优先级

1. 系统环境变量(最高优先级)
2. .env.local(本地开发)
3. .env(项目默认)
4. config-bianliang.env(可迁移配置,最低优先级)

2.2 默认值设计

// ✅ 好的做法:所有配置都有合理的默认值
const config = {
  // 数据库:默认使用本地PostgreSQL
  databaseUrl: process.env.DATABASE_URL || 'postgresql://postgres:postgres@postgres:5432/flnxi',
  
  // S3:默认使用MinIO(Docker Compose中已配置)
  s3Endpoint: process.env.S3_ENDPOINT || 'http://minio:9000',
  s3Bucket: process.env.S3_BUCKET || 'youyidian3d',
  
  // 端口:默认3000
  port: parseInt(process.env.PORT || '3000', 10),
}

3. 国内镜像源配置(必须)

3.1 核心要求

所有外部资源必须使用国内镜像源,包括但不限于:

  • npm 包(Node.js 依赖)
  • Docker 基础镜像(Alpine、Node.js 等)
  • 系统包管理器(apk、apt、yum 等)
  • 其他第三方服务(如 CDN、API 等)

3.2 Docker 构建镜像源配置

3.2.1 Alpine Linux 镜像源
# ✅ 好的做法:使用阿里云 Alpine 镜像源
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

# 其他可选镜像源:
# - 清华大学:mirrors.tuna.tsinghua.edu.cn
# - 中科大:mirrors.ustc.edu.cn
3.2.2 npm 镜像源
# ✅ 好的做法:构建时使用国内 npm 镜像源
RUN npm config set registry https://registry.npmmirror.com/

# 安装依赖
RUN npm ci --prefer-offline --no-audit

# 构建完成后清理配置(不在运行镜像中保留)
RUN npm config delete registry

推荐的国内 npm 镜像源:

  • npmmirror.com(原淘宝镜像,推荐)
  • registry.npmmirror.com(新域名)
  • cnpmjs.org(备选)
3.2.3 Docker Hub 镜像源(必须配置)

重要:所有Docker镜像(包括MinIO、PostgreSQL等)必须通过国内镜像加速器拉取。

配置方法(/etc/docker/daemon.json):

{
  "registry-mirrors": [
    "https://docker.mirrors.ustc.edu.cn",
    "https://hub-mirror.c.163.com",
    "https://mirror.baidubce.com"
  ]
}

配置后重启Docker:

sudo systemctl daemon-reload
sudo systemctl restart docker

验证配置:

docker info | grep -A 10 "Registry Mirrors"

版本管理要求:

  • ✅ 使用特定版本号(如 minio/minio:RELEASE.2025-04-22T22-12-26Z)而不是 latest
  • ✅ 确保版本号在文档中明确记录
  • ✅ 版本更新前必须在测试环境验证

3.3 本地开发环境配置

3.3.1 npm 镜像源(本地开发)
# 永久配置(推荐)
npm config set registry https://registry.npmmirror.com/

# 或使用 .npmrc 文件(项目级别)
echo "registry=https://registry.npmmirror.com/" > .npmrc
3.3.2 验证镜像源配置
# 检查 npm 镜像源
npm config get registry

# 检查 Docker 镜像源(如果配置了)
docker info | grep -A 10 "Registry Mirrors"

3.4 代码中的外部资源引用

3.4.1 CDN 资源
// ✅ 好的做法:使用国内 CDN
const CDN_BASE_URL = process.env.CDN_BASE_URL || 'https://cdn.jsdelivr.net'

// 推荐使用:
// - jsDelivr(有国内节点)
// - unpkg(有国内节点)
// - 自建 CDN 或使用阿里云/腾讯云 CDN
3.4.2 外部 API
// ✅ 好的做法:优先使用国内 API 服务
// 如果必须使用国外服务,考虑:
// 1. 使用代理
// 2. 使用国内替代方案
// 3. 实现缓存机制减少请求

3.5 部署脚本中的镜像源配置

3.5.1 PowerShell 部署脚本
# ✅ 好的做法:部署脚本中配置镜像源
# 在 Dockerfile 中已配置,部署脚本无需额外配置
# 但如果需要,可以在构建前设置环境变量
$env:NPM_CONFIG_REGISTRY = "https://registry.npmmirror.com/"
3.5.2 服务器端配置
# 如果需要在服务器上直接安装依赖,配置 npm 镜像源
ssh user@server "npm config set registry https://registry.npmmirror.com/"

3.6 镜像源配置检查清单

在提交代码前,确保:

  • Dockerfile 中配置了 Alpine 镜像源
  • Dockerfile 中配置了 npm 镜像源(构建阶段)
  • 本地开发环境配置了 npm 镜像源
  • 服务器上配置了 Docker 镜像加速器
  • docker-compose.yml 中使用特定版本号(不使用 latest)
  • 所有外部 CDN 资源优先使用国内服务
  • 部署文档中说明了镜像源配置要求

3.7 Docker镜像版本管理

3.7.1 使用特定版本号(必须)
# ✅ 好的做法:使用特定版本号
services:
  minio:
    image: minio/minio:RELEASE.2025-04-22T22-12-26Z
    
  postgres:
    image: postgres:15-alpine

# ❌ 不好的做法:使用latest(可能导致不稳定)
services:
  minio:
    image: minio/minio:latest
3.7.2 版本更新流程
  1. 测试环境验证:在测试环境部署新版本,验证兼容性
  2. 文档记录:在CHANGELOG中记录版本更新和变更
  3. 逐步部署:先在部分环境部署,确认稳定后再全面更新
  4. 回滚准备:保留旧版本配置,确保可以快速回滚
3.7.3 版本号管理建议
# 使用环境变量管理版本(推荐)
MINIO_VERSION=${MINIO_VERSION:-RELEASE.2025-04-22T22-12-26Z}

services:
  minio:
    image: minio/minio:${MINIO_VERSION}

📊 性能指标

目标指标

  1. 内存使用

    • 空闲时:< 200MB
    • 正常运行时:< 500MB
    • 峰值:< 1GB
  2. CPU使用

    • 空闲时:< 5%
    • 正常运行时:< 30%
    • 峰值:< 80%
  3. 响应时间

    • API响应:< 200ms(95分位)
    • 页面加载:< 2s(首次加载)
    • 数据库查询:< 100ms(95分位)
  4. 稳定性

    • 正常运行时间:> 99.9%
    • 错误率:< 0.1%
    • 崩溃恢复时间:< 30s

🔍 代码审查清单

在提交代码前,检查以下项目:

性能

  • 是否避免了内存泄漏?
  • 是否使用了流式处理大文件?
  • 是否限制了并发操作?
  • 是否优化了数据库查询?
  • 是否禁用了不必要的优化(如Next.js Image Optimizer)?

稳定性

  • 是否处理了所有可能的错误?
  • 是否清理了所有资源(文件流、数据库连接等)?
  • 是否设置了合理的超时时间?
  • 是否实现了重试机制?
  • 是否添加了健康检查?

部署友好性

  • 是否提供了合理的默认值?
  • 是否支持零配置运行?
  • 是否添加了必要的日志?
  • 是否处理了优雅关闭?
  • 是否配置了国内镜像源(npm、Docker、系统包管理器)?
  • 是否避免了直接引用国外 CDN 或 API?

📚 参考资源


最后更新:2025-11-15 维护者:开发团队


📌 重要提醒

国内镜像源配置(必须遵守)

本项目主要面向国内用户,所有外部资源必须使用国内镜像源:

  1. ✅ Dockerfile 中已配置:

    • Alpine Linux:使用阿里云镜像源
    • npm:使用 npmmirror.com 镜像源
  2. ✅ 本地开发环境:

    • 建议配置 npm 镜像源:npm config set registry https://registry.npmmirror.com/
  3. ⚠️ 新增依赖或修改配置时:

    • 必须确保使用国内镜像源
    • 禁止直接使用国外源(npmjs.org、docker.io 等)
    • 代码审查时必须检查镜像源配置
  4. 📝 文档要求:

    • 所有部署文档必须说明镜像源配置
    • 新加入的开发者必须了解镜像源要求