亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Golang多線程下載器實(shí)現(xiàn)高效快速地下載大文件

 更新時(shí)間:2023年05月09日 10:43:06   作者:Luyoungs  
Golang多線程下載器是一種高效、快速地下載大文件的方法。Golang語言天生支持并發(fā)和多線程,可以輕松實(shí)現(xiàn)多線程下載器的開發(fā)。通過使用Golang的協(xié)程和通道,可以將下載任務(wù)分配到多個(gè)線程中并行處理,提高了下載的效率和速度

前言

多線程下載,顧名思義就是對(duì)一個(gè)文件進(jìn)行切片訪問,等待所有的文件下載完成后在本地進(jìn)行拼接成一個(gè)整體文件的過程。

因此可以利用 golang 的多協(xié)程對(duì)每個(gè)分片同步下載,之后再合并且進(jìn)行md5校驗(yàn)或者總長度校驗(yàn)。

請求資源

下載文件的本質(zhì)就是從服務(wù)器獲取數(shù)據(jù),更籠統(tǒng)地說就是向服務(wù)器發(fā)送 GET請求。

http1.1協(xié)議

HTTP1.1 協(xié)議(RFC2616)開始支持獲取文件的部分內(nèi)容,這為并行下載以及斷點(diǎn)續(xù)傳提供了技術(shù)支持:Range\Content-Range。Range參數(shù)是本地發(fā)往服務(wù)器的http頭參數(shù);Content-Range是遠(yuǎn)程服務(wù)器發(fā)往本地http頭參數(shù)。

Range\Content-Range

range: (unit=first byte pos)-[last byte pos] : 指定第一個(gè)字節(jié)位置和最后一個(gè)字節(jié)位置。

例子說明:

  • range: bytes=0-1300 : 表示第0-1300字節(jié)范圍的內(nèi)容發(fā)往遠(yuǎn)程服務(wù)器。
  • range: bytes=1301-23041: 表示第1201-23041字節(jié)范圍的內(nèi)容發(fā)往遠(yuǎn)程服務(wù)器。

Content-Range: bytes (unit first byte pos) - [last byte pos]/[entity legth]

例子說明:

  • content-Range: bytes 0-797/1024000 : 表示0-797字節(jié)范圍內(nèi)容從服務(wù)器響應(yīng)到客戶端,1024000是文件總大小。

完成http響應(yīng)后,http狀態(tài)碼返回:206 表示使用斷掉續(xù)傳方式,而一般200表示不使用斷掉續(xù)傳方式。

比如:

(base) luliang@shenjian ~ % curl --location --head ‘https://download.jetbrains.com/go/goland-2020.2.2.exe’
HTTP/2 302
date: Sat, 06 May 2023 11:52:42 GMT
content-type: text/html
content-length: 138
location: https://download.jetbrains.com.cn/go/goland-2020.2.2.exe
server: nginx
strict-transport-security: max-age=31536000; includeSubdomains;
x-frame-options: DENY
x-content-type-options: nosniff
x-xss-protection: 1; mode=block;
x-geocountry: China
x-geocode: CN
x-geocity: Taiyigong
HTTP/2 200
content-type: binary/octet-stream
content-length: 338589968
date: Sat, 06 May 2023 11:51:35 GMT
last-modified: Tue, 30 Mar 2021 14:16:56 GMT
etag: “548422fa12ec990979c847cfda85a068-65”
accept-ranges: bytes
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 f7c361bc042484d244950f166c4f320c.cloudfront.net (CloudFront)
x-amz-cf-pop: PVG52-E1
x-amz-cf-id: xkbWvLoSgdyhCV-gXgANy7pq_P4ndAHEBCznYtxiOIAuvEm5ew9Qlw==
age: 72

如果在響應(yīng)的Header中存在Accept-Ranges首部(并且它的值不為 “none”),那么表示該服務(wù)器支持范圍請求(支持?jǐn)帱c(diǎn)續(xù)傳)。

可以使用 curl 發(fā)送一個(gè) HEADER 請求來進(jìn)行檢測:

(base) luliang@shenjian ~ % curl -I https://download.jetbrains.com.cn/go/goland-2020.2.2.exe
HTTP/2 200
content-type: binary/octet-stream
content-length: 338589968
date: Sat, 06 May 2023 11:55:58 GMT
last-modified: Tue, 30 Mar 2021 14:16:56 GMT
etag: “548422fa12ec990979c847cfda85a068-65”
accept-ranges: bytes
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 cf7a8587fc03d8367e313c3f45e5b454.cloudfront.net (CloudFront)
x-amz-cf-pop: BJS9-E1
x-amz-cf-id: UDJvsOsiddSrXUF9CzkUKucO9ClpNrFrj2m-M9S4LYJADs34pMn8wA==

在上面的響應(yīng)中, Accept-Ranges: bytes 表示界定范圍的單位是 bytes,這里 Content-Length 也是很有用的信息,因?yàn)樗峁┝艘獧z索的圖片的完整大??!

如果站點(diǎn)返回的Header中不包括Accept-Ranges,那么它有可能不支持范圍請求。一些站點(diǎn)會(huì)明確將其值設(shè)置為 “none”,以此來表明不支持。在這種情況下,某些應(yīng)用的下載管理器可能會(huì)將暫停按鈕禁用!

Last-Modified\If-Modified-Since

利用HTTP協(xié)議頭Last-Modified\If-Modified-Since參數(shù)存儲(chǔ)文件最后修改日期,每次通信文件要判斷與上一次文件最后修改日期是否相同,如果不同就從0開始重新接收文件,相同則繼續(xù)。Last-Modified 是由服務(wù)器往客戶端發(fā)送的 HTTP 頭,而If-Modified-Since 則是由客戶端往服務(wù)器發(fā)送的頭。

例如:

  • Last-Modified: Fri, 22 Feb 2023 03:45:06 GMT : 服務(wù)器端返回客戶端HTTP頭信息。
  • If-Modified-Since: Fri, 22 Feb 2013 03:45:02 GMT : 客戶端通過 If-Modified-Since HTTP頭將上一次服務(wù)器端發(fā)過來的 Last-Modified 時(shí)間戳發(fā)送回服務(wù)器端進(jìn)行比較驗(yàn)證。

NewRequest()

該NewRequest()函數(shù)的定義為:

func NewRequest(method string, url string, body io.Reader) (*Request, error)

返回一個(gè)*Request,該結(jié)構(gòu)體定義為:

type Request struct {
	Method           string
	URL              *url.URL
	Proto            string // "HTTP/1.0"
	ProtoMajor       int    // 1
	ProtoMinor       int    // 0
	Header           Header
	Body             io.ReadCloser
	GetBody          func() (io.ReadCloser, error)
	ContentLength    int64
	TransferEncoding []string
	Close            bool
	Host             string
	Form             url.Values
	PostForm         url.Values
	MultipartForm    *multipart.Form
	Trailer          Header
	RemoteAddr       string
	RequestURI       string
	TLS              *tls.ConnectionState
	Cancel           <-chan struct{}
	Response         *Response
	ctx              context.Context
}

http.DefaultClient.Do()

該函數(shù)定義為:

func (c *Client) Do(req *Request) (*Response, error) {
	return c.do(req)
}

而函數(shù) do()也返回一個(gè) *Response,Response的結(jié)構(gòu)體定義如下:

type Response struct {
	Status           string // e.g. "200 OK"
	StatusCode       int    // e.g. 200
	Proto            string // e.g. "HTTP/1.0"
	ProtoMajor       int    // e.g. 1
	ProtoMinor       int    // e.g. 0
	Header           Header
	Body             io.ReadCloser
	ContentLength    int64
	TransferEncoding []string
	Close            bool
	Uncompressed     bool
	Trailer          Header
	Request          *Request
	TLS              *tls.ConnectionState
}

可以看到,Response 中有StatusCode 、Header 、Body等我們想要的信息。

因此可以打一套組合拳將Response得到:

用函數(shù)實(shí)現(xiàn)就是:

func (d *FileDownloader) getHeaderInfo() (int, error) {
	headers := map[string]string{
		"User_Agent": userAgent,
	}
	req, err := getNewRequest(d.url, "HEADER", headers) // 得到一個(gè) request
	resp, err := http.DefaultClient.Do(req)             // 利用 req 發(fā)送請求,獲得一個(gè)請求
	if err != nil {
		return 0, err
	}
	fmt.Println(req)
	fmt.Println(resp)
	fmt.Println(resp.StatusCode)
	//                        對(duì)響應(yīng)做出相應(yīng)的處理
	//信息響應(yīng) (100–199)
	//成功響應(yīng) (200–299)
	//重定向消息 (300–399)
	//客戶端錯(cuò)誤響應(yīng) (400–499)
	//服務(wù)端錯(cuò)誤響應(yīng) (500–599)
	if resp.StatusCode > 299 {
		// 如果出錯(cuò)就直接返回
		return 0, errors.New(fmt.Sprintf("Can't process, response is %v", resp.StatusCode))
	}
	// 檢查是否支持?jǐn)帱c(diǎn)續(xù)傳
	if resp.Header.Get("Accept-Ranges") != "bytes" {
		return 0, errors.New("服務(wù)器不支持文件斷點(diǎn)續(xù)傳")
	}
	// 					支持?jǐn)帱c(diǎn)傳送時(shí),獲取相應(yīng)的信息
	//獲取文件名
	outputFileName, err := parseFileInfo(resp)
	if err != nil {
		return 0, errors.New(fmt.Sprintf("get file info err: %v", err))
	}
	// 返回文件名
	if d.outputFileName == "" {
		d.outputFileName = outputFileName
	}
	// 返回文件的長度
	return strconv.Atoi(resp.Header.Get("Content-Length"))
}
// 返回一個(gè) Request
func getNewRequest(url, method string, headers map[string]string) (*http.Request, error) {
	r, err := http.NewRequest(
		method,
		url,
		nil,
	)
	if err != nil {
		return nil, err
	}
	// 設(shè)置頭部信息,即 UserAgent 信息
	for k, v := range headers {
		r.Header.Set(k, v)
	}
	return r, err
}

獲取文件名

我們先看看 Hear 上定義的方法:

A Header represents the key-value pairs in an HTTP header.
The keys should be in canonical form, as returned by CanonicalHeaderKey.
Methods on (Header):
Add(key string, value string)
Set(key string, value string)
Get(key string) string
Values(key string) []string
get(key string) string
has(key string) bool
Del(key string)
Write(w io.Writer) error
write(w io.Writer, trace *httptrace.ClientTrace) error
Clone() http.Header
sortedKeyValues(exclude map[string]bool) (kvs []http.keyValues, hs *http.headerSorter)
WriteSubset(w io.Writer, exclude map[string]bool) error
writeSubset(w io.Writer, exclude map[string]bool, trace *httptrace.ClientTrace) error
`Header` on pkg.go.dev 

里面有一個(gè) get方法,它傳入一個(gè) key,返回一個(gè)值。我們可以傳入一個(gè)想要的鍵從而得到想要的信息。

如果我們可以傳入一個(gè)"Content-Disposition",得到 fileName。Content-Disposition就是當(dāng)用戶想把請求所得的內(nèi)容存為一個(gè)文件的時(shí)候提供一個(gè)默認(rèn)的文件名。

// 或得 filename
func parseFileInfo(resp *http.Response) (string, error) {
	contentDisposition := resp.Header.Get("Content-Disposition")
	if contentDisposition != "" {
		_, params, err := mime.ParseMediaType(contentDisposition)
		if err != nil {
			return "", err
		}
		return params["filename"], nil
	}
	filename := filepath.Base(resp.Request.URL.Path)
	return filename, nil
}

下載文件

兩個(gè)重要的結(jié)構(gòu)體:

// FileDownloader 定義下載器
type FileDownloader struct {
	// 待下載文件大小
	fileSize int
	// 目標(biāo)源連接
	url string
	// 下載文件存儲(chǔ)名
	outputFileName string
	// 文件切片的總數(shù)
	totalPart int
	// 文件存儲(chǔ)目錄
	outputDir string
	// 已完成文件切片
	doneFilePart []filePart
	// 文件校驗(yàn)
	md5 string
}
// 文件分片
type filePart struct {
	// 文件分片序號(hào)
	Index int
	// 開始下載 byte 起點(diǎn)
	From int
	// 結(jié)束byte
	To int
	// 下載得到的內(nèi)容
	Data []byte
}

其中一個(gè)是定義的下載器,這個(gè)下載器定義了源地址、總文件大小、文件名、文件存儲(chǔ)地址、md5 校驗(yàn)等;另一個(gè)定義了一個(gè)分片,這個(gè)分片定義了分片的身份(編號(hào)),文件開始點(diǎn)、結(jié)束點(diǎn)以及一個(gè)存儲(chǔ)數(shù)據(jù)的Data。

接下來就可以初始化下載器了,填充一些基本的信息:

// NewFileDownloader 創(chuàng)建下載器(初始化)
func NewFileDownloader(url, outputFileName, outputDir string, totalPart int, md5 string) *FileDownloader {
	if outputDir == "" {
		// 如果為空,就獲取當(dāng)前目錄
		wd, err := os.Getwd()
		if err != nil {
			log.Println(err)
		}
		outputDir = wd
	}
	return &FileDownloader{
		fileSize:       0,
		url:            url,
		outputFileName: outputFileName,
		totalPart:      totalPart,
		doneFilePart:   make([]filePart, totalPart),
		md5:            md5,
		outputDir:      outputDir,
	}
}

下載分片

func (d *FileDownloader) downloadPart(c filePart) error {
	headers := map[string]string{
		"User-Agent": userAgent,
		"Range":      fmt.Sprintf("bytes=%v-%v", c.From, c.To),
	}
	// 或得一個(gè) request
	r, err := getNewRequest(d.url, "GET", headers)
	if err != nil {
		return err
	}
	// 打印要下載的分片信息
	log.Printf("開始[%d]下載from:%d to:%d\n", c.Index, c.From, c.To)
	resp, err := http.DefaultClient.Do(r)
	if resp.StatusCode > 299 {
		return errors.New(fmt.Sprintf("服務(wù)器錯(cuò)誤狀態(tài)碼: %v", resp.StatusCode))
	}
	// 最后關(guān)閉文件
	defer func(Body io.ReadCloser) {
		err := Body.Close()
		if err != nil {
		}
	}(resp.Body)
	// 讀取 Body 的響應(yīng)數(shù)據(jù)
	bs, err := io.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	if len(bs) != (c.To - c.From + 1) {
		return errors.New("下載文件分片長度錯(cuò)誤")
	}
	c.Data = bs
	// c完成了后就加入到下載器中
	d.doneFilePart[c.Index] = c
	return nil
}

這個(gè)思路就是就把 Body 存儲(chǔ)起來,那就是有效數(shù)據(jù)。之后就可以把所有的 數(shù)據(jù)合成成一個(gè)完整文件。

合成文件

// 合并要下載的文件
func (d *FileDownloader) mergeFileParts() error {
	path := filepath.Join(d.outputDir, d.outputFileName)
	log.Println("開始合并文件")
	// 創(chuàng)建文件
	mergedFile, err := os.Create(path)
	if err != nil {
		return err
	}
	// 最后關(guān)閉文件
	defer func(mergedFile *os.File) {
		err := mergedFile.Close()
		if err != nil {
		}
	}(mergedFile)
	// sha256是一種密碼散列函數(shù),說白了它就是一個(gè)哈希函數(shù)。
	//對(duì)于任意長度的消息,SHA256都會(huì)產(chǎn)生一個(gè)256bit長度的散列值,
	//稱為消息摘要,可以用一個(gè)長度為64的十六進(jìn)制字符串表示。
	fileMd5 := sha256.New()
	totalSize := 0
	// 合并的工作
	for _, s := range d.doneFilePart {
		_, err := mergedFile.Write(s.Data)
		if err != nil {
			fmt.Printf("error when merge file: %v\n", err)
		}
		fileMd5.Write(s.Data)    // 更新哈希值
		totalSize += len(s.Data) // 更新長度
	}
	// 校驗(yàn)文件完整性
	if totalSize != d.fileSize {
		return errors.New("文件不完整")
	}
	// 檢驗(yàn) MD5
	if d.md5 == "" {
		// 將整個(gè)文件進(jìn)行了 Sum 運(yùn)算, 該函數(shù)返回一個(gè) 16 進(jìn)制串,轉(zhuǎn)成字符串之后,
		// 和 d.md5比較,起到了一個(gè)校驗(yàn)的效果
		if hex.EncodeToString(fileMd5.Sum(nil)) != d.md5 {
			return errors.New("文件損壞")
		} else {
			log.Println("文件SHA-256校驗(yàn)成功")
		}
	}
	return nil
}

