返回顶部
首页 > 资讯 > 后端开发 > GO >使用golang怎么实现一个比特币交易功能
  • 695
分享到

使用golang怎么实现一个比特币交易功能

2023-06-15 01:06:41 695人浏览 八月长安
摘要

使用golang怎么实现一个比特币交易功能?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。比特币交易交易(transaction)是比特币的核心所在,而区块链唯一的目的,也正

使用golang怎么实现一个比特币交易功能?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

比特币交易

交易(transaction)是比特币的核心所在,而区块链唯一的目的,也正是为了能够安全可靠地存储交易。在区块链中,交易一旦被创建,就没有任何人能够再去修改或是删除它。
对于每一笔新的交易,它的输入会引用(reference)之前一笔交易的输出(这里有个例外,coinbase 交易),引用就是花费的意思。所谓引用之前的一个输出,也就是将之前的一个输出包含在另一笔交易的输入当中,就是花费之前的交易输出。交易的输出,就是币实际存储的地方。下面的图示阐释了交易之间的互相关联:

使用golang怎么实现一个比特币交易功能 

注意:

有一些输出并没有被关联到某个输入上

一笔交易的输入可以引用之前多笔交易的输出

一个输入必须引用一个输出

贯穿本文,我们将会使用像“钱(money)”,“币(coin)”,“花费(spend)”,“发送(send)”,“账户(account)” 等等这样的词。但是在比特币中,其实并不存在这样的概念。交易仅仅是通过一个脚本(script)来定(lock)一些值(value),而这些值只可以被锁定它们的人解锁(unlock)。

每一笔比特币交易都会创造输出,输出都会被区块链记录下来。给某个人发送比特币,实际上意味着创造新的 UTXO 并注册到那个人的地址,可以为他所用。
交易的主函数:

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)    //获取区块链实例    UTXOSet := UTXOSet{bc}    //创建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数据库中获取blockchain实例(我们利用一个数据库实现区块链数据的存储,这里读者可以忽略),其中读取数据库的代码如下

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)    //打开数据库    if err != nil {        log.Panic(err)    }    err = db.Update(func(tx *bolt.Tx) error {        b := tx.Bucket([]byte(blocksBucket))        tip = b.Get([]byte("l"))  //读取最新的区块链        return nil    })    if err != nil {        log.Panic(err)    }    bc := Blockchain{tip, db}    return &bc}

其中我们的区块链的基本原型为

type Blockchain struct {    tip []byte    Db  *bolt.DB}type Block struct {    Timestamp     int64    Transactions  []*Transaction    PrevBlockHash []byte    Hash          []byte    Nonce         int    Height        int}

获取完成区块链实例后,我们创建出一个utxo集合,其数据结构

type UTXOSet struct {    Blockchain *Blockchain}

然后我们从钱包文件中获取我们的钱包集合(wallets),接着调用我们的转账函数。

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 {    //如果能够使用的输出小于目标值,则返回错误        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))    //创建新的交易输出    if acc > amount {        outputs = append(outputs, *NewTXOutput(acc-amount, from)) // a change    //找零输出    }    tx := Transaction{nil, inputs, outputs}    tx.ID = tx.Hash()    //创建一笔交易    UTXOSet.Blockchain.SignTransaction(&tx, wallet.PrivateKey)       //对交易签名    return &tx}

对于一笔交易来说,其数据结构为

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 存储的是该输出在那笔交易中所有输出的索引(因为一笔交易可能有多个输出,需要有信息指明是具体的哪一个)Signature是签名,而Pubkey是公钥,两者保证了用户无法花费属于其他人的币。

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)     //为输出开辟一块内存空间    accumulated := 0           db := u.Blockchain.db             //获取存取区块链的数据库    err := db.View(func(tx *bolt.Tx) error {               //读取数据库        b := tx.Bucket([]byte(utxoBucket))        c := b.Cursor()        for k, v := c.First(); k != nil; k, v = c.Next() {               //遍历数据库            txID := hex.EncodeToString(k)            outs := DeserializeOutputs(v)            for outIdx, out := range outs.Outputs {                if out.IsLockedWithKey(pubkeyHash) && accumulated < amount {          //如果能够解锁输出,代表utxo集中的输出是的所有者是该公钥所对应的人                    accumulated += out.Value     //累加值                    unspentOutputs[txID] = append(unspentOutputs[txID], outIdx)     //加到数组中                }            }        }        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))    //设置输出的pubhashkey    return txo}func (out *TXOutput) Lock(address []byte) {    pubKeyHash := Base58Decode(address)    pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4]    out.PubKeyHash = pubKeyHash}

