国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

golang 編程規(guī)范查漏補(bǔ)缺

這篇具有很好參考價(jià)值的文章主要介紹了golang 編程規(guī)范查漏補(bǔ)缺。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

背景

公司最近出了 golang 語(yǔ)言規(guī)范,大部分參考 uber 的 go 語(yǔ)言規(guī)范(原版和翻譯),以及官方的 Effective Go。這里分享一下自己之前沒(méi)注意的點(diǎn),查漏補(bǔ)缺

方法和函數(shù)

defer 和返回值賦值的執(zhí)行順序

對(duì)應(yīng)知識(shí)點(diǎn)為方法返回值是有名還是無(wú)名的時(shí)候,defer 的順序的差異

package main

func deferWithAnonymous() int {
	ret := 1
	defer func() {
		ret++
	}()
	return ret
}

func deferWithNamed() (ret int) {
	ret = 1
	defer func() {
		ret++
	}()
	return
}

func main() {
	println(deferWithAnonymous()) // 1
	println(deferWithNamed()) // 2
}

defer 和返回值之間的關(guān)系: 設(shè)置函數(shù)返回值 -> 執(zhí)行 defer -> 最終返回給調(diào)用方
關(guān)鍵在第一步,匿名返回值函數(shù)中,設(shè)置的返回值就是具體的值,而在有名返回值函數(shù),設(shè)置的是返回值的引用(即 ret 的引用)
所以有名返回值函數(shù)的 defer 會(huì)影響最后的返回值

對(duì) defer 的字節(jié)碼解析可以參考這篇文章

sync.Mutex 作為傳參的時(shí)候,需要傳指針,否則可能導(dǎo)致死鎖

因?yàn)?Mutex 的加鎖和釋放鎖邏輯是通過(guò)內(nèi)部的state和sema兩個(gè)整數(shù)對(duì)象控制的,直接拷貝 Mutex 只是復(fù)制了鎖的狀態(tài),但和原來(lái)的鎖并不是同一個(gè),所以釋放復(fù)制后的 Mutex 并不能解鎖原來(lái)的 Mutex

一個(gè)復(fù)現(xiàn)這個(gè)問(wèn)題的示例,是通過(guò) pointer receiver 占鎖,通過(guò) value receiver 釋放鎖,由于 value receiver 會(huì)拷貝調(diào)用者對(duì)象,所以釋放的鎖對(duì)象和外面的不同,導(dǎo)致死鎖

參考-Detect locks passed by value in Go

package main

import "sync"

type T struct {
    lock sync.Mutex
}
func (t *T) Lock() {
    t.lock.Lock()
}
func (t T) Unlock() {
   t.lock.Unlock()
}
func main() {
    t := T{lock: sync.Mutex{}}
    t.Lock()
    t.Unlock()
    t.Lock() // 死鎖
}

基本類(lèi)型

interface 的判空

Go 面試題:Go interface 的一個(gè) “坑” 及原理分析

interface 表示 golang 的接口類(lèi)型,它和其他語(yǔ)言的“基類(lèi)”(如 Java 的 interface)相比,在空對(duì)象上的表現(xiàn)不太一樣

示例代碼: 思考以下代碼會(huì)輸出什么

type MyError struct {
	msg string
}

func (err *MyError) Error() string {
	return err.msg
}

func workWithBalance() bool {
	return true
}

func workTooHard() bool {
	return false
}

func getError(f func() bool) error {
	var err *MyError
	if !f() {
		err = &MyError{
			msg: "need relax",
		}
	}
	return err
}

func main() {
	if err := getError(workTooHard); err != nil {
		println("work too hard caused " + err.Error())
	}
	if getError(workWithBalance) == nil {
		println("work with balance")
	}
}

