![]() |
||
---|---|---|
.. | ||
docs | ||
src | ||
.env.example | ||
MINIO_CONFIGURATION_GUIDE.md | ||
MINIO_SOLUTION_SUMMARY.md | ||
README.md | ||
package.json | ||
test-minio-config.js | ||
tsconfig.json |
README.md
@repo/storage
一个完全兼容 Hono 的存储解决方案,支持本地存储和 S3 兼容存储,提供 TUS 协议上传、文件管理和 REST API。
特性
- 🚀 多存储支持: 支持本地文件系统和 S3 兼容存储
- 📤 TUS 协议: 支持可恢复的文件上传
- 🔧 Hono 集成: 提供开箱即用的 Hono 中间件
- 📊 文件管理: 完整的文件生命周期管理
- 🗄️ 数据库集成: 与 Prisma 深度集成
- ⏰ 自动清理: 支持过期文件自动清理
- 🔄 存储迁移: 支持不同存储类型间的数据迁移
- 🔌 适配器模式 - 通过适配器与任何数据库后端集成
- 📁 多存储后端 - 支持本地存储和 S3 兼容存储
- 🚀 TUS 协议 - 支持可恢复文件上传
- 🔄 自动清理 - 自动清理失败的上传
- 🛡️ 类型安全 - 完整的 TypeScript 支持
安装
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 |
最大并发上传数 | ❌ |
配置示例
本地存储配置
# .env
STORAGE_TYPE=local
UPLOAD_DIR=./uploads
AWS S3 配置
# .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 配置
# .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 配置
# .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 配置
# .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. 安装依赖
npm install @repo/storage
2. 实现数据库适配器
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<ResourceData> {
// 实现创建资源的逻辑
}
async updateResource(id: string, data: any): Promise<ResourceData> {
// 实现更新资源的逻辑
}
async deleteResource(id: string): Promise<ResourceData> {
// 实现删除资源的逻辑
}
async updateResourceStatus(fileId: string, status: string, additionalData?: any): Promise<ResourceData> {
// 实现更新资源状态的逻辑
}
async deleteFailedUploadingResource(expirationPeriod: number): Promise<{ count: number }> {
// 实现清理失败上传的逻辑
}
async migrateResourcesStorageType(
fromStorageType: StorageType,
toStorageType: StorageType,
): Promise<{ count: number }> {
// 实现存储类型迁移的逻辑
}
}
3. 注册适配器
import { adapterRegistry } from '@repo/storage';
import { MyDatabaseAdapter } from './my-database-adapter';
// 在应用启动时注册适配器
adapterRegistry.setDatabaseAdapter(new MyDatabaseAdapter());
4. 使用存储功能
import { createStorageApp, startCleanupScheduler } from '@repo/storage';
// 创建存储应用
const storageApp = createStorageApp({
apiBasePath: '/api/storage',
uploadPath: '/upload',
});
// 启动清理任务
startCleanupScheduler();
Prisma 适配器示例
如果您使用 Prisma,可以参考以下实现:
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<ResourceData> {
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)
- 迁移存储类型
适配器注册器
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
- 获取上传状态
数据库操作
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',
});
文件生命周期
- 上传开始: 创建资源记录,状态为
UPLOADING
- 上传完成: 状态更新为
UPLOADED
- 处理中: 状态可更新为
PROCESSING
- 处理完成: 状态更新为
PROCESSED
- 清理: 过期文件自动清理
存储迁移
支持在不同存储类型之间迁移数据:
# API 调用示例
curl -X POST http://localhost:3000/api/storage/migrate-storage \
-H "Content-Type: application/json" \
-d '{"from": "local", "to": "s3"}'
安全考虑
- 环境变量: 敏感信息(如 S3 密钥)应存储在环境变量中
- 访问控制: 建议在生产环境中添加适当的身份验证
- CORS 配置: 根据需要配置跨域访问策略
- 文件验证: 建议添加文件类型和大小验证
故障排除
常见问题
- 找不到模块错误: 确保已正确安装依赖包
- S3 连接失败: 检查网络连接和凭据配置
- 本地存储权限: 确保应用有写入本地目录的权限
- 上传失败: 检查文件大小限制和存储空间
调试模式
启用详细日志:
DEBUG=storage:* npm start
许可证
MIT License
贡献
欢迎提交 Issue 和 Pull Request!
更新日志
v2.0.0
- 重构为模块化架构
- 添加完整的 TypeScript 支持
- 支持多种 S3 兼容服务
- 改进的错误处理和日志记录