在创建新的输出时,我们必须找到所有的为花费的输出,并且确保他们有足够的价值(value),这就是FindSpendableOutputs 要做的事情,随后,对于每个找到的输出,会创建一个引用该输出的输入。接下来,我们创建两个输出:

  1. 一个由接收者地址锁定。这是给其他地址实际转移的币。

  2. 一个由发送者地址锁定。这是一个找零。只有当未花费输出超过新交易所需时产生。记住:输出是不可再分的。

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    }//判断是是否为发币交易,因为发币交易没有输入,故不用进行签名    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 被设置为所引用输出的PubKeyHash/        dataToSign := fmt.Sprintf("%x\n", txCopy)        r, s, err := ecdsa.Sign(rand.Reader, &privKey, []byte(dataToSign))//我们通过private对txCopy进行签名将这串数字连接起来储存在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设置为空        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}

交易必须被签名,因为这是保证发送方不会花费其他人的币的唯一方式,如果一个签名是无效的,那么这笔交易也会被认为是无效的,因为这笔交易无法被加到区块链中。考虑到交易解锁的是之前的输出,然后重新分配里面的价值,并锁定新的输出,那么必须要签名一下的数据

  • 存储在已经解锁输出的公钥哈希,他识别了一笔交易的发送方

  • 存储在新的锁定输出里面的公钥哈希,他识别了一笔交易的接收方

  • 新的输出值

因此,在比特币里,所签名的并不是一个交易,而是一个去除部分签名的输入的副本,输入里面存储了被引用输出的ScriptPubKey

