golang中gRpc的安装&goland 插件protobuf support

一、简介
1、Protocol Buffers(简称protobuf)用于结构化数据和字节码之间互相转换(即实现从结构体转换为字节流以及从字节流转换为结构体),一旦你定义了自己的报文格式(message),你就可以运行ProtocolBuffer编译器,将你的.proto文件编译成特定语言的类。
2、proto里的message,pb.go里的结构体(带方法)
proto里的service,pb.go里Client API for Events service处供客户端使用的接口定义、接口实例、接口实例的初始化函数。Server API for Events service处供服务端使用的接口定义,注册函数。如果其中某一端或同时两端为流式RPC,在有流的一端,会专门为其流生成接口定义、接口实例。可以直接使用生成的实例,也可以自己实现接口,自定义实例。接口定义的主要方法就是Send和Recv。
3、GRPC的Client与Server,均通过Netty Channel作为数据通信;序列化、反序列化则使用Protobuf,每个请求都将被封装成HTTP2的Stream,在整个生命周期中,客户端Channel应该保持长连接,而不是每次调用重新创建Channel、响应结束后关闭Channel(即短连接、交互式的RPC),目的就是达到链接的复用,进而提高交互效率。
4、服务端用pb.go里的方法主要有注册;客户端用pb.go主要用生成客户端实例,再调用实例的方法。
二、安装
1、https://github.com/protocolbuffers/protobuf/releases,下载protocol buffer编译器protoc,找到对应的系统版本,如linux下为x86_64。下载后解压,将bin目录下的可执行文件放到系统环境变量$GOPATH/bin下。
2、$GOPATH:go get -u github.com/golang/protobuf/protoc-gen-go,获取protol buffer编译器的插件protoc-gen-go,gRPC 提供 protocol buffer 编译插件,能够从一个服务定义的 .proto 文件生成客户端和服务端代码。
3、$GOPATH:go get github.com/golang/protobuf/proto,将库源码拉到本地,执行此命令时会自动下载库依赖。
4、go build github.com/golang/protobuf/proto,进行编译。
(github源码:https://github.com/grpc/grpc-go)
5、git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc
6、git clone https://github.com/golang/net.git $GOPATH/src/golang.org/x/net
7、git clone https://github.com/golang/sys.git $GOPATH/src/golang.org/x/sys
8、git clone https://github.com/golang/text.git $GOPATH/src/golang.org/x/text
9、git clone https://github.com/google/go-genproto.git $GOPATH/src/google.golang.org/genproto
即protoc编译器二进制文件和插件protoc-gen-go

三、运行hellowrold例子:

$GOPATH/src/google.golang.org/grpc/examples/helloworld/greeter_server$ go run main.go
$GOPATH/src/google.golang.org/grpc/examples/helloworld/greeter_client$ go run main.go

输出:

wang@wang:~/go/src/google.golang.org/grpc/examples/helloworld/greeter_client$ go run main.go 
2018/09/22 17:20:40 Greeting: Hello world

四、再做一个Demo:
在这里插入图片描述
新建工程grpcT,目录如上。
1.common.proto内容:

syntax = "proto3";
package common;
option java_multiple_files = true;
option java_package = "io.grpc.examples.common";
option java_outer_classname = "BlockServiceProto";
message Block {
int32 id = 1;
string hash = 2;
string payload = 3;
}
service BlockService {
rpc BKService (BlockRequest) returns (BlockResponse) {}
}
message BlockRequest {
string name = 1;
}
message BlockResponse {
string message = 1;
}

使用protoc命令编译.proto文件:

  • -I 参数:指定import路径,可以指定多个 -I参数,按顺序查找,默认只查找当前目录
  • –go_out :golang编译支持,支持以下参数:
    1、plugins=plugin1+plugin2 - 指定插件,目前只有grpc,即:plugins=grpc
    2、M 参数 - 指定导入的.proto文件路径编译后对应的golang包名(不指定本参数默认就是.proto文件中import语句的路径)
    3、import_prefix=xxx - 为所有import路径添加前缀,主要用于编译子目录内的多个proto文件,这个参数按理说很有用,尤其适用替代hello_http.proto编译时的M参数,但是实际使用时有个蛋疼的问题,自己尝试看看吧
    4、import_path=foo/bar - 用于指定未声明package或go_package的文件的包名,最右面的斜线前的字符会被忽略
    5、:编译文件路径 .proto文件路径(支持通配符)
    6、同一个包内包含多个.proto文件时使用通配符同时编译所有文件,单独编译每个文件会存在变量命名冲突

完整示例:

protoc --go_out=plugins=grpc,Mfoo/bar.proto=bar,import_prefix=foo/,import_path=foo/bar:. ./*.proto

利用protoc在当前文件夹内生成pb源代码文件:
命令:protoc --go_out=plugins=grpc:. ./common.proto
对生成的common.pb.go文件,对文件内的xxx内容进行删除,删除完如下:

// Code generated by protoc-gen-go. DO NOT EDIT.
// source: common.proto

package common

import (
	fmt "fmt"
	proto "github.com/golang/protobuf/proto"
	math "math"
)

import (
	context "golang.org/x/net/context"
	grpc "google.golang.org/grpc"
)

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package

type Block struct {
	Id                   int32    `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
	Hash                 string   `protobuf:"bytes,2,opt,name=hash,proto3" json:"hash,omitempty"`
	Payload              string   `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"`
	//XXX_NoUnkeyedLiteral struct{} `json:"-"`
	//XXX_unrecognized     []byte   `json:"-"`
	//XXX_sizecache        int32    `json:"-"`
}

func (m *Block) Reset()         { *m = Block{} }
func (m *Block) String() string { return proto.CompactTextString(m) }
func (*Block) ProtoMessage()    {}
func (*Block) Descriptor() ([]byte, []int) {
	return fileDescriptor_555bd8c177793206, []int{0}
}

//func (m *Block) XXX_Unmarshal(b []byte) error {
//	return xxx_messageInfo_Block.Unmarshal(m, b)
//}
//func (m *Block) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
//	return xxx_messageInfo_Block.Marshal(b, m, deterministic)
//}
//func (m *Block) XXX_Merge(src proto.Message) {
//	xxx_messageInfo_Block.Merge(m, src)
//}
//func (m *Block) XXX_Size() int {
//	return xxx_messageInfo_Block.Size(m)
//}
//func (m *Block) XXX_DiscardUnknown() {
//	xxx_messageInfo_Block.DiscardUnknown(m)
//}
//
//var xxx_messageInfo_Block proto.InternalMessageInfo

func (m *Block) GetId() int32 {
	if m != nil {
		return m.Id
	}
	return 0
}

func (m *Block) GetHash() string {
	if m != nil {
		return m.Hash
	}
	return ""
}

func (m *Block) GetPayload() string {
	if m != nil {
		return m.Payload
	}
	return ""
}

type BlockRequest struct {
	Name                 string   `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
	//XXX_NoUnkeyedLiteral struct{} `json:"-"`
	//XXX_unrecognized     []byte   `json:"-"`
	//XXX_sizecache        int32    `json:"-"`
}

func (m *BlockRequest) Reset()         { *m = BlockRequest{} }
func (m *BlockRequest) String() string { return proto.CompactTextString(m) }
func (*BlockRequest) ProtoMessage()    {}
func (*BlockRequest) Descriptor() ([]byte, []int) {
	return fileDescriptor_555bd8c177793206, []int{1}
}

//func (m *BlockRequest) XXX_Unmarshal(b []byte) error {
//	return xxx_messageInfo_BlockRequest.Unmarshal(m, b)
//}
//func (m *BlockRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
//	return xxx_messageInfo_BlockRequest.Marshal(b, m, deterministic)
//}
//func (m *BlockRequest) XXX_Merge(src proto.Message) {
//	xxx_messageInfo_BlockRequest.Merge(m, src)
//}
//func (m *BlockRequest) XXX_Size() int {
//	return xxx_messageInfo_BlockRequest.Size(m)
//}
//func (m *BlockRequest) XXX_DiscardUnknown() {
//	xxx_messageInfo_BlockRequest.DiscardUnknown(m)
//}
//
//var xxx_messageInfo_BlockRequest proto.InternalMessageInfo

func (m *BlockRequest) GetName() string {
	if m != nil {
		return m.Name
	}
	return ""
}

type BlockResponse struct {
	Message              string   `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
	//XXX_NoUnkeyedLiteral struct{} `json:"-"`
	//XXX_unrecognized     []byte   `json:"-"`
	//XXX_sizecache        int32    `json:"-"`
}

func (m *BlockResponse) Reset()         { *m = BlockResponse{} }
func (m *BlockResponse) String() string { return proto.CompactTextString(m) }
func (*BlockResponse) ProtoMessage()    {}
func (*BlockResponse) Descriptor() ([]byte, []int) {
	return fileDescriptor_555bd8c177793206, []int{2}
}

//func (m *BlockResponse) XXX_Unmarshal(b []byte) error {
//	return xxx_messageInfo_BlockResponse.Unmarshal(m, b)
//}
//func (m *BlockResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
//	return xxx_messageInfo_BlockResponse.Marshal(b, m, deterministic)
//}
//func (m *BlockResponse) XXX_Merge(src proto.Message) {
//	xxx_messageInfo_BlockResponse.Merge(m, src)
//}
//func (m *BlockResponse) XXX_Size() int {
//	return xxx_messageInfo_BlockResponse.Size(m)
//}
//func (m *BlockResponse) XXX_DiscardUnknown() {
//	xxx_messageInfo_BlockResponse.DiscardUnknown(m)
//}
//
//var xxx_messageInfo_BlockResponse proto.InternalMessageInfo

func (m *BlockResponse) GetMessage() string {
	if m != nil {
		return m.Message
	}
	return ""
}

func init() {
	proto.RegisterType((*Block)(nil), "common.Block")
	proto.RegisterType((*BlockRequest)(nil), "common.BlockRequest")
	proto.RegisterType((*BlockResponse)(nil), "common.BlockResponse")
}

// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn

// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4

// BlockServiceClient is the client API for BlockService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type BlockServiceClient interface {
	BKService(ctx context.Context, in *BlockRequest, opts ...grpc.CallOption) (*BlockResponse, error)
}

type blockServiceClient struct {
	cc *grpc.ClientConn
}

func NewBlockServiceClient(cc *grpc.ClientConn) BlockServiceClient {
	return &blockServiceClient{cc}
}

func (c *blockServiceClient) BKService(ctx context.Context, in *BlockRequest, opts ...grpc.CallOption) (*BlockResponse, error) {
	out := new(BlockResponse)
	err := c.cc.Invoke(ctx, "/common.BlockService/BKService", in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

// BlockServiceServer is the server API for BlockService service.
type BlockServiceServer interface {
	BKService(context.Context, *BlockRequest) (*BlockResponse, error)
}

func RegisterBlockServiceServer(s *grpc.Server, srv BlockServiceServer) {
	s.RegisterService(&_BlockService_serviceDesc, srv)
}

func _BlockService_BKService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(BlockRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(BlockServiceServer).BKService(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: "/common.BlockService/BKService",
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(BlockServiceServer).BKService(ctx, req.(*BlockRequest))
	}
	return interceptor(ctx, in, info, handler)
}

var _BlockService_serviceDesc = grpc.ServiceDesc{
	ServiceName: "common.BlockService",
	HandlerType: (*BlockServiceServer)(nil),
	Methods: []grpc.MethodDesc{
		{
			MethodName: "BKService",
			Handler:    _BlockService_BKService_Handler,
		},
	},
	Streams:  []grpc.StreamDesc{},
	Metadata: "common.proto",
}

func init() { proto.RegisterFile("common.proto", fileDescriptor_555bd8c177793206) }

var fileDescriptor_555bd8c177793206 = []byte{
	// 218 bytes of a gzipped FileDescriptorProto
	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x90, 0xc1, 0x4a, 0x03, 0x31,
	0x10, 0x86, 0xcd, 0x6a, 0x2b, 0x3b, 0x54, 0xc1, 0xa0, 0x18, 0x3c, 0x95, 0x9c, 0xea, 0x25, 0x07,
	0xbd, 0x79, 0x0c, 0x78, 0xd1, 0x4b, 0x89, 0x4f, 0x10, 0xb3, 0x43, 0x1b, 0xdc, 0xec, 0xc4, 0x4d,
	0x15, 0x7d, 0x7b, 0xd9, 0x34, 0x81, 0xc5, 0xdb, 0x7c, 0xc3, 0x9f, 0xef, 0x27, 0x03, 0x2b, 0x47,
	0x21, 0xd0, 0xa0, 0xe2, 0x48, 0x07, 0xe2, 0xcb, 0x23, 0xc9, 0x67, 0x58, 0xe8, 0x9e, 0xdc, 0x07,
	0xbf, 0x84, 0xc6, 0x77, 0x82, 0xad, 0xd9, 0x66, 0x61, 0x1a, 0xdf, 0x71, 0x0e, 0x67, 0x7b, 0x9b,
	0xf6, 0xa2, 0x59, 0xb3, 0x4d, 0x6b, 0xf2, 0xcc, 0x05, 0x9c, 0x47, 0xfb, 0xdb, 0x93, 0xed, 0xc4,
	0x69, 0x5e, 0x57, 0x94, 0x12, 0x56, 0x59, 0x63, 0xf0, 0xf3, 0x0b, 0xd3, 0x61, 0x7a, 0x3d, 0xd8,
	0x80, 0xd9, 0xd7, 0x9a, 0x3c, 0xcb, 0x7b, 0xb8, 0x28, 0x99, 0x14, 0x69, 0x48, 0x38, 0xe9, 0x02,
	0xa6, 0x64, 0x77, 0x35, 0x57, 0xf1, 0xe1, 0xa5, 0xe8, 0xde, 0x70, 0xfc, 0xf6, 0x0e, 0xf9, 0x13,
	0xb4, 0xfa, 0xb5, 0xc2, 0xb5, 0x2a, 0x3f, 0x99, 0x37, 0xde, 0xdd, 0xfc, 0xdb, 0x1e, 0x3b, 0xe4,
	0x89, 0x56, 0x70, 0xeb, 0x49, 0xed, 0xc6, 0xe8, 0x14, 0xfe, 0xd8, 0x10, 0x7b, 0x4c, 0x25, 0xaa,
	0xaf, 0xe6, 0x25, 0xdb, 0xe9, 0x2e, 0x5b, 0xf6, 0xbe, 0xcc, 0x07, 0x7a, 0xfc, 0x0b, 0x00, 0x00,
	0xff, 0xff, 0x70, 0x66, 0xc7, 0xc1, 0x30, 0x01, 0x00, 0x00,
}

2.写对应的server.go和client.go。
server.go内容如下:

package main

import (
	"grpcT/common"
	"net"
	"log"
	"google.golang.org/grpc"
	"golang.org/x/net/context"
)

const (
	port = "8080"
)
type server struct {
}
func (s *server)BKService(ctx context.Context, in *common.BlockRequest) (*common.BlockResponse, error) {
	return &common.BlockResponse{"bolck msg"+in.Name}, nil
}
//listen,server on
func main() {
	listen, err := net.Listen("tcp4", "localhost:"+port)
	if err != nil {
		log.Fatal("failed to listen: %v",err)
	}
	s := grpc.NewServer()
	common.RegisterBlockServiceServer(s,&server{})
	s.Serve(listen)
}

client.go内容如下:

package main

import (
	"google.golang.org/grpc"
	"log"
	"grpcT/common"
	"os"
	"golang.org/x/net/context"
)

const (
	address = "localhost:8080"
	defaultName = "block"
)
func main() {
	conn, err := grpc.Dial(address,grpc.WithInsecure())
	if err != nil {
		log.Fatal("did not connect: %v",err )
	}
	defer conn.Close()
	client := common.NewBlockServiceClient(conn)
	name := defaultName
	if len(os.Args) >1 {
		name = os.Args[1]
	}
	request, err := client.BKService(context.Background(), &common.BlockRequest{name})
	if err != nil {
		log.Fatal("could not block: %v", err)
	}
	log.Printf("block: %s",request.Message)
}

如果没删除pb.go中内容则对server.go和client.go中内容进行对应修改:

server.go
	return &common.BlockResponse{"bolck msg"+in.Name, struct{}{},[]byte{},2}, nil
client.go
	request, err := client.BKService(context.Background(), &common.BlockRequest{name, struct{}{},[]byte{},10})

运行:

wang@wang:~/go/src/grpcT/main$ go run server.go 
wang@wang:~/go/src/grpcT/main$ go run client.go 

输出:

wang@wang:~/go/src/grpcT/main$ go run client.go 
2018/09/22 17:29:00 block: bolck msgblock

五、goland 插件protobuf support
1、直接能找到插件
linux下goland2018.1.5安装了0.10.2版本的Protobuf Support:
file->Settings->Plugins->Browse repositories->输入protobuf support->install
file->Settings->Editor->File Types,找到Protobuf,进行注册*.proto->restart goland
2、如果没有Protobuf Support插件则可手动安装:
https://github.com/protostuff/protobuf-jetbrains-plugin/releases找到对应goland版本的版本,如我用的goland2018.1,下载的protobuf-jetbrains-plugin-0.10.2.zip。下载完直接拖进打开的goland,会提示重启goland。可能出现的情况就是版本不兼容。
在这里插入图片描述
6、更换插件版本

../../vendor/github.com/hyperledger/fabric/opbridge/opbridge/opbridge.pb.go:44:11: undefined: "github.com/hyperledger/fabric/vendor/github.com/golang/protobuf/proto".ProtoPackageIsVersion4

遇到问题undefined: proto.ProtoPackageIsVersion4时候,发现fabric2.1所用的版本是:const _ = proto.ProtoPackageIsVersion3,需要将protobuf版本回退到v1.3.0,重新编译proto-gen-go插件:

wang@wang:~/go/src/github.com/golang/protobuf$ git checkout -f v1.3.0
Note: checking out 'v1.3.0'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at c823c79... ptypes: More cleanly construct a Timestamp (#797)
wang@wang:~/go/src/github.com/golang/protobuf$
wang@wang:~/go/src/github.com/golang/protobuf$ cd protoc-gen-go/
wang@wang:~/go/src/github.com/golang/protobuf/protoc-gen-go$ ls
descriptor  generator       grpc          main.go  testdata
doc.go      golden_test.go  link_grpc.go  plugin
wang@wang:~/go/src/github.com/golang/protobuf/protoc-gen-go$ go install

然后重新生成xx.pb.go文件,查看const _ = proto.ProtoPackageIsVersion3

<think>好的,我现在需要帮用户解决关于annotations.proto文件的问题。首先,用户想知道这个文件的内容、格式和使用说明。根据提供的引用内容,我得先整理相关信息。 用户提供的引用中提到了多个来源,其中引用1、2、5都提到了在Kratos框架中使用annotations.proto时遇到的路径问题。解决方案包括手动下载文件并放置到正确的位置,或者配置IDE(如Goland)的Protocol Buffers路径。引用3和4提到了两种存储方式:放在项目目录下或protobuf安装路径下,并调整编译参数或IDE设置。 接下来,我需要确定annotations.proto的具体内容和作用。根据引用3,这个文件通常与HTTP相关,可能用于定义gRPC服务的HTTP映射。例如,可能包含自定义的Protobuf选项,如HttpRule,用于指定REST API的路径和方法。 关于文件内容,可能包括package声明、import其他proto文件(如google/protobuf/descriptor.proto)、以及定义option。例如,可能扩展MethodOptions以添加HTTP配置。 用户可能需要知道如何正确导入和使用这些选项,比如在服务方法中添加http规则。同时,编译时需要指定正确的导入路径,使用protoc的-I参数指向存放这些文件的目录。 还要注意,用户可能遇到的常见问题,如文件找不到,需要确保文件路径正确,或者通过go get获取依赖。例如,引用5提到执行kratos命令时缺少文件,可能需要安装相应的依赖包。 最后,生成相关问题需要考虑用户可能的后续疑问,比如如何解决编译错误、配置IDE、或者具体的HTTP映射配置方法。需要根据引用中的解决方案来提出相关问题。</think>### `annotations.proto` 文件说明 `annotations.proto` 是 Google API 中定义协议缓冲区(Protobuf)扩展的文件,主要用于 gRPC 服务的 HTTP 映射配置。以下从内容、格式、使用场景三方面说明: --- #### **1. 文件内容** - **核心作用**:定义 HTTP 到 gRPC 方法的映射规则,例如 REST API 的路径、请求方法、参数绑定等。 - **关键字段**: ```protobuf syntax = "proto3"; package google.api; import "google/protobuf/descriptor.proto"; // 扩展描述符 // 定义HTTP方法的扩展选项 extend google.protobuf.MethodOptions { HttpRule http = 72295728; // 自定义HTTP规则 } // HTTP规则的具体配置 message HttpRule { string selector = 1; string get = 2; // 对应GET方法 string put = 3; // 对应PUT方法 // ...其他方法字段 } ``` - **依赖项**:需导入 `google/protobuf/descriptor.proto`,用于扩展 Protobuf 的选项功能[^3][^5]。 --- #### **2. 文件格式** - **标准 Protobuf 语法**:遵循 `proto3` 语法,通过 `extend` 关键字扩展方法选项。 - **HTTP 映射示例**: ```protobuf service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) { option (google.api.http) = { get: "/v1/hello/{name}" // 将gRPC方法映射到GET请求 }; } } ``` --- #### **3. 使用说明** - **文件路径配置**: - **本地存储**:将 `annotations.proto` 和依赖文件(如 `http.proto`)放入项目目录(例如 `third_party/google/api/`)。 - **编译命令**:需通过 `-I` 指定路径: ```bash protoc -I=. -I=./third_party --go_out=paths=source_relative:. api/helloworld.proto ``` - **IDE 配置**(如 Goland): - 取消自动配置 Protocol Buffers 路径,手动添加包含 `google/api` 的目录(如项目中的 `third_party`)[^2][^4]。 --- #### **常见问题解决** - **文件缺失错误**: - **方案1**:手动下载文件并放置到 `$GOPATH/src/google/api/` 或项目目录下[^1]。 - **方案2**:通过 `go get google.golang.org/genproto/googleapis/api/annotations` 安装依赖。 - **Kratos 框架集成**:需确保编译时 Protobuf 路径包含 `third_party` 文件夹[^5]。 ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值