Golang实现文件上传到互联网的方法指南
Golang实现文件上传到互联网的方法指南 使用以下代码,我可以从互联网下载文件并监控下载进度。
如何实现上传文件到互联网并监控上传进度?我想将可执行文件上传到 GitHub 的 Assets 部分。
package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
"github.com/dustin/go-humanize"
)
// WriteCounter 用于统计写入的字节数。它实现了 io.Writer 接口,
// 我们可以将其传递给 io.TeeReader(),后者将在每次写入周期报告进度。
type WriteCounter struct {
Total uint64
}
func (wc *WriteCounter) Write(p []byte) (int, error) {
n := len(p)
wc.Total += uint64(n)
wc.PrintProgress()
return n, nil
}
func (wc WriteCounter) PrintProgress() {
// 使用回车符回到行首并填充空格来清除该行
fmt.Printf("\r%s", strings.Repeat(" ", 35))
// 再次回车并打印当前的下载状态
// 我们使用 humanize 包以更易读的方式打印字节数(例如 10 MB)
fmt.Printf("\rDownloading... %s complete", humanize.Bytes(wc.Total))
}
func main() {
fmt.Println("Download Started")
fileUrl := "https://upload.wikimedia.org/wikipedia/commons/d/d6/Wp-w4-big.jpg"
err := DownloadFile("avatar.jpg", fileUrl)
if err != nil {
panic(err)
}
fmt.Println("Download Finished")
}
// DownloadFile 会将一个 URL 下载到本地文件。这种方式很高效,因为它会边下载边写入,
// 而不是将整个文件加载到内存中。我们将一个 io.TeeReader 传递给 Copy() 来报告下载进度。
func DownloadFile(filepath string, url string) error {
// 创建文件,但使用 .tmp 扩展名,这意味着在下载完成前不会覆盖原文件,
// 下载完成后我们会移除 .tmp 扩展名。
out, err := os.Create(filepath + ".tmp")
if err != nil {
return err
}
// 获取数据
resp, err := http.Get(url)
if err != nil {
out.Close()
return err
}
defer resp.Body.Close()
// 创建我们的进度报告器,并将其与写入器一起使用
counter := &WriteCounter{}
if _, err = io.Copy(out, io.TeeReader(resp.Body, counter)); err != nil {
out.Close()
return err
}
// 进度显示使用同一行,所以下载完成后需要打印一个换行符
fmt.Print("\n")
// 不使用 defer 关闭文件,以便在 Rename() 之前执行
out.Close()
if err = os.Rename(filepath+".tmp", filepath); err != nil {
return err
}
return nil
}
更多关于Golang实现文件上传到互联网的方法指南的实战教程也可以访问 https://www.itying.com/category-94-b0.html
2 回复
我猜你必须为此使用 GitHub API。
更多关于Golang实现文件上传到互联网的方法指南的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
要实现文件上传到GitHub Assets并监控进度,可以使用以下方法。这里使用GitHub Releases API,并实现类似下载进度的上传进度监控:
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"github.com/dustin/go-humanize"
)
// ReadCounter 用于统计读取的字节数
type ReadCounter struct {
Total uint64
FileName string
}
func (rc *ReadCounter) Read(p []byte) (int, error) {
n := len(p)
rc.Total += uint64(n)
rc.PrintProgress()
return n, nil
}
func (rc *ReadCounter) PrintProgress() {
fmt.Printf("\r%s", strings.Repeat(" ", 50))
fmt.Printf("\rUploading %s... %s complete", rc.FileName, humanize.Bytes(rc.Total))
}
// GitHubReleaseAsset 表示GitHub Release中的资源文件
type GitHubReleaseAsset struct {
URL string `json:"url"`
Name string `json:"name"`
Label string `json:"label,omitempty"`
State string `json:"state"`
Size int64 `json:"size"`
MimeType string `json:"content_type,omitempty"`
}
// UploadFileToGitHub 上传文件到GitHub Release的Assets
func UploadFileToGitHub(filePath, repoOwner, repoName, releaseID, githubToken string) error {
fmt.Println("Upload Started")
// 打开要上传的文件
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
// 获取文件信息
fileInfo, err := file.Stat()
if err != nil {
return err
}
// 创建进度计数器
counter := &ReadCounter{
FileName: fileInfo.Name(),
}
// 创建读取器链:文件 -> 进度监控 -> 最终读取器
reader := io.TeeReader(file, counter)
// 构建上传URL
uploadURL := fmt.Sprintf("https://uploads.github.com/repos/%s/%s/releases/%s/assets",
repoOwner, repoName, releaseID)
// 添加文件名参数
uploadURL += "?name=" + fileInfo.Name()
// 创建HTTP请求
req, err := http.NewRequest("POST", uploadURL, reader)
if err != nil {
return err
}
// 设置请求头
req.ContentLength = fileInfo.Size()
req.Header.Set("Authorization", "token "+githubToken)
req.Header.Set("Content-Type", "application/octet-stream")
req.Header.Set("Accept", "application/vnd.github.v3+json")
// 执行上传
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// 读取响应
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != http.StatusCreated {
return fmt.Errorf("upload failed: %s\n%s", resp.Status, string(body))
}
// 解析响应
var asset GitHubReleaseAsset
if err := json.Unmarshal(body, &asset); err != nil {
return err
}
fmt.Printf("\nUpload Finished: %s (%s)\n", asset.Name, humanize.Bytes(uint64(asset.Size)))
return nil
}
// 使用io.Pipe实现流式上传和进度监控
func UploadFileWithProgress(filePath, uploadURL, authToken string) error {
fmt.Println("Upload Started")
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
return err
}
// 创建管道
pr, pw := io.Pipe()
// 创建进度计数器
counter := &ReadCounter{
FileName: fileInfo.Name(),
}
// 在goroutine中读取文件并写入管道
go func() {
defer pw.Close()
_, err := io.Copy(io.MultiWriter(pw, counter), file)
if err != nil {
pw.CloseWithError(err)
}
}()
// 创建HTTP请求
req, err := http.NewRequest("POST", uploadURL, pr)
if err != nil {
return err
}
req.ContentLength = fileInfo.Size()
req.Header.Set("Authorization", "Bearer "+authToken)
req.Header.Set("Content-Type", "application/octet-stream")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("upload failed: %s\n%s", resp.Status, string(body))
}
fmt.Println("\nUpload Finished")
return nil
}
// 使用http.NewRequest和自定义Reader的简化版本
func SimpleUploadWithProgress(filePath, url, token string) error {
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
fileInfo, _ := file.Stat()
// 创建自定义Reader来监控进度
progressReader := &ProgressReader{
Reader: file,
Total: fileInfo.Size(),
Name: fileInfo.Name(),
}
req, err := http.NewRequest("POST", url, progressReader)
if err != nil {
return err
}
req.ContentLength = fileInfo.Size()
req.Header.Set("Authorization", "token "+token)
req.Header.Set("Content-Type", "application/octet-stream")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
return nil
}
// ProgressReader 实现带进度监控的Reader
type ProgressReader struct {
Reader io.Reader
Total int64
Read int64
Name string
}
func (pr *ProgressReader) Read(p []byte) (int, error) {
n, err := pr.Reader.Read(p)
pr.Read += int64(n)
// 计算并显示进度
percent := float64(pr.Read) / float64(pr.Total) * 100
fmt.Printf("\rUploading %s: %s/%s (%.1f%%)",
pr.Name,
humanize.Bytes(uint64(pr.Read)),
humanize.Bytes(uint64(pr.Total)),
percent)
return n, err
}
func main() {
// 示例:上传到GitHub Release
err := UploadFileToGitHub(
"myapp.exe", // 要上传的文件
"yourusername", // GitHub用户名
"yourrepo", // 仓库名
"12345678", // Release ID(从GitHub API获取)
"your_github_token", // GitHub个人访问令牌
)
if err != nil {
panic(err)
}
// 或者使用通用上传方法
err = SimpleUploadWithProgress(
"myapp.exe",
"https://uploads.github.com/repos/owner/repo/releases/123/assets?name=myapp.exe",
"github_pat_xxx",
)
if err != nil {
panic(err)
}
}
对于GitHub Assets上传,需要先获取Release ID:
// 获取Release信息以得到Release ID
func GetReleaseID(owner, repo, tag, token string) (string, error) {
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/tags/%s", owner, repo, tag)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", err
}
req.Header.Set("Authorization", "token "+token)
req.Header.Set("Accept", "application/vnd.github.v3+json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
var release struct {
ID int `json:"id"`
}
if err := json.Unmarshal(body, &release); err != nil {
return "", err
}
return fmt.Sprintf("%d", release.ID), nil
}
如果需要处理大文件或需要更精确的控制,可以使用分块上传:
// 分块上传实现
type ChunkedUploader struct {
File *os.File
ChunkSize int64
TotalSize int64
Uploaded int64
FileName string
UploadURL string
AuthToken string
}
func (cu *ChunkedUploader) Upload() error {
buffer := make([]byte, cu.ChunkSize)
for {
n, err := cu.File.Read(buffer)
if err != nil && err != io.EOF {
return err
}
if n == 0 {
break
}
chunk := buffer[:n]
if err := cu.uploadChunk(chunk); err != nil {
return err
}
cu.Uploaded += int64(n)
cu.printProgress()
if err == io.EOF {
break
}
}
fmt.Println("\nUpload completed")
return nil
}
func (cu *ChunkedUploader) uploadChunk(chunk []byte) error {
req, err := http.NewRequest("POST", cu.UploadURL, bytes.NewReader(chunk))
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+cu.AuthToken)
req.Header.Set("Content-Type", "application/octet-stream")
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d",
cu.Uploaded, cu.Uploaded+int64(len(chunk))-1, cu.TotalSize))
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
return fmt.Errorf("chunk upload failed: %s", resp.Status)
}
return nil
}
func (cu *ChunkedUploader) printProgress() {
percent := float64(cu.Uploaded) / float64(cu.TotalSize) * 100
fmt.Printf("\rUploading %s: %s/%s (%.1f%%)",
cu.FileName,
humanize.Bytes(uint64(cu.Uploaded)),
humanize.Bytes(uint64(cu.TotalSize)),
percent)
}
这些方法都实现了上传进度监控,可以根据具体需求选择使用。GitHub Assets上传需要有效的个人访问令牌和正确的Release ID。

