flnxi-Wu/docs/archive/架构设计与编码规范.md
WuYongJie 63ec6b7dfb fix: 修复排行榜按钮消失、tag排序和输入框浅色模式问题
1. 排行榜悬浮窗关闭后小按钮消失问题
   - 修复面板居中模式关闭时按钮位置错误
   - 保存并恢复按钮的居中状态

2. 首页tag按钮按素材数量排序
   - 添加按素材数量降序排序逻辑

3. 上传弹窗输入框浅色模式文字不可见
   - 添加 text-slate-900 确保浅色模式下文字可见
   - 添加 placeholder 样式确保占位符正确显示
2025-12-14 21:41:29 +08:00

797 lines
20 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 架构设计与编码规范
## 📋 项目目标
**最终目的**:让其他公司能够**一键部署**或**一条命令**从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 错误处理
```typescript
// ✅ 好的做法:完整的错误处理
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 资源清理
```typescript
// ✅ 好的做法:确保资源清理
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 数据库连接管理
```typescript
// ✅ 好的做法:使用连接池,设置合理的限制
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 文件描述符管理
```typescript
// ✅ 好的做法:限制并发操作
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 环境变量管理
```typescript
// ✅ 好的做法:提供合理的默认值
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 健康检查
```typescript
// ✅ 好的做法:提供健康检查端点
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 优雅关闭
```typescript
// ✅ 好的做法:处理关闭信号
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 日志记录
```typescript
// ✅ 好的做法:结构化日志
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 性能监控
```typescript
// ✅ 好的做法:记录操作耗时
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 统一响应格式
```typescript
// ✅ 好的做法:统一的响应格式
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 请求验证
```typescript
// ✅ 好的做法:验证和清理输入
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 权限检查
```typescript
// ✅ 好的做法:统一的权限检查
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 使用事务
```typescript
// ✅ 好的做法:关键操作使用事务
await prisma.$transaction(async (tx) => {
// 检查条件
const count = await tx.user.count()
// 执行操作
const user = await tx.user.create({
data: { /* ... */ }
})
return user
})
```
#### 2.2 查询优化
```typescript
// ✅ 好的做法:只查询需要的字段
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 错误处理
```typescript
// ✅ 好的做法:处理数据库错误
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 流式处理
```typescript
// ✅ 好的做法:使用流处理大文件
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 文件大小限制
```typescript
// ✅ 好的做法:检查文件大小
const MAX_FILE_SIZE = 10 * 1024 * 1024 * 1024 // 10GB
if (file.size > MAX_FILE_SIZE) {
return NextResponse.json(
{ error: '文件大小超过限制' },
{ status: 413 }
)
}
```
### 4. 前端组件
#### 4.1 性能优化
```typescript
// ✅ 好的做法:使用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 错误边界
```typescript
// ✅ 好的做法:错误边界处理
'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 多阶段构建
```dockerfile
# ✅ 好的做法:多阶段构建,最小化镜像大小
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 层缓存优化
```dockerfile
# ✅ 好的做法:依赖文件放在前面,利用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 默认值设计
```typescript
// ✅ 好的做法:所有配置都有合理的默认值
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 镜像源
```dockerfile
# ✅ 好的做法:使用阿里云 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 镜像源
```dockerfile
# ✅ 好的做法:构建时使用国内 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`):
```json
{
"registry-mirrors": [
"https://docker.mirrors.ustc.edu.cn",
"https://hub-mirror.c.163.com",
"https://mirror.baidubce.com"
]
}
```
**配置后重启Docker**:
```bash
sudo systemctl daemon-reload
sudo systemctl restart docker
```
**验证配置**:
```bash
docker info | grep -A 10 "Registry Mirrors"
```
**版本管理要求**:
- ✅ 使用特定版本号(如 `minio/minio:RELEASE.2025-04-22T22-12-26Z`)而不是 `latest`
- ✅ 确保版本号在文档中明确记录
- ✅ 版本更新前必须在测试环境验证
#### 3.3 本地开发环境配置
##### 3.3.1 npm 镜像源(本地开发)
```bash
# 永久配置(推荐)
npm config set registry https://registry.npmmirror.com/
# 或使用 .npmrc 文件(项目级别)
echo "registry=https://registry.npmmirror.com/" > .npmrc
```
##### 3.3.2 验证镜像源配置
```bash
# 检查 npm 镜像源
npm config get registry
# 检查 Docker 镜像源(如果配置了)
docker info | grep -A 10 "Registry Mirrors"
```
#### 3.4 代码中的外部资源引用
##### 3.4.1 CDN 资源
```typescript
// ✅ 好的做法:使用国内 CDN
const CDN_BASE_URL = process.env.CDN_BASE_URL || 'https://cdn.jsdelivr.net'
// 推荐使用:
// - jsDelivr(有国内节点)
// - unpkg(有国内节点)
// - 自建 CDN 或使用阿里云/腾讯云 CDN
```
##### 3.4.2 外部 API
```typescript
// ✅ 好的做法:优先使用国内 API 服务
// 如果必须使用国外服务,考虑:
// 1. 使用代理
// 2. 使用国内替代方案
// 3. 实现缓存机制减少请求
```
#### 3.5 部署脚本中的镜像源配置
##### 3.5.1 PowerShell 部署脚本
```powershell
# ✅ 好的做法:部署脚本中配置镜像源
# 在 Dockerfile 中已配置,部署脚本无需额外配置
# 但如果需要,可以在构建前设置环境变量
$env:NPM_CONFIG_REGISTRY = "https://registry.npmmirror.com/"
```
##### 3.5.2 服务器端配置
```bash
# 如果需要在服务器上直接安装依赖,配置 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 使用特定版本号(必须)
```yaml
# ✅ 好的做法:使用特定版本号
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 版本号管理建议
```yaml
# 使用环境变量管理版本(推荐)
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?
---
## 📚 参考资源
- [Next.js性能优化最佳实践](https://nextjs.org/docs/app/building-your-application/optimizing)
- [Prisma性能优化指南](https://www.prisma.io/docs/guides/performance-and-optimization)
- [Node.js最佳实践](https://github.com/goldbergyoni/nodebestpractices)
- [Docker最佳实践](https://docs.docker.com/develop/dev-best-practices/)
---
**最后更新**: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. 📝 **文档要求**:
- 所有部署文档必须说明镜像源配置
- 新加入的开发者必须了解镜像源要求