使用Golang解壓縮文件遇到的問題及解決方法


問題描述

最近做廣告業務獲取某推的廣告成效,與其他渠道不同的是,最終拿到的成效數據是一個壓縮包的HTTP流數據。

將數據寫入到本地生成了一個以.gz為后綴的壓縮包文件,解壓以后的文件存放着json格式的成效數據。

當然需要程序去解壓縮這個壓縮包獲取里面的文件了。

內置tar包的問題

參考網上大佬們之前的解決方案寫了一段測試代碼:

// TODO:解壓gz文件
func decompressionGZ(fileName string) error {
    filePath := GZIPS_PATH + fileName
    // file read
    fr, err := os.Open(filePath)
    if err != nil {
        return err
    }
    fmt.Println("fr>>> ", fr)
    defer fr.Close()
    // gzip read
    // TODO:這一步會校驗文件的Header
    gr, err := gzip.NewReader(fr)

    fmt.Println("gr_before>>> ", gr)

    //createTime := time.Date(2020, 01, 01, 00, 00, 00, 00, TIME_LOCATION_CST)
    //gr.Header.Comment = "xxx"
    //gr.Header.Name = "whw"
    //gr.Header.ModTime = createTime
    
    //fmt.Println("gr.after>>>>> ", gr)

    fmt.Println("gr.Header>>>>> ", gr.Header)
    fmt.Println("gr.err>>>>>> ", err)
    if err != nil {
        return err
    }
    defer gr.Close()
    // tar read
    tr := tar.NewReader(gr)
    //fmt.Println("tr>>> ", tr)
    // 讀取文件
    for {
        h, err := tr.Next()
        fmt.Println("h_err>>>>>> ", err)
        fmt.Println("h>>> ", h)
        if err == io.EOF {
            break
        }
        if err != nil {
            fmt.Println("err0>>> ", err)
            return err
        }
        // 打開文件
        fw, err := os.OpenFile("xxx.json", os.O_CREATE|os.O_WRONLY, os.ModePerm)
        if err != nil {
            fmt.Println("err1...... ", err)
            return err
        }
        defer fw.Close()
        // 寫文件
        _, err = io.Copy(fw, tr)
        if err != nil {
            fmt.Println("err2>>> ", err)
            return err
        }
    }
    return nil
}
使用原生golang包tar解壓的函數

運行完這段代碼一直會報一個錯:archive/tar: invalid tar header

就是說,在tar模塊進行校驗的時候檢測到了一個“非法”的tar header!(注意我們得到的文件的后綴名是xxx.json.gz)

研究了一下具體的實現過程,其實使用golang原生的tar包解壓縮文件的話都會對header做一下校驗!

至於原因,我們可以看一下使用原生golang實現壓縮文件的過程:

func compressionGZ(fileName string) error {
    // 創建文件
    fw, err := os.Create(fileName)
    if err != nil {
        fmt.Println("werr1>>> ", err)
        return err
    }
    defer fw.Close()
    // gzip write
    gw := gzip.NewWriter(fw)
    defer gw.Close()
    // tar write
    tw := tar.NewWriter(gw)
    defer tw.Close()

    // 打開文件夾
    dir, err := os.Open(GZIPS_PATH)
    if err != nil {
        fmt.Println("打開文件夾錯誤>>> ", err)
        return err
    }
    defer dir.Close()
    // 讀取文件列表
    fis, err := dir.Readdir(0)
    if err != nil {
        fmt.Println("讀取文件列表錯誤>>> ", err)
        return err
    }
    // 遍歷文件列表
    for _, fi := range fis {
        // TODO:遇到文件夾先不管,不遞歸了
        if fi.IsDir() {
            continue
        }
        // 開始寫入數據
        fr, err := os.Open(dir.Name() + "/" + fi.Name())
        if err != nil {
            fmt.Println("werr2>>> ", err)
            return err
        }
        defer fr.Close()

        // 設置信息頭
        h := new(tar.Header)
        // TODO:壓縮的時候設置Header!!!
        // TODO:注意這里的名稱需要去掉后面的 .gz
        h.Name = fileName[:len(fileName)-3]
        h.Mode = int64(fi.Mode())
        h.Size = fi.Size()
        // 寫信息頭
        err = tw.WriteHeader(h)
        if err != nil {
            fmt.Println("werr3>>> ", err)
            return err
        }

        // 寫文件
        _, err = io.Copy(tw, fr)
        if err != nil {
            fmt.Println("werr4>>> ", err)
            return err
        }
    }
    return nil
}
使用原生golang包tar壓縮的函數

里面有一段代碼需要注意:

// 設置信息頭
h := new(tar.Header)
// TODO:壓縮的時候設置Header!!!
// TODO:注意這里的名稱需要去掉后面的 .gz
h.Name = fileName[:len(fileName)-3]
h.Mode = int64(fi.Mode())
h.Size = fi.Size()
// 寫信息頭
err = tw.WriteHeader(h)

在golang的tar模塊進行壓縮文件的時候需要設置一下Header——所以在解壓的時候才會校驗tar的Header!

而且需要注意:正常情況下我們得到的壓縮文件的后綴是xxx.tar.gz,但是,twitter渠道給的文件的后綴名是xxx.json.gz,上面代碼在解壓的過程中是需要校驗一下tar包的header的!我們現在得到的文件當然是沒有tar包的header的!所以會報錯!!!

解決方案

在網上找了下,有一個第三方包可以順利解決壓縮與解壓的問題:https://github.com/c4milo/unpackit

下面是我的測試:

package pgzip

import (
    "fmt"
    "github.com/c4milo/unpackit"
    "os"
    "testing"
)

var filename = "SDJ9NgtdiyZwKaR9eEJQ7vOQm1UXJXWmeAmbZ5XmdBJ5Adj6gXadqEGXMPZNQO2H61cJXkcjMGJcQm6bWGyNB-9SZAId0SL9vVMgdoU5M8w3d6yXALPIrtxFTx5Whf3S.json.gz"

func TestUnpackIt(t *testing.T){

    filePath := GZIPS_PATH + filename
    file, err1 := os.Open(filePath)
    if err1 != nil{
        fmt.Println("err1>>> ", err1)
    }
    destPath, err := unpackit.Unpack(file,GZIPS_PATH)
    if err != nil{
        fmt.Println("err>>> ", err)
    }else{
        fmt.Println("destPath>>> ", destPath)
    }

}
unpackit包的測試

當然,這個包還可以unpack HttpRedponse,readme文件有具體的例子,帶入自己的代碼就好了。

簡單原理

簡單看了下里面的源碼,它使用的是golang另外一個內置包bufio包實現的,有時間大家可以研究一下。

並發情況下存在的問題

當然這個包雖然解決了我們上面提到的解壓的問題,但是有一個小小的問題,就是返回的文件名是固定的,在並發的場景中多個gorountine操作同一個文件十分危險,當然我們可以給多個gorountine之間加互斥鎖保證並發的安全,也可以在不同的gorountine中生成一個唯一的解壓縮后的文件名保證goruntine之間不能操作到同一個文件即可。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM