【区块链技术与应用】(四)

这篇具有很好参考价值的文章主要介绍了【区块链技术与应用】(四)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

视频8

https://pkg.go.dev/github.com/hyperledger/fabric-chaincode-go/shim#section-sourcefiles

简单资产链码

我们的应用程序是一个基本的示例链码,用来在账本上创建资产(键-值对)。

选择一个位置存放代码

如果你没有写过 Go 的程序,你可能需要确认一下你是否安装了 Go 以及你的系统是否配置正确。我们假设你用的是支持模块的版本。

现在你需要为你的链码应用程序创建一个目录。

简单起见,我们使用如下命令:

mkdir sacc && cd sacc

现在,我们创建一个用于编写代码的源文件:

go mod init sacc
touch sacc.go

内务

首先,我们从内务开始。每一个链码都要实现 Chaincode 接口 中的 InitInvoke 方法。所以,我们先使用 Go import 语句来导入链码必要的依赖。我们将导入链码 shim 包和 peer protobuf 包 。然后,我们加入一个 SimpleAsset 结构体来作为 Chaincode shim 方法的接收者。

package main

import (
    "fmt"

    "github.com/hyperledger/fabric-chaincode-go/shim"
    "github.com/hyperledger/fabric-protos-go/peer"
)

// SimpleAsset implements a simple chaincode to manage an asset
type SimpleAsset struct {
}

初始化链码

然后,我们将实现 Init 方法。

// Init is called during chaincode instantiation to initialize any data.
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {

}

注解

注意,链码升级的时候也要调用这个方法。当写一个用来升级已存在的链码的时候,请确保合理更改 Init 方法。特别地,当升级时没有“迁移”或者没东西需要初始化时,可以提供一个空的 Init 方法。

接下来,我们将使用 ChaincodeStubInterface.GetStringArgs 方法获取 Init 调用的参数,并且检查其合法性。在我们的用例中,我们希望得到一个键-值对。

// Init is called during chaincode instantiation to initialize any
// data. Note that chaincode upgrade also calls this function to reset
// or to migrate data, so be careful to avoid a scenario where you
// inadvertently clobber your ledger's data!
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
  // Get the args from the transaction proposal
  args := stub.GetStringArgs()
  if len(args) != 2 {
    return shim.Error("Incorrect arguments. Expecting a key and a value")
  }
}

【区块链技术与应用】(四),信息管理与信息系统,区块链,java,开发语言

接下来,我们已经确定了调用是合法的,我们将把初始状态存入账本中。我们将调用 ChaincodeStubInterface.PutState 并将键和值作为参数传递给它。假设一切正常,将返回一个 peer.Response 对象,表明初始化成功。

// Init is called during chaincode instantiation to initialize any
// data. Note that chaincode upgrade also calls this function to reset
// or to migrate data, so be careful to avoid a scenario where you
// inadvertently clobber your ledger's data!
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
  // Get the args from the transaction proposal
  args := stub.GetStringArgs()
  if len(args) != 2 {
    return shim.Error("Incorrect arguments. Expecting a key and a value")
  }

  // Set up any variables or assets here by calling stub.PutState()

  // We store the key and the value on the ledger
  err := stub.PutState(args[0], []byte(args[1]))
  if err != nil {
    return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
  }
  return shim.Success(nil)
}

调用链码

首先,我们增加一个 Invoke 函数的签名。

// Invoke is called per transaction on the chaincode. Each transaction is
// either a 'get' or a 'set' on the asset created by Init function. The 'set'
// method may create a new asset by specifying a new key-value pair.
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {

}

就像上边的 Init 函数一样,我们需要从 ChaincodeStubInterface 中解析参数。Invoke 函数的参数是将要调用的链码应用程序的函数名。在我们的用例中,我们的应用程序将有两个方法: setget ,用来设置或者获取资产当前的状态。我们先调用 ChaincodeStubInterface.GetFunctionAndParameters 来为链码应用程序的方法解析方法名和参数。

