激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

腳本之家,腳本語言編程技術(shù)及教程分享平臺!
分類導(dǎo)航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務(wù)器之家 - 腳本之家 - Golang - 教你用go語言實(shí)現(xiàn)比特幣交易功能(Transaction)

教你用go語言實(shí)現(xiàn)比特幣交易功能(Transaction)

2021-06-26 01:41辜智強(qiáng) -buaa Golang

每一筆比特幣交易都會創(chuàng)造輸出,輸出都會被區(qū)塊鏈記錄下來。給某個人發(fā)送比特幣,實(shí)際上意味著創(chuàng)造新的 UTXO 并注冊到那個人的地址,可以為他所用,今天通過本文給大家分享go語言實(shí)現(xiàn)比特幣交易功能,一起看看吧

比特幣交易

交易(transaction)是比特幣的核心所在,而區(qū)塊鏈唯一的目的,也正是為了能夠安全可靠地存儲交易。在區(qū)塊鏈中,交易一旦被創(chuàng)建,就沒有任何人能夠再去修改或是刪除它。
對于每一筆新的交易,它的輸入會引用(reference)之前一筆交易的輸出(這里有個例外,coinbase 交易),引用就是花費(fèi)的意思。所謂引用之前的一個輸出,也就是將之前的一個輸出包含在另一筆交易的輸入當(dāng)中,就是花費(fèi)之前的交易輸出。交易的輸出,就是幣實(shí)際存儲的地方。下面的圖示闡釋了交易之間的互相關(guān)聯(lián):

教你用go語言實(shí)現(xiàn)比特幣交易功能(Transaction)

注意:

有一些輸出并沒有被關(guān)聯(lián)到某個輸入上

一筆交易的輸入可以引用之前多筆交易的輸出

一個輸入必須引用一個輸出

貫穿本文,我們將會使用像“錢(money)”,“幣(coin)”,“花費(fèi)(spend)”,“發(fā)送(send)”,“賬戶(account)” 等等這樣的詞。但是在比特幣中,其實(shí)并不存在這樣的概念。交易僅僅是通過一個腳本(script)來鎖定(lock)一些值(value),而這些值只可以被鎖定它們的人解鎖(unlock)。

每一筆比特幣交易都會創(chuàng)造輸出,輸出都會被區(qū)塊鏈記錄下來。給某個人發(fā)送比特幣,實(shí)際上意味著創(chuàng)造新的 UTXO 并注冊到那個人的地址,可以為他所用。
交易的主函數(shù):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func (cli *CLI) send(from, to string, amount int, nodeID string, mineNow bool) {
    if !ValidateAddress(from) {  
        log.Panic("ERROR: Sender address is not valid")
    }
    if !ValidateAddress(to) {
        log.Panic("ERROR: Recipient address is not valid")
    }
    bc := NewBlockchain(nodeID)    //獲取區(qū)塊鏈實(shí)例
    UTXOSet := UTXOSet{bc}    //創(chuàng)建UTXO集
    defer bc.Db.Close()
    wallets, err := NewWallets(nodeID)
    if err != nil {
        log.Panic(err)
    }
    wallet := wallets.GetWallet(from)
    tx := NewUTXOTransaction(&wallet, to, amount, &UTXOSet)
    if mineNow {   
        cbTx := NewCoinbaseTX(from, "")
        txs := []*Transaction{cbTx, tx}
        newBlock := bc.MineBlock(txs)
        UTXOSet.Update(newBlock)
    } else {
        sendTx(knownNodes[0], tx)
    }
 
    fmt.Println("Success!")
}

我們從頭分析整個交易過程,首先利用ValidateAddress()方法判斷輸入的地址是否為有效的比特幣地址,然后從我們的blotDB數(shù)據(jù)庫中獲取blockchain實(shí)例(我們利用一個數(shù)據(jù)庫實(shí)現(xiàn)區(qū)塊鏈數(shù)據(jù)的存儲,這里讀者可以忽略),其中讀取數(shù)據(jù)庫的代碼如下

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func NewBlockchain(nodeID string) *Blockchain {
    dbFile := fmt.Sprintf(dbFile, nodeID)
    if dbExists(dbFile) == false {
        fmt.Println("No existing blockchain found. Create one first.")
        os.Exit(1)
    }
    var tip []byte
    db, err := bolt.Open(dbFile, 0600, nil)    //打開數(shù)據(jù)庫
    if err != nil {
        log.Panic(err)
    }
    err = db.Update(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(blocksBucket))
        tip = b.Get([]byte("l"))  //讀取最新的區(qū)塊鏈
        return nil
    })
    if err != nil {
        log.Panic(err)
    }
    bc := Blockchain{tip, db}
    return &bc
}

其中我們的區(qū)塊鏈的基本原型為

?
1
2
3
4
5
6
7
8
9
10
11
12
type Blockchain struct {
    tip []byte
    Db  *bolt.DB
}
type Block struct {
    Timestamp     int64
    Transactions  []*Transaction
    PrevBlockHash []byte
    Hash          []byte
    Nonce         int
    Height        int
}

獲取完成區(qū)塊鏈實(shí)例后,我們創(chuàng)建出一個utxo集合,其數(shù)據(jù)結(jié)構(gòu)為

?
1
2
3
type UTXOSet struct {
    Blockchain *Blockchain
}

然后我們從錢包文件中獲取我們的錢包集合(wallets),接著調(diào)用我們的轉(zhuǎn)賬函數(shù)。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
func NewUTXOTransaction(wallet *Wallet, to string, amount int, UTXOSet *UTXOSet) *Transaction {
    var inputs []TXInput
    var outputs []TXOutput
    pubKeyHash := HashPubKey(wallet.PublicKey)
    acc, validOutputs := UTXOSet.FindSpendableOutputs(pubKeyHash, amount)    //找到能夠使用的輸出
    if acc < amount {    //如果能夠使用的輸出小于目標(biāo)值,則返回錯誤
        log.Panic("ERROR: Not enough funds")
    }
    // Build a list of inputs
    for txid, outs := range validOutputs {        
        txID, err := hex.DecodeString(txid)
        if err != nil {
            log.Panic(err)
        }
        for _, out := range outs {
            input := TXInput{txID, out, nil, wallet.PublicKey}
            inputs = append(inputs, input)
        }
    }
    // Build a list of outputs
    from := fmt.Sprintf("%s", wallet.GetAddress())
    outputs = append(outputs, *NewTXOutput(amount, to))    //創(chuàng)建新的交易輸出
    if acc > amount {
        outputs = append(outputs, *NewTXOutput(acc-amount, from)) // a change    //找零輸出
    }
    tx := Transaction{nil, inputs, outputs}
    tx.ID = tx.Hash()    //創(chuàng)建一筆交易
    UTXOSet.Blockchain.SignTransaction(&tx, wallet.PrivateKey)       //對交易簽名
    return &tx
}

對于一筆交易來說,其數(shù)據(jù)結(jié)構(gòu)為

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type Transaction struct {
    ID   []byte
    Vin  []TXInput
    Vout []TXOutput
}
type TXInput struct {
    Txid      []byte
    Vout      int
    Signature []byte
    PubKey    []byte
}
type TXOutput struct {
    Value      int
    PubKeyHash []byte
}
type UTXOSet struct {
    Blockchain *Blockchain
}

一筆交易來說,輸出主要包含兩部分: 一定量的比特幣(Value), 一個鎖定腳本(ScriptPubKey),要花這筆錢,必須要解鎖該腳本。一個輸入引用了之前交易的一個輸出:Txid 存儲的是之前交易的 ID,Vout 存儲的是該輸出在那筆交易中所有輸出的索引(因?yàn)橐还P交易可能有多個輸出,需要有信息指明是具體的哪一個)Signature是簽名,而Pubkey是公鑰,兩者保證了用戶無法花費(fèi)屬于其他人的幣。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
func HashPubKey(pubKey []byte) []byte {  // RIPEMD160(SHA256(PubKey))
    publicSHA256 := sha256.Sum256(pubKey)
    RIPEMD160Hasher := ripemd160.New()
    _, err := RIPEMD160Hasher.Write(publicSHA256[:])
    if err != nil {
        log.Panic(err)
    }
    publicRIPEMD160 := RIPEMD160Hasher.Sum(nil)
    return publicRIPEMD160
}
func (u UTXOSet) FindSpendableOutputs(pubkeyHash []byte, amount int) (int, map[string][]int) {
    unspentOutputs := make(map[string][]int)     //為輸出開辟一塊內(nèi)存空間
    accumulated := 0      
    db := u.Blockchain.db             //獲取存取區(qū)塊鏈的數(shù)據(jù)庫
    err := db.View(func(tx *bolt.Tx) error {               //讀取數(shù)據(jù)庫
        b := tx.Bucket([]byte(utxoBucket))
        c := b.Cursor()
        for k, v := c.First(); k != nil; k, v = c.Next() {               //遍歷數(shù)據(jù)庫
            txID := hex.EncodeToString(k)
            outs := DeserializeOutputs(v)
            for outIdx, out := range outs.Outputs {
                if out.IsLockedWithKey(pubkeyHash) && accumulated < amount {          //如果能夠解鎖輸出,代表utxo集中的輸出是的所有者是該公鑰所對應(yīng)的人
                    accumulated += out.Value     //累加值
                    unspentOutputs[txID] = append(unspentOutputs[txID], outIdx)     //加到數(shù)組中
                }
            }
        }
 
        return nil
    })
    if err != nil {
        log.Panic(err)
    }
    return accumulated, unspentOutputs
}
func (out *TXOutput) IsLockedWithKey(pubKeyHash []byte) bool {      //判斷輸出是否能夠被某個公鑰解鎖
    return bytes.Compare(out.PubKeyHash, pubKeyHash) == 0
}
func NewTXOutput(value int, address string) *TXOutput {
    txo := &TXOutput{value, nil}    //注冊一個輸出
    txo.Lock([]byte(address))    //設(shè)置輸出的pubhashkey
    return txo
}
func (out *TXOutput) Lock(address []byte) {
    pubKeyHash := Base58Decode(address)
    pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4]
    out.PubKeyHash = pubKeyHash
}

在創(chuàng)建新的輸出時,我們必須找到所有的為花費(fèi)的輸出,并且確保他們有足夠的價值(value),這就是FindSpendableOutputs 要做的事情,隨后,對于每個找到的輸出,會創(chuàng)建一個引用該輸出的輸入。接下來,我們創(chuàng)建兩個輸出:

  1. 一個由接收者地址鎖定。這是給其他地址實(shí)際轉(zhuǎn)移的幣。
  2. 一個由發(fā)送者地址鎖定。這是一個找零。只有當(dāng)未花費(fèi)輸出超過新交易所需時產(chǎn)生。記住:輸出是不可再分的。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
func (bc *Blockchain) SignTransaction(tx *Transaction, privKey ecdsa.PrivateKey) {
    prevTXs := make(map[string]Transaction)
    for _, vin := range tx.Vin {
        prevTX, err := bc.FindTransaction(vin.Txid)
        if err != nil {
            log.Panic(err)
        }
        prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
    }
    tx.Sign(privKey, prevTXs)
}
func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) {//方法接受一個私鑰和之前一個交易的map
    if tx.IsCoinbase() {
        return
    }//判斷是是否為發(fā)幣交易,因?yàn)榘l(fā)幣交易沒有輸入,故不用進(jìn)行簽名
 
    for _, vin := range tx.Vin {
        if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil {
            log.Panic("ERROR: Previous transaction is not correct")
        }
    }
 
    txCopy := tx.TrimmedCopy()  //將會被簽名的是修剪后的交易副本,而不是一個完整的交易
 
    for inID, vin := range txCopy.Vin {
        prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
        txCopy.Vin[inID].Signature = nil
        txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash
//迭代副本中的每一個輸入,在每個輸入中,Pubkey 被設(shè)置為所引用輸出的PubKeyHash
/
        dataToSign := fmt.Sprintf("%x\n", txCopy)
        r, s, err := ecdsa.Sign(rand.Reader, &privKey, []byte(dataToSign))//我們通過private對txCopy進(jìn)行簽名將這串?dāng)?shù)字連接起來儲存在signature中
        if err != nil {
            log.Panic(err)
        }
        signature := append(r.Bytes(), s.Bytes()...)
        tx.Vin[inID].Signature = signature
        txCopy.Vin[inID].PubKey = nil
    }
}
 
 
func (tx *Transaction) TrimmedCopy() Transaction { 
    var inputs []TXInput
    var outputs []TXOutput
 
    for _, vin := range tx.Vin {//將輸入的TXInput.Signature 和TXIput.PubKey設(shè)置為空
        inputs = append(inputs, TXInput{vin.Txid, vin.Vout, nil, nil})
    }
 
    for _, vout := range tx.Vout {
        outputs = append(outputs, TXOutput{vout.Value, vout.PubKeyHash})
    }
    txCopy := Transaction{tx.ID, inputs, outputs}
    return txCopy
}

交易必須被簽名,因?yàn)檫@是保證發(fā)送方不會花費(fèi)其他人的幣的唯一方式,如果一個簽名是無效的,那么這筆交易也會被認(rèn)為是無效的,因?yàn)檫@筆交易無法被加到區(qū)塊鏈中。考慮到交易解鎖的是之前的輸出,然后重新分配里面的價值,并鎖定新的輸出,那么必須要簽名一下的數(shù)據(jù)

  • 存儲在已經(jīng)解鎖輸出的公鑰哈希,他識別了一筆交易的發(fā)送方
  • 存儲在新的鎖定輸出里面的公鑰哈希,他識別了一筆交易的接收方
  • 新的輸出值

因此,在比特幣里,所簽名的并不是一個交易,而是一個去除部分簽名的輸入的副本,輸入里面存儲了被引用輸出的ScriptPubKey

如果現(xiàn)在進(jìn)行過挖礦

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
   cbTx := NewCoinbaseTX(from, "")
        txs := []*Transaction{cbTx, tx}
        newBlock := bc.MineBlock(txs)
        UTXOSet.Update(newBlock)
 
 
 
func NewCoinbaseTX(to, data string) *Transaction {
    if data == "" {  //如果數(shù)據(jù)為空生成一個隨機(jī)數(shù)據(jù)
        randData := make([]byte, 20)
        _, err := rand.Read(randData)
        if err != nil {
            log.Panic(err)
        }
        data = fmt.Sprintf("%x", randData)
    }//生成一筆挖礦交易
    txin := TXInput{[]byte{}, -1, nil, []byte(data)}
    txout := NewTXOutput(subsidy, to)
    tx := Transaction{nil, []TXInput{txin}, []TXOutput{*txout}}
    tx.ID = tx.Hash()
    return &tx
}
 
