# 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)