// Invoke is called per transaction on the chaincode. Each transaction is
// either a 'get' or a 'set' on the asset created by Init function. The Set
// method may create a new asset by specifying a new key-value pair.
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    // Extract the function and args from the transaction proposal
    fn, args := stub.GetFunctionAndParameters()

}

然后,我们将验证函数名是否为 set 或者 get ,并执行链码应用程序的方法,通过 shim.Successshim.Error 返回一个适当的响应,这个响应将被序列化为 gRPC protobuf 消息。

// Invoke is called per transaction on the chaincode. Each transaction is
// either a 'get' or a 'set' on the asset created by Init function. The Set
// method may create a new asset by specifying a new key-value pair.
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    // Extract the function and args from the transaction proposal
    fn, args := stub.GetFunctionAndParameters()

    var result string
    var err error
    if fn == "set" {
            result, err = set(stub, args)
    } else {
            result, err = get(stub, args)
    }
    if err != nil {
            return shim.Error(err.Error())
    }

    // Return the result as success payload
    return shim.Success([]byte(result))
}

实现链码应用程序

就像我们说的,我们的链码应用程序实现了两个功能,它们可以通过 Invoke 方法调用。我们现在来实现这些方法。注意我们之前提到的,要访问账本状态,我们需要使用链码 shim API 中的 ChaincodeStubInterface.PutState 和 ChaincodeStubInterface.GetState 方法。

// Set stores the asset (both key and value) on the ledger. If the key exists,
// it will override the value with the new one
func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {
    if len(args) != 2 {
            return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")
    }

    err := stub.PutState(args[0], []byte(args[1]))
    if err != nil {
            return "", fmt.Errorf("Failed to set asset: %s", args[0])
    }
    return args[1], nil
}

// Get returns the value of the specified asset key
func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {
    if len(args) != 1 {
            return "", fmt.Errorf("Incorrect arguments. Expecting a key")
    }

    value, err := stub.GetState(args[0])
    if err != nil {
            return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err)
    }
    if value == nil {
            return "", fmt.Errorf("Asset not found: %s", args[0])
    }
    return string(value), nil
}

把它们组合在一起

最后,我们增加一个 main 方法,它将调用 shim.Start 方法。下边是我们链码程序的完整源码。

package main

import (
    "fmt"

    "github.com/hyperledger/fabric-chaincode-go/shim"
    "github.com/hyperledger/fabric-protos-go/peer"
)

// SimpleAsset implements a simple chaincode to manage an asset
type SimpleAsset struct {
}

// Init is called during chaincode instantiation to initialize any
// data. Note that chaincode upgrade also calls this function to reset
// or to migrate data.
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
    // Get the args from the transaction proposal
    args := stub.GetStringArgs()
    if len(args) != 2 {
            return shim.Error("Incorrect arguments. Expecting a key and a value")
    }

    // Set up any variables or assets here by calling stub.PutState()

    // We store the key and the value on the ledger
    err := stub.PutState(args[0], []byte(args[1]))
    if err != nil {
            return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
    }
    return shim.Success(nil)
}

// Invoke is called per transaction on the chaincode. Each transaction is
// either a 'get' or a 'set' on the asset created by Init function. The Set
// method may create a new asset by specifying a new key-value pair.
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    // Extract the function and args from the transaction proposal
    fn, args := stub.GetFunctionAndParameters()

    var result string
    var err error
    if fn == "set" {
            result, err = set(stub, args)
    } else { // assume 'get' even if fn is nil
            result, err = get(stub, args)
    }
    if err != nil {
            return shim.Error(err.Error())
    }

    // Return the result as success payload
    return shim.Success([]byte(result))
}

// Set stores the asset (both key and value) on the ledger. If the key exists,
// it will override the value with the new one
func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {
    if len(args) != 2 {
            return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")
    }

    err := stub.PutState(args[0], []byte(args[1]))
    if err != nil {
            return "", fmt.Errorf("Failed to set asset: %s", args[0])
    }
    return args[1], nil
}

