280 lines
6.3 KiB
Markdown
280 lines
6.3 KiB
Markdown
![]() |
# 文件访问使用指南
|
|||
|
|
|||
|
本文档说明如何使用 `@repo/storage` 包提供的文件访问功能。
|
|||
|
|
|||
|
## 功能概述
|
|||
|
|
|||
|
存储包提供统一的文件访问接口:
|
|||
|
|
|||
|
- **统一下载接口** (`/download/:fileId`) - 适用于所有存储类型,提供统一的文件访问
|
|||
|
|
|||
|
## 使用方法
|
|||
|
|
|||
|
### 1. 基础配置
|
|||
|
|
|||
|
```typescript
|
|||
|
import { createStorageApp } from '@repo/storage';
|
|||
|
|
|||
|
// 创建包含所有功能的存储应用
|
|||
|
const storageApp = createStorageApp({
|
|||
|
apiBasePath: '/api/storage', // API 管理接口
|
|||
|
uploadPath: '/upload', // TUS 上传接口
|
|||
|
downloadPath: '/download', // 文件下载接口
|
|||
|
});
|
|||
|
|
|||
|
app.route('/', storageApp);
|
|||
|
```
|
|||
|
|
|||
|
### 2. 分别配置功能
|
|||
|
|
|||
|
```typescript
|
|||
|
import { createStorageRoutes, createTusUploadRoutes, createFileDownloadRoutes } from '@repo/storage';
|
|||
|
|
|||
|
const app = new Hono();
|
|||
|
|
|||
|
// 存储管理 API
|
|||
|
app.route('/api/storage', createStorageRoutes());
|
|||
|
|
|||
|
// 文件上传
|
|||
|
app.route('/upload', createTusUploadRoutes());
|
|||
|
|
|||
|
// 文件下载(所有存储类型)
|
|||
|
app.route('/download', createFileDownloadRoutes());
|
|||
|
```
|
|||
|
|
|||
|
## 文件访问方式
|
|||
|
|
|||
|
### 统一下载接口
|
|||
|
|
|||
|
无论使用哪种存储类型,都通过统一的下载接口访问文件:
|
|||
|
|
|||
|
```bash
|
|||
|
# 访问文件(支持内联显示和下载)
|
|||
|
GET http://localhost:3000/download/2024/01/01/abc123/image.jpg
|
|||
|
GET http://localhost:3000/download/2024/01/01/abc123/document.pdf
|
|||
|
```
|
|||
|
|
|||
|
### 本地存储
|
|||
|
|
|||
|
当 `STORAGE_TYPE=local` 时:
|
|||
|
|
|||
|
- 下载接口直接读取本地文件
|
|||
|
- 自动设置正确的 Content-Type
|
|||
|
- 支持内联显示(`Content-Disposition: inline`)
|
|||
|
|
|||
|
### S3 存储
|
|||
|
|
|||
|
当 `STORAGE_TYPE=s3` 时:
|
|||
|
|
|||
|
- 下载接口重定向到 S3 URL
|
|||
|
- 也可以直接访问 S3 URL(如果存储桶是公开的)
|
|||
|
|
|||
|
```bash
|
|||
|
# 直接访问 S3 URL(如果存储桶是公开的)
|
|||
|
GET https://bucket.s3.region.amazonaws.com/2024/01/01/abc123/file.jpg
|
|||
|
```
|
|||
|
|
|||
|
## 代码示例
|
|||
|
|
|||
|
### 生成文件访问 URL
|
|||
|
|
|||
|
```typescript
|
|||
|
import { StorageUtils } from '@repo/storage';
|
|||
|
|
|||
|
const storageUtils = StorageUtils.getInstance();
|
|||
|
|
|||
|
// 生成文件访问 URL
|
|||
|
function getFileUrl(fileId: string) {
|
|||
|
// 结果: http://localhost:3000/download/2024/01/01/abc123/file.jpg
|
|||
|
return storageUtils.generateFileUrl(fileId);
|
|||
|
}
|
|||
|
|
|||
|
// 生成完整的公开访问 URL
|
|||
|
function getPublicFileUrl(fileId: string) {
|
|||
|
// 结果: https://yourdomain.com/download/2024/01/01/abc123/file.jpg
|
|||
|
return storageUtils.generateFileUrl(fileId, 'https://yourdomain.com');
|
|||
|
}
|
|||
|
|
|||
|
// 生成 S3 直接访问 URL(仅 S3 存储)
|
|||
|
function getDirectUrl(fileId: string) {
|
|||
|
try {
|
|||
|
// S3 存储: https://bucket.s3.region.amazonaws.com/2024/01/01/abc123/file.jpg
|
|||
|
return storageUtils.generateDirectUrl(fileId);
|
|||
|
} catch (error) {
|
|||
|
// 本地存储会抛出错误,使用下载接口
|
|||
|
return storageUtils.generateFileUrl(fileId);
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 在 React 组件中使用
|
|||
|
|
|||
|
```tsx
|
|||
|
import { useState, useEffect } from 'react';
|
|||
|
|
|||
|
function FileDisplay({ fileId }: { fileId: string }) {
|
|||
|
const [fileUrl, setFileUrl] = useState<string>('');
|
|||
|
|
|||
|
useEffect(() => {
|
|||
|
// 获取文件访问 URL
|
|||
|
fetch(`/api/storage/resource/${fileId}`)
|
|||
|
.then((res) => res.json())
|
|||
|
.then((data) => {
|
|||
|
if (data.status === 'ready' && data.resource) {
|
|||
|
// 生成文件访问 URL
|
|||
|
const url = `/download/${fileId}`;
|
|||
|
setFileUrl(url);
|
|||
|
}
|
|||
|
});
|
|||
|
}, [fileId]);
|
|||
|
|
|||
|
if (!fileUrl) return <div>Loading...</div>;
|
|||
|
|
|||
|
return (
|
|||
|
<div>
|
|||
|
{/* 图片会内联显示 */}
|
|||
|
<img src={fileUrl} alt="Uploaded file" />
|
|||
|
|
|||
|
{/* 下载链接 */}
|
|||
|
<a href={fileUrl} download>
|
|||
|
下载文件
|
|||
|
</a>
|
|||
|
|
|||
|
{/* PDF 等文档可以在新窗口打开 */}
|
|||
|
<a href={fileUrl} target="_blank" rel="noopener noreferrer">
|
|||
|
在新窗口打开
|
|||
|
</a>
|
|||
|
</div>
|
|||
|
);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 文件类型处理
|
|||
|
|
|||
|
```typescript
|
|||
|
function getFileDisplayUrl(fileId: string, mimeType: string) {
|
|||
|
const baseUrl = `/download/${fileId}`;
|
|||
|
|
|||
|
// 根据文件类型决定显示方式
|
|||
|
if (mimeType.startsWith('image/')) {
|
|||
|
// 图片直接显示
|
|||
|
return baseUrl;
|
|||
|
} else if (mimeType === 'application/pdf') {
|
|||
|
// PDF 可以内联显示
|
|||
|
return baseUrl;
|
|||
|
} else {
|
|||
|
// 其他文件类型强制下载
|
|||
|
return `${baseUrl}?download=true`;
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 安全考虑
|
|||
|
|
|||
|
### 1. 访问控制
|
|||
|
|
|||
|
如需要权限验证,可以添加认证中间件:
|
|||
|
|
|||
|
```typescript
|
|||
|
import { createFileDownloadRoutes } from '@repo/storage';
|
|||
|
|
|||
|
const app = new Hono();
|
|||
|
|
|||
|
// 添加认证中间件
|
|||
|
app.use('/download/*', async (c, next) => {
|
|||
|
// 检查用户权限
|
|||
|
const token = c.req.header('Authorization');
|
|||
|
if (!isValidToken(token)) {
|
|||
|
return c.json({ error: 'Unauthorized' }, 401);
|
|||
|
}
|
|||
|
await next();
|
|||
|
});
|
|||
|
|
|||
|
// 添加文件下载服务
|
|||
|
app.route('/download', createFileDownloadRoutes());
|
|||
|
```
|
|||
|
|
|||
|
### 2. 文件类型限制
|
|||
|
|
|||
|
```typescript
|
|||
|
app.use('/download/*', async (c, next) => {
|
|||
|
const fileId = c.req.param('fileId');
|
|||
|
|
|||
|
// 从数据库获取文件信息
|
|||
|
const { resource } = await getResourceByFileId(fileId);
|
|||
|
if (!resource) {
|
|||
|
return c.json({ error: 'File not found' }, 404);
|
|||
|
}
|
|||
|
|
|||
|
// 检查文件类型
|
|||
|
const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
|
|||
|
if (!allowedTypes.includes(resource.mimeType)) {
|
|||
|
return c.json({ error: 'File type not allowed' }, 403);
|
|||
|
}
|
|||
|
|
|||
|
await next();
|
|||
|
});
|
|||
|
```
|
|||
|
|
|||
|
## 性能优化
|
|||
|
|
|||
|
### 1. 缓存设置
|
|||
|
|
|||
|
```typescript
|
|||
|
app.use('/download/*', async (c, next) => {
|
|||
|
await next();
|
|||
|
|
|||
|
// 设置缓存头
|
|||
|
c.header('Cache-Control', 'public, max-age=31536000'); // 1年
|
|||
|
c.header('ETag', generateETag(c.req.path));
|
|||
|
});
|
|||
|
```
|
|||
|
|
|||
|
### 2. CDN 配置
|
|||
|
|
|||
|
对于生产环境,建议使用 CDN:
|
|||
|
|
|||
|
```typescript
|
|||
|
import { StorageUtils } from '@repo/storage';
|
|||
|
|
|||
|
const storageUtils = StorageUtils.getInstance();
|
|||
|
|
|||
|
// 使用 CDN 域名
|
|||
|
const cdnUrl = 'https://cdn.yourdomain.com';
|
|||
|
const fileUrl = storageUtils.generateFileUrl(fileId, cdnUrl);
|
|||
|
```
|
|||
|
|
|||
|
## 故障排除
|
|||
|
|
|||
|
### 常见问题
|
|||
|
|
|||
|
1. **404 文件未找到**
|
|||
|
|
|||
|
- 检查文件是否存在于数据库
|
|||
|
- 确认文件路径是否正确
|
|||
|
- 检查文件权限(本地存储)
|
|||
|
|
|||
|
2. **下载接口不工作**
|
|||
|
|
|||
|
- 检查路由配置
|
|||
|
- 确认存储配置正确
|
|||
|
- 查看服务器日志
|
|||
|
|
|||
|
3. **S3 文件无法访问**
|
|||
|
- 检查 S3 存储桶权限
|
|||
|
- 确认文件是否上传成功
|
|||
|
- 验证 S3 配置是否正确
|
|||
|
|
|||
|
### 调试方法
|
|||
|
|
|||
|
```bash
|
|||
|
# 检查文件是否存在
|
|||
|
curl -I http://localhost:3000/download/2024/01/01/abc123/file.jpg
|
|||
|
|
|||
|
# 检查存储配置
|
|||
|
curl http://localhost:3000/api/storage/storage/info
|
|||
|
|
|||
|
# 检查文件信息
|
|||
|
curl http://localhost:3000/api/storage/resource/2024/01/01/abc123/file.jpg
|
|||
|
```
|