签入版本
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
# 文件上传
|
||||
|
||||
:::tip 说明
|
||||
提供本地文件上传功能,支持图片和通用文件上传,内置扩展名白名单、大小限制、魔术字节校验和路径遍历防护。服务层位于 `src/modules/upload/service.py`,端点位于 `src/modules/upload.py`。
|
||||
:::
|
||||
|
||||
## 功能概述
|
||||
|
||||
文件上传模块提供统一的文件上传接口 `upload_file`,支持图片、文档、压缩包等各类文件,通过 `UPLOAD_ALLOWED_EXTS` 配置控制允许上传的格式。
|
||||
|
||||
```
|
||||
POST /api/v1/upload/uploadFile —— 统一文件上传入口
|
||||
```
|
||||
|
||||
## 配置项
|
||||
|
||||
在 `.env` 中配置上传相关参数:
|
||||
|
||||
```bash
|
||||
# 允许上传的文件扩展名(逗号分隔,留空使用内置智能集合)
|
||||
UPLOAD_ALLOWED_EXTS=.jpg,.png,.gif,.bmp,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.zip,.rar
|
||||
|
||||
# 单文件大小上限(MB)
|
||||
UPLOAD_MAX_SIZE_MB=10
|
||||
|
||||
# 上传文件存储根目录
|
||||
TORNADO_UPLOAD_DIR=E:\upload
|
||||
|
||||
# 文件访问域名(Nginx 或本地代理)
|
||||
TORNADO_FILE_URL=http://127.0.0.1:8041/api/file
|
||||
```
|
||||
|
||||
:::tip 温馨提示
|
||||
`UPLOAD_ALLOWED_EXTS` 配置的扩展名会与内置安全集合取交集,确保不会绕过安全校验。留空时回落到内置智能集合。
|
||||
:::
|
||||
|
||||
## 安全机制
|
||||
|
||||
上传模块内置多层安全防护:
|
||||
|
||||
### 1. 路径遍历防护
|
||||
|
||||
```python
|
||||
def _is_safe_filename(filename: str) -> bool:
|
||||
"""拒绝包含路径分隔符或 .. 的文件名"""
|
||||
normalized = os.path.normpath(filename)
|
||||
if '..' in normalized.split(os.sep):
|
||||
return False
|
||||
basename = os.path.basename(normalized)
|
||||
return basename == normalized or basename == filename
|
||||
```
|
||||
|
||||
### 2. 高危格式黑名单
|
||||
|
||||
`.exe`、`.bat`、`.sh`、`.ps1`、`.js`、`.vbs`、`.svg`、`.webp` 等高危格式无论配置如何一律拒绝。
|
||||
|
||||
### 3. 魔术字节校验
|
||||
|
||||
通过 `verify_file_magic()` 校验文件头部魔术字节,防止伪造扩展名绕过白名单。
|
||||
|
||||
### 4. 流式大小校验
|
||||
|
||||
```python
|
||||
async def _read_upload_limited(file, max_size_bytes):
|
||||
"""流式读取,超过大小上限立即中断,避免整文件读入内存"""
|
||||
chunks = []
|
||||
total = 0
|
||||
while True:
|
||||
chunk = await file.read(65536)
|
||||
if not chunk:
|
||||
break
|
||||
total += len(chunk)
|
||||
if total > max_size_bytes:
|
||||
return None, R.failed(msg=f"文件大小超过限制,最大允许 {UPLOAD_MAX_SIZE_MB}MB")
|
||||
chunks.append(chunk)
|
||||
return b''.join(chunks), None
|
||||
```
|
||||
|
||||
## 文件命名规则
|
||||
|
||||
上传文件自动重命名,格式为:`年月日时分秒 + 5位随机数 + 扩展名`
|
||||
|
||||
```python
|
||||
def _generate_filename(ext_with_dot):
|
||||
timestamp = time.strftime('%Y%m%d%H%M%S')
|
||||
random_num = '%05d' % random.randint(0, 99999)
|
||||
return f"{timestamp}{random_num}{ext_with_dot}"
|
||||
```
|
||||
|
||||
文件按日期分目录存储:`{UPLOAD_DIR}/{YYYYMMDD}/{文件名}`
|
||||
|
||||
## 接口调用
|
||||
|
||||
### 上传文件
|
||||
|
||||
```
|
||||
POST /api/v1/upload/uploadFile
|
||||
Content-Type: multipart/form-data
|
||||
Authorization: Bearer <token>
|
||||
参数: file(文件字段)
|
||||
```
|
||||
|
||||
响应示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "上传成功",
|
||||
"data": {
|
||||
"originalName": "photo.jpg",
|
||||
"fileExtension": "jpg",
|
||||
"fileType": "image/jpeg",
|
||||
"fileSize": 306995,
|
||||
"fileName": "2025030614302512345.jpg",
|
||||
"filePath": "/20250306/2025030614302512345.jpg",
|
||||
"fileUrl": "http://127.0.0.1:8041/api/file/20250306/2025030614302512345.jpg"
|
||||
},
|
||||
"ok": true
|
||||
}
|
||||
```
|
||||
|
||||
## 前端集成
|
||||
|
||||
前端使用 `Upload` 组件上传,上传成功后将返回的 `fileUrl` 存入业务字段。详见 [文件上传联调](/zh/volume4/integration/upload-sync) 章节。
|
||||
|
||||
## 总结
|
||||
|
||||
文件上传模块通过扩展名白名单、魔术字节校验、流式大小限制、路径遍历防护等多层安全机制,确保上传功能的安全性。文件自动重命名并按日期分目录存储,避免文件名冲突和信息泄露。统一的 `uploadFile` 接口同时支持图片和各类文件上传。
|
||||
Reference in New Issue
Block a user