fenghuo/docs/STATIC_FILES.md

280 lines
6.3 KiB
Markdown
Raw Normal View History

2025-05-28 20:00:36 +08:00
# 文件访问使用指南
本文档说明如何使用 `@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
```