fenghuo/docs/STATIC_FILES.md

6.3 KiB
Raw Blame History

文件访问使用指南

本文档说明如何使用 @repo/storage 包提供的文件访问功能。

功能概述

存储包提供统一的文件访问接口:

  • 统一下载接口 (/download/:fileId) - 适用于所有存储类型,提供统一的文件访问

使用方法

1. 基础配置

import { createStorageApp } from '@repo/storage';

// 创建包含所有功能的存储应用
const storageApp = createStorageApp({
	apiBasePath: '/api/storage', // API 管理接口
	uploadPath: '/upload', // TUS 上传接口
	downloadPath: '/download', // 文件下载接口
});

app.route('/', storageApp);

2. 分别配置功能

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());

文件访问方式

统一下载接口

无论使用哪种存储类型,都通过统一的下载接口访问文件:

# 访问文件(支持内联显示和下载)
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如果存储桶是公开的
# 直接访问 S3 URL如果存储桶是公开的
GET https://bucket.s3.region.amazonaws.com/2024/01/01/abc123/file.jpg

代码示例

生成文件访问 URL

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 组件中使用

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>
	);
}

文件类型处理

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. 访问控制

如需要权限验证,可以添加认证中间件:

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. 文件类型限制

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. 缓存设置

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

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 配置是否正确

调试方法

# 检查文件是否存在
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