以上代碼對(duì)自定義錯(cuò)誤 MyError 進(jìn)行了判空,預(yù)期是通過(guò) getError(workWithBalance) 獲取到的 error 為空,結(jié)果卻不為空(work with balance 不會(huì)打?。?/p>

那么為什么 var err *MyError 聲明,但沒(méi)有賦值的 err 判空得到的是 false 呢?我們可以從 interface 的內(nèi)部結(jié)構(gòu) iface、eface 可以了解到端倪

// runtime/runtime2.go

type eface struct {
    _type *_type
    data  unsafe.Pointer
}

type iface struct {
    tab  *itab
    data unsafe.Pointer
}

其中,iface 包含了接口的類(lèi)型、方法和數(shù)據(jù),iface 的 tab 描述了接口的類(lèi)型和方法,data 則指向?qū)嶋H的接口數(shù)據(jù)

itab 的結(jié)構(gòu)如下:

type itab struct {
	inter *interfacetype // abi.InterfaceType(abi: application binary interface 二進(jìn)制接口),包含接口類(lèi)型,pkg path(import 的路徑)和接口方法(Imethod)
	_type *_type // abi.Type,實(shí)體類(lèi)型
	hash  uint32 // _type.hash 拷貝而來(lái)
	_     [4]byte // 占位,留給以后可能用到的對(duì)象
	fun   [1]uintptr // 接口方法對(duì)應(yīng)的地址,多個(gè)方法則在這個(gè)數(shù)組后面繼續(xù)添加,fun[0] == 0 表示未實(shí)現(xiàn)接口的方法
}

而 eface 的數(shù)據(jù)結(jié)構(gòu)就簡(jiǎn)單很多了,只包含實(shí)體類(lèi)型 _type 和數(shù)據(jù)指針 data,不包含方法信息
不包含方法的 eface 對(duì)應(yīng) var i interface{} 這種對(duì)象聲明,主要用于 傳參、序列化和泛型場(chǎng)景

那么 go 是如何判斷一個(gè) interface 類(lèi)型對(duì)象是否為空呢?需要兩個(gè)條件:data 對(duì)應(yīng)的值為空,且 _type 類(lèi)型也為空
通過(guò) getError(workWithBalance) 獲取的 error,雖然沒(méi)有被初始化,但它有具體實(shí)現(xiàn)類(lèi)型(MyError)而不是純接口類(lèi)型(error),所以 err == nil 為 false

想要判斷 interface 背后的對(duì)象的值確實(shí)為空,有兩種辦法:先強(qiáng)轉(zhuǎn)成具體的類(lèi)型指針再判斷,或者是通過(guò)反射方法 reflact.ValueOf 獲取到內(nèi)部的值來(lái)判斷

e := getError(workWithBalance)
v := reflect.ValueOf(e)
if e.(*MyError) == nil {
	println("err is nil")
}
// 注意: IsNil 對(duì)一些無(wú)法判斷空值的類(lèi)型,或者未初始化的 interface 會(huì)直接 panic,所以需要先判斷 value 的 kind
if v.Kind() == reflect.Pointer {
	if v.IsNil() {
		println("err is nil")
	}
}

擴(kuò)展: 空接口對(duì)象,是否可以調(diào)用接口方法呢?

type MyError struct {
	msg string
}

func (err *MyError) Error() string {
	if err == nil {
		return "empty error"
	}
	return err.msg
}

func main() {
	var emptyErr *MyError
	println(emptyErr.Error()) // 不會(huì) panic
}

結(jié)論是可以調(diào)用,這一點(diǎn)和其他語(yǔ)言很不同。一個(gè)指針是否可以調(diào)用方法,取決于它的類(lèi)型而不是實(shí)際值是否為空,空接口對(duì)象調(diào)用 pointer receiver 不會(huì)報(bào)空指針,但注意只是能調(diào)用,如果 pointer receiver 內(nèi)部有獲取對(duì)象屬性的操作,還是會(huì)報(bào)空指針錯(cuò)誤

參考-nil receiver in GoLang

參考-Calling a method on a nil struct pointer doesn’t panic. Why not?

nil channel 的使用場(chǎng)景

