12 KiB
资源上传文档
概述
本系统提供了完整的文件上传和管理功能,支持本地存储(未来可扩展至云存储)。主要用于上传券商Logo、用户头像等资源文件。
目录结构
storage/
├── dto/
│ └── upload-file.dto.ts # 上传文件DTO
├── interfaces/
│ └── storage-provider.interface.ts # 存储提供者接口
├── providers/
│ └── local-storage.provider.ts # 本地存储实现
├── storage.controller.ts # 存储控制器
├── storage.module.ts # 存储模块
└── storage.service.ts # 存储服务
环境配置
必需的环境变量
在 .env 文件中配置以下环境变量:
# 存储类型:local(本地存储),未来可扩展为 qiniu、oss 等
STORAGE_TYPE=local
# 文件存储路径(相对于项目根目录)
STORAGE_PATH=./uploads
# 文件访问基础URL(用于生成文件访问链接)
STORAGE_BASE_URL=http://localhost:3200/uploads
配置说明
| 变量名 | 说明 | 默认值 | 示例 |
|---|---|---|---|
STORAGE_TYPE |
存储类型 | local |
local、qiniu(未来支持) |
STORAGE_PATH |
文件存储路径 | ./uploads |
./uploads、/var/www/uploads |
STORAGE_BASE_URL |
文件访问基础URL | http://localhost:3200/uploads |
https://api.example.com/uploads |
生产环境配置建议
# 生产环境配置示例
STORAGE_TYPE=local
STORAGE_PATH=/var/www/invest-mind/uploads
STORAGE_BASE_URL=https://api.example.com/uploads
静态文件服务配置
系统在 main.ts 中自动配置了静态文件服务,无需额外配置:
// 配置静态文件服务
const storagePath = configService.get<string>('STORAGE_PATH') || './uploads';
app.useStaticAssets(join(process.cwd(), storagePath), {
prefix: '/uploads/',
});
这意味着所有存储在 STORAGE_PATH 目录下的文件都可以通过 /uploads/ 前缀访问。
API 接口
1. 管理员上传文件
接口地址: POST /api/storage/upload
权限要求: 需要管理员权限(admin 或 super_admin)
请求格式: multipart/form-data
请求参数:
| 参数名 | 类型 | 必填 | 说明 | 可选值 |
|---|---|---|---|---|
file |
File | 是 | 要上传的文件 | - |
folder |
string | 否 | 存储文件夹 | broker、user、temp |
filename |
string | 否 | 自定义文件名 | - |
文件限制:
- 最大文件大小:5MB
- 允许的文件类型:
image/jpeg、image/jpg、image/png、image/gif、image/webp
请求示例:
curl -X POST http://localhost:3200/api/storage/upload \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-F "file=@/path/to/image.jpg" \
-F "folder=broker" \
-F "filename=custom-name.jpg"
响应示例:
{
"path": "broker/1234567890-abcdef-broker-logo.jpg",
"url": "http://localhost:3200/uploads/broker/1234567890-abcdef-broker-logo.jpg",
"filename": "1234567890-abcdef-broker-logo.jpg",
"size": 102400,
"mimetype": "image/jpeg"
}
2. 用户上传头像
接口地址: POST /api/storage/upload/avatar
权限要求: 无需鉴权(公开接口)
请求格式: multipart/form-data
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
file |
File | 是 | 要上传的头像文件 |
文件限制:
- 最大文件大小:2MB
- 允许的文件类型:
image/jpeg、image/jpg、image/png、image/gif、image/webp - 文件固定存储在
user文件夹
请求示例:
curl -X POST http://localhost:3200/api/storage/upload/avatar \
-F "file=@/path/to/avatar.jpg"
响应示例:
{
"path": "user/1234567890-abcdef-avatar.jpg",
"url": "http://localhost:3200/uploads/user/1234567890-abcdef-avatar.jpg",
"filename": "1234567890-abcdef-avatar.jpg",
"size": 51200,
"mimetype": "image/jpeg"
}
3. 删除文件
接口地址: DELETE /api/storage/{path}
权限要求: 需要管理员权限(admin 或 super_admin)
路径参数:
| 参数名 | 类型 | 说明 | 示例 |
|---|---|---|---|
path |
string | 文件相对路径 | broker/1234567890-abcdef-broker-logo.jpg |
请求示例:
curl -X DELETE http://localhost:3200/api/storage/broker/1234567890-abcdef-broker-logo.jpg \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
响应:
- 成功:
204 No Content - 文件不存在:
404 Not Found - 权限不足:
403 Forbidden
文件夹类型说明
系统支持三种文件夹类型,用于分类存储不同类型的文件:
| 文件夹 | 用途 | 说明 |
|---|---|---|
broker |
券商相关文件 | 用于存储券商Logo等基础数据文件 |
user |
用户相关文件 | 用于存储用户头像等个人文件 |
temp |
临时文件 | 用于存储临时文件,可定期清理 |
文件访问
访问方式
上传成功后,系统会返回文件的访问URL,可以通过以下方式访问:
-
直接访问URL:使用返回的
url字段直接访问http://localhost:3200/uploads/broker/1234567890-abcdef-broker-logo.jpg -
通过静态文件服务:所有文件都通过
/uploads/前缀提供静态文件服务http://localhost:3200/uploads/{folder}/{filename}
文件命名规则
如果不指定自定义文件名,系统会自动生成文件名,格式为:
{timestamp}-{random}-{originalname}
例如:
- 原始文件名:
logo.jpg - 生成文件名:
1234567890-abcdef-logo.jpg
其中:
timestamp:时间戳(毫秒)random:8字节随机十六进制字符串originalname:原始文件名(去除特殊字符)
文件名清理规则
系统会自动清理文件名中的特殊字符:
- 保留:字母、数字、下划线
_、连字符- - 替换:其他特殊字符会被替换为下划线
_
文件存储结构
本地存储的文件结构如下:
uploads/
├── broker/ # 券商相关文件
│ └── 1234567890-abcdef-broker-logo.jpg
├── user/ # 用户相关文件
│ └── 1234567890-abcdef-avatar.jpg
└── temp/ # 临时文件
└── 1234567890-abcdef-temp-file.jpg
安全特性
1. 路径遍历防护
删除文件时会进行安全检查,防止路径遍历攻击:
// 安全检查:确保文件路径在 basePath 内
const resolvedPath = path.resolve(fullPath);
const resolvedBasePath = path.resolve(this.basePath);
if (!resolvedPath.startsWith(resolvedBasePath)) {
throw new Error('非法文件路径');
}
2. 文件类型验证
- 仅允许上传图片文件(jpeg、jpg、png、gif、webp)
- 通过 MIME 类型和文件扩展名双重验证
3. 文件大小限制
- 管理员上传:最大 5MB
- 用户头像:最大 2MB
4. 权限控制
- 管理员上传和删除操作需要管理员权限
- 用户头像上传无需鉴权(用于注册场景)
使用示例
JavaScript/TypeScript 示例
// 上传文件
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('folder', 'broker');
formData.append('filename', 'custom-name.jpg');
const response = await fetch('http://localhost:3200/api/storage/upload', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
},
body: formData,
});
const result = await response.json();
console.log('文件URL:', result.url);
React 示例
import { useState } from 'react';
function FileUpload() {
const [file, setFile] = useState<File | null>(null);
const [uploading, setUploading] = useState(false);
const [result, setResult] = useState<any>(null);
const handleUpload = async () => {
if (!file) return;
setUploading(true);
const formData = new FormData();
formData.append('file', file);
formData.append('folder', 'broker');
try {
const response = await fetch(
'http://localhost:3200/api/storage/upload',
{
method: 'POST',
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
body: formData,
},
);
const data = await response.json();
setResult(data);
} catch (error) {
console.error('上传失败:', error);
} finally {
setUploading(false);
}
};
return (
<div>
<input
type="file"
accept="image/*"
onChange={(e) => setFile(e.target.files?.[0] || null)}
/>
<button onClick={handleUpload} disabled={uploading}>
{uploading ? '上传中...' : '上传'}
</button>
{result && (
<div>
<p>上传成功!</p>
<img src={result.url} alt="上传的文件" />
</div>
)}
</div>
);
}
扩展性
添加新的存储提供者
系统采用策略模式,可以轻松添加新的存储提供者(如七牛云、阿里云OSS等):
- 实现
IStorageProvider接口 - 在
StorageService中添加新的存储类型判断 - 配置相应的环境变量
示例:
// providers/qiniu-storage.provider.ts
export class QiniuStorageProvider implements IStorageProvider {
// 实现接口方法
}
// storage.service.ts
switch (storageType) {
case 'qiniu':
this.provider = new QiniuStorageProvider(configService);
break;
}
常见问题
1. 文件上传失败
问题: 上传文件时返回 400 错误
解决方案:
- 检查文件大小是否超过限制(管理员5MB,头像2MB)
- 检查文件类型是否为支持的图片格式
- 检查文件是否损坏
2. 文件无法访问
问题: 上传成功但无法通过URL访问文件
解决方案:
- 检查
STORAGE_BASE_URL配置是否正确 - 检查静态文件服务是否正常启动
- 检查文件是否实际存在于
STORAGE_PATH目录 - 检查文件权限是否正确
3. 删除文件失败
问题: 删除文件时返回 404 或 403 错误
解决方案:
- 404:检查文件路径是否正确,文件是否存在
- 403:检查是否有管理员权限
- 检查文件路径是否包含非法字符
4. 生产环境配置
问题: 如何在生产环境配置文件存储
解决方案:
- 设置
STORAGE_PATH为绝对路径(如/var/www/uploads) - 设置
STORAGE_BASE_URL为生产域名(如https://api.example.com/uploads) - 确保目录有写入权限:
chmod -R 755 /var/www/uploads - 考虑使用云存储服务(未来支持)
相关文件
storage.controller.ts- API 控制器storage.service.ts- 存储服务local-storage.provider.ts- 本地存储实现storage-provider.interface.ts- 存储提供者接口main.ts- 静态文件服务配置
更新日志
- 初始版本:支持本地存储、文件上传、文件删除功能