将pdf.gz解压后加入tar.gz时文件被截断损坏的问题排查
问题原因与修复方案
核心问题
你的代码中,用压缩后PDF文件的FileInfo生成tar文件头,而这个Info里的Size是压缩后的文件大小,并非解压后真实PDF的大小。tar writer会依据这个错误的大小写入数据,导致写到压缩后的大小就停止,最终PDF文件末尾被截断。
关键修复步骤
- 修正tar文件头的
Size字段:通过gzip reader获取解压后的真实文件大小,替换原压缩文件的Size值。 - 重置文件指针:创建gzip reader读取头信息后,需要将文件指针移回开头,才能完整读取所有解压内容。
- 完善错误处理:避免错误发生后代码继续执行,导致后续异常。
- 调整资源关闭顺序:确保tar writer先关闭,再关闭gzip writer,保证所有数据正确写入。
修复后的完整代码
package main import ( "io" "log" "os" "archive/tar" "compress/gzip" ) func main() { // 初始化输出tar.gz文件句柄 outputFile, err := os.Create("output.tar.gz") if err != nil { log.Fatalf("创建输出文件失败: %v", err) } defer outputFile.Close() // 创建gzip压缩器(级别1) gzWriter, err := gzip.NewWriterLevel(outputFile, 1) if err != nil { log.Fatalf("初始化gzip压缩器失败: %v", err) } defer gzWriter.Close() // 创建tar打包器 tarWriter := tar.NewWriter(gzWriter) defer tarWriter.Close() // 配置文件路径与tar内显示的文件名 sourceGzPath := "path-to-file.pdf.gz" tarInnerFileName := "filename-shown-in-tar.pdf" // 打开源压缩PDF文件 sourceFile, err := os.Open(sourceGzPath) if err != nil { log.Fatalf("打开源文件失败: %v", err) } defer sourceFile.Close() // 第一次创建gzip读取器,获取解压后的真实文件大小 gzReader, err := gzip.NewReader(sourceFile) if err != nil { log.Fatalf("初始化gzip读取器失败: %v", err) } // 关闭读取器,重置文件指针到开头 gzReader.Close() sourceFile.Seek(0, io.SeekStart) // 重新创建gzip读取器,用于后续解压数据 gzReader, err = gzip.NewReader(sourceFile) if err != nil { log.Fatalf("重新初始化gzip读取器失败: %v", err) } defer gzReader.Close() // 获取源文件基础信息,生成tar文件头 sourceInfo, err := sourceFile.Stat() if err != nil { log.Fatalf("获取源文件信息失败: %v", err) } tarHeader, err := tar.FileInfoHeader(sourceInfo, "") if err != nil { log.Fatalf("生成tar文件头失败: %v", err) } // 修正tar文件头的关键字段 tarHeader.Name = tarInnerFileName tarHeader.Size = gzReader.Size() // 替换为解压后的真实文件大小 // 写入tar文件头 if err := tarWriter.WriteHeader(tarHeader); err != nil { log.Fatalf("写入tar文件头失败: %v", err) } // 将解压后的PDF内容写入tar包 if _, err := io.Copy(tarWriter, gzReader); err != nil { log.Fatalf("写入解压内容失败: %v", err) } // 强制刷新tar打包器,确保所有数据写入gzip压缩器 if err := tarWriter.Flush(); err != nil { log.Fatalf("刷新tar打包器失败: %v", err) } }
大文件兼容方案(超过4GB)
如果解压后的PDF超过4GB,gzip头里的Size字段会返回0(gzip用uint32存储未压缩大小),此时可以用临时文件中转:
// 替换原代码中写入tar的部分 tempPdfFile, err := os.CreateTemp("", "temp-*.pdf") if err != nil { log.Fatalf("创建临时文件失败: %v", err) } defer os.Remove(tempPdfFile.Name()) // 处理完成后自动删除临时文件 // 将解压内容写入临时文件 if _, err := io.Copy(tempPdfFile, gzReader); err != nil { log.Fatalf("写入临时文件失败: %v", err) } tempPdfFile.Seek(0, io.SeekStart) // 重置临时文件指针到开头 // 获取临时文件的真实大小 tempFileInfo, err := tempPdfFile.Stat() if err != nil { log.Fatalf("获取临时文件信息失败: %v", err) } tarHeader.Size = tempFileInfo.Size() // 写入tar文件头与临时文件内容 tarWriter.WriteHeader(tarHeader) io.Copy(tarWriter, tempPdfFile)
内容的提问来源于stack exchange,提问作者clarkk
相关产品推荐
相关产品推荐