func (bc *Blockchain) MineBlock(transactions []*Transaction) *Block {   //開始挖礦
    var lastHash []byte
    var lastHeight int
    for _, tx := range transactions {
        // TODO: ignore transaction if it's not valid
        if bc.VerifyTransaction(tx) != true {
            log.Panic("ERROR: Invalid transaction")   //對打包在區(qū)塊中的交易進(jìn)行認(rèn)證
        }
    }
 
    err := bc.db.View(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(blocksBucket))
        lastHash = b.Get([]byte("l"))   //獲取最新的一個塊的hash值
        blockData := b.Get(lastHash)
        block := DeserializeBlock(blockData)  //將最新的一個塊解序列
        lastHeight = block.Height
        return nil
    })
    if err != nil {
        log.Panic(err)
    }
    newBlock := NewBlock(transactions, lastHash, lastHeight+1)
    err = bc.db.Update(func(tx *bolt.Tx) error {    //更新區(qū)塊鏈數(shù)據(jù)庫
        b := tx.Bucket([]byte(blocksBucket))
        err := b.Put(newBlock.Hash, newBlock.Serialize())
        if err != nil {
            log.Panic(err)
        }
        err = b.Put([]byte("l"), newBlock.Hash)
        if err != nil {
            log.Panic(err)
        }
        bc.tip = newBlock.Hash
        return nil
    })
    if err != nil {
        log.Panic(err)
    }
    return newBlock
}
func (bc *Blockchain) VerifyTransaction(tx *Transaction) bool {
    if tx.IsCoinbase() {
        return true
    }
    prevTXs := make(map[string]Transaction)
    for _, vin := range tx.Vin {
        prevTX, err := bc.FindTransaction(vin.Txid)
        if err != nil {
            log.Panic(err)
        }
        prevTXs[hex.EncodeToString(prevTX.ID)] = prevTX
    }
    return tx.Verify(prevTXs)
}
func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
    if tx.IsCoinbase() {   //判斷是否為大筆交易
        return true
    }
    for _, vin := range tx.Vin {
        if prevTXs[hex.EncodeToString(vin.Txid)].ID == nil {
            log.Panic("ERROR: Previous transaction is not correct")   //判斷輸入地址的有效性
        }
    }
    txCopy := tx.TrimmedCopy()    //創(chuàng)建一個裁剪版本的交易副本
    curve := elliptic.P256()    //我們需要相同區(qū)塊用于生成密鑰對
    for inID, vin := range tx.Vin {
        prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
        txCopy.Vin[inID].Signature = nil
        txCopy.Vin[inID].PubKey = prevTx.Vout[vin.Vout].PubKeyHash
        r := big.Int{}
        s := big.Int{}
        sigLen := len(vin.Signature)
        r.SetBytes(vin.Signature[:(sigLen / 2)])
        s.SetBytes(vin.Signature[(sigLen / 2):])
        x := big.Int{}
        y := big.Int{}
        keyLen := len(vin.PubKey)
        x.SetBytes(vin.PubKey[:(keyLen / 2)])
        y.SetBytes(vin.PubKey[(keyLen / 2):])
//這里我們解包存儲在 TXInput.Signature 和 TXInput.PubKey 中的值,因?yàn)橐粋€簽名就是一對數(shù)字,一個公鑰就是一對坐標(biāo)。我們之前為了存儲將它們連接在一起,現(xiàn)在我們需要對它們進(jìn)行解包在 crypto/ecdsa 函數(shù)中使用
        dataToVerify := fmt.Sprintf("%x\n", txCopy)
        rawPubKey := ecdsa.PublicKey{curve, &x, &y}
        if ecdsa.Verify(&rawPubKey, []byte(dataToVerify), &r, &s) == false {  //驗(yàn)證
            return false
        }
        txCopy.Vin[inID].PubKey = nil
    }
 
    return true
}
func NewBlock(transactions []*Transaction, prevBlockHash []byte, height int) *Block {//產(chǎn)生一個新的塊
    block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0, height}//定義數(shù)據(jù)結(jié)構(gòu)
    pow := NewProofOfWork(block)    //定義工作量證明的數(shù)據(jù)結(jié)構(gòu)
    nonce, hash := pow.Run()    //挖礦
    block.Hash = hash[:]
    block.Nonce = nonce
    return block
}
func (pow *ProofOfWork) Run() (int, []byte) {
    var hashInt big.Int
    var hash [32]byte
    nonce := 0
    fmt.Printf("Mining a new block")
    for nonce < maxNonce {
        data := pow.prepareData(nonce)
        hash = sha256.Sum256(data)
        fmt.Printf("\r%x", hash)
        hashInt.SetBytes(hash[:])
        if hashInt.Cmp(pow.target) == -1 {
            break
        } else {
            nonce++
        }
    }
    fmt.Print("\n\n")
 
    return nonce, hash[:]
}
func (pow *ProofOfWork) prepareData(nonce int) []byte {
    data := bytes.Join(
        [][]byte{
            pow.block.PrevBlockHash,
            pow.block.HashTransactions(),
            IntToHex(pow.block.Timestamp),
            IntToHex(int64(targetBits)),
            IntToHex(int64(nonce)),
        },
        []byte{},
    )
 
    return data
}
func (u UTXOSet) Update(block *Block) {
    db := u.Blockchain.db
    err := db.Update(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(utxoBucket))
        for _, tx := range block.Transactions {
            if tx.IsCoinbase() == false {
                for _, vin := range tx.Vin {
                    updatedOuts := TXOutputs{}
                    outsBytes := b.Get(vin.Txid)
                    outs := DeserializeOutputs(outsBytes)
 
                    for outIdx, out := range outs.Outputs {
                        if outIdx != vin.Vout {
                            updatedOuts.Outputs = append(updatedOuts.Outputs, out)
                        }
                    }
 
                    if len(updatedOuts.Outputs) == 0 {
                        err := b.Delete(vin.Txid)
                        if err != nil {
                            log.Panic(err)
                        }
                    } else {
                        err := b.Put(vin.Txid, updatedOuts.Serialize())
                        if err != nil {
                            log.Panic(err)
                        }
                    }
 
                }
            }
            newOutputs := TXOutputs{}
            for _, out := range tx.Vout {
                newOutputs.Outputs = append(newOutputs.Outputs, out)
            }
            err := b.Put(tx.ID, newOutputs.Serialize())
            if err != nil {
                log.Panic(err)
            }
        }
 
        return nil
    })
    if err != nil {
        log.Panic(err)
    }
}