// Get returns the value of the specified asset key
func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {
    if len(args) != 1 {
            return "", fmt.Errorf("Incorrect arguments. Expecting a key")
    }

    value, err := stub.GetState(args[0])
    if err != nil {
            return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err)
    }
    if value == nil {
            return "", fmt.Errorf("Asset not found: %s", args[0])
    }
    return string(value), nil
}

// main function starts up the chaincode in the container during instantiate
func main() {
    if err := shim.Start(new(SimpleAsset)); err != nil {
            fmt.Printf("Error starting SimpleAsset chaincode: %s", err)
    }
}

【区块链技术与应用】(四),信息管理与信息系统,区块链,java,开发语言

链码访问控制

链码可以通过调用 GetCreator() 方法来使用客户端(提交者)证书进行访问控制决策。另外,Go shim 提供了扩展 API ,用于从提交者的证书中提取客户端标识用于访问控制决策,该证书可以是客户端身份本身,或者组织身份,或客户端身份属性。

例如,一个以键-值对表示的资产可以将客户端的身份作为值的一部分保存其中(比如以 JSON 属性标识资产主人),以后就只有被授权的客户端才可以更新键-值对。

详细信息请查阅 client identity (CID) library documentation

To add the client identity shim extension to your chaincode as a dependency, see 管理 Go 链码的扩展依赖.

将客户端身份 shim 扩展作为依赖添加到你的链码,请查阅 管理 Go 链码的扩展依赖 。

管理 Go 链码的扩展依赖

你的 Go 链码需要 Go 标准库之外的一些依赖包(比如链码 shim)。当链码安装到 peer 的时候,这些报的源码必须被包含在你的链码包中。如果你将你的链码构造为一个模块,最简单的方法就是在打包你的链码之前使用 go mod vendor 来 “vendor” 依赖。

go mod tidy
go mod vendor

这就把你链码的扩展依赖放进了本地的 vendor 目录。

当依赖都引入到你的链码目录后, peer chaincode packagepeer chaincode install 操作将把这些依赖一起放入链码包中。

视频9

拉取项目

GOPATH`设置为`/root/go` 进入`GOPATH/src
cd $GOPATH/src && git clone https://github.com/sxguan/fabric-go-sdk.git

启动节点

cd ./fabric-go-sdk/fixtures/ && docker-compose up -d

【区块链技术与应用】(四),信息管理与信息系统,区块链,java,开发语言
【区块链技术与应用】(四),信息管理与信息系统,区块链,java,开发语言
【区块链技术与应用】(四),信息管理与信息系统,区块链,java,开发语言

启动项目

cd .. && go build && ./fabric-go-sdk
>> 开始创建通道......
>>>> 使用每个org的管理员身份更新锚节点配置...
>>>> 使用每个org的管理员身份更新锚节点配置完成
>> 创建通道成功
>> 加入通道......
>> 加入通道成功
>> 开始打包链码......
>> 打包链码成功
>> 开始安装链码......
>> 安装链码成功
>> 组织认可智能合约定义......
>>> chaincode approved by Org1 peers:
	peer0.org1.example.com:7051
	peer1.org1.example.com:9051
>> 组织认可智能合约定义完成
>> 检查智能合约是否就绪......
LifecycleCheckCCCommitReadiness cc = simplecc, = {map[Org1MSP:true]}
LifecycleCheckCCCommitReadiness cc = simplecc, = {map[Org1MSP:true]}
>> 智能合约已经就绪
>> 提交智能合约定义......
>> 智能合约定义提交完成
>> 调用智能合约初始化方法......
>> 完成智能合约初始化
>> 通过链码外部服务设置链码状态......
>> 设置链码状态完成
<--- 添加信息 --->: 18c0c86ce029d7de04461484976c5151992864b52ca28905d0ccf911443fdfcb
<--- 查询信息 --->: 123

