# @repo/storage 一个完全兼容 Hono 的存储解决方案,支持本地存储和 S3 兼容存储,提供 TUS 协议上传、文件管理和 REST API。 ## 特性 - 🚀 **多存储支持**: 支持本地文件系统和 S3 兼容存储 - 📤 **TUS 协议**: 支持可恢复的文件上传 - 🔧 **Hono 集成**: 提供开箱即用的 Hono 中间件 - 📊 **文件管理**: 完整的文件生命周期管理 - 🗄️ **数据库集成**: 与 Prisma 深度集成 - ⏰ **自动清理**: 支持过期文件自动清理 - 🔄 **存储迁移**: 支持不同存储类型间的数据迁移 - 🔌 **适配器模式** - 通过适配器与任何数据库后端集成 - 📁 **多存储后端** - 支持本地存储和 S3 兼容存储 - 🚀 **TUS 协议** - 支持可恢复文件上传 - 🔄 **自动清理** - 自动清理失败的上传 - 🛡️ **类型安全** - 完整的 TypeScript 支持 ## 安装 ```bash npm install @repo/storage ``` ## 环境变量配置 ### 基础配置 | 变量名 | 类型 | 默认值 | 描述 | | ---------------------- | --------------- | ------- | ------------------------------------- | | `STORAGE_TYPE` | `local` \| `s3` | `local` | 存储类型选择 | | `UPLOAD_EXPIRATION_MS` | `number` | `0` | 上传文件过期时间(毫秒),0表示不过期 | ### 本地存储配置 当 `STORAGE_TYPE=local` 时需要配置: | 变量名 | 类型 | 默认值 | 描述 | | ------------ | -------- | ----------- | ---------------- | | `UPLOAD_DIR` | `string` | `./uploads` | 本地存储目录路径 | ### S3 存储配置 当 `STORAGE_TYPE=s3` 时需要配置: | 变量名 | 类型 | 默认值 | 描述 | 必需 | | --------------------------- | --------- | ----------- | ---------------------------------- | ---- | | `S3_BUCKET` | `string` | - | S3 存储桶名称 | ✅ | | `S3_REGION` | `string` | `us-east-1` | S3 区域 | ✅ | | `S3_ACCESS_KEY_ID` | `string` | - | S3 访问密钥 ID | ✅ | | `S3_SECRET_ACCESS_KEY` | `string` | - | S3 访问密钥 | ✅ | | `S3_ENDPOINT` | `string` | - | 自定义 S3 端点(用于兼容其他服务) | ❌ | | `S3_FORCE_PATH_STYLE` | `boolean` | `false` | 是否强制使用路径样式 | ❌ | | `S3_PART_SIZE` | `number` | `8388608` | 分片上传大小(8MB) | ❌ | | `S3_MAX_CONCURRENT_UPLOADS` | `number` | `60` | 最大并发上传数 | ❌ | ## 配置示例 ### 本地存储配置 ```bash # .env STORAGE_TYPE=local UPLOAD_DIR=./uploads ``` ### AWS S3 配置 ```bash # .env STORAGE_TYPE=s3 S3_BUCKET=my-app-uploads S3_REGION=us-west-2 S3_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE S3_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY ``` ### MinIO 配置 ```bash # .env STORAGE_TYPE=s3 S3_BUCKET=uploads S3_REGION=us-east-1 S3_ACCESS_KEY_ID=minioadmin S3_SECRET_ACCESS_KEY=minioadmin S3_ENDPOINT=http://localhost:9000 S3_FORCE_PATH_STYLE=true ``` ### 阿里云 OSS 配置 ```bash # .env STORAGE_TYPE=s3 S3_BUCKET=my-oss-bucket S3_REGION=oss-cn-hangzhou S3_ACCESS_KEY_ID=your-access-key-id S3_SECRET_ACCESS_KEY=your-access-key-secret S3_ENDPOINT=https://oss-cn-hangzhou.aliyuncs.com S3_FORCE_PATH_STYLE=false ``` ### 腾讯云 COS 配置 ```bash # .env STORAGE_TYPE=s3 S3_BUCKET=my-cos-bucket-1234567890 S3_REGION=ap-beijing S3_ACCESS_KEY_ID=your-secret-id S3_SECRET_ACCESS_KEY=your-secret-key S3_ENDPOINT=https://cos.ap-beijing.myqcloud.com S3_FORCE_PATH_STYLE=false ``` ## 快速开始 ### 1. 安装依赖 ```bash npm install @repo/storage ``` ### 2. 实现数据库适配器 ```typescript import { DatabaseAdapter, ResourceData, CreateResourceData, StorageType } from '@repo/storage'; export class MyDatabaseAdapter implements DatabaseAdapter { async getResourceByFileId(fileId: string): Promise<{ status: string; resource?: ResourceData }> { // 实现从数据库获取资源的逻辑 } async createResource(data: CreateResourceData): Promise { // 实现创建资源的逻辑 } async updateResource(id: string, data: any): Promise { // 实现更新资源的逻辑 } async deleteResource(id: string): Promise { // 实现删除资源的逻辑 } async updateResourceStatus(fileId: string, status: string, additionalData?: any): Promise { // 实现更新资源状态的逻辑 } async deleteFailedUploadingResource(expirationPeriod: number): Promise<{ count: number }> { // 实现清理失败上传的逻辑 } async migrateResourcesStorageType( fromStorageType: StorageType, toStorageType: StorageType, ): Promise<{ count: number }> { // 实现存储类型迁移的逻辑 } } ``` ### 3. 注册适配器 ```typescript import { adapterRegistry } from '@repo/storage'; import { MyDatabaseAdapter } from './my-database-adapter'; // 在应用启动时注册适配器 adapterRegistry.setDatabaseAdapter(new MyDatabaseAdapter()); ``` ### 4. 使用存储功能 ```typescript import { createStorageApp, startCleanupScheduler } from '@repo/storage'; // 创建存储应用 const storageApp = createStorageApp({ apiBasePath: '/api/storage', uploadPath: '/upload', }); // 启动清理任务 startCleanupScheduler(); ``` ## Prisma 适配器示例 如果您使用 Prisma,可以参考以下实现: ```typescript import { prisma } from '@your/db-package'; import { DatabaseAdapter, ResourceData, CreateResourceData, StorageType } from '@repo/storage'; export class PrismaDatabaseAdapter implements DatabaseAdapter { // 将 Prisma Resource 转换为 ResourceData private transformResource(resource: any): ResourceData { return { id: resource.id, fileId: resource.fileId, title: resource.title, type: resource.type, storageType: resource.storageType as StorageType, status: resource.status || 'unknown', meta: resource.meta, createdAt: resource.createdAt, updatedAt: resource.updatedAt, }; } async getResourceByFileId(fileId: string): Promise<{ status: string; resource?: ResourceData }> { const resource = await prisma.resource.findFirst({ where: { fileId }, }); if (!resource) { return { status: 'pending' }; } return { status: resource.status || 'unknown', resource: this.transformResource(resource), }; } async createResource(data: CreateResourceData): Promise { const resource = await prisma.resource.create({ data: { fileId: data.fileId, title: data.filename, type: data.mimeType, storageType: data.storageType, status: data.status || 'UPLOADING', meta: { size: data.size, hash: data.hash, }, }, }); return this.transformResource(resource); } // ... 实现其他方法 } ``` ## API 参考 ### DatabaseAdapter 接口 所有数据库适配器都必须实现 `DatabaseAdapter` 接口: - `getResourceByFileId(fileId: string)` - 根据文件ID获取资源 - `createResource(data: CreateResourceData)` - 创建新资源 - `updateResource(id: string, data: any)` - 更新资源 - `deleteResource(id: string)` - 删除资源 - `updateResourceStatus(fileId: string, status: string, additionalData?: any)` - 更新资源状态 - `deleteFailedUploadingResource(expirationPeriod: number)` - 清理失败的上传 - `migrateResourcesStorageType(from: StorageType, to: StorageType)` - 迁移存储类型 ### 适配器注册器 ```typescript import { adapterRegistry } from '@repo/storage'; // 注册适配器 adapterRegistry.setDatabaseAdapter(adapter); // 获取当前适配器 const adapter = adapterRegistry.getDatabaseAdapter(); // 检查是否已注册适配器 const hasAdapter = adapterRegistry.hasAdapter(); ``` ## API 端点 ### 文件资源管理 - `GET /api/storage/resource/:fileId` - 获取文件资源信息 - `GET /api/storage/resources` - 获取所有资源 - `GET /api/storage/resources/storage/:storageType` - 按存储类型获取资源 - `GET /api/storage/resources/status/:status` - 按状态获取资源 - `GET /api/storage/resources/uploading` - 获取正在上传的资源 - `DELETE /api/storage/resource/:id` - 删除资源 - `PATCH /api/storage/resource/:id` - 更新资源 ### 文件访问和下载 - `GET /download/:fileId` - 文件下载和访问(支持所有存储类型) ### 统计和管理 - `GET /api/storage/stats` - 获取资源统计信息 - `POST /api/storage/cleanup` - 手动清理过期上传 - `POST /api/storage/cleanup/by-status` - 按状态清理资源 - `POST /api/storage/migrate-storage` - 迁移存储类型 ### 存储配置 - `GET /api/storage/storage/info` - 获取存储信息 - `POST /api/storage/storage/switch` - 切换存储配置 - `POST /api/storage/storage/validate` - 验证存储配置 ### 文件上传 - `POST /upload` - TUS 协议文件上传 - `PATCH /upload/:id` - 续传文件 - `HEAD /upload/:id` - 获取上传状态 ## 数据库操作 ```typescript import { getAllResources, getResourceByFileId, createResource, updateResourceStatus, deleteResource, } from '@repo/storage'; // 获取所有资源 const resources = await getAllResources(); // 根据文件ID获取资源 const { status, resource } = await getResourceByFileId('file-id'); // 创建新资源 const newResource = await createResource({ fileId: 'unique-file-id', filename: 'example.jpg', size: 1024000, mimeType: 'image/jpeg', storageType: 'local', }); ``` ## 文件生命周期 1. **上传开始**: 创建资源记录,状态为 `UPLOADING` 2. **上传完成**: 状态更新为 `UPLOADED` 3. **处理中**: 状态可更新为 `PROCESSING` 4. **处理完成**: 状态更新为 `PROCESSED` 5. **清理**: 过期文件自动清理 ## 存储迁移 支持在不同存储类型之间迁移数据: ```bash # API 调用示例 curl -X POST http://localhost:3000/api/storage/migrate-storage \ -H "Content-Type: application/json" \ -d '{"from": "local", "to": "s3"}' ``` ## 安全考虑 1. **环境变量**: 敏感信息(如 S3 密钥)应存储在环境变量中 2. **访问控制**: 建议在生产环境中添加适当的身份验证 3. **CORS 配置**: 根据需要配置跨域访问策略 4. **文件验证**: 建议添加文件类型和大小验证 ## 故障排除 ### 常见问题 1. **找不到模块错误**: 确保已正确安装依赖包 2. **S3 连接失败**: 检查网络连接和凭据配置 3. **本地存储权限**: 确保应用有写入本地目录的权限 4. **上传失败**: 检查文件大小限制和存储空间 ### 调试模式 启用详细日志: ```bash DEBUG=storage:* npm start ``` ## 许可证 MIT License ## 贡献 欢迎提交 Issue 和 Pull Request! ## 更新日志 ### v2.0.0 - 重构为模块化架构 - 添加完整的 TypeScript 支持 - 支持多种 S3 兼容服务 - 改进的错误处理和日志记录