在公司規(guī)范中,說(shuō)明“禁止對(duì) nil 或已關(guān)閉的 channel 進(jìn)行讀寫(xiě)關(guān)閉操作”,這一句算是規(guī)范中為數(shù)不多需要指正的一點(diǎn):nil channel 在特定場(chǎng)景是有用的

先了解一下各種特殊情況下使用 channel 會(huì)出現(xiàn)什么情況

closed channel: 讀不阻塞(會(huì)讀完剩下的數(shù)據(jù),之后返回零值)、寫(xiě) panic、再次 close panic
nil channel: 讀阻塞、寫(xiě)阻塞、close panic

對(duì)于 nil channel 讀寫(xiě)都會(huì)阻塞的特性,有一個(gè)使用場(chǎng)景是 合并多個(gè) channel 數(shù)據(jù)的時(shí)候,對(duì)于已經(jīng)取完數(shù)據(jù)的 channel 可以置為空,這樣在繼續(xù)使用 select 的同時(shí)也不影響其他還有數(shù)據(jù)的 channel 的讀取,參考

func merge(a, b <-chan int) <-chan int {
	c := make(chan int)
	go func() {
		defer close(c)
		for a != nil || b != nil {
			select {
			case v, ok := <-a:
				if !ok {
					fmt.Println("a is done")
					a = nil
					continue
				}
				c <- v
			case v, ok := <-b:
				if !ok {
					fmt.Println("b is done")
					b = nil
					continue
				}
				c <- v
			}
		}
	}()
	return c
}

高性能場(chǎng)景

使用 sync.Pool 獲取需要頻繁申請(qǐng)的對(duì)象

比較典型的場(chǎng)景是在高并發(fā)的數(shù)據(jù)流讀取和寫(xiě)入場(chǎng)景中,通過(guò) pool 緩存 buffer,避免每次都申請(qǐng)新的 buffer 造成頻繁內(nèi)存資源申請(qǐng)

在框架層代碼中會(huì)比較容易看到 pool 的使用,如 gin 用來(lái)緩存處理請(qǐng)求的 Context 對(duì)象,gorm 用來(lái)緩存序列化對(duì)象(SerializerInterface)等

性能測(cè)試結(jié)果:

func BenchmarkByteBufferWithoutPool(b *testing.B) {
	for i := 0; i < b.N; i++ {
		buf := bytes.Buffer{}
		buf.WriteString(longStr)
		io.Copy(io.Discard, &buf)
	}
}

func BenchmarkByteBufferWithPool(b *testing.B) {
	pool := sync.Pool{
		New: func() any {
			return new(bytes.Buffer)
		},
	}

	for i := 0; i < b.N; i++ {
		buf := pool.Get().(*bytes.Buffer)
		buf.WriteString(longStr)
		io.Copy(io.Discard, buf)
		buf.Reset()
		pool.Put(buf)
	}
}

// 測(cè)試結(jié)果
// BenchmarkByteBufferWithoutPool-8           55544210               211.1 ns/op          1072 B/op          2 allocs/op
// BenchmarkByteBufferWithPool-8           355192696               33.25 ns/op            0 B/op          0 allocs/op

從執(zhí)行次數(shù)和內(nèi)存開(kāi)銷(xiāo)來(lái)看,pool 在多協(xié)程下達(dá)到的對(duì)象復(fù)用的效果,都能帶來(lái)很大的提升

bytes 和 string 的 0 內(nèi)存申請(qǐng)方法

直接看無(wú)內(nèi)存開(kāi)銷(xiāo)的轉(zhuǎn)換方式:

func ByteSliceToString(bytes []byte) string {
	var s string
	sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&bytes))
	stringHeader := (*reflect.StringHeader)(unsafe.Pointer(&s))
	stringHeader.Data = sliceHeader.Data
	stringHeader.Len = sliceHeader.Len
	return s
}

func StringToByteSlice(s string) (bytes []byte) {
	bh := (*reflect.SliceHeader)(unsafe.Pointer(&bytes))
	sh := *(*reflect.StringHeader)(unsafe.Pointer(&s))
	bh.Data = sh.Data
	bh.Len = sh.Len
	bh.Cap = sh.Len
	return
}