【区块链技术与应用】(四),信息管理与信息系统,区块链,java,开发语言

algernon@algernon-Lenovo-Legion-Y7000:/opt/gopath/src$ go build && ./fabric-go-sdk
github.com/hyperledger/fabric-chaincode-go/shim/handler.go:11:2: cannot find package "github.com/golang/protobuf/proto" in any of:
	/usr/local/go/src/github.com/golang/protobuf/proto (from $GOROOT)
	/opt/gopath/src/github.com/golang/protobuf/proto (from $GOPATH)
github.com/hyperledger/fabric-protos-go/peer/snapshot.pb.go:10:2: cannot find package "github.com/golang/protobuf/ptypes/empty" in any of:
	/usr/local/go/src/github.com/golang/protobuf/ptypes/empty (from $GOROOT)
	/opt/gopath/src/github.com/golang/protobuf/ptypes/empty (from $GOPATH)
github.com/hyperledger/fabric-chaincode-go/shim/interfaces.go:7:2: cannot find package "github.com/golang/protobuf/ptypes/timestamp" in any of:
	/usr/local/go/src/github.com/golang/protobuf/ptypes/timestamp (from $GOROOT)
	/opt/gopath/src/github.com/golang/protobuf/ptypes/timestamp (from $GOPATH)
github.com/hyperledger/fabric-protos-go/peer/chaincode_shim.pb.go:11:2: cannot find package "google.golang.org/grpc" in any of:
	/usr/local/go/src/google.golang.org/grpc (from $GOROOT)
	/opt/gopath/src/google.golang.org/grpc (from $GOPATH)
github.com/hyperledger/fabric-protos-go/peer/chaincode_shim.pb.go:12:2: cannot find package "google.golang.org/grpc/codes" in any of:
	/usr/local/go/src/google.golang.org/grpc/codes (from $GOROOT)
	/opt/gopath/src/google.golang.org/grpc/codes (from $GOPATH)
github.com/hyperledger/fabric-chaincode-go/shim/internal/client.go:13:2: cannot find package "google.golang.org/grpc/credentials" in any of:
	/usr/local/go/src/google.golang.org/grpc/credentials (from $GOROOT)
	/opt/gopath/src/google.golang.org/grpc/credentials (from $GOPATH)
github.com/hyperledger/fabric-chaincode-go/shim/internal/client.go:14:2: cannot find package "google.golang.org/grpc/keepalive" in any of:
	/usr/local/go/src/google.golang.org/grpc/keepalive (from $GOROOT)
	/opt/gopath/src/google.golang.org/grpc/keepalive (from $GOPATH)
github.com/hyperledger/fabric-protos-go/peer/chaincode_shim.pb.go:13:2: cannot find package "google.golang.org/grpc/status" in any of:
	/usr/local/go/src/google.golang.org/grpc/status (from $GOROOT)
	/opt/gopath/src/google.golang.org/grpc/status (from $GOPATH)

 go build && ./fabric-go-sdk
>> 开始创建通道......
>> Create channel and join error: Create channel error: error should be nil for SaveChannel of orgchannel: create channel failed: create channel failed: SendEnvelope failed: calling orderer 'orderer.example.com:7050' failed: Orderer Client Status Code: (2) CONNECTION_FAILED. Description: dialing connection on target [orderer.example.com:7050]: connection is in TRANSIENT_FAILURE


【区块链技术与应用】(四),信息管理与信息系统,区块链,java,开发语言

go build && ./fabric-go-sdk
>> 开始创建通道......
>> Create channel and join error: Create channel error: error should be nil for SaveChannel of orgchannel: create channel failed: create channel failed: SendEnvelope failed: calling orderer 'orderer.example.com:7050' failed: Orderer Server Status Code: (400) BAD_REQUEST. Description: error applying config update to existing channel 'mychannel': error authorizing update: error validating ReadSet: proposed update requires that key [Group]  /Channel/Application be at version 0, but it is currently at version 1

