Files
chinese-family-tree-2/IMAGE_STORAGE_SOLUTION.md
T
freedakgmail 3d075c6076 0.0.8.5
2025-11-24 14:02:34 +08:00

273 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
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.
# 图片存储解决方案
## 🚨 当前问题
项目目前使用 **IndexedDB** 存储图片,这存在以下问题:
### 1. **本地存储限制**
- IndexedDB 是浏览器本地存储
- 数据只存在用户的浏览器中
- 无法跨设备访问
- 清除浏览器数据会丢失所有图片
### 2. **无法共享**
- 其他用户看不到上传的图片
- 协作者无法访问
- 不适合多用户系统
### 3. **存储容量限制**
- 浏览器对 IndexedDB 有容量限制
- 大量图片会占用用户设备空间
- 可能导致性能问题
## ✅ 推荐解决方案
### 方案一:云存储服务(推荐)
使用专业的云存储服务,如:
#### 国内服务
- **阿里云 OSS**
- **腾讯云 COS**
- **七牛云**
- **又拍云**
#### 国际服务
- **AWS S3**
- **Google Cloud Storage**
- **Azure Blob Storage**
- **Cloudflare R2**
#### 实现步骤
1. **注册云存储服务**
2. **创建存储桶(Bucket**
3. **获取 API 密钥**
4. **创建上传 API**
```typescript
// app/api/upload/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
const s3Client = new S3Client({
region: process.env.AWS_REGION!,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
})
export async function POST(req: NextRequest) {
const session = await getServerSession(authOptions)
if (!session?.user?.id) {
return NextResponse.json({ error: '未授权' }, { status: 401 })
}
const formData = await req.formData()
const file = formData.get('file') as File
if (!file) {
return NextResponse.json({ error: '没有文件' }, { status: 400 })
}
// 生成唯一文件名
const fileName = `avatars/${Date.now()}-${file.name}`
// 上传到 S3
const buffer = Buffer.from(await file.arrayBuffer())
await s3Client.send(new PutObjectCommand({
Bucket: process.env.AWS_BUCKET_NAME!,
Key: fileName,
Body: buffer,
ContentType: file.type,
}))
// 返回 URL
const url = `https://${process.env.AWS_BUCKET_NAME}.s3.${process.env.AWS_REGION}.amazonaws.com/${fileName}`
return NextResponse.json({ url })
}
```
5. **前端调用**
```typescript
const handleAvatarUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]
if (!file) return
try {
setIsUploadingAvatar(true)
// 上传到云存储
const formData = new FormData()
formData.append('file', file)
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
})
const { url } = await response.json()
// 更新成员头像 URL
await updateMember(member.id, {
avatarUrl: url,
})
setAvatarBlobUrl(url)
} catch (error) {
console.error('上传头像失败:', error)
} finally {
setIsUploadingAvatar(false)
}
}
```
### 方案二:服务器文件系统
将图片存储在服务器的文件系统中。
#### 优点
- 不需要额外的云服务
- 成本较低
#### 缺点
- 需要管理服务器存储空间
- 扩展性较差
- 需要处理备份
#### 实现步骤
```typescript
// app/api/upload/route.ts
import { writeFile } from 'fs/promises'
import { join } from 'path'
export async function POST(req: NextRequest) {
const formData = await req.formData()
const file = formData.get('file') as File
const bytes = await file.arrayBuffer()
const buffer = Buffer.from(bytes)
// 保存到 public/uploads 目录
const fileName = `${Date.now()}-${file.name}`
const path = join(process.cwd(), 'public', 'uploads', fileName)
await writeFile(path, buffer)
// 返回相对 URL
const url = `/uploads/${fileName}`
return NextResponse.json({ url })
}
```
### 方案三:Base64 编码(不推荐)
将图片转换为 Base64 字符串存储在数据库中。
#### 缺点
- 数据库体积急剧增大
- 查询性能下降
- 不适合大图片
- 传输效率低
**不推荐用于生产环境!**
## 📊 方案对比
| 方案 | 成本 | 性能 | 可靠性 | 扩展性 | 推荐度 |
|------|------|------|--------|--------|--------|
| 云存储 | 中 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 服务器文件系统 | 低 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
| IndexedDB | 无 | ⭐⭐ | ⭐ | ⭐ | ⭐ |
| Base64 | 无 | ⭐ | ⭐⭐ | ⭐ | ❌ |
## 🔧 迁移步骤
### 1. 选择云存储服务
推荐:阿里云 OSS(国内)或 AWS S3(国际)
### 2. 配置环境变量
```env
# .env.local
AWS_REGION=ap-southeast-1
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
AWS_BUCKET_NAME=your_bucket_name
```
### 3. 安装依赖
```bash
npm install @aws-sdk/client-s3
# 或
npm install ali-oss # 阿里云
```
### 4. 创建上传 API
参考上面的代码示例
### 5. 修改前端代码
- 移除 IndexedDB 相关代码
- 改为调用上传 API
- 使用返回的 URL
### 6. 数据迁移
- 将现有的 IndexedDB 图片迁移到云存储
- 更新数据库中的 URL
## 💡 最佳实践
### 1. 图片压缩
上传前压缩图片以节省存储和带宽:
```typescript
import imageCompression from 'browser-image-compression'
const compressedFile = await imageCompression(file, {
maxSizeMB: 1,
maxWidthOrHeight: 1920,
})
```
### 2. 图片格式
- 头像:建议 JPEG/WebP,尺寸 500x500
- 照片墙:建议 JPEG/WebP,尺寸 1920x1080
### 3. CDN 加速
使用 CDN 加速图片访问:
- 阿里云 OSS + CDN
- AWS S3 + CloudFront
- Cloudflare R2(自带 CDN
### 4. 安全性
- 使用签名 URL 控制访问
- 设置合理的 CORS 策略
- 限制上传文件大小和类型
### 5. 成本优化
- 定期清理未使用的图片
- 使用生命周期策略自动归档
- 选择合适的存储类型
## 📝 当前状态
- ✅ 头像上传功能已实现(使用 IndexedDB)
- ⚠️ 需要迁移到云存储
- 📋 照片墙功能也使用 IndexedDB
- 📋 需要统一迁移所有图片存储
## 🎯 下一步
1. 选择云存储服务
2. 创建上传 API
3. 修改前端上传逻辑
4. 迁移现有数据
5. 测试验证
6. 部署上线