Golang文件上传至Google Cloud Storage失败问题求助
Golang文件上传至Google Cloud Storage失败问题求助 各位Gopher们好,
我正在构建一个小型个人应用,它接收分块文件然后上传到Google Cloud Blob Storage。 不幸的是,文件似乎从未成功上传到Google Cloud(看起来是这样),那里没有任何显示。也没有任何错误。
有人发现这个应用有什么问题吗? 感谢任何想法。
func main() {
router := gin.Default()
config := cors.DefaultConfig()
config.AllowAllOrigins = true
config.AllowHeaders = []string{"Content-Type", "Content-Length", "Content-Range", "Accept-Encoding", "X-CSRF-Token", "Authorization", "accept", "origin", "Cache-Control", "X-Requested-With"}
router.Use(cors.New(config))
rg := router.Group("api/v1")
{
rg.POST("/photo", uploadFile)
}
router.Run()
}
func uploadFile(c *gin.Context) {
var f *os.File
file, header, e := c.Request.FormFile("file")
if f == nil {
f, e = os.OpenFile(header.Filename, os.O_APPEND|os.O_CREATE|os.O_RDWR, os.ModeAppend)
if e != nil {
panic("Error creating file on the filesystem: " + e.Error())
}
defer f.Close()
}
if _, e := io.Copy(f, file); e != nil {
panic("Error during chunk write:" + e.Error())
}
if isFileUploadCompleted(c) {
uploadToGoogle(c, f)
}
}
func isFileUploadCompleted(c *gin.Context) bool {
contentRangeHeader := c.Request.Header.Get("Content-Range")
rangeAndSize := strings.Split(contentRangeHeader, "/")
rangeParts := strings.Split(rangeAndSize[0], "-")
rangeMax, e := strconv.Atoi(rangeParts[1])
if e != nil {
panic("Could not parse range max from header")
}
fileSize, e := strconv.Atoi(rangeAndSize[1])
if e != nil {
panic("Could not parse file size from header")
}
return fileSize == rangeMax
}
func uploadToGoogle(c *gin.Context, f *os.File) {
creds, isFound := os.LookupEnv("GOOGLE_APPLICATION_CREDENTIALS")
if !isFound {
panic("GCP environment variable is not set")
} else if creds == "" {
panic("GCP environment variable is empty")
}
const bucketName = "el-my-gallery"
client, e := storage.NewClient(c)
if e != nil {
panic("Error creating client: " + e.Error())
}
defer client.Close()
bucket := client.Bucket(bucketName)
w := bucket.Object(f.Name()).NewWriter(c)
fmt.Printf("%v is the upload filename", f.Name())
fmt.Println()
f.Seek(0, io.SeekStart)
if bw, e := io.Copy(w, f); e != nil {
panic("Error during GCP upload:" + e.Error())
} else {
fmt.Printf("%v bytes written to Cloud", bw)
fmt.Println()
}
}
对于这行代码 fmt.Printf("%v bytes written to Cloud", bw),我确实得到了很多已写入的字节数。
更多关于Golang文件上传至Google Cloud Storage失败问题求助的实战教程也可以访问 https://www.itying.com/category-94-b0.html
2 回复
修复很简单,写入器没有关闭。 
更多关于Golang文件上传至Google Cloud Storage失败问题求助的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
你的代码存在几个关键问题导致文件无法上传到Google Cloud Storage:
- 文件指针位置问题:在写入本地文件后,文件指针位于末尾,需要重置
- GCS写入器未正确关闭:必须调用
w.Close()来提交上传 - 本地文件处理逻辑问题:
f == nil检查永远不会为真
以下是修复后的代码:
func uploadFile(c *gin.Context) {
// 获取上传的文件
file, header, err := c.Request.FormFile("file")
if err != nil {
c.JSON(400, gin.H{"error": "无法获取文件"})
return
}
defer file.Close()
// 创建本地临时文件
tempFile, err := os.CreateTemp("", "upload-*")
if err != nil {
c.JSON(500, gin.H{"error": "创建临时文件失败: " + err.Error()})
return
}
defer os.Remove(tempFile.Name())
defer tempFile.Close()
// 写入临时文件
if _, err := io.Copy(tempFile, file); err != nil {
c.JSON(500, gin.H{"error": "写入临时文件失败: " + err.Error()})
return
}
// 重置文件指针到开头
if _, err := tempFile.Seek(0, io.SeekStart); err != nil {
c.JSON(500, gin.H{"error": "重置文件指针失败: " + err.Error()})
return
}
// 检查文件是否上传完成
if isFileUploadCompleted(c) {
uploadToGoogle(c, tempFile, header.Filename)
} else {
c.JSON(200, gin.H{"status": "分块接收成功"})
}
}
func uploadToGoogle(c *gin.Context, f *os.File, filename string) {
// 验证环境变量
if _, err := os.Stat(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")); err != nil {
c.JSON(500, gin.H{"error": "GCP凭证文件不存在: " + err.Error()})
return
}
const bucketName = "el-my-gallery"
// 创建存储客户端
ctx := context.Background()
client, err := storage.NewClient(ctx)
if err != nil {
c.JSON(500, gin.H{"error": "创建GCS客户端失败: " + err.Error()})
return
}
defer client.Close()
// 创建存储桶句柄和写入器
bucket := client.Bucket(bucketName)
obj := bucket.Object(filename)
w := obj.NewWriter(ctx)
// 设置内容类型(重要)
w.ContentType = "application/octet-stream"
// 复制文件内容到GCS
if _, err := io.Copy(w, f); err != nil {
w.Close()
c.JSON(500, gin.H{"error": "上传到GCS失败: " + err.Error()})
return
}
// 必须关闭写入器以提交上传
if err := w.Close(); err != nil {
c.JSON(500, gin.H{"error": "关闭GCS写入器失败: " + err.Error()})
return
}
// 验证文件是否成功上传
attrs, err := obj.Attrs(ctx)
if err != nil {
c.JSON(500, gin.H{"error": "验证上传失败: " + err.Error()})
return
}
fmt.Printf("文件 %s 成功上传到GCS,大小: %d 字节\n", filename, attrs.Size)
c.JSON(200, gin.H{
"status": "上传成功",
"filename": filename,
"size": attrs.Size,
"bucket": bucketName,
})
}
主要修复点:
- 使用临时文件:避免并发写入问题
- 重置文件指针:
f.Seek(0, io.SeekStart)在复制前执行 - 关闭GCS写入器:
w.Close()是必需的,否则上传不会完成 - 添加错误处理:使用
c.JSON()返回错误而不是panic - 正确的上下文传递:使用
context.Background()而不是gin的上下文 - 验证上传结果:通过
obj.Attrs()确认文件已存在
分块上传的完整处理示例:
// 用于跟踪分块上传的状态
type uploadSession struct {
TempFile *os.File
FileName string
Received int64
TotalSize int64
mu sync.Mutex
}
var sessions = make(map[string]*uploadSession)
var sessionsMu sync.Mutex
func uploadChunk(c *gin.Context) {
// 获取上传ID(前端应提供)
uploadID := c.Query("upload_id")
if uploadID == "" {
uploadID = uuid.New().String()
}
sessionsMu.Lock()
session, exists := sessions[uploadID]
if !exists {
// 创建新会话
tempFile, err := os.CreateTemp("", fmt.Sprintf("upload-%s-*", uploadID))
if err != nil {
sessionsMu.Unlock()
c.JSON(500, gin.H{"error": err.Error()})
return
}
session = &uploadSession{
TempFile: tempFile,
FileName: c.Query("filename"),
}
sessions[uploadID] = session
}
sessionsMu.Unlock()
// 处理分块
file, _, err := c.Request.FormFile("file")
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
defer file.Close()
session.mu.Lock()
defer session.mu.Unlock()
// 追加分块到临时文件
n, err := io.Copy(session.TempFile, file)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
session.Received += n
// 获取总文件大小
contentRange := c.GetHeader("Content-Range")
if contentRange != "" {
parts := strings.Split(contentRange, "/")
if len(parts) == 2 {
if total, err := strconv.ParseInt(parts[1], 10, 64); err == nil {
session.TotalSize = total
}
}
}
// 检查是否完成
if session.TotalSize > 0 && session.Received >= session.TotalSize {
// 重置文件指针并上传
session.TempFile.Seek(0, io.SeekStart)
uploadToGoogle(c, session.TempFile, session.FileName)
// 清理会话
sessionsMu.Lock()
delete(sessions, uploadID)
sessionsMu.Unlock()
session.TempFile.Close()
os.Remove(session.TempFile.Name())
} else {
c.JSON(200, gin.H{
"upload_id": uploadID,
"received": session.Received,
"total": session.TotalSize,
"status": "chunk_received",
})
}
}
这些修复确保文件正确上传到Google Cloud Storage,并提供适当的分块处理机制。

