Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 中文字幕日韩在线一区国内 ,国内精自线一二区,日韩精品在线电影

          整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          GCTT 出品 - 使用 Go 語言完成 HTTP

          GCTT 出品 - 使用 Go 語言完成 HTTP 文件上傳與下載

          近我使用 Go 語言完成了一個正式的 web 應用,有一些方面的問題在使用 Go 開發 web 應用過程中比較重要。過去,我將 web 開發作為一項職業并且把使用不同的語言和范式開發 web 應用作為一項愛好,因此對于 web 開發領域有一些心得體會。

          總的來說,我喜歡使用 Go 語言進行 web 開發,盡管開始一段時間需要去適應它。Go 語言有一些坑,但是正如本篇文章中所要討論的文件上傳與下載,Go 語言的標準庫與內置函數,使得開發是種愉快的體驗。

          在接下來的幾篇文章中,我將重點討論我在 Go 中編寫生產級 Web 應用程序時遇到的一些問題,特別是關于身份驗證/授權的問題。

          這篇文章將展示HTTP文件上傳和下載的基本示例。我們將一個有 type 文本框和一個 uploadFile 上傳框的 HTML 表單作為客戶端。

          讓我們來看下 Go 語言中是如何解決這種在 web 開發中隨處可見的問題的。

          代碼示例

          首先,我們在服務器端設定兩個路由,/upload 用于文件上傳, /files/* 用于文件下載。

          const maxUploadSize=2 * 1024 * 2014 // 2 MB
          const uploadPath="./tmp"
          func main() {
           http.HandleFunc("/upload", uploadFileHandler())
           fs :=http.FileServer(http.Dir(uploadPath))
           http.Handle("/files/", http.StripPrefix("/files", fs))
           log.Print("Server started on localhost:8080, use /upload for uploading files and /files/{fileName} for downloading files.")
           log.Fatal(http.ListenAndServe(":8080", nil))
          }
          

          我們還將要上傳的目標目錄,以及我們接受的最大文件大小定義為常量。注意這里,整個文件服務的概念是如此的簡單 —— 我們僅使用標準庫中的工具,使用 http.FileServe 創建一個 HTTP 處理程序,它將使用 http.Dir(uploadPath) 提供的目錄來上傳文件。

          現在我們只需要實現 uploadFileHandler。

          這個處理程序將包含以下功能:

          • 驗證文件最大值
          • 從請求驗證文件和 POST 參數
          • 檢查所提供的文件類型(我們只接受圖像和 PDF)
          • 創建一個隨機文件名
          • 將文件寫入硬盤
          • 處理所有錯誤,如果一切順利返回成功消息

          第一步,我們定義處理程序:

          func uploadFileHandler() http.HandlerFunc {
           return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
          

          然后,我們使用 http.MaxBytesReader 驗證文件大小,當文件大小大于設定值時它將返回一個錯誤。錯誤將被一個助手程序 renderError 進行處理,它返回錯誤信息及對應的 HTTP 狀態碼。

           r.Body=http.MaxBytesReader(w, r.Body, maxUploadSize)
           if err :=r.ParseMultipartForm(maxUploadSize); err !=nil {
           renderError(w, "FILE_TOO_BIG", http.StatusBadRequest)
           return
           }
          

          如果文件大小驗證通過,我們將檢查并解析表單參數類型和上傳的文件,并讀取文件。在本例中,為了清晰起見,我們不使用花哨的 io.Reader 和 io.Writer 接口,我們只是簡單的將文件讀取到一個字節數組中,這點我們后面會寫到。

           fileType :=r.PostFormValue("type")
           file, _, err :=r.FormFile("uploadFile")
           if err !=nil {
           renderError(w, "INVALID_FILE", http.StatusBadRequest)
           return
           }
           defer file.Close()
           fileBytes, err :=ioutil.ReadAll(file)
           if err !=nil {
           renderError(w, "INVALID_FILE", http.StatusBadRequest)
           return
           }
          

          現在我們成功的驗證了文件的大小,并且讀取了文件,接下來我們該檢驗文件的類型了。一種廉價但是并不安全的方式,只檢查文件擴展名,并相信用戶沒有改變它,但是對于一個正式的項目來講不應該這么做。

          幸運的是,Go 標準庫提供給我們一個 http.DetectContentType 函數,這個函數基于 mimesniff 算法,只需要讀取文件的前 512 個字節就能夠判定文件類型。

           filetype :=http.DetectContentType(fileBytes)
           if filetype !="image/jpeg" && filetype !="image/jpg" &&
           filetype !="image/gif" && filetype !="image/png" &&
           filetype !="application/pdf" {
           renderError(w, "INVALID_FILE_TYPE", http.StatusBadRequest)
           return
           }
          

          在實際應用程序中,我們可能會使用文件元數據做一些事情,例如將其保存到數據庫或將其推送到外部服務——以任何方式,我們將解析和操作元數據。這里我們創建一個隨機的新名字(這在實踐中可能是一個UUID)并將新文件名記錄下來。

           fileName :=randToken(12)
           fileEndings, err :=mime.ExtensionsByType(fileType)
           if err !=nil {
           renderError(w, "CANT_READ_FILE_TYPE", http.StatusInternalServerError)
           return
           }
           newPath :=filepath.Join(uploadPath, fileName+fileEndings[0])
           fmt.Printf("FileType: %s, File: %s\n", fileType, newPath)
          

          馬上就大功告成了,只剩下一個關鍵步驟-寫文件。如上文所提到的,我們只需要復制讀取的二進制文件到一個新創建的名為 newFile 的文件處理程序里。

          如果所有部分都沒問題,我們給用戶返回一個 SUCCESS 信息。

           newFile, err :=os.Create(newPath)
           if err !=nil {
           renderError(w, "CANT_WRITE_FILE", http.StatusInternalServerError)
           return
           }
           defer newFile.Close()
           if _, err :=newFile.Write(fileBytes); err !=nil {
           renderError(w, "CANT_WRITE_FILE", http.StatusInternalServerError)
           return
           }
           w.Write([]byte("SUCCESS"))
          

          這樣可以了. 你可以對這個簡單的例子進行測試,使用虛擬的文件上傳 HTML 頁面,cURL 或者工具例如 postman[1]

          這里是完整的代碼示例 這里[2]

          結論

          這是又一個證明了 Go 如何允許用戶為 web 編寫簡單而強大的軟件,而不必像處理其他語言和生態系統中固有的無數抽象層。

          在接下來的篇幅中,我將展示一些在我第一次使用 Go 語言編寫正式的 web 應用中其他細節,敬請期待。;)

          // 根據 reddit 用戶 lstokeworth 的反饋對部分代碼進行了修改。謝謝:)

          資源

          完整代碼示例[3]


          via: https://zupzup.org/go-http-file-upload-download/ 作者:zupzup[4] 譯者:fengchunsgit[5] 校對:polaris1119[6]

          本文由 GCTT[7] 原創編譯,Go 中文網[8] 榮譽推出

          References

          [1] postman: https://www.getpostman.com/

          [2] 這里: https://github.com/zupzup/golang-http-file-upload-download

          [3] 完整代碼示例: https://github.com/zupzup/golang-http-file-upload-download

          [4] zupzup: https://zupzup.org/about/

          [5] fengchunsgit: https://github.com/fengchunsgit

          [6] polaris1119: https://github.com/polaris1119

          [7] GCTT: https://github.com/studygolang/GCTT

          [8] Go 中文網: https://studygolang.com/

          文是 InfoQ“解讀 2020”年終技術盤點系列文章之一。

          在作者去年年底撰寫《解讀Go語言的2019》的時候,絕沒有想到 2020 年將會如此的不平凡。全球范圍內的疫情在大大地限制了人們和企業的對外活動之余,還帶來了一個副作用,即:線下活動向線上的迅速遷移。

          實際上,對于這種遷移,我們國內的民營企業和事業單位早就在做了,只不過在 2020 年之前還沒有這么急迫。不知道你發現了沒有,在 2020 這一年,那些已經存在的遠程辦公、視頻會議、在線醫療、在線教育等方面的基礎設施和應用程序給予了我們莫大的支撐。即便說它們輔助保障了社會的正常運轉,也不為過。

          目前來看,全球的疫情還會存在一段時間。雖然這個事件本身絕對不值得高興,但是反過來想,這會倒逼國內數字經濟的大踏步前進,甚至飛躍。

          從基礎層面講,數字經濟的發展必須要有半導體等高精尖領域的強力支持。而從應用層面說,數字經濟將會依托于云計算、大數據和人工智能。更具體地說,云計算是高級的基礎設施,大數據和人工智能是建立在云計算之上的高級應用。Go 語言,早已霸占了云計算的大半個江山,今后它也將在大數據和人工智能方面發揮重要作用。

          趨勢縱覽

          下面,我們依舊先從整體趨勢上看看 Go 語言在今年的發展。

          在全球范圍內,從 2010 年的集體追新,到之后幾年內的理性對待,再到 2016 年、2017 年的“第二春”,直至 2018 年的升降大反差和 2020 年的新反彈。Go 語言可謂是經歷了諸多風風雨雨,持續地在各種好評和詬病之間砥礪前行,既得意過也失意過。

          下圖展現了 TIOBE Index(著名編程語言排行榜)對 Go 語言使用情況的最新統計。

          圖 1 - TIOBE Index 之 Go 語言(2020 年 12 月)

          ? 圖 2 - TIOBE Index(2020 年 12 月)

          我們從上面這兩幅圖中可以看出, Go 語言在今年的排名又有了大幅的提升。作者個人認為,這與 go mod 工具的轉正和推廣,以及“泛型”實現的排期確定是分不開的。

          同時,據 StackOverflow(全球最大的編程社區和問答網站)在前不久發布的一份開發者生存報告顯示,Go 語言在 2020 年是繼 Python、Java、C++和 C 之后、排名第五的通用型、全平臺編程語言。如果把腳本語言和標記語言都算在內的話,它的總排名是第 12 名。

          圖 3 - Stack Overflow Servey 2020 - The Most Popular Languages

          不但如此,Go 語言在“最喜愛”和“最需要”的編程語言排行中也名列前茅。

          ? 圖 4 - Stack Overflow Servey 2020 - The Most Loved Languages

          圖 5 - Stack Overflow Servey 2020 - The Most Wanted Languages

          我們可以看到,Go 語言不但是開發者們非常喜愛的編程語言之一(“最喜愛”排行榜第五名),而且從實際應用的角度看,大家也是非常需要它的(“最需要”排行榜第三名)。作者認為,正因為 Go 語言有著崇尚簡約和實用主義的編程哲學,廣大軟件工程師才會如此地愛用它。

          更重要的是,Go 軟件工程師的薪資待遇也是相當不錯的。

          圖 6 - Stack Overflow Servey 2020 - The Highest Salaries

          你可能會奇怪,為什么 Perl 程序員的薪資排在了第一位?這可能是因為物以稀為貴,Perl 程序員在當代已經非常少見了。而在當今很熱門的通用型編程語言中,從薪資角度來看,Scala 語言、Go 語言和 Rust 語言都有著相當大的優勢。

          當然了,這是在全球范圍內的情況,并且參與這份調查的中國開發者并不多。很可惜,作者沒能找出一份公認且權威的國內開發者調查報告。

          不過,從作者的親身經歷來看,Go 語言在國內恐怕并不亞于國際上的熱度,甚至還要更火熱一些。

          作者這兩年一直在斷斷續續地幫助一些互聯網企業招聘 Go 軟件工程師。除了作為老一代霸主的 BAT(百度、阿里巴巴、騰訊)以及作為新一代翹楚的 TMD(、美團、滴滴)之外,還有很多知名的互聯網公司都在招聘掌握 Go 語言的開發工程師和系統運維人員。像 PingCAP、七牛、嗶哩嗶哩、探探、Grab 這些公司,在很早以前就混跡于 Go 語言圈子了。而在最近幾年才進入 Go 語言圈子的知名公司還有華為、小米、映客、云智聯、輕松籌、貝殼網、美菜網、游族網絡等等。就連剛開始大紅大紫的工業互聯網領域,也有不少公司選擇 Go 語言作為其主力開發語言之一。比如,積夢智能、必可測等。

          這么多的優秀企業,以及活躍在技術社區中的大佬和新秀共同營造出了 Go 語言工程師的供需網絡。作者認為,在國內的服務端編程市場,除了 Java 和 PHP,就當屬 Go 語言了。

          2020 年回顧

          在了解了 Go 語言的發展趨勢之后,我們再一起來看看它在 2020 年都有哪些重要的更新。

          模塊:終于穩定

          自 2020 年 2 月份發布的 1.14 版本起,Go 語言官方就開始正式地推廣 go modules 了。這說明它已經完全可以在生成環境中使用了。

          如果你是老牌的 Go 工程師的話,那么肯定使用過像 glide、dep、govendor 這類第三方的依賴管理工具。這些工具都非常的優秀,并且在很大程度上解決了我們在項目開發過程中遇到的痛點。

          不過,現在是時候遷移到 go modules 了。Go modules 綜合了這些第三方工具的優點,并經歷了數年的設計和磨合,最終成為了 Go 程序依賴管理的官方工具。

          即使現存的項目已經使用了前面提及的某一個依賴管理工具,那么也無需擔心。我們只需要在 Go 項目的根目錄中運行命令“go mod init <項目主模塊的導入路徑>”就可以實現自動地遷移。go mod 命令會讀取那些已經存在的依賴配置文件,然后在其創建的 go.mod 文件中添加相應的內容。不過在這之后,我們最好再次使用 go build 命令構建一下項目并運行相應的單元測試,以確保一切正常。

          還記得系統環境變量 GOPATH 嗎?現在的 go 命令會自動地把項目所需的依賴包下載到它指向的第一個工作區目錄中的 pkg/mod 子目錄里。這里有一點需要注意,如果我們的項目中存在處于頂層的 vendor 目錄,那么 go 命令將會優先在該目錄中查找對應的依賴包。

          如果我們使用的是 Go 語言的 1.15 版本,那么也可以通過設置系統環境變量 GOMODCACHE 來自定義上述存儲依賴包的目錄。這實際上是為以后徹底廢棄 GOPATH 埋下的一個伏筆。

          另外,執行一下 go mod tidy 命令也是一個很好的主意。這個命令會對 go modules 的依賴配置文件進行整理,添加那些實際在用的依賴項,并去除那些未用的依賴項。換句話說,它會確保項目的依賴配置文件與項目源碼的實際依賴相對應。

          Go 語言的大多數標準命令都得到了不同程度的改進以更加適配 go modules,包括一些標記(flag)的調整和一些行為上的優化。比如,go get 命令在默認情況下不再會去更新非兼容版本的依賴庫。不兼容的依賴庫更新常常會讓我們很惱火,但現在不會再出現這種情況了。

          環境變量:跟進的調整

          Go 語言可識別的系統環境變量 GO111MODULE 在 1.14 和 1.15 版本中的默認值都是 auto。這意味著,go 命令僅在當前目錄或上層目錄中存在 go.mod 文件的情況下才會以 go modules 的方式運行,否則它就會退回到之前以 GOPATH 為中心的運行方式。不過,預計在明年發布的 1.16 版本中,Go 語言將會把這個環境變量的默認值設置為 on。也就是說,到了那時,GOPATH 這一古老但能勾起我們滿滿回憶的東西終于要默默地退出了。

          另外,我們現在可以在系統環境變量 GOPROXY 的值中使用管道符“|”了。在這之前,GOPROXY 的值中只能出現分隔符“,”。如果一個代理 URL 跟在了分隔符后面,那么只有在前一個代理 URL 指向的服務器返回 404 或 401 錯誤時,go 命令才會嘗試使用當前的代理 URL。現在,如果一個代理 URL 跟在了管道符后面,那么只要在訪問前一個服務器時發生了(任何的)錯誤,go 命令就會馬上使用當前的代理 URL。換句話說,新的管道符讓我們多了一種容錯的選擇,即范圍更廣的容錯。合理使用它,可以讓我們更快地從可用的代理那里下載到所需的代碼包。

          順便說一下,我們現在有了一個新的系統環境變量 GOINSECURE。這個環境變量的存在單純是為了讓我們能夠從非 HTTPS 的代碼包服務器上下載依賴包(或者模塊)。

          有關環境變量的更多細節,我就不在這里說了。大家如果想了解的話,可以去參看 Go 語言的相關文檔。

          語言語法:可重疊的接口方法

          我們都知道,Go 語言這些年在語法方面一向很穩定,少有改動,更沒有不兼容的變化出現。在 2020 年,Go 語言只做了一項語法改進。這是關于接口聲明的,并且完全保證了向后兼容性。我們下面來看一組代碼示例。假設,我們有如下兩個接口聲明:

          // MyReader 代表可讀的自定義接口。
          type MyReader interface {
              io.ReadCloser
          }
          // MyWriter 代表可寫的自定義接口。
          type MyWriter interface {
              io.WriteCloser
          }
          
          

          在 Go 1.14 之前,這兩個接口是無法內嵌到同一個接口聲明中去的。就像這樣:

          // MyIO 代表可輸入輸出的自定義接口。
          type MyIO interface {
              MyReader
              MyWriter
              io.Closer
          }
          
          

          這會讓 Go 語言的編譯器報錯。報錯的原因是:在同一個接口聲明中發現了重復的方法聲明。更具體地說,Go 語言標準庫中的 io.ReadCloser 接口和 io.WriteCloser 接口都包含了一個名為 Close 的方法,而分別內嵌了這兩個接口的 MyReader 和 MyWriter 又已經嵌入到了接口 MyIO 之中。這導致 MyIO 接口里現在存在兩個 Close 方法的聲明。所以,MyIO 的聲明是無效的,并且無法通過編譯。

          這看上去是合規的,但卻不一定合理。因為在很多情況下,我們想做的只是把多個接口合并在一起,而不在乎方法聲明是否有重疊。我們一般認為,如果有重疊的方法,那么就當作一個就好了。很可惜,之前的 Go 語言并不這么認為。更重要的是,對于像上面那樣深層次的接口內嵌問題,我們排查和解決起來都會很麻煩。有時候,這還會涉及到第三方庫。

          值得慶幸的是,自 Go 1.14 開始,我們的這個合理訴求終于得到了滿足。Go 語言的語法已經認可了上述情況。這將給我們的接口整合工作帶來極大的便利。

          不過請注意,Go 語言只接受在同一個接口聲明中完全重疊的多個方法聲明。換句話說,只有這些方法聲明在名稱和簽名上完全一致,它們才能夠合而為一。別忘了,接口中方法的簽名包括了參數列表和結果列表。如果僅名稱相同但簽名不同,那么 Go 語言編譯器照樣會報錯。例如:

          // MyIO 代表可輸入輸出的自定義接口。
          type MyIO interface {
              MyReader
              MyWriter
              Close()
          }
          
          

          由于這里最后面的方法聲明 Close() 與接口 io.ReadCloser 和 io.WriteCloser 中的方法聲明 Close() error 不完全一致(請注意結果聲明上的差異),所以 Go 語言仍然會報出錯誤“duplicate method Close”,并使得程序編譯不通過。

          運行時內部:性能提升

          Go 語言每次的版本更新都會包含針對其運行時系統的性能提升。在 2020 年的優化中,有幾點值得我們注意:

          1. goroutine 真正實現了異步的搶占。也就是說,現在即使是不包含任何函數調用的 for 循環也絕不會引起程序的死鎖和垃圾回收的延誤了。

          2. defer 語句的執行效率又得到了進一步的提升,額外的開銷已幾乎為零。所以,我們已經完全可以將 defer 語句用在對性能有嚴苛要求的應用場景中了。

          3. 運行時系統內部的內存分配器獲得了改進。這使得在系統環境變量 GOMAXPROCS 有較高數值的情況下,內存的申請效率會得到大幅的提升。這間接地讓運行時系統的整體性能明顯提高。

          除了以上這些,Go 語言運行時系統還有一些比較小的改進,比如:panic 函數已經可以正確地打印出我們傳入的參數值當中的各種數值了、在將較小的整數值轉換為接口值的過程中不再會引起內存分配、針對通道的非阻塞接收操作得到了進一步的優化,等等。

          并發編程:一些微調

          我們都知道,runtime.Goexit 函數在被調用之后會中止當前的 goroutine 的運行。然而,在嵌套的 panic/recover 處理流程中,程序對它的調用會被忽略掉。雖然這種應用場景非常少見,但終歸是一個問題。幸好自 Go 1.14 開始,這個問題被徹底地解決了。

          還要注意,如果 runtime.Goexit 函數被主 goroutine 中的代碼調用了,那么它并不會終止整個 Go 程序的運行,而只會中止主 goroutine 而已。在這種情況下,其他的 goroutine 會繼續運行。如果,在這之后,這些其他的 goroutine 都運行完畢了,那么程序就會崩潰。所以說,我們千萬不要在主 goroutine 中調用 runtime.Goexit 函數。

          在同步工具方面,現在的 Go 運行時系統會更加關注那些競爭非常激烈的互斥鎖。一旦這樣的鎖被解鎖,系統會立即安排 CPU 去運行正在等待該鎖的下一個 goroutine(也就是說,它會跳過調度的過程)。這顯然可以大大提高此類場景下的互斥鎖性能。

          并發安全字典 sync.Map 有了一個新方法 LoadAndDelete。從方法名上我們也可以看得出來,這個方法能夠在獲取某個元素值的同時把它從字典中刪除掉。更重要的是,Go 語言會保證這個“獲取+刪除”的操作的原子性。另外,該方法返回的第二個結果值會告知我們,字典先前是否包含與我們傳入的健值(key)對應的元素值(element)。如果不包含,那么它返回的第一個結果值就會是 nil。

          再來說 context.Context。雖然它并不在 sync 代碼包中,但是我們常常用它來做上下文的同步。因此,它也算是一個很重要的同步工具。關于它的調整很簡單也很明確,那就是:不再允許使用 nil 作為父級的上下文來創建衍生的 Context 實例了。這一要求顯然是合理的,因為所謂的衍生 Context 就應該有實實在在的父級。然而,在 Go 1.15 之前,并沒有專門的防衛語句來對此進行前置檢查。此項改進涉及到了 context 包中的幾個重要函數,如 WithCancel、WithDeadline、WithTimeout 和 WithValue。現在,如果我們傳給這些方法的第一個參數值是 nil,那么它們都將會立即拋出包含了對應錯誤信息的 panic。

          標準庫:新的散列算法包

          標準庫中多了一個新的代碼包:hash/maphash。這是一個通用的散列算法包,可以將任意的字節序列或者字符串散列成 64 位的整數。從名字上我們也可以看出,它能夠幫助我們實現那些基于散列表的數據結構。該包中的 Hash 類型的基本行為如下:

          1. 此 Hash 類型是開箱即用的。也就是說,我們在實例化它的時候無需額外的初始化工作,僅創建一個它的零值即可使用。

          2. 不同的 Hash 實例在默認的情況下會有不同的默認種子。因此,這些默認實例為同一個對象計算出的散列值將會不同。

          3. Hash 實例允許手動設置種子(必須由 MakeSeed 函數產生)。在單一進程中,只要種子相同,Hash 實例為同一個對象計算出的散列值就會相同。不論進行計算的 Hash 實例是一個還是多個,都會如此。

          4. 被計算的對象的表現形式可以是字節序列,也可以是字符串。只要內容一致,不論它們是以怎樣的方式寫入 Hash 實例的,計算出的散列值都會相同(大前提是進程和種子都相同)。

          5. Hash 實例可以被重置。此操作會清空已寫入當前 Hash 實例的內容,但并不會改變當前的種子。

          總之,hash/maphash.Hash 類型代表著一種基于種子的、可重用、可重置、穩定、通用的散列算法。這種算法的優勢是可以有效地避免散列沖突,但它并不是加密安全的。如果你的關注點是加密安全,那么可以考慮使用標準庫 crypto 包中的算法包,如 md5、sha256、sha512 等。

          單元測試:資源清理

          今年,Go 語言還在單元測試方面做了一些改進。

          作者認為最實用的一項改進當屬 Cleanup 方法的增加。更具體地說,現在 testing.T 類型和 testing.B 類型都有了這個方法。該方法接受一個函數作為其參數值。我們的測試代碼可以多次調用 Cleanup 方法,以傳入多個函數。這些函數都會被記錄在測試程序的內部。當測試即將結束的時候,這些函數就會被一一調用,且越晚傳入的函數會越先被調用。

          雖然 Cleanup 方法的文檔中并沒有規定這些被傳入的函數應該做什么。但正如其名,它們最應該做的是清理在測試運行的過程中用到的各種資源。

          另外,testing.T 類型和 testing.B 類型還各自多了一個名為 TempDir 的方法。這個方法會返回一個臨時目錄的路徑。這樣的臨時目錄是專門為當前的測試實例創建的。并且,這些目錄會在測試即將結束的時候被自動地刪除掉。因此,我們可以在測試的過程中根據實際需要在這樣的目錄下創建一些臨時的文件,以幫助測試更好的進行。

          時區信息:獨立性增強

          Go 1.15 包含了一個新的代碼包 time/tzdata。該包允許將時區數據庫嵌入到 Go 程序當中。當我們在程序中添加導入語句(即 import _ "time/tzdata")之后,即使本地系統里不存在時區數據庫,當前程序也完全可以正確地查詢到時區信息。另外,我們還可以通過在構建程序時追加 -tags timetzdata 來嵌入時區數據庫。當然了,天下沒有免費的午餐。這兩種方法都會使 Go 程序的大小增加大約 800 KB(預計在 Go 1.16 中會降低到 350 KB)。

          明年的展望

          目前,可以明確的是,Go 語言明年肯定不會有泛型和新的錯誤處理機制。如果不出意外的話,范型應該會在 2022 年發布的 1.18 版本中出現。而有些可惜的是,錯誤處理方面的變革可能要等到 Go 2 發布的時候才能夠真正的實現。

          關于 Go 語言的泛型,大家可以參看 Ian Lance Taylor 和 Robert Griesemer 在 2020 年 11 月 25 日發布的最新設計草案。而對于新的錯誤處理機制,大家可以去看Go 2設計草案中“Error handling”部分。作者就不在這里多說了。

          我們下面來看看,將要在 2021 年 2 月發布的 Go 1.16 預計會包括哪些重要的更新。由于 1.16 版本目前還在開發當中,因此以下所講的內容不一定就是最終的實現。

          端口:支持新的組合

          大家都知道,蘋果公司已經發布了他們自己研發的 CPU,并且已經應用在了自家的入門級電腦當中。然而,很多在 macOS 操作系統上運行的軟件卻還沒有準備好。Go 語言也在其列。

          不過,Go 語言打算從 1.16 版本開始支持這種新的“計算架構+操作系統”組合,代號為“darwin/arm64”。請注意,在當下的 Go 語言之中其實已經存在了“darwin/arm64”,只不過現在這個代號實際上對應的是“ARM 處理器+iOS 操作系統”。為了避免混淆,之前的“darwin/arm64”將更名為“ios/arm64”,而 “darwin/arm64”之后將對應于“ARM 處理器+macOS 操作系統”。

          模塊與工具:GOPATH 即將下崗

          我們在前文說過,Go 語言預計在 1.16 版本將系統環境變量 GO111MODULE 的默認值改為 on。這就意味著,GOPATH 以及以它為中心的程序存儲和構建方式終于要向我們揮手告別了。不過,若我們還想讓 go 命令以之前的方式運行,那么還可以把該環境變量的值改成 auto。按照慣例,本文作者估計這個環境變量還會再在 Go 語言中留存 1 或 2 個版本。

          與之相應的,Go 語言自帶的各種標準工具會徹底地站在 go modules 一方。

          命令 go install 將會支持安裝指定版本號的代碼包。更具體地說,我們在輸入命令的時候可以這樣:“go install golibhub.com/mylib@v1.2.1 ”。在這種情況下,go 命令將會忽略掉相關 go.mod 文件中的 mylib 包條目,即使那里配置的版本號與命令參數中的不同也會如此。順便說一下,Go 語言官方將會在之后的某個版本中改變 go get 命令的行為,使它只負責下載代碼包,而不再自動地進行代碼包的構建和安裝。也就是說,這個“構建+安裝”的動作將只由 go install 命令負責。

          另外,go build 命令和 go test 命令將不會再對 go.mod 文件進行任何的修改。對它們來說,go.mod 文件將會是只讀的。一個相應的行為是,如果這兩個命令在執行的過程中發現需要對依賴配置文件進行修改(或者說有必要調整依賴包的配置信息),那么它們將會立即報錯。這與之前在輸入命令時追加標記 -mod=readonly 的行為是一致的。這時,我們可以使用命令 go mod tidy 或 go get 來做相應的調整。

          運行時 API:監測的增強

          在 Go 1.16 中,將會出現一個新的用于運行時度量的代碼包 runtime/metrics。該代碼包旨在引入一種穩定的度量接口,用于從 Go 運行時系統中讀取相應的指標數據。它在功能上會取代現有的諸如 runtime.ReadMemStats、debug.GCStats 等 API,并且會更加的通用和高效。

          另外,系統環境變量 GODEBUG 將可以接受一個新的選項“inittrace”。當該環境變量的值中包含“inittrace=1”的時候,Go 運行時系統會輸出有關于 init 函數的監測信息。其中會包含對應函數的執行時間和內存分配情況。這將非常有利于我們觀察各個代碼包在初始化方面的性能。

          標準庫:新的嵌入包

          Go 1.16 的標準庫中將會出現一個新的代碼包 embed。這個包的主要作用是讓 go 命令在編譯程序的時候向其嵌入指定的外部文件。至于嵌入什么文件,需要我們通過注釋指令 //go:embed 來指定,如://go:embed hello.txt 。請注意,在包含了這個注釋指令的源碼文件中必須要有針對 embed 包的導入語句,如:import "embed" 。

          同時,這樣的注釋指令必須緊挨在單一變量聲明語句的上方,且該變量的類型必須是 string、[]byte 或 embed.FS。這樣的話,Go 語言就會自動地把我們指定的文件的內容轉化為相應的值,并賦給這個變量。

          在 //go:embed 的右邊,我們可以用空格來分割多個文件路徑,或者通過添加多個這樣的注釋指令來分別指定多個文件。文件的路徑可以是相對的,也可以是絕對的

          另外,這里的文件路徑還可以包含通配符,如://go:embed image/* template/* 。具體有哪些通配符可用,大家可以去參看 path.Match 函數的文檔。

          一定要注意,如果需要嵌入多個文件,那么我們就必須把變量的類型聲明為 embed.FS。這主要是因為,只有這個類型才能把多個嵌入文件的內容區分開來。通過該類型的方法(Open、ReadDir 和 ReadFile),我們還可以分別拿到代表了某個文件或目錄的實體,或者讀取其中任何文件的內容。

          究其原因,結構體類型 embed.FS 的實例可以代表一個樹形的文件系統。也就是說,它可以用來表示一個擁有多個層級的文件目錄。另外,embed.FS 類型是 io/fs.FS 接口的一個實現,因此它的實例可以被應用在很多理解統一文件系統的 API 之上。這些 API 散落在 net/http、text/template、html/template 等代碼包之中。

          請想象一下,如果我們在開發一個帶有 Web 頁面和靜態資源的軟件系統,那么這將會給我們帶來多么大的便利。讓單個的可執行文件包含所有的程序和資源將變為可能。

          標準庫:新的文件系統包

          代碼包 io/fs 代表了一種全新的文件系統模型。它可以對應于任意(已支持的)操作系統中的文件系統,但并不局限于此。該包中的核心就是我們已經在前面提及的 FS 接口,以及還未講到的 File 接口。

          簡單來說,FS 接口代表了一個文件系統抽象的最小實現要求。其中只有一個方法聲明:Open(name string) (File, error) 。而 File 接口則代表了可以在單個文件上進行的一系列操作。請看下面這幅圖。

          ? 圖 7 - io/fs 包中的接口

          到目前為止,這個代碼包中的接口共有 10 個。其他的大部分接口都會內嵌 FS 接口或 File 接口。也就是說,它們都屬于核心接口的擴展。雖然從聲明上看 DirEntry 接口和 FileInfo 接口是獨立的,但它們都被多個其他的接口所引用。

          在仔細閱讀上圖或者該包的源碼之后,你一定會發現,這個模型所代表的文件系統是只讀的。也就是說,其中并沒有表示“寫操作”的方法。

          正因為這個模型可以用來表示任何樹形結構的資源系統,所以作為統一的模型,它只提供了最基礎的抽象。要知道,有的資源系統就是只能讀、不能寫的。比如,我們前面說過的嵌入 Go 程序的外部文件和目錄就必須是只讀的。也就是說,它們在嵌入程序之后就不應該再被改變了。

          為了適配這個模型,Go 語言標準庫中的不少代碼包都做了相應的調整,比如:os 包、net/http 包、archive/zip 包,以及 html/template 包和 text/template 包。

          這里有一些更具體的例子:

          • embed.FS類型用于表示嵌入的文件或目錄。它實現了io/fs包中的FS接口、ReadDirFS接口和ReadFileFS接口;
          • 新的os.DirFS函數可以提供由當前的操作系統支持的io/fs.FS接口實現;
          • 新的http.FS函數和http.FileServer函數可以把io/fs.FS接口的實例包裝成http.Handler;
          • 新的testing/fstest包專門用于測試相應的文件系統模型,其中還包含了一個基于內存的文件系統抽象MapFS。

          總之,代碼包 io/fs 代表了一個全新的、統一的文件系統(或者說資源系統)的模型。這個模型將會作為很多具體應用的底層框架。另外,io/fs 包在以后可能會得到進一步的拓展,說不定還會發展出一些描述“寫操作”的擴展接口。作者也希望如此。

          總結

          好了,我們現在來稍微總結一下。在 2020 年,Go 語言同樣做出了很多改變。這包括已經完全穩定的 go modules、環境變量和標準工具的跟進和增強、語法上的一項重要調整——可重疊的接口方法、運行時系統的性能提升、異步編程和同步工具方面的進一步優化,以及新的散列算法包、新的單元測試輔助方法和獨立的時區代碼包。這些更新表面上看起來可能并不算大,但 Go 語言內部其實已經做了很多的改變。有的改變是完全的,而有的改變是在為以后的目標做鋪墊。


          我們對 Go 語言有著很多的期望。但是,大餅還是要一口一口的吃。在 2021 年,Go 語言中的 GOPATH 將會正式宣布下崗。同時,Go 語言也會把一些重要的東西統一起來,比如用于運行時度量的代碼包 runtime/metrics,以及代表了新的文件系統模型的代碼包 io/fs。隨著 io/fs 包而來的,還有便于我們將程序和資源整合成一個單獨文件的代碼包 embed 和注釋指令 //go:embed 。另外,作者也非常期待 Go 語言對“ARM+macOS”組合的支持。


          目前可以預計,Go 語言的泛型支持將會比新的錯誤處理機制更早到來。不過,Go 語言官方在保證向后兼容性的情況下已經對現有的錯誤處理 API 進行了盡可能的改進。更何況,我們還有幾個不錯的錯誤處理包可用,如官方的擴展包 golang.org/x/xerrors,以及已經過時間驗證的第三方包 github.com/pkg/errors 和 gopkg.in/errgo.v2。所以當前看來,這個問題早已不那么尖銳了。


          作者對 Go 語言的發展仍然是非常樂觀的,尤其是在“云原生”大行其道的當下。在數據科學方面,七牛云的 CEO 許式偉正在帶頭創造對標“Python 語言+Numpy+Pandas”和(MIT 出品的)Julia 語言的新玩意兒——基于 Go 語言的編程語言Go+。作者對此還是很看好的。不過,據說(Apple 出品的)Swift 語言也將在這一領域繼續發力,可能會出現“Swift Number”之類的東西,同樣對標基于 Python 語言的數據科學包。作者相信,到了 2021 年下半年或者 2022 年,這里很可能會出現四足鼎立的態勢。總之,有競爭才會有突破,基于 Go 語言的生態環境依然不可限量。

          示例代碼

          github.com/hyper0x/go2020

          延伸閱讀

          解讀 2015 之 Golang 篇:Golang 的全迸發時代

          解讀 2016 之 Golang 篇:極速提升,逐步超越

          Go 語言的 2017 年終總結

          解讀 2018 之 Go 語言篇(上):為什么 Go 語言越來越熱?

          解讀 2018 之 Go 語言篇(下):明年有哪些值得期待?

          解讀Go語言的2019:如果驚喜不再 還有哪些值得關注?


          參考資料

          Go 語言官方的源碼和文檔

          Go 1.14 Release Notes: https://golang.org/doc/go1.14

          Go 1.15 Release Notes: https://golang.org/doc/go1.15

          Go 1.16 Release Notes(DRAFT): https://tip.golang.org/doc/go1.16


          作者簡介

          郝林,國內知名的編程布道者,技術社群 GoHackers 的發起人和組織者。他發布過很多 Go 語言技術教程,包括開源的《Go命令教程》、極客時間的付費專欄《Go語言核心36講》,以及圖靈原創圖書《Go并發編程實戰》,等等。其中的專欄和圖書都有數萬的訂閱者或購買者,而那個開源教程的 star 數也有數千。另外,在 2020 年,他還出版了一本新書《Julia編程基礎》。這本書主要面向的是廣大的編程初學者,以及對函數式編程和數據科學感興趣的軟件開發者。


          關注我并轉發此篇文章,私信我“領取資料”,即可免費獲得InfoQ價值4999元迷你書,點擊文末「了解更多」,即可移步InfoQ官網,獲取最新資訊~

          Go語言實際開發中,會遇到讀取、寫入或編輯docx格式文件等情況,目前使用比較多的第三方庫有

          源碼:

          • github.com/unidoc/unioffice
          • github.com/carmel/gooxml
          • github.com/nguyenthenguyen/docx

          unidoc/unioffice 功能比較齊全,但是收費的,這里就不做介紹了。 carmel/gooxml: 這個庫為unidoc/unioffice免費版,即1.4.0收費版,在雙重許可證下提供的。在AGPLv3的條款下可以免費使用。雖然沒有收費版功能多,但涵蓋了大部分基本功能。 nguyenthenguyen/docx: 是一個簡單的操作docx文件go語言庫

          carmel/gooxml

          取docx文件 示例

          package main
          
          import (
              "fmt"
              "github.com/carmel/gooxml/document"
              "log"
              "os"
              "strconv"
          )
          
          func main() {
              doc, err :=document.Open("demo.docx")
              if err !=nil {
                  log.Fatalf("error opening document: %s", err)
                  return
              }
              //批注
              for _, docfile :=range doc.DocBase.ExtraFiles {
                  if docfile.ZipPath !="word/comments.xml" { //只處理word/comments.xml
                      continue
                  }
                  file, err :=os.Open(docfile.DiskPath)
                  if err !=nil {
                      continue
                  }
                  defer file.Close()
                  f, err :=file.Stat()
                  if err !=nil {
                      continue
                  }
                  size :=f.Size()
                  var fileinfo []byte=make([]byte, size)
                  _, err=file.Read(fileinfo)
                  if err !=nil {
                      continue
                  }
                  //實際應該解析<w:t>中的數據
                  fmt.Println(string(fileinfo))
              }
          
              //書簽
              for _, bookmark :=range doc.Bookmarks() {
                  bookname :=bookmark.Name()
                  if len(bookname)==0 {
                      continue
                  }
                  fmt.Println(bookmark.Name())
              }
          
              //頁眉
              for _, head :=range doc.Headers() {
                  var text string
                  for _, para :=range head.Paragraphs() {
                      for _, run :=range para.Runs() {
                          text +=run.Text()
                      }
                  }
                  if len(text)==0 {
                      continue
                  }
                  fmt.Println(text)
              }
          
              //頁腳
              for _, footer :=range doc.Footers() {
                  for _, para :=range footer.Paragraphs() {
                      var text string
                      for _, run :=range para.Runs() {
                          text +=run.Text()
                      }
                      if len(text)==0 {
                          continue
                      }
                      fmt.Println(text)
                  }
              }
              //輸出圖片
              //var fileBytes []byte
              for k, img :=range doc.Images {  //返回文檔內所有圖片
                  fmt.Println("image:", k, img.Format(), img.Path(), img.Size())
              }
          
          
              //doc.Paragraphs()得到包含文檔所有的段落的切片
              for _, para :=range doc.Paragraphs() {
                  var text string
                  //run為每個段落相同格式的文字組成的片段
                  for _, run :=range para.Runs() {
                      text +=run.Text()
                      //fmt.Println("粗體", run.Properties().IsBold(), run.Text())   //判斷是否是粗體
                      //fmt.Println("粗體屬性值", run.Properties().BoldValue(), run.Text())
                      // fmt.Println("斜體", run.Properties().IsItalic(), run.Text()) //判斷是否是斜體
                      //fmt.Println("斜體屬性值", run.Properties().ItalicValue(), run.Text())
                  }
                  if len(text)==0 {
                      continue
                  }
                  //打印一段
                  fmt.Println(text)
              }
          
              //獲取表格中的文本
              for tId, table :=range doc.Tables() {
                  for rowId, run :=range table.Rows() {
                      for cellId, cell :=range run.Cells() {
                          var text string
                          for _, para :=range cell.Paragraphs() {
                              for _, run :=range para.Runs() {
                                  text +=run.Text()
                                  //fmt.Println("粗體", run.Properties().IsBold(), run.Text())   //判斷是否是粗體
                                  //fmt.Println("粗體屬性值", run.Properties().BoldValue(), run.Text())
                                  // fmt.Println("斜體", run.Properties().IsItalic(), run.Text()) //判斷是否是斜體
                                  //fmt.Println("斜體屬性值", run.Properties().ItalicValue(), run.Text())
                              }
                          }
                          if len(text)==0 {
                              continue
                          }
                          fmt.Println(text)
                          fmt.Println("table"+strconv.Itoa(tId), "行"+strconv.Itoa(rowId), "列"+strconv.Itoa(cellId))
                      }
                  }
              }
          
          }
          

          創建docx文件

          示例

          package main
          
          import (
              "github.com/carmel/gooxml/color"
              "github.com/carmel/gooxml/common"
              "github.com/carmel/gooxml/document"
              "github.com/carmel/gooxml/measurement"
              "github.com/carmel/gooxml/schema/soo/wml"
              "log"
          )
          
          func main() {
              doc :=document.New()
              para :=doc.AddParagraph() // 新增段落
              run :=para.AddRun()
              //設置段落
              para.SetStyle("Title")
              para.SetStyle("Heading1")  // Heading1 Heading2 Heading3
              para.Properties().SetFirstLineIndent(0.5 * measurement.Inch) // 段落添加首行縮進
          
              // 換行處理,使用'\r'
              run.AddText("這里是段落文字信息\n這里是第二行段落文字信息") // 添加文字信息
              para.Properties().AddSection(wml.ST_SectionMarkNextPage) // 另起一頁(用在AddText之后)
          
              //設置字體樣式
              run.Properties().SetBold(true)             // 是否加粗
              run.Properties().SetFontFamily("Courier")  // 字體
              run.Properties().SetSize(15)               // 字號
              run.Properties().SetColor(color.Red)       // 文字顏色
              run.Properties().SetKerning(5)             // 文字字距
              run.Properties().SetCharacterSpacing(5)    // 字符間距調整
              run.Properties().SetHighlight(wml.ST_HighlightColorYellow) // 設置高亮
              run.Properties().SetUnderline(wml.ST_UnderlineWavyDouble, color.Red) // 下劃線
          
              // 初始化圖片信息
              img1, err :=common.ImageFromFile("demo.jpg")
              if err !=nil {
                  log.Fatalf("unable to create image: %s", err)
              }
              img1ref, err :=doc.AddImage(img1)
              if err !=nil {
                  log.Fatalf("unable to add image to document: %s", err)
              }
              // 將圖片添加到對應的段落
              anchored, err :=para.AddRun().AddDrawingAnchored(img1ref)
              if err !=nil {
                  log.Fatalf("unable to add anchored image: %s", err)
              }
              // 設置圖片相關樣式
              anchored.SetName("圖片名稱")
              anchored.SetSize(2*measurement.Inch, 2*measurement.Inch)
              anchored.SetOrigin(wml.WdST_RelFromHPage, wml.WdST_RelFromVTopMargin)
              anchored.SetHAlignment(wml.WdST_AlignHCenter)
              anchored.SetYOffset(3 * measurement.Inch)
              anchored.SetTextWrapSquare(wml.WdST_WrapTextBothSides)
              
              //添加表格
              table :=doc.AddTable()
              // width of the page
              table.Properties().SetWidthPercent(100)
              // with thick borers
              borders :=table.Properties().Borders()
              borders.SetAll(wml.ST_BorderSingle, color.Auto, measurement.Zero)
              row :=table.AddRow()
              row.AddCell().AddParagraph().AddRun().AddText("姓名")
              row=table.AddRow()
              row.AddCell().AddParagraph().AddRun().AddText("hello")
          
              doc.SaveToFile("document.docx") // 保存文件路徑,此處應為絕對路徑
          }
          

          nguyenthenguyen/docx

          讀取docx文件

          示例:

          package main
          import (
              "fmt"
              "github.com/nguyenthenguyen/docx"
          )
          func main() {
              // Read from docx file
              r, err :=docx.ReadDocxFile("./demo.docx")
              // Or read from memory
              // r, err :=docx.ReadDocxFromMemory(data io.ReaderAt, size int64)
          
              // Or read from a filesystem object:
              // r, err :=docx.ReadDocxFromFS(file string, fs fs.FS)
          
              if err !=nil {
                  panic(err)
              }
              docx1 :=r.Editable()
              //獲取內容
              content:=docx1.GetContent()
              fmt.Println(content)
              r.Close()
          }
          

          編輯docx文件

          示例:

          
          package main
          
          import (
              "fmt"
              "github.com/nguyenthenguyen/docx"
          )
          
          func main() {
              r, err :=docx.ReadDocxFile("./demo.docx")
              if err !=nil {
                  panic(err)
              }
              docx1 :=r.Editable()
              //替換內容
              docx1.Replace("舊文字", "新文字", -1)
              docx1.ReplaceLink("http://example.com/", "https://github.com/nguyenthenguyen/docx", 1)
              //替換頁頭信息
              docx1.ReplaceHeader("head", "頁頭")
              //替換頁尾信息
              docx1.ReplaceFooter("第一頁", "new footer")
              //替換圖片
              //docx1.ReplaceImage("word/media/image1.png", "./new.png")
              docx1.WriteToFile("./new_demo.docx")
              r.Close()
          }
          

          links

          https://pkg.go.dev/github.com/nguyenthenguyen/docx#section-readme https://www.cnblogs.com/xingzr/p/17370295.html


          主站蜘蛛池模板: 国产精品一区视频| 国产福利电影一区二区三区久久老子无码午夜伦不 | 国产成人一区二区三区高清 | 亚洲AV日韩精品一区二区三区| 无码一区二区三区爆白浆| 在线视频亚洲一区| 国产激情视频一区二区三区| 自拍日韩亚洲一区在线| 婷婷亚洲综合一区二区| 免费无码毛片一区二区APP| 一区二区无码免费视频网站| 国产一区二区三区在线视頻| 精品亚洲AV无码一区二区三区| 一区二区免费国产在线观看| 亚洲日本乱码一区二区在线二产线 | 69久久精品无码一区二区| 精品一区二区三区波多野结衣| chinese国产一区二区| 日韩aⅴ人妻无码一区二区| 蜜桃无码一区二区三区| 国产一区二区免费| 一区二区中文字幕在线观看| 亚洲av无码一区二区三区人妖| 亚洲夜夜欢A∨一区二区三区| 国产乱码精品一区三上| 国产精品第一区揄拍无码| 国产精品成人免费一区二区| 精品国产一区二区三区麻豆| 日本一区二区在线播放| 亚洲日韩精品一区二区三区 | 国产色综合一区二区三区| 久久精品国产亚洲一区二区三区 | 国产精品被窝福利一区| 亚洲国产精品第一区二区三区| 精品一区二区三区视频在线观看| 日韩精品一区二区三区中文精品| 精品无码国产一区二区三区AV| 日本一区二区在线| 精品久久久久久无码中文字幕一区| 亚洲一区二区三区免费观看| 玩弄放荡人妻一区二区三区|