在Golang中实现文件上传进度显示,核心是通过自定义实现io.Reader接口,在读取文件内容时同步记录已读取的字节数,再通过回调函数将进度信息传递给上层业务。这种方式不需要修改HTTP Client的底层逻辑,适配性较强。

实现原理
HTTP Client上传文件时,会将文件内容作为请求体发送给服务端。我们可以封装一个带进度跟踪的读取器,在每次读取文件数据时,更新已读取的字节数,计算当前上传进度,再触发进度回调。整体流程如下:
- 打开本地待上传的文件,获取文件总大小
- 创建自定义进度读取器,包装文件句柄
- 将进度读取器作为HTTP请求的Body传入
- 在进度读取器的Read方法中更新已读字节数,计算进度并回调
自定义进度读取器实现
首先定义进度读取器的结构体,包含原始文件句柄、文件总大小、已读字节数和进度回调函数:
package main
import (
"io"
"os"
)
// 进度回调函数类型,参数分别是已读字节数和总字节数
type ProgressFunc func(readBytes, totalBytes int64)
// 进度读取器结构体
type ProgressReader struct {
reader io.Reader // 原始读取器
total int64 // 文件总大小
readBytes int64 // 已读取字节数
progressFn ProgressFunc // 进度回调函数
}
// 实现Read方法,符合io.Reader接口
func (pr *ProgressReader) Read(p []byte) (n int, err error) {
// 调用原始读取器的Read方法
n, err = pr.reader.Read(p)
if n > 0 {
// 更新已读字节数
pr.readBytes += int64(n)
// 触发进度回调
if pr.progressFn != nil {
pr.progressFn(pr.readBytes, pr.total)
}
}
return
}
完整上传示例
接下来结合HTTP Client实现完整的文件上传功能,包含进度打印逻辑:
package main
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
)
func main() {
// 待上传的文件路径
filePath := "./test_file.zip"
// 上传目标URL
uploadURL := "http://127.0.0.1:8080/upload"
// 打开文件
file, err := os.Open(filePath)
if err != nil {
fmt.Printf("打开文件失败: %vn", err)
return
}
defer file.Close()
// 获取文件信息,得到文件总大小
fileInfo, err := file.Stat()
if err != nil {
fmt.Printf("获取文件信息失败: %vn", err)
return
}
totalSize := fileInfo.Size()
// 创建multipart表单写入器
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
// 创建表单文件字段
part, err := writer.CreateFormFile("file", filepath.Base(filePath))
if err != nil {
fmt.Printf("创建表单字段失败: %vn", err)
return
}
// 创建进度读取器,包装文件句柄
progressReader := &ProgressReader{
reader: file,
total: totalSize,
readBytes: 0,
progressFn: func(readBytes, totalBytes int64) {
// 计算进度百分比
percent := float64(readBytes) / float64(totalBytes) * 100
fmt.Printf("上传进度: %.2f%% (%d/%d 字节)n", percent, readBytes, totalBytes)
},
}
// 将文件内容写入表单字段,这里使用进度读取器读取文件
_, err = io.Copy(part, progressReader)
if err != nil {
fmt.Printf("写入文件内容失败: %vn", err)
return
}
// 关闭multipart写入器,生成结束边界
err = writer.Close()
if err != nil {
fmt.Printf("关闭表单写入器失败: %vn", err)
return
}
// 创建HTTP请求
req, err := http.NewRequest("POST", uploadURL, body)
if err != nil {
fmt.Printf("创建请求失败: %vn", err)
return
}
// 设置Content-Type为multipart表单类型
req.Header.Set("Content-Type", writer.FormDataContentType())
// 发送请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Printf("发送请求失败: %vn", err)
return
}
defer resp.Body.Close()
// 读取响应
respBody, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Printf("读取响应失败: %vn", err)
return
}
fmt.Printf("上传完成,服务端响应: %sn", respBody)
}
注意事项
在实际使用中需要注意以下几点:
- 如果上传的文件较大,进度回调的触发频率会比较高,可根据需求增加节流逻辑,比如每上传1%再触发一次回调,避免频繁打印日志影响性能
- 进度读取器的Read方法可能被多次调用,已读字节数的累加逻辑需要保证线程安全,如果是并发场景需要加锁
- 如果上传过程中出现网络错误,已读字节数不会回滚,可根据业务需求调整进度计算的基准
服务端简易示例
为了测试上传功能,这里提供一个简易的Golang HTTP服务端代码,接收上传的文件:
package main
import (
"fmt"
"net/http"
"os"
)
func uploadHandler(w http.ResponseWriter, r *http.Request) {
// 限制上传文件大小为100MB
r.ParseMultipartForm(100 << 20)
// 获取上传的文件
file, handler, err := r.FormFile("file")
if err != nil {
fmt.Fprintf(w, "获取文件失败: %v", err)
return
}
defer file.Close()
// 创建本地文件保存上传内容
dst, err := os.Create("./upload_" + handler.Filename)
if err != nil {
fmt.Fprintf(w, "创建文件失败: %v", err)
return
}
defer dst.Close()
// 拷贝文件内容
_, err = io.Copy(dst, file)
if err != nil {
fmt.Fprintf(w, "保存文件失败: %v", err)
return
}
fmt.Fprintf(w, "文件上传成功")
}
func main() {
http.HandleFunc("/upload", uploadHandler)
fmt.Println("服务端启动,监听8080端口")
http.ListenAndServe(":8080", nil)
}
GolangHTTP_Client文件上传进度显示修改时间:2026-06-21 23:15:37