Hyperledger fabric的链码接口整理

fabric java chaincode开发详细步骤! # fabric java chaincode开发详细步骤! 作者:小桥夜雪 ## 一. 配置相关环境 操作系统:ubuntu 16.04 LTS   fabric版本:0.6 ### 1.配置fabric网络环境。 笔目前使用fabric0.6版本,基于docker容器配置,网上有各种教程,自己可以百度。 ### 2.安装gradle环境。 安装gradle环境可以参考这一篇文... 阅读详情

1.Chaincode接口必须被所有的链上代码实现,fabric运行交易通过调用这些指定的函数

 type Chaincode interface{ 
      // 在容器建立连接之后再部署交易期间调用Init函数,准许链上代码初始化内部数据
    Init(stub ChaincodeStubInterface, function string, args []string) ([]byte, error)
    // 每次调用交易都会调用Invoke接口. 链上代码可能会改变状态变量
    Invoke(stub ChaincodeStubInterface, function string, args []string) ([]byte, error)
    // 查询交易时调用Query接口. 链上代码仅仅读(而不是修改)它的状态变量并其返回结果
    Query(stub ChaincodeStubInterface, function string, args []string) ([]byte, error)
}
2. ChaincodeStubInterface用来部署链上代码apps来进入和修改他们的账本

type ChaincodeStubInterface interface {
    // Get the arguments to the stub call as a 2D byte array
    // 获取stub调用的参数来作为一个2维字节数组
    GetArgs() [][]byte

    // Get the arguments to the stub call as a string array
    // 获取stub调用的参数来作为一个字符数组
    GetStringArgs() []string

    // 获取交易的ID
    GetTxID() string

    // InvokeChaincode 本地调用指定的链上代码,`Invoke`使用相同的交易,也就说链上代码调用
    // 链上代码不会创建一个新的交易消息
    InvokeChaincode(chaincodeName string, args [][]byte) ([]byte, error)

    // InvokeChaincode 本地调用指定的链上代码,`Query`使用相同的交易,也就说链上代码调用
    // 链上代码不会创建一个新的交易消息
    QueryChaincode(chaincodeName string, args [][]byte) ([]byte, error)

    // GetState通过Key来返回数组的特定值
    GetState(key string) ([]byte, error)

    // PutState向账本中写入特定的键和值
    PutState(key string, value []byte) error

    // DelState从账本中移除指定的键和值
    DelState(key string) error

    // RangeQueryState函数可以通过chaincode调用来查询状态范围内的键。假设startKey和endKey
    // 在词典中,将返回一个迭代器,它可以用来遍历startKey和endKey之间的所有键。
    // 迭代器返回键的顺序是随机的。
    RangeQueryState(startKey, endKey string) (StateRangeQueryIteratorInterface, error)

    // CreateTable创建一张新表,给出表名和列定义
    CreateTable(name string, columnDefinitions []*ColumnDefinition) error

    // GetTable如果表存在,返回指定的一张表,如果表不存在ErrTableNotFound错误
    GetTable(tableName string) (*Table, error)

    // DeleteTable删除表和实体相关的所有行
    DeleteTable(tableName string) error

    // InsertRow 插入一个新行进入指定的表
    // 返回 -
    // 如果行成功插入返回true和no error
    // 如果已经存在给定的行就返回false和no error
    // 如果指定的表名不存在返回false和TableNotFoundError
    // 如果出现一个没有预料到的错误条件返回false和error
    InsertRow(tableName string, row Row) (bool, error)

    // ReplaceRow 在指定的表中更新行.
    // 返回 -
    // 如果行成功更新就返回false和no error
    // 如果给出的行不存在相应的键就返回false和no error
    // 如果指定的表名不存在返回false和TableNotFoundError
    // 如果出现一个没有预料到的错误条件返回false和error
    ReplaceRow(tableName string, row Row) (bool, error)

    // 通过键从指定的表中获取行
    GetRow(tableName string, key []Column) (Row, error)

    // 基于特定的key来返回多行。例如,给出表| A | B | C | D |,A,C和D是键,可以使用[A,C]调用GetRows来返回所有具有A,
    // C和任何D值的行作为它们的键。 GetRows也可以用A调用,返回所有具有A和C和D作为其键值的行。
    GetRows(tableName string, key []Column) (<-chan Row, error)

    //DeleteRow从指定的表中通过key来删除特定的行
    DeleteRow(tableName string, key []Column) error

    // ReadCertAttribute用来从交易证书中读取指定的属性
    // *attributeName* 是这个函数的入参.
    // 例如:
    //  attrValue,error:=stub.ReadCertAttribute("position")
    ReadCertAttribute(attributeName string) ([]byte, error)

    // VerifyAttribute用于验证事务证书是否具有名称为* attribute Name *和value * attributeValue *的属性,
    // attributeName和attributeValue是此函数接收的输入参数
    // 例如:
    //    containsAttr, error := stub.VerifyAttribute("position", "Software Engineer")
    VerifyAttribute(attributeName string, attributeValue []byte) (bool, error)

    // VerifyAttributes与VerifyAttribute相同,但它检查属性列表及其相应的值,而不是单个属性/值对
    // 例如:
    //    containsAttrs, error:= stub.VerifyAttributes(&attr.Attribute{"position",  "Software Engineer"}, &attr.Attribute{"company", "ACompany"})
    VerifyAttributes(attrs ...*attr.Attribute) (bool, error)

    // VerifySignature核实交易的签名,如果正确返回true,否则返回false
    VerifySignature(certificate, signature, message []byte) (bool, error)

    // GetCallerCertificate 返回调用者证书
    GetCallerCertificate() ([]byte, error)

    // GetCallerMetadata 返回调用方元数据
    GetCallerMetadata() ([]byte, error)

    // GetBinding 返回交易捆绑
    GetBinding() ([]byte, error)

    // GetPayload 返回交易的payload, payload是一个定义在fabric/protos/chaincode.proto
    // 中的ChaincodeSpecwhich
    GetPayload() ([]byte, error)

    // GetTxTimestamp返回交易创建的时间戳,这个时间戳是peer收到交易的当前时间。
    // 请注意,此时间戳可能与其他对等端peer的时间不同
    GetTxTimestamp() (*timestamp.Timestamp, error)

    // SetEvent保存当交易组成一个块时要发送的事件
    SetEvent(name string, payload []byte) error
}

