問題描述
最近做廣告業務獲取某推的廣告成效,與其他渠道不同的是,最終拿到的成效數據是一個壓縮包的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 }
運行完這段代碼一直會報一個錯: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 }
里面有一段代碼需要注意:
// 設置信息頭 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) } }
當然,這個包還可以unpack HttpRedponse,readme文件有具體的例子,帶入自己的代碼就好了。
簡單原理
簡單看了下里面的源碼,它使用的是golang另外一個內置包bufio包實現的,有時間大家可以研究一下。
並發情況下存在的問題
當然這個包雖然解決了我們上面提到的解壓的問題,但是有一個小小的問題,就是返回的文件名是固定的,在並發的場景中多個gorountine操作同一個文件十分危險,當然我們可以給多個gorountine之間加互斥鎖保證並發的安全,也可以在不同的gorountine中生成一個唯一的解壓縮后的文件名保證goruntine之間不能操作到同一個文件即可。