如果现在进行过挖矿

   cbTx := NewCoinbaseTX(from, "")        txs := []*Transaction{cbTx, tx}        newBlock := bc.MineBlock(txs)        UTXOSet.Update(newBlock)func NewCoinbaseTX(to, data string) *Transaction {    if data == "" {  //如果数据为空生成一个随机数据        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")   //对打包在区块中的交易进行认证        }    }    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 {    //更新区块链数据库        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()    //创建一个裁剪版本的交易副本    curve := elliptic.P256()    //我们需要相同区块用于生成密钥对    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 中的值,因为一个签名就是一对数字,一个公钥就是一对坐标。我们之前为了存储将它们连接在一起,现在我们需要对它们进行解包在 crypto/ecdsa 函数中使用        dataToVerify := fmt.Sprintf("%x\n", txCopy)        rawPubKey := ecdsa.PublicKey{curve, &x, &y}        if ecdsa.Verify(&rawPubKey, []byte(dataToVerify), &r, &s) == false {  //验证            return false        }        txCopy.Vin[inID].PubKey = nil    }    return true}func NewBlock(transactions []*Transaction, prevBlockHash []byte, height int) *Block {//产生一个新的块    block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0, height}//定义数据结构    pow := NewProofOfWork(block)    //定义工作量证明的数据结构    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)    }}

Golang适合做什么

golang可以做服务器开发,但golang很适合做日志处理、数据打包、虚拟机处理、数据库代理等工作。在网络编程方面,它还广泛应用于WEB应用、api应用等领域。

关于使用golang怎么实现一个比特币交易功能问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注编程网GO频道了解更多相关知识。

您可能感兴趣的文档:

--结束END--

本文标题: 使用golang怎么实现一个比特币交易功能

本文链接: https://lsjlt.com/news/276600.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 使用golang怎么实现一个比特币交易功能
    使用golang怎么实现一个比特币交易功能?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。比特币交易交易(transaction)是比特币的核心所在,而区块链唯一的目的,也正...
    99+
    2023-06-15
  • 教你用go语言实现比特币交易功能(Transaction)
    比特币交易 交易(transaction)是比特币的核心所在,而区块链唯一的目的,也正是为了能够安全可靠地存储交易。在区块链中,交易一旦被创建,就没有任何人能够再去修改或是删除...
    99+
    2022-06-07
    GO 比特币交易 比特 go语言 比特币
  • go语言实现简易比特币系统之交易签名及校验功能
    目录介绍 签名校验拷贝交易最后介绍 签名的输入: 待签名的交易数据,包括输入和输出 引用的UTXO信息 私钥 签名的输出: 数字数字签...
    99+
    2022-06-07
    校验 GO 系统 比特 go语言 比特币
  • 使用Servlet怎么实现一个表单提交功能
    使用Servlet怎么实现一个表单提交功能?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。用servlet实现一个注册的小功能 ,后台获取数据。注册页面:  注册页面代码 :&l...
    99+
    2023-05-31
    servlet 一个表
  • 使用golang怎么实现一个京东支付功能
    这篇文章主要介绍了使用golang怎么实现一个京东支付功能,此处通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考价值,需要的朋友可以参考下:什么是golanggolang 是Google开发的一种静态强类型、编译型、并发型...
    99+
    2023-06-06
  • 使用golang怎么实现一个登录验证码功能
    这篇文章将为大家详细讲解有关使用golang怎么实现一个登录验证码功能,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。golang适合做什么golang可以做服务器端开发,但golang很适合...
    99+
    2023-06-06
  • Golang怎么使用channel实现一个优雅退出功能
    这篇文章主要介绍了Golang怎么使用channel实现一个优雅退出功能的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Golang怎么使用channel实现一个优雅退出功能文章都会有所收获,下面我们一起来看看吧...
    99+
    2023-07-05
  • 利用java怎么实现一个即时提交功能
    本篇文章给大家分享的是有关利用java怎么实现一个即时提交功能,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。具体内容如下package com.tian.batis;impor...
    99+
    2023-05-31
    java ava
  • 使用java怎么实现一个ATM功能
    使用java怎么实现一个ATM功能?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。Java的特点有哪些Java的特点有哪些1.Java语言作为静态面向对象编程语言...
    99+
    2023-06-14
  • Golang使用channel实现一个优雅退出功能
    目录前言实现思路读源码HookEngine Status优雅退出自己实现适配 Hook适配 Engine Status适配 Graceful Shutdown总结前言 最近补 Gol...
    99+
    2023-03-09
    Golang channel实现退出功能 Golang channel退出 Golang channel
  • 利用golang怎么实现一个微信支付功能
    本文章向大家介绍利用golang怎么实现一个微信支付功能的基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。golang的优点有哪些golang是一种编译语言,可以将代码编译为机器代码,编译后的二进制文件可以直接部署...
    99+
    2023-06-06
  • 使用canvas怎么实现一个滤镜功能
    使用canvas怎么实现一个滤镜功能?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。1 了解 canvas?1.1 什么是 canvas?这个 HTML 元素是为...
    99+
    2023-06-09
  • 使用canvas怎么实现一个拼图功能
    使用canvas怎么实现一个拼图功能?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。实现的思路其实挺简单的,主要是通过服务端获取图片链接,图片宽度,图片高度,然后...
    99+
    2023-06-09
  • 使用ajax怎么实现一个登录功能
    本篇文章给大家分享的是有关使用ajax怎么实现一个登录功能,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。ajax的优点:最大的一点是页面无刷新,用户的体验非常好。使用异步方式与...
    99+
    2023-06-08
  • 使用Python怎么实现一个词云功能
    使用Python怎么实现一个词云功能?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。Python的优点有哪些1、简单易用,与C/C++、Java、C# 等传统语言...
    99+
    2023-06-14
  • 使用Django怎么实现一个分页功能
    这篇文章主要为大家详细介绍了使用Django怎么实现一个分页功能,文中示例代码介绍的非常详细,具有一定的参考价值,发现的小伙伴们可以参考一下:创建项目创建APP,添加APP这些就不在多说我们这次重点来看到视图函数下面是路由设置视图函数继承T...
    99+
    2023-06-06
  • 使用JavaScript怎么实现一个圆角功能
    使用JavaScript怎么实现一个圆角功能?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。找在IE下实现css3效果的圆角时找到的一个实例,没有测试,不知道使用起来怎么样,...
    99+
    2023-06-08
  • 使用CSS3怎么实现一个弹幕功能
    本篇文章给大家分享的是有关使用CSS3怎么实现一个弹幕功能,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。1.首先创建弹幕区域<div class="b...
    99+
    2023-06-08
  • 使用ajax怎么实现一个实时验证功能
    本篇文章给大家分享的是有关使用ajax怎么实现一个实时验证功能,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。什么是ajaxAjax 即“Asynchronous Javascr...
    99+
    2023-06-08
  • 如何在Golang中使用WebSocket实现一个通信功能
    本篇文章给大家分享的是有关如何在Golang中使用WebSocket实现一个通信功能,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。什么是golanggolang 是Google...
    99+
    2023-06-06
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作