3.StateRangeQueryIteratorInterface允许在一个链上代码在状态上迭代一定范围的键值

type StateRangeQueryIteratorInterface interface {
    // HasNext如果查询迭代器范围内包含额外的键和值就返回true
    HasNext() bool
    // Next在迭代器范围内返回下一个键和值
    Next() (string, []byte, error)
    // Close 关闭范围查询迭代器,当从迭代器中读完被释放资源的时候被调用
    Close() error
}

4.chaincode_example02解析

package main


import (
    "errors"
    "fmt"
    "strconv"

    "github.com/hyperledger/fabric/core/chaincode/shim"
)

// SimpleChaincode 样例链上代码实现
type SimpleChaincode struct {
}

func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
    var A, B string    // 字符实体
    var Aval, Bval int // 资产持股
    var err error

    if len(args) != 4 {
        return nil, errors.New("Incorrect number of arguments. Expecting 4")
    }

    // 初始化链上代码
    A = args[0]
    Aval, err = strconv.Atoi(args[1])
    if err != nil {
        return nil, errors.New("Expecting integer value for asset holding")
    }
    B = args[2]
    Bval, err = strconv.Atoi(args[3])
    if err != nil {
        return nil, errors.New("Expecting integer value for asset holding")
    }
    fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)

    // 写状态到账本
    err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
    if err != nil {
        return nil, err
    }

    err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
    if err != nil {
        return nil, err
    }

    return nil, nil
}

