65 lines
2.4 KiB
Markdown
65 lines
2.4 KiB
Markdown
# 7.1 文件上传全流程
|
||
|
||
:::tip 概述
|
||
上传采用**两段式**:先传临时目录(temp),表单提交保存时才转正到模块正式目录。未落库的临时文件不对外暴露,杜绝"传了不保存"的孤儿文件被枚举。
|
||
:::
|
||
|
||
## 两段式流程
|
||
|
||
```text
|
||
① 上传接口(POST,multipart)
|
||
upload.SaveTemp → {upload_dir}/temp/{yyyymmdd}/{32位随机}.{ext}
|
||
返回访问 URL(前端表单持有)
|
||
|
||
② 保存记录(Add/Update)
|
||
ModuleSpec FileFields 声明 → saveTransform
|
||
upload.SaveFromTemp → {upload_dir}/{模块目录}/{yyyymmdd}/{时分秒_随机}.{ext}
|
||
入库的是相对 uploadDir 的路径(不含 upload 段)
|
||
|
||
③ 读侧
|
||
readTransform 补访问域名 → 完整 URL 返回前端
|
||
```
|
||
|
||
多文件字段走 `SaveFromTempMulti`(逗号分隔逐段转正)。
|
||
|
||
未转正的临时文件由**每日 03:30 的清理任务**(cmd/api 启动序列注册)按 `file.tempExpireDays`(默认 7 天)清理 `temp/{日期}` 目录——"传了不保存"的中间文件不会永久滞留。
|
||
|
||
## 物理路径契约
|
||
|
||
```text
|
||
临时:{upload_dir}/temp/{yyyymmdd}/{32位随机}.{ext}
|
||
正式:{upload_dir}/{正式子目录}/{yyyymmdd}/{时分秒_随机}.{ext}
|
||
入库:相对 uploadDir 的路径(不含 upload 段)
|
||
访问:file.domainUrl + "/" + 入库路径(nginx 直供)
|
||
```
|
||
|
||
## 核心 API(utility/upload)
|
||
|
||
| 函数 | 说明 |
|
||
|---|---|
|
||
| SaveTemp(ctx, fileHeader, typ) | 两段式第一段:落临时目录 |
|
||
| SaveFromTemp(ctx, fileUrl, saveDir) | 第二段:转正到模块目录 |
|
||
| SaveFromTempMulti | 多文件版本 |
|
||
| GetFileUrl(ctx, path) | 相对路径 → 访问 URL |
|
||
| SafeJoin(base, rel) | 路径穿越防护(清洗后必须仍在基目录内) |
|
||
| BaseDir / TempBaseDir / GetAccessBase | 目录基址查询 |
|
||
|
||
## 安全设计
|
||
|
||
| 防护 | 实现 |
|
||
|---|---|
|
||
| 扩展名白名单 | imageExt / fileExt 配置(服务端校验) |
|
||
| 内容嗅探 | 图片类校验真实文件头(isImage) |
|
||
| 大小限制 | file.maxSize(应用)+ client_max_body_size(nginx)对齐 |
|
||
| 路径穿越 | SafeJoin 断言 |
|
||
| temp 枚举 | nginx 与应用开发回退均封禁 /temp/ |
|
||
| SVG 风险 | nginx 强制附件下载 + nosniff |
|
||
|
||
## 与 dto 的配合
|
||
|
||
文件上传字段用 `*multipart.FileHeader` + `UnmarshalBody` 逃生舱解析(见 [3.3 契约层开发](/zh/guide/dto.html))。
|
||
|
||
## 下一步
|
||
|
||
→ [7.2 上传配置详解](/zh/tools/upload-config.html)
|