消息定义与数据类型
2026/1/15大约 4 分钟ProtobufMessage数据类型
消息定义与数据类型
一、消息定义
1.1 基本消息
syntax = "proto3";
message User {
int32 id = 1;
string name = 2;
string email = 3;
}1.2 嵌套消息
message Person {
string name = 1;
message Address {
string street = 1;
string city = 2;
string country = 3;
}
Address address = 2;
}1.3 导入其他 proto 文件
// common.proto
syntax = "proto3";
message Timestamp {
int64 seconds = 1;
int32 nanos = 2;
}// user.proto
syntax = "proto3";
import "common.proto";
message User {
string name = 1;
Timestamp created_at = 2;
}二、标量类型
2.1 数值类型
message Numbers {
// 整数类型
int32 age = 1; // -2^31 到 2^31-1
int64 timestamp = 2; // -2^63 到 2^63-1
uint32 count = 3; // 0 到 2^32-1
uint64 big_count = 4; // 0 到 2^64-1
// 固定长度整数(总是 4/8 字节)
fixed32 id = 5;
fixed64 big_id = 6;
sfixed32 signed_id = 7;
sfixed64 big_signed_id = 8;
// 浮点数
float score = 9;
double price = 10;
}2.2 字符串和字节
message TextData {
string name = 1; // UTF-8 或 ASCII 字符串
bytes data = 2; // 任意字节序列
bytes image = 3; // 可用于存储二进制数据
}2.3 布尔类型
message Flags {
bool active = 1;
bool verified = 2;
bool deleted = 3;
}三、复合类型
3.1 枚举(Enum)
enum Status {
STATUS_UNSPECIFIED = 0; // 必须有 0 值
STATUS_PENDING = 1;
STATUS_APPROVED = 2;
STATUS_REJECTED = 3;
}
message Order {
int32 id = 1;
Status status = 2;
}Go 使用:
order := &pb.Order{
Id: 1,
Status: pb.Status_STATUS_APPROVED,
}3.2 重复字段(Repeated)
message User {
string name = 1;
repeated string emails = 2; // 字符串数组
repeated int32 scores = 3; // 整数数组
repeated Address addresses = 4; // 消息数组
}Go 使用:
user := &pb.User{
Name: "张三",
Emails: []string{"a@example.com", "b@example.com"},
Scores: []int32{90, 85, 95},
}3.3 Map 类型
message User {
string name = 1;
map<string, string> metadata = 2; // 字符串映射
map<int32, Address> addresses = 3; // 整数到消息的映射
map<string, int32> scores = 4; // 字符串到整数的映射
}Go 使用:
user := &pb.User{
Name: "张三",
Metadata: map[string]string{
"role": "admin",
"level": "5",
},
Scores: map[string]int32{
"math": 90,
"english": 85,
},
}四、Oneof
4.1 基本用法
message Payment {
oneof payment_method {
string credit_card = 1;
string paypal_email = 2;
string bank_account = 3;
}
}Go 使用:
// 使用信用卡
payment := &pb.Payment{
PaymentMethod: &pb.Payment_CreditCard{
CreditCard: "1234-5678-9012-3456",
},
}
// 使用 PayPal
payment := &pb.Payment{
PaymentMethod: &pb.Payment_PaypalEmail{
PaypalEmail: "user@example.com",
},
}
// 检查类型
switch x := payment.PaymentMethod.(type) {
case *pb.Payment_CreditCard:
fmt.Println("信用卡:", x.CreditCard)
case *pb.Payment_PaypalEmail:
fmt.Println("PayPal:", x.PaypalEmail)
}五、默认值
5.1 Proto3 默认值
| 类型 | 默认值 |
|---|---|
| string | 空字符串 "" |
| bytes | 空字节 |
| bool | false |
| 数值类型 | 0 |
| 枚举 | 第一个值(必须是 0) |
| 消息 | nil |
| repeated | 空列表 |
| map | 空映射 |
5.2 示例
message User {
string name = 1; // 默认 ""
int32 age = 2; // 默认 0
bool active = 3; // 默认 false
}user := &pb.User{}
fmt.Println(user.Name) // ""
fmt.Println(user.Age) // 0
fmt.Println(user.Active) // false六、保留字段
6.1 保留字段编号
message User {
reserved 2, 15, 9 to 11; // 保留字段编号
reserved "old_field"; // 保留字段名
string name = 1;
// int32 age = 2; // 错误:2 已被保留
string email = 3;
}6.2 为什么需要保留
// 旧版本
message User {
string name = 1;
int32 age = 2; // 后来删除了
}
// 新版本(错误)
message User {
string name = 1;
string email = 2; // 复用了 age 的编号,会导致兼容性问题
}
// 新版本(正确)
message User {
reserved 2; // 保留 age 的编号
string name = 1;
string email = 3;
}七、Any 类型
7.1 定义
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}7.2 使用
import (
"google.golang.org/protobuf/types/known/anypb"
)
// 打包任意消息
user := &pb.User{Name: "张三"}
anyMsg, _ := anypb.New(user)
status := &pb.ErrorStatus{
Message: "错误详情",
Details: []*anypb.Any{anyMsg},
}
// 解包
for _, detail := range status.Details {
if detail.MessageIs(&pb.User{}) {
user := &pb.User{}
detail.UnmarshalTo(user)
fmt.Println(user.Name)
}
}八、Timestamp 和 Duration
8.1 Timestamp
import "google/protobuf/timestamp.proto";
message Event {
string name = 1;
google.protobuf.Timestamp created_at = 2;
}import (
"google.golang.org/protobuf/types/known/timestamppb"
"time"
)
event := &pb.Event{
Name: "用户登录",
CreatedAt: timestamppb.New(time.Now()),
}
// 转换回 time.Time
t := event.CreatedAt.AsTime()8.2 Duration
import "google/protobuf/duration.proto";
message Task {
string name = 1;
google.protobuf.Duration timeout = 2;
}import (
"google.golang.org/protobuf/types/known/durationpb"
"time"
)
task := &pb.Task{
Name: "数据处理",
Timeout: durationpb.New(5 * time.Minute),
}
// 转换回 time.Duration
d := task.Timeout.AsDuration()九、完整示例
9.1 用户系统
syntax = "proto3";
package user;
option go_package = "example.com/pb/user";
import "google/protobuf/timestamp.proto";
// 用户状态
enum UserStatus {
USER_STATUS_UNSPECIFIED = 0;
USER_STATUS_ACTIVE = 1;
USER_STATUS_INACTIVE = 2;
USER_STATUS_BANNED = 3;
}
// 地址
message Address {
string street = 1;
string city = 2;
string state = 3;
string country = 4;
string zip_code = 5;
}
// 用户
message User {
int32 id = 1;
string username = 2;
string email = 3;
UserStatus status = 4;
repeated string roles = 5;
map<string, string> metadata = 6;
Address address = 7;
google.protobuf.Timestamp created_at = 8;
google.protobuf.Timestamp updated_at = 9;
}
// 用户列表
message UserList {
repeated User users = 1;
int32 total = 2;
int32 page = 3;
int32 page_size = 4;
}9.2 Go 使用
package main
import (
"fmt"
"time"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/timestamppb"
pb "example.com/pb/user"
)
func main() {
user := &pb.User{
Id: 1,
Username: "zhangsan",
Email: "zhangsan@example.com",
Status: pb.UserStatus_USER_STATUS_ACTIVE,
Roles: []string{"admin", "user"},
Metadata: map[string]string{
"department": "IT",
"level": "senior",
},
Address: &pb.Address{
Street: "123 Main St",
City: "Beijing",
Country: "China",
ZipCode: "100000",
},
CreatedAt: timestamppb.New(time.Now()),
UpdatedAt: timestamppb.New(time.Now()),
}
// 序列化
data, _ := proto.Marshal(user)
fmt.Printf("序列化大小: %d 字节\n", len(data))
// 反序列化
newUser := &pb.User{}
proto.Unmarshal(data, newUser)
fmt.Printf("用户: %s\n", newUser.Username)
fmt.Printf("状态: %s\n", newUser.Status)
fmt.Printf("角色: %v\n", newUser.Roles)
fmt.Printf("地址: %s, %s\n", newUser.Address.City, newUser.Address.Country)
}