【区块链技术与应用】(四),信息管理与信息系统,区块链,java,开发语言
【区块链技术与应用】(四),信息管理与信息系统,区块链,java,开发语言

go build && ./fabric-go-sdk
>> 开始创建通道......
>>>> 使用每个org的管理员身份更新锚节点配置...
>>>> 使用每个org的管理员身份更新锚节点配置完成
>> 创建通道成功
>> 加入通道......
>> 加入通道成功
>> 开始打包链码......
>> 打包链码成功
>> 开始安装链码......
 [fabsdk/fab] 2022/10/26 15:19:14 UTC - peer.(*peerEndorser).sendProposal -> ERRO process proposal failed [rpc error: code = DeadlineExceeded desc = context deadline exceeded]
 [fabsdk/fab] 2022/10/26 15:19:14 UTC - peer.(*peerEndorser).sendProposal -> ERRO process proposal failed [rpc error: code = DeadlineExceeded desc = context deadline exceeded]
>> create chaincode lifecycle error: %v installCC error: LifecycleInstallCC error: Multiple errors occurred: - Transaction processing for endorser [peer0.org1.example.com:7051]: gRPC Transport Status Code: (4) DeadlineExceeded. Description: context deadline exceeded - Transaction processing for endorser [peer1.org1.example.com:9051]: gRPC Transport Status Code: (4) DeadlineExceeded. Description: context deadline exceeded

go build && ./fabric-go-sdk
>> 开始创建通道......
>>>> 使用每个org的管理员身份更新锚节点配置...
>>>> 使用每个org的管理员身份更新锚节点配置完成
>> 创建通道成功
>> 加入通道......
>> 加入通道成功
>> 开始打包链码......
>> 打包链码成功
>> 开始安装链码......
>> 安装链码成功
>> 组织认可智能合约定义......
>>> chaincode approved by Org1 peers:
	peer0.org1.example.com:7051
	peer1.org1.example.com:9051
>> 组织认可智能合约定义完成
>> 检查智能合约是否就绪......
LifecycleCheckCCCommitReadiness cc = simplecc, = {map[Org1MSP:true]}
LifecycleCheckCCCommitReadiness cc = simplecc, = {map[Org1MSP:true]}
>> 智能合约已经就绪
>> 提交智能合约定义......
>> 智能合约定义提交完成
>> 调用智能合约初始化方法......
>> 完成智能合约初始化
>> 通过链码外部服务设置链码状态......
>> 设置链码状态完成
<--- 添加信息 --->: 18c0c86ce029d7de04461484976c5151992864b52ca28905d0ccf911443fdfcb
<--- 查询信息 --->123

完整内容

https://sxguan0529.gitbook.io/hyperledger-fabric/fabric-sdk-go#san-pei-zhi-wen-jian-config.yaml文章来源地址https://www.toymoban.com/news/detail-661083.html

