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

303 lines
5.9 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.
# 服务器文件系统存储实现
## ✅ 已完成
成功实现了将图片存储在服务器文件系统的功能。
## 📁 文件结构
```
chinese-family-tree/
├── app/
│ └── api/
│ └── upload/
│ └── route.ts # 上传 API
├── public/
│ └── uploads/ # 上传文件存储目录
│ └── .gitkeep # 保留目录结构
└── .gitignore # 忽略上传的文件
```
## 🔧 实现细节
### 1. 上传 API (`app/api/upload/route.ts`)
**功能:**
- 接收文件上传请求
- 验证用户登录状态
- 验证文件类型(只允许图片)
- 验证文件大小(限制 5MB
- 生成唯一文件名
- 保存到 `public/uploads` 目录
- 返回可访问的 URL
**安全措施:**
- ✅ 用户身份验证
- ✅ 文件类型验证
- ✅ 文件大小限制
- ✅ 唯一文件名(防止覆盖)
**文件命名规则:**
```
{timestamp}-{random}.{ext}
例如:1732435200000-a3b5c7.jpg
```
### 2. 前端上传逻辑
**修改文件:** `app/members/[id]/page.tsx`
**流程:**
1. 用户选择图片
2. 创建 FormData
3. 调用 `/api/upload` API
4. 获取返回的 URL
5. 更新成员的 `avatarUrl` 字段
6. 显示新头像
**兼容性:**
- 优先使用 `avatarUrl`(新方案)
- 兼容 `avatarImageId`(旧的 IndexedDB 方案)
### 3. Git 配置
**.gitignore 规则:**
```gitignore
# Uploads
public/uploads/*
!public/uploads/.gitkeep
```
**说明:**
- 忽略所有上传的文件
- 保留 `.gitkeep` 文件以维持目录结构
- 避免将用户上传的图片提交到 Git
## 📊 数据库字段
### FamilyMember 表
| 字段 | 类型 | 说明 |
|------|------|------|
| `avatarUrl` | String? | 头像 URL(新方案) |
| `avatarImageId` | String? | IndexedDB ID(旧方案,兼容) |
**推荐:** 使用 `avatarUrl` 字段
## 🎯 使用方式
### 上传头像
1. 访问成员详情页
2. 将鼠标悬停在头像上
3. 点击出现的相机图标
4. 选择图片文件
5. 自动上传并更新
### API 调用示例
```typescript
const formData = new FormData()
formData.append('file', file)
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
})
const { url } = await response.json()
// url: "/uploads/1732435200000-a3b5c7.jpg"
```
## 🔒 安全性
### 已实现
1. **身份验证**:需要登录才能上传
2. **文件类型验证**:只允许图片格式
3. **文件大小限制**:最大 5MB
4. **唯一文件名**:防止文件覆盖
### 建议增强
1. **图片压缩**:上传前压缩以节省空间
2. **病毒扫描**:扫描上传的文件
3. **访问控制**:限制只有家族成员可访问
4. **定期清理**:删除未使用的图片
## 📈 性能优化
### 当前实现
- ✅ 直接存储在 `public` 目录
- ✅ Next.js 自动处理静态文件服务
- ✅ 支持浏览器缓存
### 可选优化
1. **图片压缩**
```typescript
import imageCompression from 'browser-image-compression'
const compressed = await imageCompression(file, {
maxSizeMB: 1,
maxWidthOrHeight: 1920,
})
```
2. **CDN 加速**
- 使用 Nginx 反向代理
- 配置 CDN 服务
- 启用 Gzip 压缩
3. **懒加载**
```tsx
<img
src={avatarUrl}
loading="lazy"
alt="头像"
/>
```
## 🚀 部署注意事项
### 1. 文件权限
确保 `public/uploads` 目录有写入权限:
```bash
chmod 755 public/uploads
```
### 2. 磁盘空间
监控磁盘使用情况:
```bash
du -sh public/uploads
```
### 3. 备份策略
定期备份上传的文件:
```bash
# 备份到其他位置
rsync -av public/uploads/ /backup/uploads/
# 或使用 tar 打包
tar -czf uploads-backup-$(date +%Y%m%d).tar.gz public/uploads/
```
### 4. Nginx 配置(可选)
如果使用 Nginx 作为反向代理:
```nginx
location /uploads/ {
alias /path/to/app/public/uploads/;
expires 30d;
add_header Cache-Control "public, immutable";
}
```
## 🔄 迁移指南
### 从 IndexedDB 迁移
如果有旧数据使用 IndexedDB 存储:
1. **导出旧数据**
```typescript
const images = await db.images.toArray()
for (const image of images) {
const formData = new FormData()
formData.append('file', new File([image.blob], 'image.jpg'))
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
})
const { url } = await response.json()
// 更新成员记录
await updateMember(memberId, { avatarUrl: url })
}
```
2. **清理旧数据**
```typescript
await db.images.clear()
```
## 💡 最佳实践
### 1. 文件命名
- ✅ 使用时间戳 + 随机字符串
- ✅ 保留原始文件扩展名
- ❌ 不要使用用户提供的文件名
### 2. 存储位置
-`public/uploads/` - 可直接访问
-`private/uploads/` - 需要额外处理
### 3. 错误处理
- ✅ 提供友好的错误提示
- ✅ 记录错误日志
- ✅ 失败时清理临时文件
### 4. 用户体验
- ✅ 显示上传进度
- ✅ 支持拖拽上传
- ✅ 预览上传的图片
## 📝 后续改进
### 短期
- [ ] 添加图片压缩
- [ ] 支持拖拽上传
- [ ] 显示上传进度
### 中期
- [ ] 图片裁剪功能
- [ ] 支持多图上传
- [ ] 图片管理界面
### 长期
- [ ] 迁移到云存储
- [ ] CDN 加速
- [ ] 图片处理服务
## ✨ 优势
1. **简单易用**:不需要额外的云服务
2. **成本低**:只需服务器存储空间
3. **完全控制**:数据完全在自己手中
4. **易于备份**:直接备份文件夹即可
5. **快速部署**:无需配置云服务
## ⚠️ 限制
1. **扩展性**:单服务器存储容量有限
2. **性能**:大量访问时可能需要 CDN
3. **备份**:需要自己管理备份策略
4. **多服务器**:需要共享存储或同步
## 🎉 总结
成功实现了服务器文件系统存储方案,适合:
- 自托管部署
- 中小型应用
- 对数据隐私有要求的场景
- 不想依赖第三方云服务
如果未来需要更好的扩展性,可以轻松迁移到云存储服务!