參考

兩者的相互轉(zhuǎn)換都用到了反射包中表示底層結(jié)構(gòu)的對(duì)象,如 slice 的 SliceHeader,string 和 StringHeader。因?yàn)?string 和 byte 數(shù)組兩者的底層數(shù)據(jù)結(jié)構(gòu)非常相似,只相差 slice 的 cap,所以轉(zhuǎn)換邏輯并不復(fù)雜

string 和 slice 的底層結(jié)構(gòu)在go源碼中如下:

// runtime/string.go
type stringStruct struct {
	str unsafe.Pointer
	len int
}

// runtime/slice.go
type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

// reflect/value.go
type StringHeader struct {
	Data uintptr
	Len  int
}

type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

go 1.20 之后,StringHeader 和 SliceHeader 被標(biāo)注為 Deprecated,改為推薦使用 StringData 和 SliceData,寫(xiě)法上更簡(jiǎn)單了

參考-The conversion of byte slice and string has changed again in Go 1.20

func byteSliceToString(bytes []byte) string {
	return unsafe.String(unsafe.SliceData(bytes), len(bytes))
}

func stringToByteSlice(s string) (bytes []byte) {
	return unsafe.Slice(unsafe.StringData(s), len(s))
}

實(shí)測(cè): 直接強(qiáng)轉(zhuǎn)和通過(guò)反射轉(zhuǎn)換的benchmark測(cè)試結(jié)果對(duì)比

bytes 轉(zhuǎn) string

BenchmarkForceConvertBytesToString-8    66501550               178.7 ns/op          1024 B/op          1 allocs/op

BenchmarkConvertBytesToString-8         1000000000               0.3236 ns/op          0 B/op          0 allocs/op

可以看到,強(qiáng)轉(zhuǎn)的方式執(zhí)行速度(平均每次 178ns)遠(yuǎn)小于通過(guò)反射方式執(zhí)行的,并且強(qiáng)轉(zhuǎn)每次需要申請(qǐng) 1kb 內(nèi)存,剛好和轉(zhuǎn)換的字符串大小對(duì)應(yīng)

string 轉(zhuǎn) bytes

BenchmarkForceConvertStringToBytes-8    67139846               200.6 ns/op          1024 B/op          1 allocs/op

BenchmarkConvertStringToBytes-8         1000000000               0.3230 ns/op          0 B/op          0 allocs/op

結(jié)果和 bytes 轉(zhuǎn) string 類(lèi)似,不再贅述

高并發(fā)的任務(wù)(如接口)創(chuàng)建協(xié)程池去消費(fèi)和執(zhí)行

協(xié)程確實(shí)很”輕“,相比操作系統(tǒng)線程默認(rèn)大小為1M 來(lái)說(shuō),它的初始大小只有 2k,確實(shí)很小(但隨著??臻g擴(kuò)大可能會(huì)擴(kuò)縮容),不過(guò)在高并發(fā)場(chǎng)景下還是需要對(duì)開(kāi)啟協(xié)程進(jìn)行控制的

協(xié)程池的選型有很多,常見(jiàn)的開(kāi)源項(xiàng)目有 tunny 和 ants,兩者實(shí)現(xiàn)方式略有區(qū)別,tunny 提交任務(wù)時(shí)是同步提交,可以拿到執(zhí)行后的返回值,ants 是異步提交,不支持獲取返回值,要拿到返回值的話得自己實(shí)現(xiàn)。示例如下:

import (
	"github.com/Jeffail/tunny"
	"github.com/panjf2000/ants/v2"
)

func TestTunnyPool(t *testing.T) {
	wg := sync.WaitGroup{}
	wg.Add(100)
	pool := tunny.NewFunc(10, func(payload interface{}) interface{} {
		time.Sleep(3 * time.Second)
		wg.Done()
		return payload
	})
	defer pool.Close()

	for i := 0; i < 100; i++ {
		// tunny.pool.Process 是同步方法,所以需要開(kāi)啟協(xié)程才能并發(fā)
		go func(i int) {
			pool.Process(i)
		}(i)
	}

	wg.Wait()
}

