Skip to content

7.1 文件上传全流程 ​

概述

上传采用两段式:SaveTemp 先入临时目录(白名单+大小+图片嗅探校验),表单保存时 SaveFromTemp 移动到正式子目录。未走第二段的 temp 文件由每日 03:30 清理任务过期回收,不污染正式目录。

上传接口 ​

text
POST /api/upload/uploadFile   (sys:upload:file)multipart 字段名 file
POST /api/upload/uploadFiles  (sys:upload:files)多文件
入参 type=image 时按图片规则校验,否则按普通文件

multipart 字段约定:*ghttp.UploadFile + p:"file" 标签。

两段式流程图 ​

text
阶段一(上传时,POST /api/upload/uploadFile):
 浏览器 ──multipart──▶ SaveTemp
    │ ① 大小 ≤ file.maxSize(MB)
    │ ② 扩展名 ∈ file.fileExt / file.imageExt
    │ ③ type=image:前512B 魔数嗅探须 image/*
    ▼
 落盘 {uploadDir}/temp/{yyyymmdd}/{32位hex}.{ext}
    ▼
 返回 {url:"{domainUrl}/temp/...", path:"temp/.../x.png"(入库形态)} → 前端预览

阶段二(表单保存时,spec.saveTransform / SaveFileField):
 表单携带 temp URL ──▶ SaveFromTemp(url, saveDir="user")
    │ 剥离 domainUrl → 相对路径
    │ 以 temp/ 开头?──否──▶ 已正式/外站:归一化或原样返回
    │ 是
    ▼
 {uploadDir}/{saveDir}/{yyyymmdd}/{HHmmss}_{4字节hex}.{ext}(Rename,跨卷复制回退)
    ▼
 库中写入相对路径 "user/20261017/153012_ab12.png"
    ▼
 读侧 GetFileUrl:domainUrl + "/" + 相对路径 ──▶ 前端展示

SaveTemp 校验链(第一段) ​

text
1) 大小 ≤ file.maxSize(MB,默认 10)
2) 扩展名白名单:file.fileExt(普通)/ file.imageExt(图片)
3) 读取前 512 字节:type=image 时做魔数嗅探(http.DetectContentType 须 image/*),并据文件头嗅探 MIME
落盘:{file.uploadDir}/{file.tempDir}/{yyyymmdd}/{32位随机hex}.{ext}
返回 Result{Url(可预览完整地址), Path(相对 uploadDir,入库形态), Name, Size, Ext, Mime}

SaveFromTemp 处理规则(第二段) ​

入参形态处理
空值返回 ""
本站 URL(匹配 file.domainUrl 前缀)剥离域名得相对路径
相对路径以 temp/ 开头移动到 {saveDir}/{yyyymmdd}/{HHmmss}_{4字节hex}.{ext}(os.Rename,跨卷回退"复制后删源");返回新相对路径(入库形态)
已是正式相对路径归一化后原样返回(幂等)
外站 http(s) URL原样返回(不处理外链)

SaveFromTempMulti:按逗号拆分逐个转正(如 article.images 多图字段)。

路径契约 ​

text
数据库统一存"相对 uploadDir 的路径"(如 tenant/20260923/x.png)
对外访问地址 = GetFileUrl = file.domainUrl + "/" + 相对路径
  (domainUrl 为空时用 /upload/ 相对路径)
静态访问:router SetServerRoot("resource") 提供 /upload/**
Response 中间件对 .svg 强制 attachment + nosniff(防存储型 XSS)

声明式用法(ModuleSpec) ​

go
FileFields: []string{"avatar"} + FileSaveDir: "user"
// 写侧 saveTransform 自动 temp→正式;读侧 readTransform 自动 GetFileUrl 补域名
// MultiFileFields 处理逗号多文件;ContentFields 走富文本流程

模块已接入:user(avatar)、article(cover/images/content)、tenant(logo)、example(avatar) 等。

临时目录清理(运营闭环) ​

text
cmd.go 注册 gcron "0 30 3 * * *"(每日 03:30)→ logic.CleanTemp:
  扫描 {uploadDir}/{tempDir} 下目录名为 yyyymmdd 且早于
  now-file.tempExpireDays(默认 7 天,0=不清理)的日期目录整目录删除
  返回删除数量并记日志

常见问题 ​

症状排查
上传图片 404file.domainUrl 与静态服务根的相对关系;库中存相对路径,展示层必须经 GetFileUrl
上传被拒扩展名不在白名单 / 超 maxSize / 图片魔数嗅探不过(改后缀伪造)
temp 文件堆积CleanTemp 的 gcron 已注册?file.tempExpireDays > 0?
跨卷移动失败os.Rename 失败自动回退"复制后删源";检查磁盘空间与权限
富文本图片不显示读取侧走 Detail/List(基座自动 Expand),不要直接 Struct 输出

小蚂蚁云团队 · 提供技术支持