// 支持从A到B支付X股
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
    if function == "delete" {
        // Deletes an entity from its state
        // 从他的状态中删除entity
        return t.delete(stub, args)
    }

    var A, B string    // 字符实体
    var Aval, Bval int // 持股资产
    var X int          // 交易值
    var err error

    if len(args) != 3 {
        return nil, errors.New("Incorrect number of arguments. Expecting 3")
    }

    A = args[0]
    B = args[1]

    // 从账本中获取状态
    // TODO: will be nice to have a GetAllState call to ledger
    Avalbytes, err := stub.GetState(A)
    if err != nil {
        return nil, errors.New("Failed to get state")
    }
    if Avalbytes == nil {
        return nil, errors.New("Entity not found")
    }
    Aval, _ = strconv.Atoi(string(Avalbytes))

    Bvalbytes, err := stub.GetState(B)
    if err != nil {
        return nil, errors.New("Failed to get state")
    }
    if Bvalbytes == nil {
        return nil, errors.New("Entity not found")
    }
    Bval, _ = strconv.Atoi(string(Bvalbytes))

    // 执行execution
    X, err = strconv.Atoi(args[2])
    if err != nil {
        return nil, errors.New("Invalid transaction amount, expecting a integer value")
    }
    Aval = Aval - X
    Bval = Bval + X
    fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)

    // 写状态到账本
    err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
    if err != nil {
        return nil, err
    }

    err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
    if err != nil {
        return nil, err
    }

    return nil, nil
}

// 从账本中删除实体
func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) ([]byte, error) {
    if len(args) != 1 {
        return nil, errors.New("Incorrect number of arguments. Expecting 1")
    }

    A := args[0]

    // 从账本的状态中删除密钥
    err := stub.DelState(A)
    if err != nil {
        return nil, errors.New("Failed to delete state")
    }

    return nil, nil
}

// Query callback representing the query of a chaincode
// Query 查询链上代码
func (t *SimpleChaincode) Query(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
    if function != "query" {
        return nil, errors.New("Invalid query function name. Expecting \"query\"")
    }
    var A string // 字符实体
    var err error

    if len(args) != 1 {
        return nil, errors.New("Incorrect number of arguments. Expecting name of the person to query")
    }

    A = args[0]

    // 从账本中获取状态
    Avalbytes, err := stub.GetState(A)
    if err != nil {
        jsonResp := "{\"Error\":\"Failed to get state for " + A + "\"}"
        return nil, errors.New(jsonResp)
    }

    if Avalbytes == nil {
        jsonResp := "{\"Error\":\"Nil amount for " + A + "\"}"
        return nil, errors.New(jsonResp)
    }

    jsonResp := "{\"Name\":\"" + A + "\",\"Amount\":\"" + string(Avalbytes) + "\"}"
    fmt.Printf("Query Response:%s\n", jsonResp)
    return Avalbytes, nil
}

func main() {
    // ChainCode 调用 err := shim.Start(new(SimpleChaincode))
    // 接入到ChainCodeSupportServer
    err := shim.Start(new(SimpleChaincode))
    if err != nil {
        fmt.Printf("Error starting Simple chaincode: %s", err)
    }
}

fabric链码编写(一):常用接口(以fabcar.go为例) fabric链码编写思路和常见的API整理: type SmartContract struct { contractapi.Contract } contractapi.Contract :关于资产调用的常用接口,很多官方链码都使用了,自己编写链码的时候可以不用,后面的都换成Stub()方法即可 首先需要init方法进行初始化: func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) er 阅读详情

相关推荐

hyperledger fabric 2.0(一)环境搭建以及测试网络部署