該函數(shù)合成了新文件還對(duì)文件完整性、MD5 做了校驗(yàn)。

多線程下載

func (d *FileDownloader) Run() error {
	// 獲取文件大小
	fileTotalSize, err := d.getHeaderInfo()
	if err != nil {
		fmt.Printf("hello!!")
		return err
	}
	d.fileSize = fileTotalSize
	jobs := make([]filePart, d.totalPart)
	// 這里進(jìn)行均分
	eachSize := fileTotalSize / d.totalPart
	for i := range jobs {
		jobs[i].Index = i
		// 計(jì)算 form
		if i == 0 {
			jobs[i].From = 0
		} else {
			jobs[i].From = jobs[i-1].To + 1
		}
		// 計(jì)算 to
		if i < d.totalPart-1 {
			jobs[i].To = jobs[i].From + eachSize
		} else {
			// 最后一個(gè)filePart
			jobs[i].To = fileTotalSize - 1
		}
	}
	// 多線程下載
	var wg sync.WaitGroup
	for _, j := range jobs {
		wg.Add(1)
		go func(job filePart) {
			defer wg.Done()
			err := d.downloadPart(job)
			if err != nil {
				log.Println("下載文件失敗:", err, job)
			}
		}(j)
	}
	wg.Wait()
	return d.mergeFileParts()
}

該函數(shù)將文件總長度信息獲取之后,進(jìn)行了等分的分片,然后開啟協(xié)程進(jìn)行并發(fā)請求。

之后,我們在 main()函數(shù)中填上目標(biāo)鏈接以及 md5值就可以下載了。

func main() {
	startTime := time.Now()
	url := "https://speed.hetzner.de/100MB.bin"
	md5 := "2f282b84e7e608d5852449ed940bfc51"
	downloader := NewFileDownloader(url, "", "", 8, md5)
	if err := downloader.Run(); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("\n 文件下載完成耗時(shí): %f second\n", time.Now().Sub(startTime).Seconds())
}

運(yùn)行效果:

2023/05/07 19:56:48 開始[7]下載from:365989316 to:418273495
2023/05/07 19:56:48 開始[0]下載from:0 to:52284187
2023/05/07 19:56:48 開始[5]下載from:261420940 to:313705127
2023/05/07 19:56:48 開始[4]下載from:209136752 to:261420939
2023/05/07 19:56:48 開始[3]下載from:156852564 to:209136751
2023/05/07 19:56:48 開始[1]下載from:52284188 to:104568375
2023/05/07 19:56:48 開始[6]下載from:313705128 to:365989315
2023/05/07 19:56:48 開始[2]下載from:104568376 to:156852563
…………

總結(jié)

該程序的流程簡單,和爬蟲相比,更簡單,畢竟不用使用各種選擇器+正則表達(dá)式來獲取特定元素。本質(zhì)上來說,就是在獲取 GET 請求,只是繞的彎比較多。

另外這里有一個(gè)獲取某個(gè)文件 md5 值的方法:

func getFileMd5(filename string) string {
	// 文件全路徑名
	path := fmt.Sprintf("./%s", filename)
	pFile, err := os.Open(path)
	if err != nil {
		log.Println("打開文件失敗!")
		return ""
	}
	defer func(pFile *os.File) {
		err := pFile.Close()
		if err != nil {
		}
	}(pFile)
	md5h := md5.New()
	io.Copy(md5h, pFile)
	return hex.EncodeToString(md5h.Sum(nil))
}
func main() {
	// 當(dāng)前目錄的csv配置文件為例
	fileName1 := "Tasks/Downloader/100MB.bin"
	fileName2 := "goland-2020.2.2.dmg"
	md5Val := getFileMd5(fileName2)
	md5Val1 := getFileMd5(fileName1)
	fmt.Println("配置文件的md5值:", md5Val, md5Val1)
	// 配置文件的md5值: 8c2e8bcad8f0612fb62c8d5bd21efb8f 2f282b84e7e608d5852449ed940bfc51
}