func TestAntsPool(t *testing.T) {
	wg := sync.WaitGroup{}
	wg.Add(100)
	pool, _ := ants.NewPoolWithFunc(10, func(i interface{}) {
		fmt.Printf("%d execute\n", i)
		time.Sleep(3 * time.Second)
		fmt.Printf("%d finish\n", i)
		wg.Done()
	})
	defer pool.Release()

	for i := 0; i < 100; i++ {
		pool.Invoke(i)
	}

	wg.Wait()
}

當(dāng)然,對(duì)于 web 框架來(lái)說(shuō),這種控制并發(fā)的功能官方都有。如 gin 通過(guò) limit 插件,本質(zhì)也是通過(guò) channel 控制并發(fā)協(xié)程數(shù)文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-727234.html

到了這里,關(guān)于golang 編程規(guī)范查漏補(bǔ)缺的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 數(shù)據(jù)結(jié)構(gòu):階段測(cè)試(查漏補(bǔ)缺)

    數(shù)據(jù)結(jié)構(gòu):階段測(cè)試(查漏補(bǔ)缺)

    目錄 選擇題: 題一: 題二: 題三: 題四: 編程題: 題一:左葉子之和 思路一: 題二:約瑟夫問(wèn)題(用單鏈表實(shí)現(xiàn)) 思路一: 本人實(shí)力有限可能對(duì)一些地方解釋和理解的不夠清晰,可以自己嘗試讀代碼,或者評(píng)論區(qū)指出錯(cuò)誤,望海涵! 感謝大佬們的一鍵三連!?感謝大

    2024年02月08日
    瀏覽(17)
  • C++新經(jīng)典 | C++ 查漏補(bǔ)缺(智能指針)

    C++新經(jīng)典 | C++ 查漏補(bǔ)缺(智能指針)

    目錄 一、動(dòng)態(tài)分配 1.初始化的三種方式 2. 釋放內(nèi)存 (1)懸空指針 ?3.創(chuàng)建新工程與觀察內(nèi)存泄漏 二、深入了解new/delete 1.new和delete 2.operator new()和operator delete() 3.申請(qǐng)和釋放一個(gè)數(shù)組 三、智能指針 ?1.shared_ptr (1)常規(guī)初始化 (2)make_shared (3)引用計(jì)數(shù) (3.1)引用計(jì)

    2024年02月07日
    瀏覽(27)
  • Android SystemUI 信號(hào)欄后添加信號(hào)圖標(biāo),查漏補(bǔ)缺

    Android SystemUI 信號(hào)欄后添加信號(hào)圖標(biāo),查漏補(bǔ)缺

    android:layout_width=“wrap_content” android:layout_marginStart=“1dp” android:visibility=“gone” android:tag=“mobile_slot_indicator_4” / ???? ImageView android:id=“@+id/custom_signal_4g” android:layout_height=“wrap_content” android:layout_width=“wrap_content” android:visibility=“gone” android:background=“@drawable/stat_sys

    2024年03月26日
    瀏覽(22)
  • 5種常用Web安全掃描工具,快來(lái)查漏補(bǔ)缺吧!

    漏洞掃描是一種安全檢測(cè)行為,更是一類(lèi)重要的網(wǎng)絡(luò)安全技術(shù),它能夠有效提高網(wǎng)絡(luò)的安全性,而且漏洞掃描屬于主動(dòng)的防范措施,可以很好地避免黑客攻擊行為,做到防患于未然。那么好用的漏洞掃描工具有哪些? 答案就在本文! 1、AWVS Acunetix Web Vulnerability Scanner(簡(jiǎn)稱(chēng)

    2024年02月08日
    瀏覽(17)
  • Android-高級(jí)-UI-進(jìn)階之路-(二)-深入理解-Android-8-0-View-觸摸事件分發(fā)機(jī)制,查漏補(bǔ)缺

    Android-高級(jí)-UI-進(jìn)階之路-(二)-深入理解-Android-8-0-View-觸摸事件分發(fā)機(jī)制,查漏補(bǔ)缺

    我們看到內(nèi)部又調(diào)用了父類(lèi) dispatchTouchEvent 方法, 所以最終是交給 ViewGroup 頂級(jí) View 來(lái)處理分發(fā)了。 頂級(jí) View 對(duì)點(diǎn)擊事件的分發(fā)過(guò)程 在上一小節(jié)中我們知道了一個(gè)事件的傳遞流程,這里我們就大致在回顧一下。首先點(diǎn)擊事件到達(dá)頂級(jí) ViewGroup 之后,會(huì)調(diào)用自身的 dispatchTouchE

    2024年04月14日
    瀏覽(38)
  • C溫故補(bǔ)缺(十八):網(wǎng)絡(luò)編程

    C溫故補(bǔ)缺(十八):網(wǎng)絡(luò)編程

    參考:TCP三次握手詳解. 簡(jiǎn)單分層: 其中,鏈路層還可以分出物理層和數(shù)據(jù)鏈路層。應(yīng)用層可以分出會(huì)話層,表示層和應(yīng)用層。 七層模型: 鏈路層:只是物理的比特流和簡(jiǎn)單封裝的數(shù)據(jù)幀 網(wǎng)絡(luò)層:主要任務(wù)是,通過(guò)路由選擇算法,為報(bào)文通過(guò)通信子網(wǎng)選擇最適當(dāng)?shù)穆窂?。也?/p>

    2024年02月07日
    瀏覽(13)
  • golang推薦的命名規(guī)范

    很少見(jiàn)人總結(jié)一些命名規(guī)范,也可能是筆者孤陋寡聞, 作為一個(gè)兩年的golang 開(kāi)發(fā)者, 我根據(jù)很多知名的項(xiàng)目,如 moby, kubernetess 等總結(jié)了一些常見(jiàn)的命名規(guī)范。 命名規(guī)范可以使得代碼更容易與閱讀, 更少的出現(xiàn)錯(cuò)誤。 文件命名規(guī)范 由于文件跟包無(wú)任何關(guān)系, 而又避免win

    2024年02月02日
    瀏覽(21)
  • Java編程規(guī)范(代碼規(guī)范)--精選

    說(shuō)明 本文介紹精選的Java編程規(guī)范(代碼規(guī)范)。遵守這些規(guī)范,代碼的bug數(shù)將會(huì)大幅減少,代碼可維護(hù)性、可讀性、擴(kuò)展性會(huì)大幅上升。(本文持續(xù)更新) 為什么要有編程規(guī)范? 編程規(guī)范有如下作用: 提高代碼可讀性、維護(hù)性、擴(kuò)展性 提高開(kāi)發(fā)速度、減少bug 有助于留住人

    2024年02月05日
    瀏覽(27)
  • 【編程】C++語(yǔ)言編程規(guī)范-2

    結(jié)合C++ Effective系列參考樹(shù)、尤其是工程經(jīng)驗(yàn)教訓(xùn)的總結(jié)。 并發(fā) 除非必要,盡量少用線程。 多線程編程要守護(hù)好內(nèi)存,使用atomic、mutex、condition variable、future、semaphore、latch、barrier等同步機(jī)制避免數(shù)據(jù)競(jìng)爭(zhēng)。 盡量縮小臨界區(qū),臨界區(qū)指獨(dú)占的資源,禁止其他線程訪問(wèn)變量的代

    2024年02月21日
    瀏覽(25)
  • UE5 編程規(guī)范

    官方文檔 使用現(xiàn)代C++編程標(biāo)準(zhǔn), 使用前沿C++標(biāo)準(zhǔn)庫(kù)版本.

    2024年02月12日
    瀏覽(55)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包