參考

https://jeiwan.cc/

到此這篇關(guān)于利用go語言實(shí)現(xiàn)比特幣交易(Transaction)的文章就介紹到這了,更多相關(guān)go語言比特幣交易內(nèi)容請搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!

原文鏈接:https://blog.csdn.net/m0_37719047/article/details/81950648

延伸 · 閱讀

精彩推薦
  • GolangGolang中Bit數(shù)組的實(shí)現(xiàn)方式

    Golang中Bit數(shù)組的實(shí)現(xiàn)方式

    這篇文章主要介紹了Golang中Bit數(shù)組的實(shí)現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    天易獨(dú)尊11682021-06-09
  • Golanggolang json.Marshal 特殊html字符被轉(zhuǎn)義的解決方法

    golang json.Marshal 特殊html字符被轉(zhuǎn)義的解決方法

    今天小編就為大家分享一篇golang json.Marshal 特殊html字符被轉(zhuǎn)義的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧 ...

    李浩的life12792020-05-27
  • Golanggolang 通過ssh代理連接mysql的操作

    golang 通過ssh代理連接mysql的操作

    這篇文章主要介紹了golang 通過ssh代理連接mysql的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    a165861639710342021-03-08
  • Golanggo日志系統(tǒng)logrus顯示文件和行號的操作

    go日志系統(tǒng)logrus顯示文件和行號的操作

    這篇文章主要介紹了go日志系統(tǒng)logrus顯示文件和行號的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    SmallQinYan12302021-02-02
  • GolangGolang通脈之?dāng)?shù)據(jù)類型詳情

    Golang通脈之?dāng)?shù)據(jù)類型詳情

    這篇文章主要介紹了Golang通脈之?dāng)?shù)據(jù)類型,在編程語言中標(biāo)識符就是定義的具有某種意義的詞,比如變量名、常量名、函數(shù)名等等,Go語言中標(biāo)識符允許由...

    4272021-11-24
  • Golanggo語言制作端口掃描器

    go語言制作端口掃描器

    本文給大家分享的是使用go語言編寫的TCP端口掃描器,可以選擇IP范圍,掃描的端口,以及多線程,有需要的小伙伴可以參考下。 ...

    腳本之家3642020-04-25
  • Golanggolang的httpserver優(yōu)雅重啟方法詳解

    golang的httpserver優(yōu)雅重啟方法詳解

    這篇文章主要給大家介紹了關(guān)于golang的httpserver優(yōu)雅重啟的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,...

    helight2992020-05-14
  • Golanggolang如何使用struct的tag屬性的詳細(xì)介紹

    golang如何使用struct的tag屬性的詳細(xì)介紹

    這篇文章主要介紹了golang如何使用struct的tag屬性的詳細(xì)介紹,從例子說起,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看...

    Go語言中文網(wǎng)11352020-05-21
主站蜘蛛池模板: 免费观看的毛片手机视频 | 中国hdxxxx护士爽在线观看 | 新久久久久久 | 久久精品电影网 | 午夜视频在线观看免费视频 | 国产精品久久久久久久久久久久久久久久 | 国产亚洲欧美日韩高清 | 韩国精品视频在线观看 | 免费看成年人网站 | 国产精品jk白丝蜜臀av软件 | 久久99精品国产99久久6男男 | 亚洲情视频 | 91久久久国产精品 | 综合网日日天干夜夜久久 | 91亚洲精品一区二区福利 | 成年免费视频黄网站在线观看 | 97黄色网 | 欧美性猛交xxxxx按摩国内 | 毛片免费大全短视频 | 一级毛片免费观看 | 7777视频| 日本精品网| 日本在线视频免费观看 | 秋霞影院一二三区 | 青青草成人影视 | 欧美大胆xxxx肉体摄影 | 亚洲伊人色欲综合网 | 深夜影院一级毛片 | 久久69精品久久久久久国产越南 | 精国产品一区二区三区四季综 | 一级免费a | 蜜桃精品视频 | 免费在线观看成年人视频 | av在线播放免费观看 | 一区二区网| 欧美激情性色生活片在线观看 | 亚洲精品动漫在线观看 | 激情综合网俺也去 | 国产永久免费观看 | 国产精品九九久久一区hh | 欧美1—12sexvideos |