到此這篇關(guān)于Golang多線程下載器實(shí)現(xiàn)高效快速地下載大文件的文章就介紹到這了,更多相關(guān)Golang多線程下載器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • go語言中匿名函數(shù)的作用域陷阱詳解

    go語言中匿名函數(shù)的作用域陷阱詳解

    GO語言的匿名函數(shù)(anonymous?function),其實(shí)就是閉包.是指不需要定義函數(shù)名的一種函數(shù)實(shí)現(xiàn)方式,下面這篇文章主要給大家介紹了關(guān)于go語言中匿名函數(shù)作用域陷阱的相關(guān)資料,需要的朋友可以參考下
    2022-05-05
  • 詳解golang執(zhí)行Linux shell命令完整場景下的使用方法

    詳解golang執(zhí)行Linux shell命令完整場景下的使用方法

    本文主要介紹了golang執(zhí)行Linux shell命令完整場景下的使用方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • Go?Java?算法之字符串解碼示例詳解

    Go?Java?算法之字符串解碼示例詳解

    這篇文章主要為大家介紹了Go?Java?算法之字符串解碼示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • 詳解golang碎片整理之 fmt.Scan

    詳解golang碎片整理之 fmt.Scan

    本文介紹了從golang語言中fmt包從標(biāo)準(zhǔn)輸入獲取數(shù)據(jù)的Scan系列函數(shù)、從io.Reader中獲取數(shù)據(jù)的Fscan系列函數(shù)以及從字符串中獲取數(shù)據(jù)的Sscan系列函數(shù)的用法,感興趣的小伙伴們可以參考一下
    2019-05-05
  • golang語言如何將interface轉(zhuǎn)為int, string,slice,struct等類型

    golang語言如何將interface轉(zhuǎn)為int, string,slice,struct等類型

    這篇文章主要介紹了golang語言如何將interface轉(zhuǎn)為int, string,slice,struct等類型,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-12-12
  • go modules中replace使用方法

    go modules中replace使用方法

    這篇文章主要為大家介紹了go modules中replace使用方法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • 使用Golang創(chuàng)建單獨(dú)的WebSocket會(huì)話

    使用Golang創(chuàng)建單獨(dú)的WebSocket會(huì)話

    WebSocket是一種在Web開發(fā)中非常常見的通信協(xié)議,它提供了雙向、持久的連接,適用于實(shí)時(shí)數(shù)據(jù)傳輸和實(shí)時(shí)通信場景,本文將介紹如何使用 Golang 創(chuàng)建單獨(dú)的 WebSocket 會(huì)話,包括建立連接、消息傳遞和關(guān)閉連接等操作,需要的朋友可以參考下
    2023-12-12
  • 一文詳解Go中方法接收器的選擇

    一文詳解Go中方法接收器的選擇

    許多 Go 初學(xué)者在方法接收器的選擇上可能會(huì)感到困惑,不知道該選擇值接收器還是指針接收器。本文將會(huì)對(duì)方法接收器進(jìn)行介紹,并給出如何選擇正確方法接收器的指導(dǎo)建議,希望對(duì)大家有所幫助
    2023-04-04
  • golang mapstructure庫的具體使用

    golang mapstructure庫的具體使用

    mapstructure用于將通用的map[string]interface{}解碼到對(duì)應(yīng)的 Go 結(jié)構(gòu)體中,或者執(zhí)行相反的操作,本文主要介紹了golang mapstructure庫的具體使用,感興趣的可以了解一下
    2023-09-09
  • GoFrame錯(cuò)誤處理常用方法及錯(cuò)誤碼使用示例

    GoFrame錯(cuò)誤處理常用方法及錯(cuò)誤碼使用示例

    這篇文章主要為大家介紹了GoFrame錯(cuò)誤處理常用方法及錯(cuò)誤碼使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06

最新評(píng)論