GoFrame文件上传接口开发指南
2026/5/20大约 6 分钟
GoFrame文件上传接口开发指南
本文详细介绍如何在GoFrame框架中开发符合gf风味的文件上传接口,包括单文件上传、多文件上传、文件验证、安全控制等核心功能。
目录
概述
GoFrame提供了简洁强大的文件上传能力,核心特点包括:
- API简洁:通过
ghttp.Request对象即可完成文件接收 - 自动解析:自动处理
multipart/form-data格式 - 灵活存储:支持自定义存储路径和文件名
- 安全可靠:内置文件大小限制和验证机制
核心API:
r.GetUploadFile(name)- 获取单个上传文件r.GetUploadFiles(name)- 获取多个上传文件file.Save(path, randomName)- 保存文件
基础配置
服务端配置
package main
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
func main() {
s := g.Server()
// 配置上传文件大小限制(默认8MB)
s.SetConfigWithMap(g.Map{
"ClientMaxBodySize": "100MB", // 设置最大上传大小
})
// 注册上传路由
s.Group("/api", func(group *ghttp.RouterGroup) {
group.POST("/upload", UploadHandler)
group.POST("/upload/batch", BatchUploadHandler)
})
s.SetPort(8080)
s.Run()
}配置说明
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
ClientMaxBodySize | string | 8MB | 最大请求体大小 |
LogAccess | bool | true | 开启访问日志 |
LogDebug | bool | false | 开启调试日志 |
单文件上传
基础实现
// UploadHandler 单文件上传处理
func UploadHandler(r *ghttp.Request) {
// 获取上传的文件
file := r.GetUploadFile("file")
if file == nil {
r.Response.WriteJson(g.Map{
"code": 1,
"message": "请选择要上传的文件",
})
return
}
// 保存文件(使用随机文件名)
savePath := "./uploads/"
filename, err := file.Save(savePath, true)
if err != nil {
r.Response.WriteJson(g.Map{
"code": 1,
"message": "文件保存失败: " + err.Error(),
})
return
}
// 返回成功响应
r.Response.WriteJson(g.Map{
"code": 0,
"message": "上传成功",
"data": g.Map{
"filename": filename,
"size": file.Size,
"original": file.Filename,
},
})
}测试接口
使用curl测试:
curl -X POST http://127.0.0.1:8080/api/upload \
-F "file=@/path/to/your/file.jpg"使用HTML表单测试:
<form action="/api/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">上传</button>
</form>多文件上传
基础实现
// BatchUploadHandler 多文件上传处理
func BatchUploadHandler(r *ghttp.Request) {
// 获取所有上传的文件
files := r.GetUploadFiles("files")
if len(files) == 0 {
r.Response.WriteJson(g.Map{
"code": 1,
"message": "请选择要上传的文件",
})
return
}
// 保存所有文件
savePath := "./uploads/"
names, err := files.Save(savePath, true)
if err != nil {
r.Response.WriteJson(g.Map{
"code": 1,
"message": "文件保存失败: " + err.Error(),
})
return
}
// 返回成功响应
r.Response.WriteJson(g.Map{
"code": 0,
"message": "上传成功",
"data": g.Map{
"count": len(names),
"filenames": names,
},
})
}测试多文件上传
curl -X POST http://127.0.0.1:8080/api/upload/batch \
-F "files=@/path/to/file1.jpg" \
-F "files=@/path/to/file2.png" \
-F "files=@/path/to/file3.pdf"文件验证
文件类型验证
import (
"strings"
"path"
)
// ValidateFileExtension 验证文件扩展名
func ValidateFileExtension(file *ghttp.UploadFile, allowedExts []string) bool {
ext := strings.ToLower(path.Ext(file.Filename))
for _, allowed := range allowedExts {
if ext == allowed {
return true
}
}
return false
}文件大小验证
// ValidateFileSize 验证文件大小
func ValidateFileSize(file *ghttp.UploadFile, maxSize int64) bool {
return file.Size <= maxSize
}完整验证示例
// UploadWithValidation 带验证的文件上传
func UploadWithValidation(r *ghttp.Request) {
file := r.GetUploadFile("file")
if file == nil {
r.Response.WriteJsonExit(g.Map{
"code": 1,
"message": "请选择文件",
})
}
// 文件类型验证
allowedExts := []string{".jpg", ".jpeg", ".png", ".gif"}
if !ValidateFileExtension(file, allowedExts) {
r.Response.WriteJsonExit(g.Map{
"code": 1,
"message": "不支持的文件类型",
})
}
// 文件大小验证(最大10MB)
maxSize := int64(10 * 1024 * 1024)
if !ValidateFileSize(file, maxSize) {
r.Response.WriteJsonExit(g.Map{
"code": 1,
"message": "文件大小超出限制(最大10MB)",
})
}
// 保存文件
filename, err := file.Save("./uploads/", true)
if err != nil {
r.Response.WriteJsonExit(g.Map{
"code": 1,
"message": "文件保存失败: " + err.Error(),
})
}
r.Response.WriteJson(g.Map{
"code": 0,
"message": "上传成功",
"data": filename,
})
}安全控制
中间件验证
// FileUploadMiddleware 文件上传中间件
func FileUploadMiddleware(r *ghttp.Request) {
// 检查请求方法
if r.Method != "POST" {
r.Response.WriteJsonExit(g.Map{
"code": 1,
"message": "不支持的请求方法",
})
}
// 检查Content-Type
contentType := r.Header.Get("Content-Type")
if !strings.HasPrefix(contentType, "multipart/form-data") {
r.Response.WriteJsonExit(g.Map{
"code": 1,
"message": "不支持的Content-Type",
})
}
r.Middleware.Next()
}注册中间件
func main() {
s := g.Server()
s.Group("/api", func(group *ghttp.RouterGroup) {
group.Middleware(FileUploadMiddleware)
group.POST("/upload", UploadHandler)
})
s.SetPort(8080)
s.Run()
}高级特性
自定义文件名规则
// GenerateFileName 生成自定义文件名
func GenerateFileName(file *ghttp.UploadFile) string {
ext := path.Ext(file.Filename)
timestamp := time.Now().Unix()
hash := sha256.Sum256([]byte(file.Filename + fmt.Sprintf("%d", timestamp)))
return fmt.Sprintf("%x%s", hash[:8], ext)
}
// UploadWithCustomName 使用自定义文件名
func UploadWithCustomName(r *ghttp.Request) {
file := r.GetUploadFile("file")
if file == nil {
r.Response.WriteJsonExit(g.Map{"code": 1, "message": "请选择文件"})
}
// 自定义保存路径
savePath := "./uploads/" + time.Now().Format("2006/01/02/")
// 创建目录(如果不存在)
if !gfile.Exists(savePath) {
gfile.Mkdir(savePath)
}
// 使用自定义文件名
customName := GenerateFileName(file)
fullPath := savePath + customName
// 手动保存
f, err := gfile.Create(fullPath)
if err != nil {
r.Response.WriteJsonExit(g.Map{"code": 1, "message": "创建文件失败"})
}
defer f.Close()
srcFile, err := file.Open()
if err != nil {
r.Response.WriteJsonExit(g.Map{"code": 1, "message": "读取上传文件失败"})
}
defer srcFile.Close()
_, err = io.Copy(f, srcFile)
if err != nil {
r.Response.WriteJsonExit(g.Map{"code": 1, "message": "保存文件失败"})
}
r.Response.WriteJson(g.Map{
"code": 0,
"message": "上传成功",
"data": fullPath,
})
}分片上传
// ChunkUploadHandler 分片上传处理
func ChunkUploadHandler(r *ghttp.Request) {
var (
fileId = r.Get("fileId").String()
chunkNumber = r.Get("chunkNumber").Int()
totalChunks = r.Get("totalChunks").Int()
file = r.GetUploadFile("chunk")
)
if file == nil || fileId == "" {
r.Response.WriteJsonExit(g.Map{"code": 1, "message": "参数错误"})
}
// 创建临时目录
tempDir := "./temp/" + fileId + "/"
gfile.Mkdir(tempDir)
// 保存分片
chunkPath := tempDir + fmt.Sprintf("%04d", chunkNumber)
file.Save(chunkPath, false)
// 检查是否所有分片都已上传
if chunkNumber == totalChunks {
// 合并分片
outputPath := "./uploads/" + fileId + ".tmp"
outputFile, _ := gfile.Create(outputPath)
defer outputFile.Close()
for i := 1; i <= totalChunks; i++ {
chunkFile, _ := gfile.Open(tempDir + fmt.Sprintf("%04d", i))
io.Copy(outputFile, chunkFile)
chunkFile.Close()
}
// 删除临时目录
gfile.Remove(tempDir)
r.Response.WriteJson(g.Map{"code": 0, "message": "上传完成"})
} else {
r.Response.WriteJson(g.Map{"code": 0, "message": "分片上传成功"})
}
}客户端调用
Go客户端
package main
import (
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
)
func main() {
ctx := gctx.New()
// 单文件上传
result, err := g.Client().Post(ctx,
"http://127.0.0.1:8080/api/upload",
"file=@file:/path/to/local/file.jpg",
)
if err != nil {
panic(err)
}
defer result.Close()
fmt.Println(result.ReadAllString())
// 多文件上传
result2, err := g.Client().Post(ctx,
"http://127.0.0.1:8080/api/upload/batch",
"files=@file:/path/to/file1.jpg",
"files=@file:/path/to/file2.png",
)
if err != nil {
panic(err)
}
defer result2.Close()
fmt.Println(result2.ReadAllString())
}JavaScript客户端
// 使用Fetch API上传文件
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
return response.json();
}
// 多文件上传
async function uploadFiles(files) {
const formData = new FormData();
files.forEach(file => {
formData.append('files', file);
});
const response = await fetch('/api/upload/batch', {
method: 'POST',
body: formData,
});
return response.json();
}最佳实践
1. 文件存储策略
// 根据日期组织文件
func GetUploadPath() string {
return "./uploads/" + time.Now().Format("2006/01/02/")
}
// 使用CDN域名
func GetFileURL(filename string) string {
return "https://cdn.example.com/uploads/" + filename
}2. 错误处理
func UploadHandler(r *ghttp.Request) {
defer func() {
if err := recover(); err != nil {
r.Response.WriteJson(g.Map{
"code": 500,
"message": "服务器内部错误",
})
}
}()
// 业务逻辑...
}3. 日志记录
func UploadHandler(r *ghttp.Request) {
file := r.GetUploadFile("file")
// 记录上传日志
glog.Info(r.Context(), "文件上传:", file.Filename, "大小:", file.Size)
// 业务逻辑...
}4. 并发控制
var uploadSemaphore = make(chan struct{}, 10) // 限制10个并发上传
func UploadHandler(r *ghttp.Request) {
uploadSemaphore <- struct{}{}
defer func() { <-uploadSemaphore }()
// 文件处理逻辑...
}5. 清理策略
// 定时清理临时文件
func CleanTempFiles() {
tempDir := "./temp/"
files, _ := gfile.ScanDir(tempDir, "*", true)
for _, file := range files {
info, _ := gfile.Stat(file)
if time.Since(info.ModTime()) > time.Hour*24 {
gfile.Remove(file)
}
}
}总结
GoFrame的文件上传功能具有以下优势:
- API简洁:通过
r.GetUploadFile()即可获取上传文件 - 功能完备:支持单文件、多文件、分片上传
- 安全可靠:内置文件大小限制和验证机制
- 易于扩展:支持自定义存储策略和文件名规则
遵循最佳实践,可以构建出高性能、安全可靠的文件上传服务。