一、基础环境整理(可参照:https://blog.youkuaiyun.com/tianshuhao521/article/details/83892178 https://blog.youkuaiyun.com/tianshuhao521/article/details/83894142) 1、安装curl: # yum install curl 安装成功后查看版本: # curl --version ...

tianshuhao521的博客 1万+

Michael.W谈hyperledger Fabric第24期-详细带读Fabric的源码9-peer节点有关账本的重要接口

Michael.W谈hyperledger Fabric第24期-详细带读Fabric的源码9-peer节点有关账本的重要接口1 PeerLedgerProvider接口-账本操作2 PeerLedger接口-账本查询3 ValidatedLedger接口-账本存储 1 PeerLedgerProvider接口-账本操作 Fabric账本存储的接口定义在core/ledger/ledger_int...

Revelation_of_Turing 532

Hyperledger Fabric Java SDK最新教程

Fabric Java SDK是Fabric区块链官方提供的用于Java应用开发的SDK,全称为Fabric-sdk-java,网上可用资料不多,本文列出了精心整理的针对Fabric Java SDK的最新精选教程。 如果希望快速掌握Fabric Java SDK的使用方法,建议访问汇智网的在线互动教程: Fabric区块链Java开发详解 1、官方文档 使用Fabric Java SDK...

mongo_node的专栏 2449

Hyperledger Fabric 链码(2) 接口

1.Chaincode interface:每个链码程序必须实现链码接口,用以响应接收的事务。 1.1 go语言的“shim ”包中,接口规范如下: Init:在链码实例化或者升级的时候被调用,完成数据初始化 Invoke:客户端调用Invoke方法来提交交易提案,在更新或查询提案事务中分类帐本数据状态的时候被调用 type Chaincode interface { // Init is called during Instantiate transaction after the chain

thefist的专栏 766

Hyperledger Fabric 入门必读书籍

下面整理一些 Hyperledger Fabric 一些比较好的区块链书籍,仅供学习参考。 1. 区块链原理、设计与应用 1.1 书籍封面 1.2 介绍 本书由超级账本核心设计和开发者撰写,是区块链开发落地专业指南。由浅入深,系统化介绍超级账本Fabric设计精华、应用开发等。全书分为理论篇和实践篇两大部分;第1-3章介绍区块链技术的由来、核心思想及典型的应用场景;第4-5章重点介绍区块链技术中...

boss2967的博客 5738

易懂案例:用班费记账来理解区块链Fabric系统链码、配置系统链码、背书管理系统链码、生命周期系统链码、查询系统链码、验证系统链码是什么?各自原理、数学逻辑、区别和联系是什么?

摘要 Hyperledger Fabric中的系统链码是区块链网络的内置管理工具,类比班级的班费管理系统:CSCC是规则制定工具,管理通道配置;ESCC负责交易背书,如同审批签字流程;LSCC管理链码生命周期,相当于规则启用工具;QSCC提供查询功能,类似账本检索目录;VSCC是最终审核员,验证交易合规性。这些系统链码各司其职又相互协作,共同保障Fabric网络的稳定运行。

通俗易懂学IT 1724

hyperledger fabric 智能合约开发(六)------其他合约接口

其他合约接口 我们在开发中,还会遇到其他合约接口调用,可能不会太频繁,但会遇见。下面我来介绍一下其他合约接口开发: Package cid(import "github.com/hyperledger/fabric/core/chaincode/shim/ext/cid") func AssertAttributeValue检查属性值是否等于指定值 func AssertAttribute...

Rcvisual的博客 1590

Fabric Java SDK最新教程

Fabric Java SDK是Fabric区块链官方提供的用于Java应用开发的SDK,全称为Fabric-sdk-java,网上可用资料不多,本文列出了精心整理的针对Fabric Java SDK的最新精选教程。 如果希望快速掌握Fabric Java SDK的使用方法,建议访问汇智网的在线互动教程: Fabric区块链Java开发详解 1、官...

rejames的博客 6603

Hyperledger fabric0.6的链码接口整理

1.Chaincode接口必须被所有的链上代码实现,fabric运行交易通过调用这些指定的函数  type Chaincode interface{    // 在容器建立连接之后再部署交易期间调用Init函数,准许链上代码初始化内部数据     Init(stub ChaincodeStubInterface, function string, args []string) ([

Anonymous 1215

【Labview】对连续采集数据的一种采样方法

图1如图1,想要对这样一个连续信号进行数据点的采集,我根据网上收集到的方法,在此分享一下最终的结果。可以拓展结合文件的写入,完成对连续信号的采集和输出。 已知: 一个连续信号,如图1. 目标: 设定一定采集点个数、采集点时间间隔、和采集周期次数,得到这条连续信号的数据点,如图2. 图2 方法如下: 主要利用的是队列的方法。 在连续信号生成的时候,不断的写入队列,并且清除队列。 ...

李时珍食疗志的博客 1万+

ZLMediaKit在Windows下启用WebRtc编译(64位)

准备软件 cmake-gui vs2017 已经编译好的windows版本openssl(下载地址) 当然,你完全可以自己按照openssl的官网教程自己编译windows版本的openssl libsrtp编译安装 首先,需要将下载好的openssl解压并添加到环境变量。如下图,将下图路径添加到环境变量 其次,下载libsrtp(下载地址) 下载后解压并新建build文件夹,如下图: 在合适位置新建文件夹放置install后的srtp文件,如"D:\develop20..

byna11sina11的博客 1万+

Awesome Blockchain CN:区块链开发常用API接口文档与使用示例

在区块链(BlockChain)开发过程中,开发者经常需要与各种API接口打交道,无论是查询区块信息、调用智能合约还是管理链码。本文整理了Hyperledger Fabric和其他主流区块链平台的常用API接口,提供清晰的使用示例和调用方法,帮助开发者快速上手实际项目开发。 ## 一、Hyperledger Fabric API接口 ### 1.1 链码(Chaincode)管理接口 Hyp...

gitblog_00896的博客 1150

VLSI数字信号处理系统——第九章滤波器和变换中的算法强度缩减

[课程学习] VLSI数字信号处理系统——第九章滤波器和变换中的算法强度缩减

夏风喃喃的博客 1604

Hyperledger Fabric2中文文档-开发链码

翻译官方文档https://hyperledger-fabric.readthedocs.io/en/latest/chaincode4ade.html 什么是链码 链码是程序,用Go,Node.js,Java其中一种语言编程。链码运行在Peer的独立进程中,负责初始化账本,管理账本状态。 链码通常用来处理网络成员同意的逻辑事务,所以它也被称为“智能合约”。可以调用链码更新或者查询交易。如果有合适的权限,两码可以调用另一个链码,无论是否在一个channel中,获取账本状态。注意如果被调用的链码和链码处

ling的专栏 1401

ZYNQ Linux下UIO中断配置避坑实录:从设备树修改到用户层测试(Vivado 2018.2)

本文详细解析了ZYNQ Linux下UIO中断配置的全流程,从设备树修改到用户层测试,特别针对Vivado 2018.2环境中的常见陷阱提供解决方案。内容涵盖中断号配置规则、用户空间中断处理程序编写以及性能优化技巧,帮助开发者高效实现硬件中断的用户空间控制。

weixin_31854867的博客 334

深度强化学习在机器人控制中的应用

非常感谢您提供如此详细的任务要求和约束条件。作为一位世界级的人工智能专家和计算机领域大师,我将全身心投入到这篇技术博客的撰写中。 深度强化学习在机器人控制中的应用 1. 背景介绍 近年来,随着人工智能技术的飞速发展,深度学习和强化学习在各个领域都得到了广泛应用,尤其是在机器人

AI天才研究院 1324

Hyperledger Fabric基础知识笔记

地址 https://github.com/togettoyou/fabric-realty https://mp.weixin.qq.com/s/yDmGwfRjXxDJfgv1d0p3Ig 笔记 区块链基础知识 1.Block 每个区块记录上一区块hash值、本区块中的交易集合、本区块hash值等基础数据。区块串联形成链。 2.Blockchain 多方共享的分布式账本技术,记录交易。 去中心化:多参与者协作维护,无中心管理者。 不可修改:信息以附加形式记录到账本,使用加密技术。 3.Transacti

wohoo~ 8503

YOLO26 电子产品零部件质检:缺陷检测 + Streamlit

YOLO26s 电子质检链路:1100 张工业零部件图 (4 类缺陷) → 增强 (blur+shear+低光照) → 150 epoch → mAP@0.5=0.91 → Streamlit 质检界面 (上传/摄像头双模式) → 缺陷严重度分级 (pass/minor/major/critical)。核心贡献:blur+shear 模拟产线振动和视角偏移, 使模糊场景 recall 从 72% 提升至 78%;Streamlit 一键部署, 无需前端经验。建议先收集 200 张纯正常件做负样本防过检;

m0_71589036的博客 375

fabric可以跨链吗?

前言 今天公司让我整理一个基于fabric的跨链的方案,之前没怎么接触过跨链,在这里记录下自己的思路吧。 首先,先明白几个概念。什么是跨链?我的理解是跨链是跨channel。下面详细说下我的理由: 回顾下fabric的启动过程:创建证书,生成创世区块,通道配置交易块,创建通道,节点加入通道,安装链码,实例化链码,链码的调用。这个是完整的生命周期。 一个节点上可以安装多个chaincode,且每个...

俊语的博客 3464

UG\NX二次开发 分割曲线

分割曲线没有ufun函数?那就换一种思路实现。

王牌飞行员_里海的博客 1146

大数据数据埋点技术面试题及参考答案(持续更新)

事件:指的是用户执行的具体动作,如页面浏览、按钮点击、商品购买等。事件是触发数据记录的直接原因,是分析用户行为的关键点。属性:是与事件相关的额外信息,可以提供关于事件的更多上下文。例如,用户点击的按钮颜色、大小、页面位置等。属性有助于更细致地分析用户行为,理解事件发生的具体情境。事件和属性共同构成了数据埋点的数据模型,它们使得收集的数据具有可分析性,从而可以用于深入理解用户行为和优化产品。

大模型大数据攻城狮的专栏 2097

Fabric1.1.0链码部署

上传链码至宿主机目录 删除已存在的go文件,上传新的文件。 拷贝链码到docker容器里面 在docker容器中操作 docker exec -it cli bash cd /opt/gopath/src/github.com/hyperledger/fabric/mylc/chaincode/go/digital-rights/ 删除已存在的文件 rm -rf * 在宿主机进行chaincode拷贝 docker cp /www/fabric-chaincode/d..

yuch_hong的博客 579

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

2.08 hyperledger fabric完整案例

1.fabric开发流程 需求整理 合约编写 合约部署 合约交互 外部服务编写 2.需求分析 开发一个资产转让功能模块 平台功能 用户开户和销户 资产登记,解决资产上链和用户绑定资产 资产转让,资产所有权的变更 查询功能,用户查询、资产查询、资产变更历史查询 3.合约编写 assetsExchange.go 4.合约部署 关闭docker 生成通道的创世交易 查看...

尹成的技术博客 3501

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

对hyperledger fabric的理解和部署(使用docker镜像)

对hyperledger fabric的理解和部署(使用docker镜像) 区块链基础 要学习hyperledger fabric,首先要了解一点区块链知识,下面博主自己整理的 区块链的一些基础知识(用思维导图展示) hyperledger fabric基础 在对区块链有一些基础的了解后,我们进入正题,开始接触hyperledger fabric,下面是我整合网络上对hyperledger fa...

weixin_41229044的博客 4466

hyperLedger Fabric 系列一

1. 前言   当前在公司一直学习和研究区块链的相关知识,在大致了解了公链(bitcoin)之后,即将主要的精力投入到联盟链的学习和研究中。对于联盟链而言,我们是以hyperLedger作为主要的研究对象。本人作为一个区块链行业的小白级人物,将以此系列作为自己学习和调研经历的总结和整理,为后续区块链项目应用提供指导。   本文档作为整个系列的第一部分,介绍的内容大致包括: hyperLeder项...

wangyuezhilang的博客 7281
上一篇: 认证、密钥、随机数与技术应用(上)
下一篇: gRPC简介
The_Web3_社区
博客等级 码龄10年 388粉丝 154原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

The_Web3_社区

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值