到了这里,关于【区块链技术与应用】(四)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 基于C语言的学生信息管理系统开发

    软件功能描述 功能模块图 2.信息管理模块 功能说明:输入、增加、删除、修改学生信息同时返回上一级 查询模块 功能说明:根据性别民族等查找学生人数、按照课程最高分查询学生信息、按照课程统计学生的平均分数同时返回上一级 成绩排序模块 功能说明:根据课程对学

    2024年02月04日
    浏览(56)
  • 【区块链技术开发语言】在ubuntu18 系统环境下命令操作配置以太坊go-ethereum环境

    项目简介: 以太坊是一个基于区块链技术的分布式平台,用于构建去中心化应用程序(DApps)。go-ethereum 是以太坊官方开发团队维护的 Go 语言实现的以太坊客户端,也被称为 Geth。它提供了一个完整的以太坊节点,用于参与以太坊网络,执行智能合约,进行交易等。 前提条件

    2024年02月21日
    浏览(45)
  • 学生信息管理系统——JAVA 语言版(主页面+增+删+改+查+退)

    学生管理系统要能够实现添加学生信息,删除,修改以及查看学生信息的功能。 你是否尝试过使用JAVA语言编写一个代码程序,使用该代码实现学生信息管理呢? 如果你还没有任何头绪,接下来推荐你看一下我的做法,我使用的编程工具是IDEA,以下给出了学生信息管理系统的程

    2024年02月03日
    浏览(44)
  • 【软考高项】新一代信息技术及应用之区块链

    信息技术在智能化、系统化、微型化、云端化的基础上不断融合创新,促进了物联网、云计算、大数据、区块链、人工智能、虚拟现实等新一代信息技术的诞生。新一代信息技术与信息资源充分开发利用形成的新模式、新业态等,是信息化发展的主要趋势,也是信息系统集成

    2024年02月06日
    浏览(48)
  • Java+Angular开发的医院信息管理系统源码,系统部署于云端,支持多租户

    云HIS系统源码, 采用云端SaaS服务的方式提供 基于云计算技术的B/S架构的云HIS系统源码, 采用云端SaaS服务的方式提供,使用用户通过浏览器 即 能访问,无需关注系统的部署、维护、升级等问题,系统充分考虑了模板化、配置化、智能化、扩展化等设计方法,覆盖了基层医疗

    2024年02月08日
    浏览(44)
  • AI&BlockChain:“知名博主独家讲授”人工智能创新应用竞赛【精选实战作品】之《基于计算机视觉、自然语言处理和区块链技术的乘客智能报警系统》案例的界面简介、功能介绍分享之自然语言处理技术

    AIBlockChain:“知名博主独家讲授”人工智能创新应用竞赛【精选实战作品】之《基于计算机视觉、自然语言处理和区块链技术的乘客智能报警系统》案例的界面简介、功能介绍分享之自然语言处理技术 目录 人工智能创新应用竞赛【精选实战作品】之《基于计算机视觉、自然

    2024年01月17日
    浏览(89)
  • 区块链技术在金融行业的应用与风险管理

    近年来,随着我国数字经济飞速发展,区块链技术已开始广泛应用,全面融入社会经济发展体系之中,成为继大数据、人工智能、云计算的又一新型技术领域。区块链技术本身具有去中心化、分布式存储、防篡改、可追溯等特性,其安全性远高于传统网络体系,具有广阔的应

    2024年02月06日
    浏览(49)
  • 【JAVA】Eclipse+MYSQL数据库+JSP+基础Servlet开发JavaWeb学生信息管理系统

    目录 前言 一、搭建环境  二、功能实现、 1、   登陆界面 注册按钮 2、学生信息管理系统主界面 3、dao包  4、用户的信息展示,添加,删除,修改功能(只展示添加代码) 5、学生的信息展示,添加,删除,修改功能(只展示添加代码) 6、成绩的信息展示,添加,删除,修

    2024年02月05日
    浏览(65)
  • 中文编程开发语言工具应用案例:ps5体验馆计时收费管理系统软件

    ps5体验馆计时收费管理系统软件 软件部分功能: 1、计时计费功能:只需点开始计时即可,时间直观显示 2、商品管理功能:可以管理饮料等商品 3、会员管理功能:支持只用手机号作为卡号使用。 4、定时提醒功能:定时时间可以自由设定,到时间电脑会发出提醒声音,并改

    2024年02月08日
    浏览(48)
  • 区块链技术在电子档案管理中的应用场景分析

    在上一篇文章《区块链基本特性及其与电子档案管理的契合点》中已经提到,基于区块链技术“去中心化、不可篡改、公开透明、可追溯”等基本特性,有望解决电子档案管理中可信管理、资源整合、数据确权、权责界定、数据备份、标准统一等一系列问题。那么,在电子档

    2024年02月04日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包