From 1225a0c3a0e44ef5111060438a28a31f5c81fc56 Mon Sep 17 00:00:00 2001 From: 170-carry Date: Wed, 29 Apr 2026 23:07:50 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=92=B1=E5=8C=85=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E5=8E=BB=E6=8E=89=E5=85=BC=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 18 +- Makefile | 45 +- README.md | 20 +- api/proto/activity/v1/activity.pb.go | 250 +- api/proto/activity/v1/activity_grpc.pb.go | 2 +- api/proto/events/room/v1/events.pb.go | 899 +++--- api/proto/events/room/v1/events.proto | 42 +- api/proto/room/v1/room.pb.go | 2482 +++++++++-------- api/proto/room/v1/room.proto | 111 +- api/proto/room/v1/room_grpc.pb.go | 248 +- api/proto/user/v1/auth.pb.go | 617 ++-- api/proto/user/v1/auth.proto | 27 +- api/proto/user/v1/auth_grpc.pb.go | 2 +- api/proto/user/v1/user.pb.go | 2052 ++++---------- api/proto/user/v1/user_grpc.pb.go | 2 +- api/proto/wallet/v1/wallet.pb.go | 580 +++- api/proto/wallet/v1/wallet.proto | 54 +- api/proto/wallet/v1/wallet_grpc.pb.go | 84 +- docker-compose.yml | 2 +- docs/auth-login-register-technical-design.md | 47 +- docs/graceful-shutdown.md | 2 +- docs/health-checks.md | 2 +- ...lucky-gift-activity-service-development.md | 12 +- docs/openapi/activity.swagger.yaml | 177 ++ docs/openapi/gateway.swagger.yaml | 419 ++- docs/openapi/room.swagger.yaml | 286 +- docs/openapi/user.swagger.yaml | 4 - docs/openapi/wallet.swagger.yaml | 175 ++ docs/service-foundation-technical-design.md | 18 +- ...ncent-rtc-token-uid-mapping-development.md | 67 +- docs/user-region-country-development.md | 49 +- docs/voice-room-basic-loop-implementation.md | 2 +- docs/voice-room-feature-inventory.md | 8 +- ...ce-room-mic-room-management-development.md | 747 +++++ docs/wallet-service-architecture.md | 480 ++++ pkg/grpchealth/health.go | 2 +- pkg/roomid/roomid.go | 4 +- pkg/tencentim/rest_client.go | 30 + pkg/tencentim/rest_client_test.go | 29 + pkg/tencentrtc/token.go | 2 +- pkg/tencentrtc/token_test.go | 5 +- scripts/print-compose-addresses.sh | 12 +- services/activity-service/configs/config.yaml | 2 +- .../internal/config/config.go | 2 +- .../internal/client/room_client.go | 30 + .../internal/transport/http/auth_handler.go | 14 +- .../internal/transport/http/handler.go | 3 +- .../internal/transport/http/response.go | 7 +- .../internal/transport/http/response_test.go | 141 +- .../internal/transport/http/room_handler.go | 148 +- .../internal/transport/http/router.go | 6 + .../transport/http/tencent_im_callback.go | 3 - .../room-service/configs/config.docker.yaml | 2 + .../configs/config.tencent.example.yaml | 2 + services/room-service/configs/config.yaml | 4 +- services/room-service/internal/app/app.go | 9 + .../room-service/internal/config/config.go | 12 +- .../internal/integration/tencent_im.go | 84 +- .../internal/integration/tencent_im_test.go | 48 + .../internal/room/command/command.go | 144 +- .../internal/room/service/authorization.go | 117 + .../internal/room/service/create_room.go | 48 +- .../internal/room/service/event_attrs.go | 16 + .../internal/room/service/gift.go | 56 +- .../internal/room/service/management.go | 288 ++ .../room-service/internal/room/service/mic.go | 282 +- .../room/service/mic_publish_timeout.go | 90 + .../internal/room/service/moderation.go | 105 +- .../internal/room/service/outbox_worker.go | 8 + .../internal/room/service/pipeline.go | 63 +- .../internal/room/service/presence.go | 2 +- .../internal/room/service/recovery.go | 114 +- .../internal/room/service/repository.go | 6 +- .../internal/room/service/service.go | 17 + .../internal/room/service/service_test.go | 547 +++- .../internal/room/service/snapshot.go | 3 + .../room-service/internal/room/state/state.go | 58 +- .../internal/storage/mysql/repository.go | 16 +- .../internal/transport/grpc/server.go | 40 +- .../internal/transport/grpc/server_test.go | 6 +- services/user-service/configs/config.yaml | 2 +- .../deploy/mysql/initdb/001_user_service.sql | 11 - services/user-service/internal/app/app.go | 15 +- .../user-service/internal/config/config.go | 2 +- .../internal/domain/user/region.go | 15 - .../user-service/internal/domain/user/user.go | 22 - .../internal/service/auth/service.go | 22 - .../internal/service/auth/service_test.go | 54 +- .../internal/service/auth/token.go | 5 +- .../internal/service/user/pretty_id.go | 2 - .../internal/service/user/service.go | 28 - .../internal/service/user/service_test.go | 82 +- .../mysql/country_alias_integration_test.go | 86 - .../storage/mysql/identity_repository.go | 1 - .../storage/mysql/region_repository.go | 40 +- .../internal/storage/mysql/user_repository.go | 16 +- .../internal/testutil/mysqltest/mysqltest.go | 10 +- .../internal/transport/grpc/server.go | 24 +- .../internal/transport/grpc/server_test.go | 31 +- .../wallet-service/configs/config.docker.yaml | 3 - .../configs/config.tencent.example.yaml | 3 - services/wallet-service/configs/config.yaml | 5 +- .../mysql/initdb/001_wallet_service.sql | 92 +- services/wallet-service/internal/app/app.go | 9 +- .../wallet-service/internal/config/config.go | 17 +- .../internal/domain/ledger/ledger.go | 63 +- .../internal/domain/wallet/wallet.go | 14 - .../internal/service/wallet/service.go | 63 +- .../internal/service/wallet/service_test.go | 158 +- .../internal/storage/mysql/repository.go | 788 +++++- .../internal/testutil/mysqltest/mysqltest.go | 78 +- .../internal/transport/grpc/server.go | 66 +- 112 files changed, 9686 insertions(+), 4800 deletions(-) create mode 100644 docs/openapi/activity.swagger.yaml create mode 100644 docs/openapi/wallet.swagger.yaml create mode 100644 docs/voice-room-mic-room-management-development.md create mode 100644 docs/wallet-service-architecture.md create mode 100644 services/room-service/internal/room/service/authorization.go create mode 100644 services/room-service/internal/room/service/event_attrs.go create mode 100644 services/room-service/internal/room/service/management.go create mode 100644 services/room-service/internal/room/service/mic_publish_timeout.go delete mode 100644 services/user-service/internal/storage/mysql/country_alias_integration_test.go delete mode 100644 services/wallet-service/internal/domain/wallet/wallet.go diff --git a/AGENTS.md b/AGENTS.md index 32f87ba2..cf10bd83 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,6 +2,7 @@ ## Non-negotiables +0. 任何开发,不需要历史兼容,目前只是在开发阶段 1. 写代码不要猜。先读当前实现、接口、配置和测试,再改。 2. 不写流水账。文档、注释和总结都要解释边界、决策和可执行动作。 3. 前端或客户端改动不要添加无意义描述文案。界面文字必须服务真实操作或状态。 @@ -34,23 +35,10 @@ - `request_id` 是链路追踪字段,必须透传到内部 gRPC `RequestMeta.request_id`;不要把它当幂等键使用。 - 外部实时消息入口在腾讯云 IM SDK;本仓库只提供 `/api/v1/im/usersig` 签发登录票据。 - 内部服务通信统一 gRPC + protobuf。 -- Proto/API 必须保持向后兼容。不能直接删除字段、复用字段编号、修改字段含义、修改已有枚举值语义。 -- 新旧版本会在滚动发布期间同时在线,必须允许旧 `gateway-service` 调新 `room-service`,也必须允许新 `gateway-service` 调旧 `room-service`。 +- 当前处于开发阶段,不保留历史协议;Proto/API/DB 可以按当前事实直接调整。 - 修改 `api/proto` 后必须运行 `make proto`,并提交生成后的 `.pb.go` / `_grpc.pb.go`。 - 不要在服务之间直接 import 对方 `internal` 包。跨服务只走 protobuf client 或明确的接口抽象。 -## Compatibility Rules - -以下的注释都是目前不采用的策略,不需要根据这个策略去做 - - - ## Port Rules 本项目本地端口使用 `13xxx`: @@ -60,7 +48,7 @@ - `13004`: wallet gRPC - `13005`: user gRPC - `13006`: activity gRPC -- `13306`: local Docker MySQL host port +- `23306`: local Docker MySQL host port - `13379`: local Docker Redis host port 不要重新引入 `808x` 作为默认端口。 diff --git a/Makefile b/Makefile index 5cb38831..6518825a 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ SERVICE_ALIASES := gs gateway rs room ws wallet us user as activity mysql redis SERVICE_TOKEN := $(firstword $(filter $(SERVICE_ALIASES),$(MAKECMDGOALS))) SERVICE_ID := $(strip $(or $(SERVICE),$(if $(filter gs gateway,$(SERVICE_TOKEN)),gateway-service,$(if $(filter rs room,$(SERVICE_TOKEN)),room-service,$(if $(filter ws wallet,$(SERVICE_TOKEN)),wallet-service,$(if $(filter us user,$(SERVICE_TOKEN)),user-service,$(if $(filter as activity,$(SERVICE_TOKEN)),activity-service,$(SERVICE_TOKEN)))))))) -.PHONY: proto test build up rebuild restart stop logs ps addr down up-service compose-up compose-addresses compose-logs compose-down compose-up-service compose-rebuild-service compose-restart-service compose-stop-service compose-logs-service compose-ps-service check-service $(SERVICE_ALIASES) +.PHONY: proto test build up rebuild restart stop logs ps addr down up-service check-service $(SERVICE_ALIASES) # `proto` 负责把仓库内的 .proto 重新生成成 Go 代码。 proto: @@ -74,19 +74,6 @@ addr: down: docker compose down -# `compose-up` 兼容旧命令,等价于 `up`。 -compose-up: - docker compose up --build -d - ./scripts/print-compose-addresses.sh - -# `compose-addresses` 兼容旧命令,等价于 `addr`。 -compose-addresses: - ./scripts/print-compose-addresses.sh - -# `compose-logs` 兼容旧命令,等价于 `logs`。 -compose-logs: - docker compose logs -f - # `check-service` 校验单服务部署目标的 SERVICE 参数。 check-service: @if [ -z "$(SERVICE_ID)" ]; then \ @@ -107,36 +94,6 @@ up-service: check-service docker compose up -d $(SERVICE_ID) ./scripts/print-compose-addresses.sh -# `compose-up-service` 兼容旧命令,等价于 `up SERVICE=...`。 -compose-up-service: check-service - docker compose up -d $(SERVICE_ID) - ./scripts/print-compose-addresses.sh - -# `compose-rebuild-service` 兼容旧命令,等价于 `rebuild SERVICE=...`。 -compose-rebuild-service: check-service - docker compose up -d --build $(SERVICE_ID) - ./scripts/print-compose-addresses.sh - -# `compose-restart-service` 兼容旧命令,等价于 `restart SERVICE=...`。 -compose-restart-service: check-service - docker compose restart $(SERVICE_ID) - -# `compose-stop-service` 兼容旧命令,等价于 `stop SERVICE=...`。 -compose-stop-service: check-service - docker compose stop $(SERVICE_ID) - -# `compose-logs-service` 兼容旧命令,等价于 `logs SERVICE=...`。 -compose-logs-service: check-service - docker compose logs -f --tail=200 $(SERVICE_ID) - -# `compose-ps-service` 兼容旧命令,等价于 `ps SERVICE=...`。 -compose-ps-service: check-service - docker compose ps $(SERVICE_ID) - # 服务别名只用于 `make restart rs` 这类第二参数,不执行动作。 $(SERVICE_ALIASES): @: - -# `compose-down` 兼容旧命令,等价于 `down`。 -compose-down: - docker compose down diff --git a/README.md b/README.md index ce8ceb59..6a5b29d8 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ HY Voice Room 是语音房后端 monorepo。当前架构不再自研 IM 长连 核心边界: +任何开发,不需要历史兼容,目前只是在开发阶段 + - `gateway-service` 是客户端 HTTP JSON 入口,负责鉴权、协议转换、签发腾讯云 IM UserSig,以及调用内部 gRPC 服务。 - `room-service` 是房间业务状态 owner,负责 Room Cell、麦位、房间 presence、本地礼物榜、送礼落房间、snapshot、command log、outbox、Redis lease,以及腾讯云 IM 群组/系统消息桥接。 - `wallet-service` 当前提供 `DebitGift` 送礼扣费语义,余额、流水和幂等记录落 MySQL;`room-service` 的 `SendGift` 同步调用它。 @@ -24,15 +26,15 @@ HY Voice Room 是语音房后端 monorepo。当前架构不再自研 IM 长连 本地和 Docker 编排统一使用 `13xxx` 端口: -| Component | Port | Purpose | -| --- | ---: | --- | -| `gateway-service` | `13000` | HTTP JSON | -| `room-service` | `13001` | gRPC | -| `wallet-service` | `13004` | gRPC | -| `user-service` | `13005` | gRPC | -| `activity-service` | `13006` | gRPC | -| MySQL | `13306 -> 3306` | local Docker only | -| Redis | `13379 -> 6379` | local Docker only | +| Component | Port | Purpose | +| ------------------ | --------------: | ----------------- | +| `gateway-service` | `13000` | HTTP JSON | +| `room-service` | `13001` | gRPC | +| `wallet-service` | `13004` | gRPC | +| `user-service` | `13005` | gRPC | +| `activity-service` | `13006` | gRPC | +| MySQL | `23306 -> 3306` | local Docker only | +| Redis | `13379 -> 6379` | local Docker only | ## Run Locally diff --git a/api/proto/activity/v1/activity.pb.go b/api/proto/activity/v1/activity.pb.go index a87bf0d1..b384585e 100644 --- a/api/proto/activity/v1/activity.pb.go +++ b/api/proto/activity/v1/activity.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 -// protoc v5.29.2 +// protoc-gen-go v1.36.6 +// protoc v5.27.3 // source: api/proto/activity/v1/activity.proto package activityv1 @@ -11,6 +11,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -22,23 +23,20 @@ const ( // RequestMeta 是 activity-service 内部 RPC 的最小追踪元信息。 type RequestMeta struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + Caller string `protobuf:"bytes,2,opt,name=caller,proto3" json:"caller,omitempty"` + GatewayNodeId string `protobuf:"bytes,3,opt,name=gateway_node_id,json=gatewayNodeId,proto3" json:"gateway_node_id,omitempty"` + SentAtMs int64 `protobuf:"varint,4,opt,name=sent_at_ms,json=sentAtMs,proto3" json:"sent_at_ms,omitempty"` unknownFields protoimpl.UnknownFields - - RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` - Caller string `protobuf:"bytes,2,opt,name=caller,proto3" json:"caller,omitempty"` - GatewayNodeId string `protobuf:"bytes,3,opt,name=gateway_node_id,json=gatewayNodeId,proto3" json:"gateway_node_id,omitempty"` - SentAtMs int64 `protobuf:"varint,4,opt,name=sent_at_ms,json=sentAtMs,proto3" json:"sent_at_ms,omitempty"` + sizeCache protoimpl.SizeCache } func (x *RequestMeta) Reset() { *x = RequestMeta{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_activity_v1_activity_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_activity_v1_activity_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RequestMeta) String() string { @@ -49,7 +47,7 @@ func (*RequestMeta) ProtoMessage() {} func (x *RequestMeta) ProtoReflect() protoreflect.Message { mi := &file_api_proto_activity_v1_activity_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -94,20 +92,17 @@ func (x *RequestMeta) GetSentAtMs() int64 { // PingActivityRequest 用于验证 activity-service gRPC 链路可用。 type PingActivityRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + sizeCache protoimpl.SizeCache } func (x *PingActivityRequest) Reset() { *x = PingActivityRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_activity_v1_activity_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_activity_v1_activity_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PingActivityRequest) String() string { @@ -118,7 +113,7 @@ func (*PingActivityRequest) ProtoMessage() {} func (x *PingActivityRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_activity_v1_activity_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -142,21 +137,18 @@ func (x *PingActivityRequest) GetMeta() *RequestMeta { // PingActivityResponse 返回服务是否接受请求。 type PingActivityResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"` + NodeId string `protobuf:"bytes,2,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` unknownFields protoimpl.UnknownFields - - Ok bool `protobuf:"varint,1,opt,name=ok,proto3" json:"ok,omitempty"` - NodeId string `protobuf:"bytes,2,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *PingActivityResponse) Reset() { *x = PingActivityResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_activity_v1_activity_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_activity_v1_activity_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *PingActivityResponse) String() string { @@ -167,7 +159,7 @@ func (*PingActivityResponse) ProtoMessage() {} func (x *PingActivityResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_activity_v1_activity_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -198,21 +190,18 @@ func (x *PingActivityResponse) GetNodeId() string { // GetActivityStatusRequest 查询活动服务底座状态。 type GetActivityStatusRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + ActivityId string `protobuf:"bytes,2,opt,name=activity_id,json=activityId,proto3" json:"activity_id,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - ActivityId string `protobuf:"bytes,2,opt,name=activity_id,json=activityId,proto3" json:"activity_id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *GetActivityStatusRequest) Reset() { *x = GetActivityStatusRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_activity_v1_activity_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_activity_v1_activity_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetActivityStatusRequest) String() string { @@ -223,7 +212,7 @@ func (*GetActivityStatusRequest) ProtoMessage() {} func (x *GetActivityStatusRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_activity_v1_activity_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -254,21 +243,18 @@ func (x *GetActivityStatusRequest) GetActivityId() string { // GetActivityStatusResponse 返回活动是否启用。 type GetActivityStatusResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + ActivityId string `protobuf:"bytes,1,opt,name=activity_id,json=activityId,proto3" json:"activity_id,omitempty"` + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` unknownFields protoimpl.UnknownFields - - ActivityId string `protobuf:"bytes,1,opt,name=activity_id,json=activityId,proto3" json:"activity_id,omitempty"` - Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + sizeCache protoimpl.SizeCache } func (x *GetActivityStatusResponse) Reset() { *x = GetActivityStatusResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_activity_v1_activity_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_activity_v1_activity_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetActivityStatusResponse) String() string { @@ -279,7 +265,7 @@ func (*GetActivityStatusResponse) ProtoMessage() {} func (x *GetActivityStatusResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_activity_v1_activity_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -310,68 +296,41 @@ func (x *GetActivityStatusResponse) GetStatus() string { var File_api_proto_activity_v1_activity_proto protoreflect.FileDescriptor -var file_api_proto_activity_v1_activity_proto_rawDesc = []byte{ - 0x0a, 0x24, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x69, 0x74, 0x79, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x22, 0x8a, 0x01, 0x0a, 0x0b, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x61, 0x6c, 0x6c, - 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, - 0x12, 0x26, 0x0a, 0x0f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x6e, 0x6f, 0x64, 0x65, - 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x67, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x0a, 0x73, 0x65, 0x6e, 0x74, - 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x65, - 0x6e, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x49, 0x0a, 0x13, 0x50, 0x69, 0x6e, 0x67, 0x41, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x22, 0x3f, 0x0a, 0x14, 0x50, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, - 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, - 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, - 0x49, 0x64, 0x22, 0x6f, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, - 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, - 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, - 0x79, 0x49, 0x64, 0x22, 0x54, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, - 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x49, - 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x32, 0xe2, 0x01, 0x0a, 0x0f, 0x41, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5f, 0x0a, - 0x0c, 0x50, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x12, 0x26, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, - 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x41, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6e, - 0x0a, 0x11, 0x47, 0x65, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x69, 0x74, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, - 0x69, 0x74, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, - 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x28, - 0x5a, 0x26, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x2f, 0x76, 0x31, 0x3b, 0x61, 0x63, - 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_api_proto_activity_v1_activity_proto_rawDesc = "" + + "\n" + + "$api/proto/activity/v1/activity.proto\x12\x11hyapp.activity.v1\"\x8a\x01\n" + + "\vRequestMeta\x12\x1d\n" + + "\n" + + "request_id\x18\x01 \x01(\tR\trequestId\x12\x16\n" + + "\x06caller\x18\x02 \x01(\tR\x06caller\x12&\n" + + "\x0fgateway_node_id\x18\x03 \x01(\tR\rgatewayNodeId\x12\x1c\n" + + "\n" + + "sent_at_ms\x18\x04 \x01(\x03R\bsentAtMs\"I\n" + + "\x13PingActivityRequest\x122\n" + + "\x04meta\x18\x01 \x01(\v2\x1e.hyapp.activity.v1.RequestMetaR\x04meta\"?\n" + + "\x14PingActivityResponse\x12\x0e\n" + + "\x02ok\x18\x01 \x01(\bR\x02ok\x12\x17\n" + + "\anode_id\x18\x02 \x01(\tR\x06nodeId\"o\n" + + "\x18GetActivityStatusRequest\x122\n" + + "\x04meta\x18\x01 \x01(\v2\x1e.hyapp.activity.v1.RequestMetaR\x04meta\x12\x1f\n" + + "\vactivity_id\x18\x02 \x01(\tR\n" + + "activityId\"T\n" + + "\x19GetActivityStatusResponse\x12\x1f\n" + + "\vactivity_id\x18\x01 \x01(\tR\n" + + "activityId\x12\x16\n" + + "\x06status\x18\x02 \x01(\tR\x06status2\xe2\x01\n" + + "\x0fActivityService\x12_\n" + + "\fPingActivity\x12&.hyapp.activity.v1.PingActivityRequest\x1a'.hyapp.activity.v1.PingActivityResponse\x12n\n" + + "\x11GetActivityStatus\x12+.hyapp.activity.v1.GetActivityStatusRequest\x1a,.hyapp.activity.v1.GetActivityStatusResponseB(Z&hyapp/api/proto/activity/v1;activityv1b\x06proto3" var ( file_api_proto_activity_v1_activity_proto_rawDescOnce sync.Once - file_api_proto_activity_v1_activity_proto_rawDescData = file_api_proto_activity_v1_activity_proto_rawDesc + file_api_proto_activity_v1_activity_proto_rawDescData []byte ) func file_api_proto_activity_v1_activity_proto_rawDescGZIP() []byte { file_api_proto_activity_v1_activity_proto_rawDescOnce.Do(func() { - file_api_proto_activity_v1_activity_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_activity_v1_activity_proto_rawDescData) + file_api_proto_activity_v1_activity_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_api_proto_activity_v1_activity_proto_rawDesc), len(file_api_proto_activity_v1_activity_proto_rawDesc))) }) return file_api_proto_activity_v1_activity_proto_rawDescData } @@ -403,73 +362,11 @@ func file_api_proto_activity_v1_activity_proto_init() { if File_api_proto_activity_v1_activity_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_api_proto_activity_v1_activity_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*RequestMeta); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_activity_v1_activity_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*PingActivityRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_activity_v1_activity_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*PingActivityResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_activity_v1_activity_proto_msgTypes[3].Exporter = func(v any, i int) any { - switch v := v.(*GetActivityStatusRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_activity_v1_activity_proto_msgTypes[4].Exporter = func(v any, i int) any { - switch v := v.(*GetActivityStatusResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_api_proto_activity_v1_activity_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_proto_activity_v1_activity_proto_rawDesc), len(file_api_proto_activity_v1_activity_proto_rawDesc)), NumEnums: 0, NumMessages: 5, NumExtensions: 0, @@ -480,7 +377,6 @@ func file_api_proto_activity_v1_activity_proto_init() { MessageInfos: file_api_proto_activity_v1_activity_proto_msgTypes, }.Build() File_api_proto_activity_v1_activity_proto = out.File - file_api_proto_activity_v1_activity_proto_rawDesc = nil file_api_proto_activity_v1_activity_proto_goTypes = nil file_api_proto_activity_v1_activity_proto_depIdxs = nil } diff --git a/api/proto/activity/v1/activity_grpc.pb.go b/api/proto/activity/v1/activity_grpc.pb.go index aa41f66b..8e996f50 100644 --- a/api/proto/activity/v1/activity_grpc.pb.go +++ b/api/proto/activity/v1/activity_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v5.29.2 +// - protoc v5.27.3 // source: api/proto/activity/v1/activity.proto package activityv1 diff --git a/api/proto/events/room/v1/events.pb.go b/api/proto/events/room/v1/events.pb.go index ec74bbf4..f7ba52c5 100644 --- a/api/proto/events/room/v1/events.pb.go +++ b/api/proto/events/room/v1/events.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 -// protoc v5.29.2 +// protoc-gen-go v1.36.6 +// protoc v5.27.3 // source: api/proto/events/room/v1/events.proto package roomeventsv1 @@ -11,6 +11,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -23,25 +24,22 @@ const ( // EventEnvelope 是 room-service outbox 的统一事件外壳。 // body 保存具体事件消息序列化后的二进制,event_type 负责告诉消费者如何解码。 type EventEnvelope struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + EventId string `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"` + RoomId string `protobuf:"bytes,2,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` + EventType string `protobuf:"bytes,3,opt,name=event_type,json=eventType,proto3" json:"event_type,omitempty"` + RoomVersion int64 `protobuf:"varint,4,opt,name=room_version,json=roomVersion,proto3" json:"room_version,omitempty"` + OccurredAtMs int64 `protobuf:"varint,5,opt,name=occurred_at_ms,json=occurredAtMs,proto3" json:"occurred_at_ms,omitempty"` + Body []byte `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"` unknownFields protoimpl.UnknownFields - - EventId string `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"` - RoomId string `protobuf:"bytes,2,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` - EventType string `protobuf:"bytes,3,opt,name=event_type,json=eventType,proto3" json:"event_type,omitempty"` - RoomVersion int64 `protobuf:"varint,4,opt,name=room_version,json=roomVersion,proto3" json:"room_version,omitempty"` - OccurredAtMs int64 `protobuf:"varint,5,opt,name=occurred_at_ms,json=occurredAtMs,proto3" json:"occurred_at_ms,omitempty"` - Body []byte `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"` + sizeCache protoimpl.SizeCache } func (x *EventEnvelope) Reset() { *x = EventEnvelope{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_events_room_v1_events_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *EventEnvelope) String() string { @@ -52,7 +50,7 @@ func (*EventEnvelope) ProtoMessage() {} func (x *EventEnvelope) ProtoReflect() protoreflect.Message { mi := &file_api_proto_events_room_v1_events_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -111,23 +109,20 @@ func (x *EventEnvelope) GetBody() []byte { // RoomCreated 表达房间第一次建立成功。 type RoomCreated struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + OwnerUserId int64 `protobuf:"varint,1,opt,name=owner_user_id,json=ownerUserId,proto3" json:"owner_user_id,omitempty"` + HostUserId int64 `protobuf:"varint,2,opt,name=host_user_id,json=hostUserId,proto3" json:"host_user_id,omitempty"` + SeatCount int32 `protobuf:"varint,3,opt,name=seat_count,json=seatCount,proto3" json:"seat_count,omitempty"` + Mode string `protobuf:"bytes,4,opt,name=mode,proto3" json:"mode,omitempty"` unknownFields protoimpl.UnknownFields - - OwnerUserId int64 `protobuf:"varint,1,opt,name=owner_user_id,json=ownerUserId,proto3" json:"owner_user_id,omitempty"` - HostUserId int64 `protobuf:"varint,2,opt,name=host_user_id,json=hostUserId,proto3" json:"host_user_id,omitempty"` - SeatCount int32 `protobuf:"varint,3,opt,name=seat_count,json=seatCount,proto3" json:"seat_count,omitempty"` - Mode string `protobuf:"bytes,4,opt,name=mode,proto3" json:"mode,omitempty"` + sizeCache protoimpl.SizeCache } func (x *RoomCreated) Reset() { *x = RoomCreated{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_events_room_v1_events_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RoomCreated) String() string { @@ -138,7 +133,7 @@ func (*RoomCreated) ProtoMessage() {} func (x *RoomCreated) ProtoReflect() protoreflect.Message { mi := &file_api_proto_events_room_v1_events_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -183,21 +178,18 @@ func (x *RoomCreated) GetMode() string { // RoomUserJoined 表达用户业务态进房成功。 type RoomUserJoined struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Role string `protobuf:"bytes,2,opt,name=role,proto3" json:"role,omitempty"` unknownFields protoimpl.UnknownFields - - UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - Role string `protobuf:"bytes,2,opt,name=role,proto3" json:"role,omitempty"` + sizeCache protoimpl.SizeCache } func (x *RoomUserJoined) Reset() { *x = RoomUserJoined{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_events_room_v1_events_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RoomUserJoined) String() string { @@ -208,7 +200,7 @@ func (*RoomUserJoined) ProtoMessage() {} func (x *RoomUserJoined) ProtoReflect() protoreflect.Message { mi := &file_api_proto_events_room_v1_events_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -239,20 +231,17 @@ func (x *RoomUserJoined) GetRole() string { // RoomUserLeft 表达用户离房成功。 type RoomUserLeft struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` unknownFields protoimpl.UnknownFields - - UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *RoomUserLeft) Reset() { *x = RoomUserLeft{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_events_room_v1_events_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RoomUserLeft) String() string { @@ -263,7 +252,7 @@ func (*RoomUserLeft) ProtoMessage() {} func (x *RoomUserLeft) ProtoReflect() protoreflect.Message { mi := &file_api_proto_events_room_v1_events_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -287,24 +276,29 @@ func (x *RoomUserLeft) GetUserId() int64 { // RoomMicChanged 表达任意上下麦和换麦位动作。 type RoomMicChanged struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - ActorUserId int64 `protobuf:"varint,1,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"` - TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` - FromSeat int32 `protobuf:"varint,3,opt,name=from_seat,json=fromSeat,proto3" json:"from_seat,omitempty"` - ToSeat int32 `protobuf:"varint,4,opt,name=to_seat,json=toSeat,proto3" json:"to_seat,omitempty"` - Action string `protobuf:"bytes,5,opt,name=action,proto3" json:"action,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + ActorUserId int64 `protobuf:"varint,1,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"` + TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + FromSeat int32 `protobuf:"varint,3,opt,name=from_seat,json=fromSeat,proto3" json:"from_seat,omitempty"` + ToSeat int32 `protobuf:"varint,4,opt,name=to_seat,json=toSeat,proto3" json:"to_seat,omitempty"` + Action string `protobuf:"bytes,5,opt,name=action,proto3" json:"action,omitempty"` + // mic_session_id 用于把业务麦位事件和 RTC 发流会话绑定,客户端和 webhook 都必须透传。 + MicSessionId string `protobuf:"bytes,6,opt,name=mic_session_id,json=micSessionId,proto3" json:"mic_session_id,omitempty"` + // publish_state 透出 pending_publish/publishing/idle,避免把“占麦”和“已发流”混成一个状态。 + PublishState string `protobuf:"bytes,7,opt,name=publish_state,json=publishState,proto3" json:"publish_state,omitempty"` + // reason 表示系统触发的下麦原因,例如 publish_timeout。 + Reason string `protobuf:"bytes,8,opt,name=reason,proto3" json:"reason,omitempty"` + PublishDeadlineMs int64 `protobuf:"varint,9,opt,name=publish_deadline_ms,json=publishDeadlineMs,proto3" json:"publish_deadline_ms,omitempty"` + PublishEventTimeMs int64 `protobuf:"varint,10,opt,name=publish_event_time_ms,json=publishEventTimeMs,proto3" json:"publish_event_time_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *RoomMicChanged) Reset() { *x = RoomMicChanged{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_events_room_v1_events_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RoomMicChanged) String() string { @@ -315,7 +309,7 @@ func (*RoomMicChanged) ProtoMessage() {} func (x *RoomMicChanged) ProtoReflect() protoreflect.Message { mi := &file_api_proto_events_room_v1_events_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -365,24 +359,292 @@ func (x *RoomMicChanged) GetAction() string { return "" } +func (x *RoomMicChanged) GetMicSessionId() string { + if x != nil { + return x.MicSessionId + } + return "" +} + +func (x *RoomMicChanged) GetPublishState() string { + if x != nil { + return x.PublishState + } + return "" +} + +func (x *RoomMicChanged) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *RoomMicChanged) GetPublishDeadlineMs() int64 { + if x != nil { + return x.PublishDeadlineMs + } + return 0 +} + +func (x *RoomMicChanged) GetPublishEventTimeMs() int64 { + if x != nil { + return x.PublishEventTimeMs + } + return 0 +} + +// RoomMicSeatLocked 表达单个麦位锁定状态变更。 +type RoomMicSeatLocked struct { + state protoimpl.MessageState `protogen:"open.v1"` + ActorUserId int64 `protobuf:"varint,1,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"` + SeatNo int32 `protobuf:"varint,2,opt,name=seat_no,json=seatNo,proto3" json:"seat_no,omitempty"` + Locked bool `protobuf:"varint,3,opt,name=locked,proto3" json:"locked,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RoomMicSeatLocked) Reset() { + *x = RoomMicSeatLocked{} + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RoomMicSeatLocked) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoomMicSeatLocked) ProtoMessage() {} + +func (x *RoomMicSeatLocked) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoomMicSeatLocked.ProtoReflect.Descriptor instead. +func (*RoomMicSeatLocked) Descriptor() ([]byte, []int) { + return file_api_proto_events_room_v1_events_proto_rawDescGZIP(), []int{5} +} + +func (x *RoomMicSeatLocked) GetActorUserId() int64 { + if x != nil { + return x.ActorUserId + } + return 0 +} + +func (x *RoomMicSeatLocked) GetSeatNo() int32 { + if x != nil { + return x.SeatNo + } + return 0 +} + +func (x *RoomMicSeatLocked) GetLocked() bool { + if x != nil { + return x.Locked + } + return false +} + +// RoomChatEnabledChanged 表达公屏开关状态变更。 +type RoomChatEnabledChanged struct { + state protoimpl.MessageState `protogen:"open.v1"` + ActorUserId int64 `protobuf:"varint,1,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"` + Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RoomChatEnabledChanged) Reset() { + *x = RoomChatEnabledChanged{} + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RoomChatEnabledChanged) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoomChatEnabledChanged) ProtoMessage() {} + +func (x *RoomChatEnabledChanged) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoomChatEnabledChanged.ProtoReflect.Descriptor instead. +func (*RoomChatEnabledChanged) Descriptor() ([]byte, []int) { + return file_api_proto_events_room_v1_events_proto_rawDescGZIP(), []int{6} +} + +func (x *RoomChatEnabledChanged) GetActorUserId() int64 { + if x != nil { + return x.ActorUserId + } + return 0 +} + +func (x *RoomChatEnabledChanged) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +// RoomAdminChanged 表达房间管理员集合变更。 +type RoomAdminChanged struct { + state protoimpl.MessageState `protogen:"open.v1"` + ActorUserId int64 `protobuf:"varint,1,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"` + TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + Enabled bool `protobuf:"varint,3,opt,name=enabled,proto3" json:"enabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RoomAdminChanged) Reset() { + *x = RoomAdminChanged{} + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RoomAdminChanged) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoomAdminChanged) ProtoMessage() {} + +func (x *RoomAdminChanged) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoomAdminChanged.ProtoReflect.Descriptor instead. +func (*RoomAdminChanged) Descriptor() ([]byte, []int) { + return file_api_proto_events_room_v1_events_proto_rawDescGZIP(), []int{7} +} + +func (x *RoomAdminChanged) GetActorUserId() int64 { + if x != nil { + return x.ActorUserId + } + return 0 +} + +func (x *RoomAdminChanged) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *RoomAdminChanged) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +// RoomHostTransferred 表达主持人身份转移。 +type RoomHostTransferred struct { + state protoimpl.MessageState `protogen:"open.v1"` + ActorUserId int64 `protobuf:"varint,1,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"` + OldHostUserId int64 `protobuf:"varint,2,opt,name=old_host_user_id,json=oldHostUserId,proto3" json:"old_host_user_id,omitempty"` + NewHostUserId int64 `protobuf:"varint,3,opt,name=new_host_user_id,json=newHostUserId,proto3" json:"new_host_user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RoomHostTransferred) Reset() { + *x = RoomHostTransferred{} + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RoomHostTransferred) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoomHostTransferred) ProtoMessage() {} + +func (x *RoomHostTransferred) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoomHostTransferred.ProtoReflect.Descriptor instead. +func (*RoomHostTransferred) Descriptor() ([]byte, []int) { + return file_api_proto_events_room_v1_events_proto_rawDescGZIP(), []int{8} +} + +func (x *RoomHostTransferred) GetActorUserId() int64 { + if x != nil { + return x.ActorUserId + } + return 0 +} + +func (x *RoomHostTransferred) GetOldHostUserId() int64 { + if x != nil { + return x.OldHostUserId + } + return 0 +} + +func (x *RoomHostTransferred) GetNewHostUserId() int64 { + if x != nil { + return x.NewHostUserId + } + return 0 +} + // RoomUserMuted 表达禁言状态变更。 type RoomUserMuted struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + ActorUserId int64 `protobuf:"varint,1,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"` + TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + Muted bool `protobuf:"varint,3,opt,name=muted,proto3" json:"muted,omitempty"` unknownFields protoimpl.UnknownFields - - ActorUserId int64 `protobuf:"varint,1,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"` - TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` - Muted bool `protobuf:"varint,3,opt,name=muted,proto3" json:"muted,omitempty"` + sizeCache protoimpl.SizeCache } func (x *RoomUserMuted) Reset() { *x = RoomUserMuted{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_events_room_v1_events_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RoomUserMuted) String() string { @@ -392,8 +654,8 @@ func (x *RoomUserMuted) String() string { func (*RoomUserMuted) ProtoMessage() {} func (x *RoomUserMuted) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_events_room_v1_events_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[9] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -405,7 +667,7 @@ func (x *RoomUserMuted) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomUserMuted.ProtoReflect.Descriptor instead. func (*RoomUserMuted) Descriptor() ([]byte, []int) { - return file_api_proto_events_room_v1_events_proto_rawDescGZIP(), []int{5} + return file_api_proto_events_room_v1_events_proto_rawDescGZIP(), []int{9} } func (x *RoomUserMuted) GetActorUserId() int64 { @@ -431,21 +693,18 @@ func (x *RoomUserMuted) GetMuted() bool { // RoomUserKicked 表达踢人动作。 type RoomUserKicked struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + ActorUserId int64 `protobuf:"varint,1,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"` + TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` unknownFields protoimpl.UnknownFields - - ActorUserId int64 `protobuf:"varint,1,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"` - TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *RoomUserKicked) Reset() { *x = RoomUserKicked{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_events_room_v1_events_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RoomUserKicked) String() string { @@ -455,8 +714,8 @@ func (x *RoomUserKicked) String() string { func (*RoomUserKicked) ProtoMessage() {} func (x *RoomUserKicked) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_events_room_v1_events_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[10] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -468,7 +727,7 @@ func (x *RoomUserKicked) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomUserKicked.ProtoReflect.Descriptor instead. func (*RoomUserKicked) Descriptor() ([]byte, []int) { - return file_api_proto_events_room_v1_events_proto_rawDescGZIP(), []int{6} + return file_api_proto_events_room_v1_events_proto_rawDescGZIP(), []int{10} } func (x *RoomUserKicked) GetActorUserId() int64 { @@ -485,27 +744,77 @@ func (x *RoomUserKicked) GetTargetUserId() int64 { return 0 } +// RoomUserUnbanned 表达解除房间 ban。 +type RoomUserUnbanned struct { + state protoimpl.MessageState `protogen:"open.v1"` + ActorUserId int64 `protobuf:"varint,1,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"` + TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RoomUserUnbanned) Reset() { + *x = RoomUserUnbanned{} + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RoomUserUnbanned) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoomUserUnbanned) ProtoMessage() {} + +func (x *RoomUserUnbanned) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoomUserUnbanned.ProtoReflect.Descriptor instead. +func (*RoomUserUnbanned) Descriptor() ([]byte, []int) { + return file_api_proto_events_room_v1_events_proto_rawDescGZIP(), []int{11} +} + +func (x *RoomUserUnbanned) GetActorUserId() int64 { + if x != nil { + return x.ActorUserId + } + return 0 +} + +func (x *RoomUserUnbanned) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + // RoomGiftSent 表达送礼成功且已落房间状态。 type RoomGiftSent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - SenderUserId int64 `protobuf:"varint,1,opt,name=sender_user_id,json=senderUserId,proto3" json:"sender_user_id,omitempty"` - TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` - GiftId string `protobuf:"bytes,3,opt,name=gift_id,json=giftId,proto3" json:"gift_id,omitempty"` - GiftCount int32 `protobuf:"varint,4,opt,name=gift_count,json=giftCount,proto3" json:"gift_count,omitempty"` - GiftValue int64 `protobuf:"varint,5,opt,name=gift_value,json=giftValue,proto3" json:"gift_value,omitempty"` - BillingReceiptId string `protobuf:"bytes,6,opt,name=billing_receipt_id,json=billingReceiptId,proto3" json:"billing_receipt_id,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + SenderUserId int64 `protobuf:"varint,1,opt,name=sender_user_id,json=senderUserId,proto3" json:"sender_user_id,omitempty"` + TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + GiftId string `protobuf:"bytes,3,opt,name=gift_id,json=giftId,proto3" json:"gift_id,omitempty"` + GiftCount int32 `protobuf:"varint,4,opt,name=gift_count,json=giftCount,proto3" json:"gift_count,omitempty"` + GiftValue int64 `protobuf:"varint,5,opt,name=gift_value,json=giftValue,proto3" json:"gift_value,omitempty"` + BillingReceiptId string `protobuf:"bytes,6,opt,name=billing_receipt_id,json=billingReceiptId,proto3" json:"billing_receipt_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *RoomGiftSent) Reset() { *x = RoomGiftSent{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_events_room_v1_events_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RoomGiftSent) String() string { @@ -515,8 +824,8 @@ func (x *RoomGiftSent) String() string { func (*RoomGiftSent) ProtoMessage() {} func (x *RoomGiftSent) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_events_room_v1_events_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[12] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -528,7 +837,7 @@ func (x *RoomGiftSent) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomGiftSent.ProtoReflect.Descriptor instead. func (*RoomGiftSent) Descriptor() ([]byte, []int) { - return file_api_proto_events_room_v1_events_proto_rawDescGZIP(), []int{7} + return file_api_proto_events_room_v1_events_proto_rawDescGZIP(), []int{12} } func (x *RoomGiftSent) GetSenderUserId() int64 { @@ -575,21 +884,18 @@ func (x *RoomGiftSent) GetBillingReceiptId() string { // RoomHeatChanged 表达热度变化结果。 type RoomHeatChanged struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Delta int64 `protobuf:"varint,1,opt,name=delta,proto3" json:"delta,omitempty"` + CurrentHeat int64 `protobuf:"varint,2,opt,name=current_heat,json=currentHeat,proto3" json:"current_heat,omitempty"` unknownFields protoimpl.UnknownFields - - Delta int64 `protobuf:"varint,1,opt,name=delta,proto3" json:"delta,omitempty"` - CurrentHeat int64 `protobuf:"varint,2,opt,name=current_heat,json=currentHeat,proto3" json:"current_heat,omitempty"` + sizeCache protoimpl.SizeCache } func (x *RoomHeatChanged) Reset() { *x = RoomHeatChanged{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_events_room_v1_events_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RoomHeatChanged) String() string { @@ -599,8 +905,8 @@ func (x *RoomHeatChanged) String() string { func (*RoomHeatChanged) ProtoMessage() {} func (x *RoomHeatChanged) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_events_room_v1_events_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[13] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -612,7 +918,7 @@ func (x *RoomHeatChanged) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomHeatChanged.ProtoReflect.Descriptor instead. func (*RoomHeatChanged) Descriptor() ([]byte, []int) { - return file_api_proto_events_room_v1_events_proto_rawDescGZIP(), []int{8} + return file_api_proto_events_room_v1_events_proto_rawDescGZIP(), []int{13} } func (x *RoomHeatChanged) GetDelta() int64 { @@ -631,22 +937,19 @@ func (x *RoomHeatChanged) GetCurrentHeat() int64 { // RoomRankChanged 表达本地礼物榜变化结果。 type RoomRankChanged struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Score int64 `protobuf:"varint,2,opt,name=score,proto3" json:"score,omitempty"` + GiftValue int64 `protobuf:"varint,3,opt,name=gift_value,json=giftValue,proto3" json:"gift_value,omitempty"` unknownFields protoimpl.UnknownFields - - UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - Score int64 `protobuf:"varint,2,opt,name=score,proto3" json:"score,omitempty"` - GiftValue int64 `protobuf:"varint,3,opt,name=gift_value,json=giftValue,proto3" json:"gift_value,omitempty"` + sizeCache protoimpl.SizeCache } func (x *RoomRankChanged) Reset() { *x = RoomRankChanged{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_events_room_v1_events_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RoomRankChanged) String() string { @@ -656,8 +959,8 @@ func (x *RoomRankChanged) String() string { func (*RoomRankChanged) ProtoMessage() {} func (x *RoomRankChanged) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_events_room_v1_events_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_api_proto_events_room_v1_events_proto_msgTypes[14] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -669,7 +972,7 @@ func (x *RoomRankChanged) ProtoReflect() protoreflect.Message { // Deprecated: Use RoomRankChanged.ProtoReflect.Descriptor instead. func (*RoomRankChanged) Descriptor() ([]byte, []int) { - return file_api_proto_events_room_v1_events_proto_rawDescGZIP(), []int{9} + return file_api_proto_events_room_v1_events_proto_rawDescGZIP(), []int{14} } func (x *RoomRankChanged) GetUserId() int64 { @@ -695,116 +998,113 @@ func (x *RoomRankChanged) GetGiftValue() int64 { var File_api_proto_events_room_v1_events_proto protoreflect.FileDescriptor -var file_api_proto_events_room_v1_events_proto_rawDesc = []byte{ - 0x0a, 0x25, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x76, 0x65, 0x6e, - 0x74, 0x73, 0x2f, 0x72, 0x6f, 0x6f, 0x6d, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, - 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x14, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x65, - 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x22, 0xbf, 0x01, - 0x0a, 0x0d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, - 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, - 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, - 0x6d, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x56, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x63, 0x63, 0x75, 0x72, 0x72, 0x65, - 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6f, - 0x63, 0x63, 0x75, 0x72, 0x72, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x62, - 0x6f, 0x64, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, - 0x86, 0x01, 0x0a, 0x0b, 0x52, 0x6f, 0x6f, 0x6d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, - 0x22, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, 0x61, 0x74, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x22, 0x3d, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x6d, - 0x55, 0x73, 0x65, 0x72, 0x4a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x22, 0x27, 0x0a, 0x0c, 0x52, 0x6f, 0x6f, 0x6d, 0x55, - 0x73, 0x65, 0x72, 0x4c, 0x65, 0x66, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x22, 0xa8, 0x01, 0x0a, 0x0e, 0x52, 0x6f, 0x6f, 0x6d, 0x4d, 0x69, 0x63, 0x43, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, - 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, - 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x73, 0x65, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x08, 0x66, 0x72, 0x6f, 0x6d, 0x53, 0x65, 0x61, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x6f, - 0x5f, 0x73, 0x65, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x74, 0x6f, 0x53, - 0x65, 0x61, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x6f, 0x0a, 0x0d, 0x52, - 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x4d, 0x75, 0x74, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, - 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x22, 0x5a, 0x0a, 0x0e, - 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x4b, 0x69, 0x63, 0x6b, 0x65, 0x64, 0x12, 0x22, - 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0xdf, 0x01, 0x0a, 0x0c, 0x52, 0x6f, 0x6f, - 0x6d, 0x47, 0x69, 0x66, 0x74, 0x53, 0x65, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, - 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, - 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, - 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2c, 0x0a, 0x12, - 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, - 0x67, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x49, 0x64, 0x22, 0x4a, 0x0a, 0x0f, 0x52, 0x6f, - 0x6f, 0x6d, 0x48, 0x65, 0x61, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x14, 0x0a, - 0x05, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x64, 0x65, - 0x6c, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x68, - 0x65, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x75, 0x72, 0x72, 0x65, - 0x6e, 0x74, 0x48, 0x65, 0x61, 0x74, 0x22, 0x5f, 0x0a, 0x0f, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x61, - 0x6e, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, - 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x67, 0x69, - 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x2d, 0x5a, 0x2b, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, - 0x73, 0x2f, 0x72, 0x6f, 0x6f, 0x6d, 0x2f, 0x76, 0x31, 0x3b, 0x72, 0x6f, 0x6f, 0x6d, 0x65, 0x76, - 0x65, 0x6e, 0x74, 0x73, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_api_proto_events_room_v1_events_proto_rawDesc = "" + + "\n" + + "%api/proto/events/room/v1/events.proto\x12\x14hyapp.events.room.v1\"\xbf\x01\n" + + "\rEventEnvelope\x12\x19\n" + + "\bevent_id\x18\x01 \x01(\tR\aeventId\x12\x17\n" + + "\aroom_id\x18\x02 \x01(\tR\x06roomId\x12\x1d\n" + + "\n" + + "event_type\x18\x03 \x01(\tR\teventType\x12!\n" + + "\froom_version\x18\x04 \x01(\x03R\vroomVersion\x12$\n" + + "\x0eoccurred_at_ms\x18\x05 \x01(\x03R\foccurredAtMs\x12\x12\n" + + "\x04body\x18\x06 \x01(\fR\x04body\"\x86\x01\n" + + "\vRoomCreated\x12\"\n" + + "\rowner_user_id\x18\x01 \x01(\x03R\vownerUserId\x12 \n" + + "\fhost_user_id\x18\x02 \x01(\x03R\n" + + "hostUserId\x12\x1d\n" + + "\n" + + "seat_count\x18\x03 \x01(\x05R\tseatCount\x12\x12\n" + + "\x04mode\x18\x04 \x01(\tR\x04mode\"=\n" + + "\x0eRoomUserJoined\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x12\n" + + "\x04role\x18\x02 \x01(\tR\x04role\"'\n" + + "\fRoomUserLeft\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x03R\x06userId\"\xee\x02\n" + + "\x0eRoomMicChanged\x12\"\n" + + "\ractor_user_id\x18\x01 \x01(\x03R\vactorUserId\x12$\n" + + "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\x12\x1b\n" + + "\tfrom_seat\x18\x03 \x01(\x05R\bfromSeat\x12\x17\n" + + "\ato_seat\x18\x04 \x01(\x05R\x06toSeat\x12\x16\n" + + "\x06action\x18\x05 \x01(\tR\x06action\x12$\n" + + "\x0emic_session_id\x18\x06 \x01(\tR\fmicSessionId\x12#\n" + + "\rpublish_state\x18\a \x01(\tR\fpublishState\x12\x16\n" + + "\x06reason\x18\b \x01(\tR\x06reason\x12.\n" + + "\x13publish_deadline_ms\x18\t \x01(\x03R\x11publishDeadlineMs\x121\n" + + "\x15publish_event_time_ms\x18\n" + + " \x01(\x03R\x12publishEventTimeMs\"h\n" + + "\x11RoomMicSeatLocked\x12\"\n" + + "\ractor_user_id\x18\x01 \x01(\x03R\vactorUserId\x12\x17\n" + + "\aseat_no\x18\x02 \x01(\x05R\x06seatNo\x12\x16\n" + + "\x06locked\x18\x03 \x01(\bR\x06locked\"V\n" + + "\x16RoomChatEnabledChanged\x12\"\n" + + "\ractor_user_id\x18\x01 \x01(\x03R\vactorUserId\x12\x18\n" + + "\aenabled\x18\x02 \x01(\bR\aenabled\"v\n" + + "\x10RoomAdminChanged\x12\"\n" + + "\ractor_user_id\x18\x01 \x01(\x03R\vactorUserId\x12$\n" + + "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\x12\x18\n" + + "\aenabled\x18\x03 \x01(\bR\aenabled\"\x8b\x01\n" + + "\x13RoomHostTransferred\x12\"\n" + + "\ractor_user_id\x18\x01 \x01(\x03R\vactorUserId\x12'\n" + + "\x10old_host_user_id\x18\x02 \x01(\x03R\roldHostUserId\x12'\n" + + "\x10new_host_user_id\x18\x03 \x01(\x03R\rnewHostUserId\"o\n" + + "\rRoomUserMuted\x12\"\n" + + "\ractor_user_id\x18\x01 \x01(\x03R\vactorUserId\x12$\n" + + "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\x12\x14\n" + + "\x05muted\x18\x03 \x01(\bR\x05muted\"Z\n" + + "\x0eRoomUserKicked\x12\"\n" + + "\ractor_user_id\x18\x01 \x01(\x03R\vactorUserId\x12$\n" + + "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\"\\\n" + + "\x10RoomUserUnbanned\x12\"\n" + + "\ractor_user_id\x18\x01 \x01(\x03R\vactorUserId\x12$\n" + + "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\"\xdf\x01\n" + + "\fRoomGiftSent\x12$\n" + + "\x0esender_user_id\x18\x01 \x01(\x03R\fsenderUserId\x12$\n" + + "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\x12\x17\n" + + "\agift_id\x18\x03 \x01(\tR\x06giftId\x12\x1d\n" + + "\n" + + "gift_count\x18\x04 \x01(\x05R\tgiftCount\x12\x1d\n" + + "\n" + + "gift_value\x18\x05 \x01(\x03R\tgiftValue\x12,\n" + + "\x12billing_receipt_id\x18\x06 \x01(\tR\x10billingReceiptId\"J\n" + + "\x0fRoomHeatChanged\x12\x14\n" + + "\x05delta\x18\x01 \x01(\x03R\x05delta\x12!\n" + + "\fcurrent_heat\x18\x02 \x01(\x03R\vcurrentHeat\"_\n" + + "\x0fRoomRankChanged\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x14\n" + + "\x05score\x18\x02 \x01(\x03R\x05score\x12\x1d\n" + + "\n" + + "gift_value\x18\x03 \x01(\x03R\tgiftValueB-Z+hyapp/api/proto/events/room/v1;roomeventsv1b\x06proto3" var ( file_api_proto_events_room_v1_events_proto_rawDescOnce sync.Once - file_api_proto_events_room_v1_events_proto_rawDescData = file_api_proto_events_room_v1_events_proto_rawDesc + file_api_proto_events_room_v1_events_proto_rawDescData []byte ) func file_api_proto_events_room_v1_events_proto_rawDescGZIP() []byte { file_api_proto_events_room_v1_events_proto_rawDescOnce.Do(func() { - file_api_proto_events_room_v1_events_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_events_room_v1_events_proto_rawDescData) + file_api_proto_events_room_v1_events_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_api_proto_events_room_v1_events_proto_rawDesc), len(file_api_proto_events_room_v1_events_proto_rawDesc))) }) return file_api_proto_events_room_v1_events_proto_rawDescData } -var file_api_proto_events_room_v1_events_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_api_proto_events_room_v1_events_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_api_proto_events_room_v1_events_proto_goTypes = []any{ - (*EventEnvelope)(nil), // 0: hyapp.events.room.v1.EventEnvelope - (*RoomCreated)(nil), // 1: hyapp.events.room.v1.RoomCreated - (*RoomUserJoined)(nil), // 2: hyapp.events.room.v1.RoomUserJoined - (*RoomUserLeft)(nil), // 3: hyapp.events.room.v1.RoomUserLeft - (*RoomMicChanged)(nil), // 4: hyapp.events.room.v1.RoomMicChanged - (*RoomUserMuted)(nil), // 5: hyapp.events.room.v1.RoomUserMuted - (*RoomUserKicked)(nil), // 6: hyapp.events.room.v1.RoomUserKicked - (*RoomGiftSent)(nil), // 7: hyapp.events.room.v1.RoomGiftSent - (*RoomHeatChanged)(nil), // 8: hyapp.events.room.v1.RoomHeatChanged - (*RoomRankChanged)(nil), // 9: hyapp.events.room.v1.RoomRankChanged + (*EventEnvelope)(nil), // 0: hyapp.events.room.v1.EventEnvelope + (*RoomCreated)(nil), // 1: hyapp.events.room.v1.RoomCreated + (*RoomUserJoined)(nil), // 2: hyapp.events.room.v1.RoomUserJoined + (*RoomUserLeft)(nil), // 3: hyapp.events.room.v1.RoomUserLeft + (*RoomMicChanged)(nil), // 4: hyapp.events.room.v1.RoomMicChanged + (*RoomMicSeatLocked)(nil), // 5: hyapp.events.room.v1.RoomMicSeatLocked + (*RoomChatEnabledChanged)(nil), // 6: hyapp.events.room.v1.RoomChatEnabledChanged + (*RoomAdminChanged)(nil), // 7: hyapp.events.room.v1.RoomAdminChanged + (*RoomHostTransferred)(nil), // 8: hyapp.events.room.v1.RoomHostTransferred + (*RoomUserMuted)(nil), // 9: hyapp.events.room.v1.RoomUserMuted + (*RoomUserKicked)(nil), // 10: hyapp.events.room.v1.RoomUserKicked + (*RoomUserUnbanned)(nil), // 11: hyapp.events.room.v1.RoomUserUnbanned + (*RoomGiftSent)(nil), // 12: hyapp.events.room.v1.RoomGiftSent + (*RoomHeatChanged)(nil), // 13: hyapp.events.room.v1.RoomHeatChanged + (*RoomRankChanged)(nil), // 14: hyapp.events.room.v1.RoomRankChanged } var file_api_proto_events_room_v1_events_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type @@ -819,135 +1119,13 @@ func file_api_proto_events_room_v1_events_proto_init() { if File_api_proto_events_room_v1_events_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_api_proto_events_room_v1_events_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*EventEnvelope); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_events_room_v1_events_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*RoomCreated); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_events_room_v1_events_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*RoomUserJoined); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_events_room_v1_events_proto_msgTypes[3].Exporter = func(v any, i int) any { - switch v := v.(*RoomUserLeft); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_events_room_v1_events_proto_msgTypes[4].Exporter = func(v any, i int) any { - switch v := v.(*RoomMicChanged); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_events_room_v1_events_proto_msgTypes[5].Exporter = func(v any, i int) any { - switch v := v.(*RoomUserMuted); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_events_room_v1_events_proto_msgTypes[6].Exporter = func(v any, i int) any { - switch v := v.(*RoomUserKicked); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_events_room_v1_events_proto_msgTypes[7].Exporter = func(v any, i int) any { - switch v := v.(*RoomGiftSent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_events_room_v1_events_proto_msgTypes[8].Exporter = func(v any, i int) any { - switch v := v.(*RoomHeatChanged); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_events_room_v1_events_proto_msgTypes[9].Exporter = func(v any, i int) any { - switch v := v.(*RoomRankChanged); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_api_proto_events_room_v1_events_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_proto_events_room_v1_events_proto_rawDesc), len(file_api_proto_events_room_v1_events_proto_rawDesc)), NumEnums: 0, - NumMessages: 10, + NumMessages: 15, NumExtensions: 0, NumServices: 0, }, @@ -956,7 +1134,6 @@ func file_api_proto_events_room_v1_events_proto_init() { MessageInfos: file_api_proto_events_room_v1_events_proto_msgTypes, }.Build() File_api_proto_events_room_v1_events_proto = out.File - file_api_proto_events_room_v1_events_proto_rawDesc = nil file_api_proto_events_room_v1_events_proto_goTypes = nil file_api_proto_events_room_v1_events_proto_depIdxs = nil } diff --git a/api/proto/events/room/v1/events.proto b/api/proto/events/room/v1/events.proto index e7b0e1db..3d8b78b4 100644 --- a/api/proto/events/room/v1/events.proto +++ b/api/proto/events/room/v1/events.proto @@ -41,6 +41,41 @@ message RoomMicChanged { int32 from_seat = 3; int32 to_seat = 4; string action = 5; + // mic_session_id 用于把业务麦位事件和 RTC 发流会话绑定,客户端和 webhook 都必须透传。 + string mic_session_id = 6; + // publish_state 透出 pending_publish/publishing/idle,避免把“占麦”和“已发流”混成一个状态。 + string publish_state = 7; + // reason 表示系统触发的下麦原因,例如 publish_timeout。 + string reason = 8; + int64 publish_deadline_ms = 9; + int64 publish_event_time_ms = 10; +} + +// RoomMicSeatLocked 表达单个麦位锁定状态变更。 +message RoomMicSeatLocked { + int64 actor_user_id = 1; + int32 seat_no = 2; + bool locked = 3; +} + +// RoomChatEnabledChanged 表达公屏开关状态变更。 +message RoomChatEnabledChanged { + int64 actor_user_id = 1; + bool enabled = 2; +} + +// RoomAdminChanged 表达房间管理员集合变更。 +message RoomAdminChanged { + int64 actor_user_id = 1; + int64 target_user_id = 2; + bool enabled = 3; +} + +// RoomHostTransferred 表达主持人身份转移。 +message RoomHostTransferred { + int64 actor_user_id = 1; + int64 old_host_user_id = 2; + int64 new_host_user_id = 3; } // RoomUserMuted 表达禁言状态变更。 @@ -56,6 +91,12 @@ message RoomUserKicked { int64 target_user_id = 2; } +// RoomUserUnbanned 表达解除房间 ban。 +message RoomUserUnbanned { + int64 actor_user_id = 1; + int64 target_user_id = 2; +} + // RoomGiftSent 表达送礼成功且已落房间状态。 message RoomGiftSent { int64 sender_user_id = 1; @@ -78,4 +119,3 @@ message RoomRankChanged { int64 score = 2; int64 gift_value = 3; } - diff --git a/api/proto/room/v1/room.pb.go b/api/proto/room/v1/room.pb.go index fdf16562..392de65a 100644 --- a/api/proto/room/v1/room.pb.go +++ b/api/proto/room/v1/room.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 -// protoc v5.29.2 +// protoc-gen-go v1.36.6 +// protoc v5.27.3 // source: api/proto/room/v1/room.proto package roomv1 @@ -11,6 +11,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -23,28 +24,25 @@ const ( // RequestMeta 固定承载所有命令的调用元信息。 // room-service 用它串起幂等、追踪、路由和业务操作者身份。 type RequestMeta struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` - CommandId string `protobuf:"bytes,2,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` - ActorUserId int64 `protobuf:"varint,3,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + CommandId string `protobuf:"bytes,2,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + ActorUserId int64 `protobuf:"varint,3,opt,name=actor_user_id,json=actorUserId,proto3" json:"actor_user_id,omitempty"` // room_id 是命令路由、持久化和腾讯 IM/RTC 映射共同依赖的必填字段。 // CreateRoom 时必须满足 ^[A-Za-z0-9_-]{1,48}$。 RoomId string `protobuf:"bytes,4,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` GatewayNodeId string `protobuf:"bytes,5,opt,name=gateway_node_id,json=gatewayNodeId,proto3" json:"gateway_node_id,omitempty"` SessionId string `protobuf:"bytes,6,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` SentAtMs int64 `protobuf:"varint,7,opt,name=sent_at_ms,json=sentAtMs,proto3" json:"sent_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *RequestMeta) Reset() { *x = RequestMeta{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RequestMeta) String() string { @@ -55,7 +53,7 @@ func (*RequestMeta) ProtoMessage() {} func (x *RequestMeta) ProtoReflect() protoreflect.Message { mi := &file_api_proto_room_v1_room_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -121,22 +119,19 @@ func (x *RequestMeta) GetSentAtMs() int64 { // CommandResult 统一返回命令是否真正落地,以及落地后的房间版本。 type CommandResult struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Applied bool `protobuf:"varint,1,opt,name=applied,proto3" json:"applied,omitempty"` + RoomVersion int64 `protobuf:"varint,2,opt,name=room_version,json=roomVersion,proto3" json:"room_version,omitempty"` + ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` unknownFields protoimpl.UnknownFields - - Applied bool `protobuf:"varint,1,opt,name=applied,proto3" json:"applied,omitempty"` - RoomVersion int64 `protobuf:"varint,2,opt,name=room_version,json=roomVersion,proto3" json:"room_version,omitempty"` - ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + sizeCache protoimpl.SizeCache } func (x *CommandResult) Reset() { *x = CommandResult{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CommandResult) String() string { @@ -147,7 +142,7 @@ func (*CommandResult) ProtoMessage() {} func (x *CommandResult) ProtoReflect() protoreflect.Message { mi := &file_api_proto_room_v1_room_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -185,23 +180,20 @@ func (x *CommandResult) GetServerTimeMs() int64 { // RoomUser 只表达 room-service 需要保存的房间内轻量用户态。 type RoomUser struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Role string `protobuf:"bytes,2,opt,name=role,proto3" json:"role,omitempty"` + JoinedAtMs int64 `protobuf:"varint,3,opt,name=joined_at_ms,json=joinedAtMs,proto3" json:"joined_at_ms,omitempty"` + LastSeenAtMs int64 `protobuf:"varint,4,opt,name=last_seen_at_ms,json=lastSeenAtMs,proto3" json:"last_seen_at_ms,omitempty"` unknownFields protoimpl.UnknownFields - - UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - Role string `protobuf:"bytes,2,opt,name=role,proto3" json:"role,omitempty"` - JoinedAtMs int64 `protobuf:"varint,3,opt,name=joined_at_ms,json=joinedAtMs,proto3" json:"joined_at_ms,omitempty"` - LastSeenAtMs int64 `protobuf:"varint,4,opt,name=last_seen_at_ms,json=lastSeenAtMs,proto3" json:"last_seen_at_ms,omitempty"` + sizeCache protoimpl.SizeCache } func (x *RoomUser) Reset() { *x = RoomUser{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RoomUser) String() string { @@ -212,7 +204,7 @@ func (*RoomUser) ProtoMessage() {} func (x *RoomUser) ProtoReflect() protoreflect.Message { mi := &file_api_proto_room_v1_room_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -255,24 +247,31 @@ func (x *RoomUser) GetLastSeenAtMs() int64 { return 0 } -// SeatState 表达单个麦位当前占用关系。 +// SeatState 表达单个麦位当前占用和 RTC 发流确认状态。 type SeatState struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - SeatNo int32 `protobuf:"varint,1,opt,name=seat_no,json=seatNo,proto3" json:"seat_no,omitempty"` - UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - Locked bool `protobuf:"varint,3,opt,name=locked,proto3" json:"locked,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + SeatNo int32 `protobuf:"varint,1,opt,name=seat_no,json=seatNo,proto3" json:"seat_no,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Locked bool `protobuf:"varint,3,opt,name=locked,proto3" json:"locked,omitempty"` + // publish_state 为空表示空麦或未进入发流流程;上麦后先进入 pending_publish,确认后进入 publishing。 + PublishState string `protobuf:"bytes,4,opt,name=publish_state,json=publishState,proto3" json:"publish_state,omitempty"` + // mic_session_id 是单次上麦发流会话 ID;所有客户端确认或 RTC webhook 必须带回它。 + MicSessionId string `protobuf:"bytes,5,opt,name=mic_session_id,json=micSessionId,proto3" json:"mic_session_id,omitempty"` + // publish_deadline_ms 是 pending_publish 必须确认发流的截止时间。 + PublishDeadlineMs int64 `protobuf:"varint,6,opt,name=publish_deadline_ms,json=publishDeadlineMs,proto3" json:"publish_deadline_ms,omitempty"` + // mic_session_room_version 是创建本次 mic_session 的房间版本,用于丢弃旧版本 RTC 事件。 + MicSessionRoomVersion int64 `protobuf:"varint,7,opt,name=mic_session_room_version,json=micSessionRoomVersion,proto3" json:"mic_session_room_version,omitempty"` + // last_publish_event_time_ms 记录已接受的最新 RTC/客户端发流事件时间。 + LastPublishEventTimeMs int64 `protobuf:"varint,8,opt,name=last_publish_event_time_ms,json=lastPublishEventTimeMs,proto3" json:"last_publish_event_time_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *SeatState) Reset() { *x = SeatState{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SeatState) String() string { @@ -283,7 +282,7 @@ func (*SeatState) ProtoMessage() {} func (x *SeatState) ProtoReflect() protoreflect.Message { mi := &file_api_proto_room_v1_room_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -319,25 +318,57 @@ func (x *SeatState) GetLocked() bool { return false } +func (x *SeatState) GetPublishState() string { + if x != nil { + return x.PublishState + } + return "" +} + +func (x *SeatState) GetMicSessionId() string { + if x != nil { + return x.MicSessionId + } + return "" +} + +func (x *SeatState) GetPublishDeadlineMs() int64 { + if x != nil { + return x.PublishDeadlineMs + } + return 0 +} + +func (x *SeatState) GetMicSessionRoomVersion() int64 { + if x != nil { + return x.MicSessionRoomVersion + } + return 0 +} + +func (x *SeatState) GetLastPublishEventTimeMs() int64 { + if x != nil { + return x.LastPublishEventTimeMs + } + return 0 +} + // RankItem 表达房间内本地礼物榜项目。 type RankItem struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Score int64 `protobuf:"varint,2,opt,name=score,proto3" json:"score,omitempty"` + GiftValue int64 `protobuf:"varint,3,opt,name=gift_value,json=giftValue,proto3" json:"gift_value,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,4,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` unknownFields protoimpl.UnknownFields - - UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - Score int64 `protobuf:"varint,2,opt,name=score,proto3" json:"score,omitempty"` - GiftValue int64 `protobuf:"varint,3,opt,name=gift_value,json=giftValue,proto3" json:"gift_value,omitempty"` - UpdatedAtMs int64 `protobuf:"varint,4,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + sizeCache protoimpl.SizeCache } func (x *RankItem) Reset() { *x = RankItem{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RankItem) String() string { @@ -348,7 +379,7 @@ func (*RankItem) ProtoMessage() {} func (x *RankItem) ProtoReflect() protoreflect.Message { mi := &file_api_proto_room_v1_room_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -393,34 +424,31 @@ func (x *RankItem) GetUpdatedAtMs() int64 { // RoomSnapshot 把 room-service 对外需要暴露的房间投影集中返回。 type RoomSnapshot struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + RoomId string `protobuf:"bytes,1,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` + OwnerUserId int64 `protobuf:"varint,2,opt,name=owner_user_id,json=ownerUserId,proto3" json:"owner_user_id,omitempty"` + HostUserId int64 `protobuf:"varint,3,opt,name=host_user_id,json=hostUserId,proto3" json:"host_user_id,omitempty"` + Mode string `protobuf:"bytes,4,opt,name=mode,proto3" json:"mode,omitempty"` + Status string `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"` + ChatEnabled bool `protobuf:"varint,6,opt,name=chat_enabled,json=chatEnabled,proto3" json:"chat_enabled,omitempty"` + MicSeats []*SeatState `protobuf:"bytes,7,rep,name=mic_seats,json=micSeats,proto3" json:"mic_seats,omitempty"` + OnlineUsers []*RoomUser `protobuf:"bytes,8,rep,name=online_users,json=onlineUsers,proto3" json:"online_users,omitempty"` + AdminUserIds []int64 `protobuf:"varint,9,rep,packed,name=admin_user_ids,json=adminUserIds,proto3" json:"admin_user_ids,omitempty"` + BanUserIds []int64 `protobuf:"varint,10,rep,packed,name=ban_user_ids,json=banUserIds,proto3" json:"ban_user_ids,omitempty"` + MuteUserIds []int64 `protobuf:"varint,11,rep,packed,name=mute_user_ids,json=muteUserIds,proto3" json:"mute_user_ids,omitempty"` + GiftRank []*RankItem `protobuf:"bytes,12,rep,name=gift_rank,json=giftRank,proto3" json:"gift_rank,omitempty"` + RoomExt map[string]string `protobuf:"bytes,13,rep,name=room_ext,json=roomExt,proto3" json:"room_ext,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Heat int64 `protobuf:"varint,14,opt,name=heat,proto3" json:"heat,omitempty"` + Version int64 `protobuf:"varint,15,opt,name=version,proto3" json:"version,omitempty"` unknownFields protoimpl.UnknownFields - - RoomId string `protobuf:"bytes,1,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` - OwnerUserId int64 `protobuf:"varint,2,opt,name=owner_user_id,json=ownerUserId,proto3" json:"owner_user_id,omitempty"` - HostUserId int64 `protobuf:"varint,3,opt,name=host_user_id,json=hostUserId,proto3" json:"host_user_id,omitempty"` - Mode string `protobuf:"bytes,4,opt,name=mode,proto3" json:"mode,omitempty"` - Status string `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"` - ChatEnabled bool `protobuf:"varint,6,opt,name=chat_enabled,json=chatEnabled,proto3" json:"chat_enabled,omitempty"` - MicSeats []*SeatState `protobuf:"bytes,7,rep,name=mic_seats,json=micSeats,proto3" json:"mic_seats,omitempty"` - OnlineUsers []*RoomUser `protobuf:"bytes,8,rep,name=online_users,json=onlineUsers,proto3" json:"online_users,omitempty"` - AdminUserIds []int64 `protobuf:"varint,9,rep,packed,name=admin_user_ids,json=adminUserIds,proto3" json:"admin_user_ids,omitempty"` - BanUserIds []int64 `protobuf:"varint,10,rep,packed,name=ban_user_ids,json=banUserIds,proto3" json:"ban_user_ids,omitempty"` - MuteUserIds []int64 `protobuf:"varint,11,rep,packed,name=mute_user_ids,json=muteUserIds,proto3" json:"mute_user_ids,omitempty"` - GiftRank []*RankItem `protobuf:"bytes,12,rep,name=gift_rank,json=giftRank,proto3" json:"gift_rank,omitempty"` - RoomExt map[string]string `protobuf:"bytes,13,rep,name=room_ext,json=roomExt,proto3" json:"room_ext,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - Heat int64 `protobuf:"varint,14,opt,name=heat,proto3" json:"heat,omitempty"` - Version int64 `protobuf:"varint,15,opt,name=version,proto3" json:"version,omitempty"` + sizeCache protoimpl.SizeCache } func (x *RoomSnapshot) Reset() { *x = RoomSnapshot{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RoomSnapshot) String() string { @@ -431,7 +459,7 @@ func (*RoomSnapshot) ProtoMessage() {} func (x *RoomSnapshot) ProtoReflect() protoreflect.Message { mi := &file_api_proto_room_v1_room_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -553,28 +581,22 @@ func (x *RoomSnapshot) GetVersion() int64 { // CreateRoomRequest 创建一个新的房间执行单元。 // 必填语义:meta.room_id、meta.command_id、meta.actor_user_id、seat_count 和 mode。 -// owner_user_id/host_user_id 是内部兼容字段,外部 gateway 创建路径不接收客户端自报值。 type CreateRoomRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - OwnerUserId int64 `protobuf:"varint,2,opt,name=owner_user_id,json=ownerUserId,proto3" json:"owner_user_id,omitempty"` - HostUserId int64 `protobuf:"varint,3,opt,name=host_user_id,json=hostUserId,proto3" json:"host_user_id,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` // seat_count 必须大于 0;没有麦位的语音房不进入 Room Cell。 - SeatCount int32 `protobuf:"varint,4,opt,name=seat_count,json=seatCount,proto3" json:"seat_count,omitempty"` + SeatCount int32 `protobuf:"varint,2,opt,name=seat_count,json=seatCount,proto3" json:"seat_count,omitempty"` // mode 必须非空;当前只作为房间状态字段保存和透出。 - Mode string `protobuf:"bytes,5,opt,name=mode,proto3" json:"mode,omitempty"` + Mode string `protobuf:"bytes,3,opt,name=mode,proto3" json:"mode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *CreateRoomRequest) Reset() { *x = CreateRoomRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CreateRoomRequest) String() string { @@ -585,7 +607,7 @@ func (*CreateRoomRequest) ProtoMessage() {} func (x *CreateRoomRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_room_v1_room_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -607,20 +629,6 @@ func (x *CreateRoomRequest) GetMeta() *RequestMeta { return nil } -func (x *CreateRoomRequest) GetOwnerUserId() int64 { - if x != nil { - return x.OwnerUserId - } - return 0 -} - -func (x *CreateRoomRequest) GetHostUserId() int64 { - if x != nil { - return x.HostUserId - } - return 0 -} - func (x *CreateRoomRequest) GetSeatCount() int32 { if x != nil { return x.SeatCount @@ -637,21 +645,18 @@ func (x *CreateRoomRequest) GetMode() string { // CreateRoomResponse 返回新建后的房间快照。 type CreateRoomResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + Room *RoomSnapshot `protobuf:"bytes,2,opt,name=room,proto3" json:"room,omitempty"` unknownFields protoimpl.UnknownFields - - Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` - Room *RoomSnapshot `protobuf:"bytes,2,opt,name=room,proto3" json:"room,omitempty"` + sizeCache protoimpl.SizeCache } func (x *CreateRoomResponse) Reset() { *x = CreateRoomResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CreateRoomResponse) String() string { @@ -662,7 +667,7 @@ func (*CreateRoomResponse) ProtoMessage() {} func (x *CreateRoomResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_room_v1_room_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -693,21 +698,18 @@ func (x *CreateRoomResponse) GetRoom() *RoomSnapshot { // JoinRoomRequest 把用户 presence 接入房间业务态。 type JoinRoomRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + Role string `protobuf:"bytes,2,opt,name=role,proto3" json:"role,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - Role string `protobuf:"bytes,2,opt,name=role,proto3" json:"role,omitempty"` + sizeCache protoimpl.SizeCache } func (x *JoinRoomRequest) Reset() { *x = JoinRoomRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *JoinRoomRequest) String() string { @@ -718,7 +720,7 @@ func (*JoinRoomRequest) ProtoMessage() {} func (x *JoinRoomRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_room_v1_room_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -749,22 +751,19 @@ func (x *JoinRoomRequest) GetRole() string { // JoinRoomResponse 返回加入后的房间快照。 type JoinRoomResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + User *RoomUser `protobuf:"bytes,2,opt,name=user,proto3" json:"user,omitempty"` + Room *RoomSnapshot `protobuf:"bytes,3,opt,name=room,proto3" json:"room,omitempty"` unknownFields protoimpl.UnknownFields - - Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` - User *RoomUser `protobuf:"bytes,2,opt,name=user,proto3" json:"user,omitempty"` - Room *RoomSnapshot `protobuf:"bytes,3,opt,name=room,proto3" json:"room,omitempty"` + sizeCache protoimpl.SizeCache } func (x *JoinRoomResponse) Reset() { *x = JoinRoomResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *JoinRoomResponse) String() string { @@ -775,7 +774,7 @@ func (*JoinRoomResponse) ProtoMessage() {} func (x *JoinRoomResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_room_v1_room_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -813,20 +812,17 @@ func (x *JoinRoomResponse) GetRoom() *RoomSnapshot { // LeaveRoomRequest 把用户从房间 presence 移出。 type LeaveRoomRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + sizeCache protoimpl.SizeCache } func (x *LeaveRoomRequest) Reset() { *x = LeaveRoomRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *LeaveRoomRequest) String() string { @@ -837,7 +833,7 @@ func (*LeaveRoomRequest) ProtoMessage() {} func (x *LeaveRoomRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_room_v1_room_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -861,21 +857,18 @@ func (x *LeaveRoomRequest) GetMeta() *RequestMeta { // LeaveRoomResponse 返回离房后的房间快照。 type LeaveRoomResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + Room *RoomSnapshot `protobuf:"bytes,2,opt,name=room,proto3" json:"room,omitempty"` unknownFields protoimpl.UnknownFields - - Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` - Room *RoomSnapshot `protobuf:"bytes,2,opt,name=room,proto3" json:"room,omitempty"` + sizeCache protoimpl.SizeCache } func (x *LeaveRoomResponse) Reset() { *x = LeaveRoomResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *LeaveRoomResponse) String() string { @@ -886,7 +879,7 @@ func (*LeaveRoomResponse) ProtoMessage() {} func (x *LeaveRoomResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_room_v1_room_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -917,21 +910,18 @@ func (x *LeaveRoomResponse) GetRoom() *RoomSnapshot { // MicUpRequest 申请把用户放到指定麦位。 type MicUpRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + SeatNo int32 `protobuf:"varint,2,opt,name=seat_no,json=seatNo,proto3" json:"seat_no,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - SeatNo int32 `protobuf:"varint,2,opt,name=seat_no,json=seatNo,proto3" json:"seat_no,omitempty"` + sizeCache protoimpl.SizeCache } func (x *MicUpRequest) Reset() { *x = MicUpRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *MicUpRequest) String() string { @@ -942,7 +932,7 @@ func (*MicUpRequest) ProtoMessage() {} func (x *MicUpRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_room_v1_room_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -973,22 +963,21 @@ func (x *MicUpRequest) GetSeatNo() int32 { // MicUpResponse 返回最新房间快照与目标麦位。 type MicUpResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` - SeatNo int32 `protobuf:"varint,2,opt,name=seat_no,json=seatNo,proto3" json:"seat_no,omitempty"` - Room *RoomSnapshot `protobuf:"bytes,3,opt,name=room,proto3" json:"room,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + SeatNo int32 `protobuf:"varint,2,opt,name=seat_no,json=seatNo,proto3" json:"seat_no,omitempty"` + Room *RoomSnapshot `protobuf:"bytes,3,opt,name=room,proto3" json:"room,omitempty"` + MicSessionId string `protobuf:"bytes,4,opt,name=mic_session_id,json=micSessionId,proto3" json:"mic_session_id,omitempty"` + PublishDeadlineMs int64 `protobuf:"varint,5,opt,name=publish_deadline_ms,json=publishDeadlineMs,proto3" json:"publish_deadline_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *MicUpResponse) Reset() { *x = MicUpResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *MicUpResponse) String() string { @@ -999,7 +988,7 @@ func (*MicUpResponse) ProtoMessage() {} func (x *MicUpResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_room_v1_room_proto_msgTypes[13] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1035,23 +1024,36 @@ func (x *MicUpResponse) GetRoom() *RoomSnapshot { return nil } +func (x *MicUpResponse) GetMicSessionId() string { + if x != nil { + return x.MicSessionId + } + return "" +} + +func (x *MicUpResponse) GetPublishDeadlineMs() int64 { + if x != nil { + return x.PublishDeadlineMs + } + return 0 +} + // MicDownRequest 把用户从当前麦位移下。 type MicDownRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + // reason 为空表示主动下麦;系统自动释放麦位时会写入稳定原因,例如 publish_timeout。 + Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *MicDownRequest) Reset() { *x = MicDownRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *MicDownRequest) String() string { @@ -1062,7 +1064,7 @@ func (*MicDownRequest) ProtoMessage() {} func (x *MicDownRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_room_v1_room_proto_msgTypes[14] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1091,24 +1093,28 @@ func (x *MicDownRequest) GetTargetUserId() int64 { return 0 } +func (x *MicDownRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + // MicDownResponse 返回最新房间快照与目标麦位。 type MicDownResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + SeatNo int32 `protobuf:"varint,2,opt,name=seat_no,json=seatNo,proto3" json:"seat_no,omitempty"` + Room *RoomSnapshot `protobuf:"bytes,3,opt,name=room,proto3" json:"room,omitempty"` unknownFields protoimpl.UnknownFields - - Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` - SeatNo int32 `protobuf:"varint,2,opt,name=seat_no,json=seatNo,proto3" json:"seat_no,omitempty"` - Room *RoomSnapshot `protobuf:"bytes,3,opt,name=room,proto3" json:"room,omitempty"` + sizeCache protoimpl.SizeCache } func (x *MicDownResponse) Reset() { *x = MicDownResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *MicDownResponse) String() string { @@ -1119,7 +1125,7 @@ func (*MicDownResponse) ProtoMessage() {} func (x *MicDownResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_room_v1_room_proto_msgTypes[15] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1157,22 +1163,19 @@ func (x *MicDownResponse) GetRoom() *RoomSnapshot { // ChangeMicSeatRequest 直接调整指定用户所在麦位。 type ChangeMicSeatRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + SeatNo int32 `protobuf:"varint,3,opt,name=seat_no,json=seatNo,proto3" json:"seat_no,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` - SeatNo int32 `protobuf:"varint,3,opt,name=seat_no,json=seatNo,proto3" json:"seat_no,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ChangeMicSeatRequest) Reset() { *x = ChangeMicSeatRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ChangeMicSeatRequest) String() string { @@ -1183,7 +1186,7 @@ func (*ChangeMicSeatRequest) ProtoMessage() {} func (x *ChangeMicSeatRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_room_v1_room_proto_msgTypes[16] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1221,21 +1224,18 @@ func (x *ChangeMicSeatRequest) GetSeatNo() int32 { // ChangeMicSeatResponse 返回变更后的房间快照。 type ChangeMicSeatResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + Room *RoomSnapshot `protobuf:"bytes,2,opt,name=room,proto3" json:"room,omitempty"` unknownFields protoimpl.UnknownFields - - Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` - Room *RoomSnapshot `protobuf:"bytes,2,opt,name=room,proto3" json:"room,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ChangeMicSeatResponse) Reset() { *x = ChangeMicSeatResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ChangeMicSeatResponse) String() string { @@ -1246,7 +1246,7 @@ func (*ChangeMicSeatResponse) ProtoMessage() {} func (x *ChangeMicSeatResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_room_v1_room_proto_msgTypes[17] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1275,24 +1275,609 @@ func (x *ChangeMicSeatResponse) GetRoom() *RoomSnapshot { return nil } +// ConfirmMicPublishingRequest 确认当前 mic_session 已经在 RTC 侧成功发布音频。 +// 客户端 SDK 回调和后续 RTC webhook 都必须带 mic_session_id、room_version 和 event_time_ms。 +type ConfirmMicPublishingRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + // target_user_id 为空时默认确认 actor_user_id;RTC webhook 入口可显式指定目标用户。 + TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + MicSessionId string `protobuf:"bytes,3,opt,name=mic_session_id,json=micSessionId,proto3" json:"mic_session_id,omitempty"` + RoomVersion int64 `protobuf:"varint,4,opt,name=room_version,json=roomVersion,proto3" json:"room_version,omitempty"` + EventTimeMs int64 `protobuf:"varint,5,opt,name=event_time_ms,json=eventTimeMs,proto3" json:"event_time_ms,omitempty"` + Source string `protobuf:"bytes,6,opt,name=source,proto3" json:"source,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConfirmMicPublishingRequest) Reset() { + *x = ConfirmMicPublishingRequest{} + mi := &file_api_proto_room_v1_room_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConfirmMicPublishingRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfirmMicPublishingRequest) ProtoMessage() {} + +func (x *ConfirmMicPublishingRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_room_v1_room_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConfirmMicPublishingRequest.ProtoReflect.Descriptor instead. +func (*ConfirmMicPublishingRequest) Descriptor() ([]byte, []int) { + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{18} +} + +func (x *ConfirmMicPublishingRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *ConfirmMicPublishingRequest) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *ConfirmMicPublishingRequest) GetMicSessionId() string { + if x != nil { + return x.MicSessionId + } + return "" +} + +func (x *ConfirmMicPublishingRequest) GetRoomVersion() int64 { + if x != nil { + return x.RoomVersion + } + return 0 +} + +func (x *ConfirmMicPublishingRequest) GetEventTimeMs() int64 { + if x != nil { + return x.EventTimeMs + } + return 0 +} + +func (x *ConfirmMicPublishingRequest) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +// ConfirmMicPublishingResponse 返回确认后的最新房间快照。 +type ConfirmMicPublishingResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + SeatNo int32 `protobuf:"varint,2,opt,name=seat_no,json=seatNo,proto3" json:"seat_no,omitempty"` + Room *RoomSnapshot `protobuf:"bytes,3,opt,name=room,proto3" json:"room,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConfirmMicPublishingResponse) Reset() { + *x = ConfirmMicPublishingResponse{} + mi := &file_api_proto_room_v1_room_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConfirmMicPublishingResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfirmMicPublishingResponse) ProtoMessage() {} + +func (x *ConfirmMicPublishingResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_room_v1_room_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConfirmMicPublishingResponse.ProtoReflect.Descriptor instead. +func (*ConfirmMicPublishingResponse) Descriptor() ([]byte, []int) { + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{19} +} + +func (x *ConfirmMicPublishingResponse) GetResult() *CommandResult { + if x != nil { + return x.Result + } + return nil +} + +func (x *ConfirmMicPublishingResponse) GetSeatNo() int32 { + if x != nil { + return x.SeatNo + } + return 0 +} + +func (x *ConfirmMicPublishingResponse) GetRoom() *RoomSnapshot { + if x != nil { + return x.Room + } + return nil +} + +// SetMicSeatLockRequest 锁定或解锁指定麦位。 +type SetMicSeatLockRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + SeatNo int32 `protobuf:"varint,2,opt,name=seat_no,json=seatNo,proto3" json:"seat_no,omitempty"` + Locked bool `protobuf:"varint,3,opt,name=locked,proto3" json:"locked,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetMicSeatLockRequest) Reset() { + *x = SetMicSeatLockRequest{} + mi := &file_api_proto_room_v1_room_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetMicSeatLockRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetMicSeatLockRequest) ProtoMessage() {} + +func (x *SetMicSeatLockRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_room_v1_room_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetMicSeatLockRequest.ProtoReflect.Descriptor instead. +func (*SetMicSeatLockRequest) Descriptor() ([]byte, []int) { + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{20} +} + +func (x *SetMicSeatLockRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *SetMicSeatLockRequest) GetSeatNo() int32 { + if x != nil { + return x.SeatNo + } + return 0 +} + +func (x *SetMicSeatLockRequest) GetLocked() bool { + if x != nil { + return x.Locked + } + return false +} + +// SetMicSeatLockResponse 返回锁麦后的最新房间快照。 +type SetMicSeatLockResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + Room *RoomSnapshot `protobuf:"bytes,2,opt,name=room,proto3" json:"room,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetMicSeatLockResponse) Reset() { + *x = SetMicSeatLockResponse{} + mi := &file_api_proto_room_v1_room_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetMicSeatLockResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetMicSeatLockResponse) ProtoMessage() {} + +func (x *SetMicSeatLockResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_room_v1_room_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetMicSeatLockResponse.ProtoReflect.Descriptor instead. +func (*SetMicSeatLockResponse) Descriptor() ([]byte, []int) { + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{21} +} + +func (x *SetMicSeatLockResponse) GetResult() *CommandResult { + if x != nil { + return x.Result + } + return nil +} + +func (x *SetMicSeatLockResponse) GetRoom() *RoomSnapshot { + if x != nil { + return x.Room + } + return nil +} + +// SetChatEnabledRequest 开启或关闭房间公屏。 +type SetChatEnabledRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + Enabled bool `protobuf:"varint,2,opt,name=enabled,proto3" json:"enabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetChatEnabledRequest) Reset() { + *x = SetChatEnabledRequest{} + mi := &file_api_proto_room_v1_room_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetChatEnabledRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetChatEnabledRequest) ProtoMessage() {} + +func (x *SetChatEnabledRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_room_v1_room_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetChatEnabledRequest.ProtoReflect.Descriptor instead. +func (*SetChatEnabledRequest) Descriptor() ([]byte, []int) { + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{22} +} + +func (x *SetChatEnabledRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *SetChatEnabledRequest) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +// SetChatEnabledResponse 返回聊天开关变更后的最新房间快照。 +type SetChatEnabledResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + Room *RoomSnapshot `protobuf:"bytes,2,opt,name=room,proto3" json:"room,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetChatEnabledResponse) Reset() { + *x = SetChatEnabledResponse{} + mi := &file_api_proto_room_v1_room_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetChatEnabledResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetChatEnabledResponse) ProtoMessage() {} + +func (x *SetChatEnabledResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_room_v1_room_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetChatEnabledResponse.ProtoReflect.Descriptor instead. +func (*SetChatEnabledResponse) Descriptor() ([]byte, []int) { + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{23} +} + +func (x *SetChatEnabledResponse) GetResult() *CommandResult { + if x != nil { + return x.Result + } + return nil +} + +func (x *SetChatEnabledResponse) GetRoom() *RoomSnapshot { + if x != nil { + return x.Room + } + return nil +} + +// SetRoomAdminRequest 添加或移除房间管理员。 +type SetRoomAdminRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + Enabled bool `protobuf:"varint,3,opt,name=enabled,proto3" json:"enabled,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetRoomAdminRequest) Reset() { + *x = SetRoomAdminRequest{} + mi := &file_api_proto_room_v1_room_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetRoomAdminRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetRoomAdminRequest) ProtoMessage() {} + +func (x *SetRoomAdminRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_room_v1_room_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetRoomAdminRequest.ProtoReflect.Descriptor instead. +func (*SetRoomAdminRequest) Descriptor() ([]byte, []int) { + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{24} +} + +func (x *SetRoomAdminRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *SetRoomAdminRequest) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *SetRoomAdminRequest) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +// SetRoomAdminResponse 返回管理员集合变更后的最新房间快照。 +type SetRoomAdminResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + Room *RoomSnapshot `protobuf:"bytes,2,opt,name=room,proto3" json:"room,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SetRoomAdminResponse) Reset() { + *x = SetRoomAdminResponse{} + mi := &file_api_proto_room_v1_room_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SetRoomAdminResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetRoomAdminResponse) ProtoMessage() {} + +func (x *SetRoomAdminResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_room_v1_room_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SetRoomAdminResponse.ProtoReflect.Descriptor instead. +func (*SetRoomAdminResponse) Descriptor() ([]byte, []int) { + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{25} +} + +func (x *SetRoomAdminResponse) GetResult() *CommandResult { + if x != nil { + return x.Result + } + return nil +} + +func (x *SetRoomAdminResponse) GetRoom() *RoomSnapshot { + if x != nil { + return x.Room + } + return nil +} + +// TransferRoomHostRequest 把主持人身份转移给房间内用户。 +type TransferRoomHostRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransferRoomHostRequest) Reset() { + *x = TransferRoomHostRequest{} + mi := &file_api_proto_room_v1_room_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransferRoomHostRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransferRoomHostRequest) ProtoMessage() {} + +func (x *TransferRoomHostRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_room_v1_room_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TransferRoomHostRequest.ProtoReflect.Descriptor instead. +func (*TransferRoomHostRequest) Descriptor() ([]byte, []int) { + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{26} +} + +func (x *TransferRoomHostRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *TransferRoomHostRequest) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +// TransferRoomHostResponse 返回主持人转移后的最新房间快照。 +type TransferRoomHostResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + Room *RoomSnapshot `protobuf:"bytes,2,opt,name=room,proto3" json:"room,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransferRoomHostResponse) Reset() { + *x = TransferRoomHostResponse{} + mi := &file_api_proto_room_v1_room_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransferRoomHostResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransferRoomHostResponse) ProtoMessage() {} + +func (x *TransferRoomHostResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_room_v1_room_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TransferRoomHostResponse.ProtoReflect.Descriptor instead. +func (*TransferRoomHostResponse) Descriptor() ([]byte, []int) { + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{27} +} + +func (x *TransferRoomHostResponse) GetResult() *CommandResult { + if x != nil { + return x.Result + } + return nil +} + +func (x *TransferRoomHostResponse) GetRoom() *RoomSnapshot { + if x != nil { + return x.Room + } + return nil +} + // MuteUserRequest 修改房间内禁言态。 type MuteUserRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + Muted bool `protobuf:"varint,3,opt,name=muted,proto3" json:"muted,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` - Muted bool `protobuf:"varint,3,opt,name=muted,proto3" json:"muted,omitempty"` + sizeCache protoimpl.SizeCache } func (x *MuteUserRequest) Reset() { *x = MuteUserRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *MuteUserRequest) String() string { @@ -1302,8 +1887,8 @@ func (x *MuteUserRequest) String() string { func (*MuteUserRequest) ProtoMessage() {} func (x *MuteUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_room_v1_room_proto_msgTypes[18] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_api_proto_room_v1_room_proto_msgTypes[28] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1315,7 +1900,7 @@ func (x *MuteUserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MuteUserRequest.ProtoReflect.Descriptor instead. func (*MuteUserRequest) Descriptor() ([]byte, []int) { - return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{18} + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{28} } func (x *MuteUserRequest) GetMeta() *RequestMeta { @@ -1341,21 +1926,18 @@ func (x *MuteUserRequest) GetMuted() bool { // MuteUserResponse 返回最新房间快照。 type MuteUserResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + Room *RoomSnapshot `protobuf:"bytes,2,opt,name=room,proto3" json:"room,omitempty"` unknownFields protoimpl.UnknownFields - - Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` - Room *RoomSnapshot `protobuf:"bytes,2,opt,name=room,proto3" json:"room,omitempty"` + sizeCache protoimpl.SizeCache } func (x *MuteUserResponse) Reset() { *x = MuteUserResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *MuteUserResponse) String() string { @@ -1365,8 +1947,8 @@ func (x *MuteUserResponse) String() string { func (*MuteUserResponse) ProtoMessage() {} func (x *MuteUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_room_v1_room_proto_msgTypes[19] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_api_proto_room_v1_room_proto_msgTypes[29] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1378,7 +1960,7 @@ func (x *MuteUserResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MuteUserResponse.ProtoReflect.Descriptor instead. func (*MuteUserResponse) Descriptor() ([]byte, []int) { - return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{19} + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{29} } func (x *MuteUserResponse) GetResult() *CommandResult { @@ -1397,21 +1979,18 @@ func (x *MuteUserResponse) GetRoom() *RoomSnapshot { // KickUserRequest 把指定用户踢出房间。 type KickUserRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *KickUserRequest) Reset() { *x = KickUserRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[20] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *KickUserRequest) String() string { @@ -1421,8 +2000,8 @@ func (x *KickUserRequest) String() string { func (*KickUserRequest) ProtoMessage() {} func (x *KickUserRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_room_v1_room_proto_msgTypes[20] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_api_proto_room_v1_room_proto_msgTypes[30] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1434,7 +2013,7 @@ func (x *KickUserRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use KickUserRequest.ProtoReflect.Descriptor instead. func (*KickUserRequest) Descriptor() ([]byte, []int) { - return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{20} + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{30} } func (x *KickUserRequest) GetMeta() *RequestMeta { @@ -1453,21 +2032,18 @@ func (x *KickUserRequest) GetTargetUserId() int64 { // KickUserResponse 返回最新房间快照。 type KickUserResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + Room *RoomSnapshot `protobuf:"bytes,2,opt,name=room,proto3" json:"room,omitempty"` unknownFields protoimpl.UnknownFields - - Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` - Room *RoomSnapshot `protobuf:"bytes,2,opt,name=room,proto3" json:"room,omitempty"` + sizeCache protoimpl.SizeCache } func (x *KickUserResponse) Reset() { *x = KickUserResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[21] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *KickUserResponse) String() string { @@ -1477,8 +2053,8 @@ func (x *KickUserResponse) String() string { func (*KickUserResponse) ProtoMessage() {} func (x *KickUserResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_room_v1_room_proto_msgTypes[21] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_api_proto_room_v1_room_proto_msgTypes[31] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1490,7 +2066,7 @@ func (x *KickUserResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use KickUserResponse.ProtoReflect.Descriptor instead. func (*KickUserResponse) Descriptor() ([]byte, []int) { - return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{21} + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{31} } func (x *KickUserResponse) GetResult() *CommandResult { @@ -1507,26 +2083,128 @@ func (x *KickUserResponse) GetRoom() *RoomSnapshot { return nil } +// UnbanUserRequest 解除房间内 ban,用户仍需重新 JoinRoom。 +type UnbanUserRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UnbanUserRequest) Reset() { + *x = UnbanUserRequest{} + mi := &file_api_proto_room_v1_room_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UnbanUserRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UnbanUserRequest) ProtoMessage() {} + +func (x *UnbanUserRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_room_v1_room_proto_msgTypes[32] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UnbanUserRequest.ProtoReflect.Descriptor instead. +func (*UnbanUserRequest) Descriptor() ([]byte, []int) { + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{32} +} + +func (x *UnbanUserRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *UnbanUserRequest) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +// UnbanUserResponse 返回解封后的最新房间快照。 +type UnbanUserResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + Room *RoomSnapshot `protobuf:"bytes,2,opt,name=room,proto3" json:"room,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UnbanUserResponse) Reset() { + *x = UnbanUserResponse{} + mi := &file_api_proto_room_v1_room_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UnbanUserResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UnbanUserResponse) ProtoMessage() {} + +func (x *UnbanUserResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_room_v1_room_proto_msgTypes[33] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UnbanUserResponse.ProtoReflect.Descriptor instead. +func (*UnbanUserResponse) Descriptor() ([]byte, []int) { + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{33} +} + +func (x *UnbanUserResponse) GetResult() *CommandResult { + if x != nil { + return x.Result + } + return nil +} + +func (x *UnbanUserResponse) GetRoom() *RoomSnapshot { + if x != nil { + return x.Room + } + return nil +} + // SendGiftRequest 是首版房间内最重要的变现命令。 type SendGiftRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + GiftId string `protobuf:"bytes,3,opt,name=gift_id,json=giftId,proto3" json:"gift_id,omitempty"` + GiftCount int32 `protobuf:"varint,4,opt,name=gift_count,json=giftCount,proto3" json:"gift_count,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` - GiftId string `protobuf:"bytes,3,opt,name=gift_id,json=giftId,proto3" json:"gift_id,omitempty"` - GiftCount int32 `protobuf:"varint,4,opt,name=gift_count,json=giftCount,proto3" json:"gift_count,omitempty"` - GiftUnitValue int64 `protobuf:"varint,5,opt,name=gift_unit_value,json=giftUnitValue,proto3" json:"gift_unit_value,omitempty"` + sizeCache protoimpl.SizeCache } func (x *SendGiftRequest) Reset() { *x = SendGiftRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[22] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SendGiftRequest) String() string { @@ -1536,8 +2214,8 @@ func (x *SendGiftRequest) String() string { func (*SendGiftRequest) ProtoMessage() {} func (x *SendGiftRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_room_v1_room_proto_msgTypes[22] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_api_proto_room_v1_room_proto_msgTypes[34] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1549,7 +2227,7 @@ func (x *SendGiftRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SendGiftRequest.ProtoReflect.Descriptor instead. func (*SendGiftRequest) Descriptor() ([]byte, []int) { - return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{22} + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{34} } func (x *SendGiftRequest) GetMeta() *RequestMeta { @@ -1580,33 +2258,23 @@ func (x *SendGiftRequest) GetGiftCount() int32 { return 0 } -func (x *SendGiftRequest) GetGiftUnitValue() int64 { - if x != nil { - return x.GiftUnitValue - } - return 0 -} - // SendGiftResponse 返回扣费成功并落到房间后的状态结果。 type SendGiftResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` - BillingReceiptId string `protobuf:"bytes,2,opt,name=billing_receipt_id,json=billingReceiptId,proto3" json:"billing_receipt_id,omitempty"` - RoomHeat int64 `protobuf:"varint,3,opt,name=room_heat,json=roomHeat,proto3" json:"room_heat,omitempty"` - GiftRank []*RankItem `protobuf:"bytes,4,rep,name=gift_rank,json=giftRank,proto3" json:"gift_rank,omitempty"` - Room *RoomSnapshot `protobuf:"bytes,5,opt,name=room,proto3" json:"room,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Result *CommandResult `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + BillingReceiptId string `protobuf:"bytes,2,opt,name=billing_receipt_id,json=billingReceiptId,proto3" json:"billing_receipt_id,omitempty"` + RoomHeat int64 `protobuf:"varint,3,opt,name=room_heat,json=roomHeat,proto3" json:"room_heat,omitempty"` + GiftRank []*RankItem `protobuf:"bytes,4,rep,name=gift_rank,json=giftRank,proto3" json:"gift_rank,omitempty"` + Room *RoomSnapshot `protobuf:"bytes,5,opt,name=room,proto3" json:"room,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *SendGiftResponse) Reset() { *x = SendGiftResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[23] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SendGiftResponse) String() string { @@ -1616,8 +2284,8 @@ func (x *SendGiftResponse) String() string { func (*SendGiftResponse) ProtoMessage() {} func (x *SendGiftResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_room_v1_room_proto_msgTypes[23] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_api_proto_room_v1_room_proto_msgTypes[35] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1629,7 +2297,7 @@ func (x *SendGiftResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SendGiftResponse.ProtoReflect.Descriptor instead. func (*SendGiftResponse) Descriptor() ([]byte, []int) { - return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{23} + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{35} } func (x *SendGiftResponse) GetResult() *CommandResult { @@ -1669,21 +2337,18 @@ func (x *SendGiftResponse) GetRoom() *RoomSnapshot { // CheckSpeakPermissionRequest 让腾讯云 IM 发言回调或 gateway 在公屏前同步问房间业务态。 type CheckSpeakPermissionRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + RoomId string `protobuf:"bytes,1,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` unknownFields protoimpl.UnknownFields - - RoomId string `protobuf:"bytes,1,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` - UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *CheckSpeakPermissionRequest) Reset() { *x = CheckSpeakPermissionRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[24] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CheckSpeakPermissionRequest) String() string { @@ -1693,8 +2358,8 @@ func (x *CheckSpeakPermissionRequest) String() string { func (*CheckSpeakPermissionRequest) ProtoMessage() {} func (x *CheckSpeakPermissionRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_room_v1_room_proto_msgTypes[24] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_api_proto_room_v1_room_proto_msgTypes[36] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1706,7 +2371,7 @@ func (x *CheckSpeakPermissionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckSpeakPermissionRequest.ProtoReflect.Descriptor instead. func (*CheckSpeakPermissionRequest) Descriptor() ([]byte, []int) { - return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{24} + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{36} } func (x *CheckSpeakPermissionRequest) GetRoomId() string { @@ -1725,22 +2390,19 @@ func (x *CheckSpeakPermissionRequest) GetUserId() int64 { // CheckSpeakPermissionResponse 返回当前用户是否允许发言。 type CheckSpeakPermissionResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Allowed bool `protobuf:"varint,1,opt,name=allowed,proto3" json:"allowed,omitempty"` + Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"` + RoomVersion int64 `protobuf:"varint,3,opt,name=room_version,json=roomVersion,proto3" json:"room_version,omitempty"` unknownFields protoimpl.UnknownFields - - Allowed bool `protobuf:"varint,1,opt,name=allowed,proto3" json:"allowed,omitempty"` - Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"` - RoomVersion int64 `protobuf:"varint,3,opt,name=room_version,json=roomVersion,proto3" json:"room_version,omitempty"` + sizeCache protoimpl.SizeCache } func (x *CheckSpeakPermissionResponse) Reset() { *x = CheckSpeakPermissionResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[25] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CheckSpeakPermissionResponse) String() string { @@ -1750,8 +2412,8 @@ func (x *CheckSpeakPermissionResponse) String() string { func (*CheckSpeakPermissionResponse) ProtoMessage() {} func (x *CheckSpeakPermissionResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_room_v1_room_proto_msgTypes[25] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_api_proto_room_v1_room_proto_msgTypes[37] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1763,7 +2425,7 @@ func (x *CheckSpeakPermissionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckSpeakPermissionResponse.ProtoReflect.Descriptor instead. func (*CheckSpeakPermissionResponse) Descriptor() ([]byte, []int) { - return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{25} + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{37} } func (x *CheckSpeakPermissionResponse) GetAllowed() bool { @@ -1789,23 +2451,20 @@ func (x *CheckSpeakPermissionResponse) GetRoomVersion() int64 { // VerifyRoomPresenceRequest 让外部 IM 入口在进群前确认用户业务上仍在房间内。 type VerifyRoomPresenceRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + RoomId string `protobuf:"bytes,1,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + SessionId string `protobuf:"bytes,3,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + RequestId string `protobuf:"bytes,4,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` unknownFields protoimpl.UnknownFields - - RoomId string `protobuf:"bytes,1,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` - UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - SessionId string `protobuf:"bytes,3,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` - RequestId string `protobuf:"bytes,4,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *VerifyRoomPresenceRequest) Reset() { *x = VerifyRoomPresenceRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[26] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *VerifyRoomPresenceRequest) String() string { @@ -1815,8 +2474,8 @@ func (x *VerifyRoomPresenceRequest) String() string { func (*VerifyRoomPresenceRequest) ProtoMessage() {} func (x *VerifyRoomPresenceRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_room_v1_room_proto_msgTypes[26] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_api_proto_room_v1_room_proto_msgTypes[38] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1828,7 +2487,7 @@ func (x *VerifyRoomPresenceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyRoomPresenceRequest.ProtoReflect.Descriptor instead. func (*VerifyRoomPresenceRequest) Descriptor() ([]byte, []int) { - return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{26} + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{38} } func (x *VerifyRoomPresenceRequest) GetRoomId() string { @@ -1861,22 +2520,19 @@ func (x *VerifyRoomPresenceRequest) GetRequestId() string { // VerifyRoomPresenceResponse 返回用户是否仍然属于该房间。 type VerifyRoomPresenceResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Present bool `protobuf:"varint,1,opt,name=present,proto3" json:"present,omitempty"` + Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"` + RoomVersion int64 `protobuf:"varint,3,opt,name=room_version,json=roomVersion,proto3" json:"room_version,omitempty"` unknownFields protoimpl.UnknownFields - - Present bool `protobuf:"varint,1,opt,name=present,proto3" json:"present,omitempty"` - Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"` - RoomVersion int64 `protobuf:"varint,3,opt,name=room_version,json=roomVersion,proto3" json:"room_version,omitempty"` + sizeCache protoimpl.SizeCache } func (x *VerifyRoomPresenceResponse) Reset() { *x = VerifyRoomPresenceResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_room_v1_room_proto_msgTypes[27] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_room_v1_room_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *VerifyRoomPresenceResponse) String() string { @@ -1886,8 +2542,8 @@ func (x *VerifyRoomPresenceResponse) String() string { func (*VerifyRoomPresenceResponse) ProtoMessage() {} func (x *VerifyRoomPresenceResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_room_v1_room_proto_msgTypes[27] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_api_proto_room_v1_room_proto_msgTypes[39] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1899,7 +2555,7 @@ func (x *VerifyRoomPresenceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyRoomPresenceResponse.ProtoReflect.Descriptor instead. func (*VerifyRoomPresenceResponse) Descriptor() ([]byte, []int) { - return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{27} + return file_api_proto_room_v1_room_proto_rawDescGZIP(), []int{39} } func (x *VerifyRoomPresenceResponse) GetPresent() bool { @@ -1925,354 +2581,233 @@ func (x *VerifyRoomPresenceResponse) GetRoomVersion() int64 { var File_api_proto_room_v1_room_proto protoreflect.FileDescriptor -var file_api_proto_room_v1_room_proto_rawDesc = []byte{ - 0x0a, 0x1c, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x72, 0x6f, 0x6f, 0x6d, - 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x22, 0xed, 0x01, - 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, - 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, - 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x61, - 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x67, 0x61, 0x74, 0x65, - 0x77, 0x61, 0x79, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0d, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x64, - 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, - 0x1c, 0x0a, 0x0a, 0x73, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x08, 0x73, 0x65, 0x6e, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x72, 0x0a, - 0x0d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x18, - 0x0a, 0x07, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x07, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x6d, - 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, - 0x72, 0x6f, 0x6f, 0x6d, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, - 0x73, 0x22, 0x80, 0x01, 0x0a, 0x08, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x12, 0x17, - 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x12, 0x20, 0x0a, 0x0c, 0x6a, - 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0a, 0x6a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x25, 0x0a, - 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x65, 0x6e, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x65, 0x6e, - 0x41, 0x74, 0x4d, 0x73, 0x22, 0x55, 0x0a, 0x09, 0x53, 0x65, 0x61, 0x74, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x22, 0x7c, 0x0a, 0x08, 0x52, - 0x61, 0x6e, 0x6b, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x05, 0x73, 0x63, 0x6f, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x80, 0x05, 0x0a, 0x0c, 0x52, 0x6f, - 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, - 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, - 0x6d, 0x49, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x77, 0x6e, 0x65, - 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x68, - 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x68, 0x61, 0x74, 0x5f, 0x65, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x63, 0x68, 0x61, - 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x35, 0x0a, 0x09, 0x6d, 0x69, 0x63, 0x5f, - 0x73, 0x65, 0x61, 0x74, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x61, 0x74, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x08, 0x6d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x73, 0x12, - 0x3a, 0x0a, 0x0c, 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, - 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, - 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x52, 0x0b, - 0x6f, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x61, - 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x09, 0x20, - 0x03, 0x28, 0x03, 0x52, 0x0c, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x73, 0x12, 0x20, 0x0a, 0x0c, 0x62, 0x61, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0a, 0x62, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x75, 0x74, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0b, 0x6d, 0x75, 0x74, 0x65, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x12, 0x34, 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, - 0x72, 0x61, 0x6e, 0x6b, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x6e, 0x6b, 0x49, - 0x74, 0x65, 0x6d, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, 0x52, 0x61, 0x6e, 0x6b, 0x12, 0x43, 0x0a, - 0x08, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x65, 0x78, 0x74, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x2e, 0x52, 0x6f, 0x6f, - 0x6d, 0x45, 0x78, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x45, - 0x78, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x65, 0x61, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x04, 0x68, 0x65, 0x61, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x1a, 0x3a, 0x0a, 0x0c, 0x52, 0x6f, 0x6f, 0x6d, 0x45, 0x78, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xbc, 0x01, 0x0a, - 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6f, 0x77, 0x6e, 0x65, 0x72, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x68, 0x6f, - 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x61, 0x74, - 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x65, - 0x61, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x22, 0x7b, 0x0a, 0x12, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, - 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, - 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, - 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, - 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x55, 0x0a, 0x0f, 0x4a, 0x6f, 0x69, 0x6e, - 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x72, - 0x6f, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x22, - 0xa6, 0x01, 0x0a, 0x10, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, - 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2b, 0x0a, 0x04, 0x75, 0x73, - 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x55, 0x73, 0x65, - 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, - 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, - 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x42, 0x0a, 0x10, 0x4c, 0x65, 0x61, 0x76, - 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x22, 0x7a, 0x0a, 0x11, - 0x4c, 0x65, 0x61, 0x76, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, - 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, - 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, - 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, - 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x57, 0x0a, 0x0c, 0x4d, 0x69, 0x63, 0x55, - 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, - 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, - 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, - 0x6f, 0x22, 0x8f, 0x01, 0x0a, 0x0d, 0x4d, 0x69, 0x63, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, - 0x74, 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, - 0x4e, 0x6f, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, - 0x6f, 0x6f, 0x6d, 0x22, 0x66, 0x0a, 0x0e, 0x4d, 0x69, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x91, 0x01, 0x0a, 0x0f, - 0x4d, 0x69, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x12, 0x2f, - 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, - 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, - 0x85, 0x01, 0x0a, 0x14, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, - 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, - 0x0a, 0x07, 0x73, 0x65, 0x61, 0x74, 0x5f, 0x6e, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x06, 0x73, 0x65, 0x61, 0x74, 0x4e, 0x6f, 0x22, 0x7e, 0x0a, 0x15, 0x43, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, - 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, - 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, - 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x7d, 0x0a, 0x0f, 0x4d, 0x75, 0x74, 0x65, 0x55, - 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x05, 0x6d, 0x75, 0x74, 0x65, 0x64, 0x22, 0x79, 0x0a, 0x10, 0x4d, 0x75, 0x74, 0x65, 0x55, 0x73, - 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, - 0x6d, 0x22, 0x67, 0x0a, 0x0f, 0x4b, 0x69, 0x63, 0x6b, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, - 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x79, 0x0a, 0x10, 0x4b, 0x69, - 0x63, 0x6b, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, - 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, - 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0xc7, 0x01, 0x0a, 0x0f, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, - 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, - 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, - 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, - 0x66, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x5f, - 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0d, 0x67, 0x69, 0x66, 0x74, 0x55, 0x6e, 0x69, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, - 0xfa, 0x01, 0x0a, 0x10, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, - 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x69, - 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x52, - 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6f, 0x6d, - 0x5f, 0x68, 0x65, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x6f, 0x6f, - 0x6d, 0x48, 0x65, 0x61, 0x74, 0x12, 0x34, 0x0a, 0x09, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x72, 0x61, - 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x61, 0x6e, 0x6b, 0x49, 0x74, 0x65, - 0x6d, 0x52, 0x08, 0x67, 0x69, 0x66, 0x74, 0x52, 0x61, 0x6e, 0x6b, 0x12, 0x2f, 0x0a, 0x04, 0x72, - 0x6f, 0x6f, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x6f, 0x6d, 0x53, 0x6e, - 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x04, 0x72, 0x6f, 0x6f, 0x6d, 0x22, 0x4f, 0x0a, 0x1b, - 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x70, 0x65, 0x61, 0x6b, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x72, - 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, - 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x73, 0x0a, - 0x1c, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x70, 0x65, 0x61, 0x6b, 0x50, 0x65, 0x72, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, - 0x07, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, - 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, - 0x21, 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x56, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x22, 0x8b, 0x01, 0x0a, 0x19, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x6f, 0x6f, - 0x6d, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, - 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, - 0x22, 0x71, 0x0a, 0x1a, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x72, - 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, - 0x0a, 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x07, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, - 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x6d, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x32, 0xd5, 0x05, 0x0a, 0x12, 0x52, 0x6f, 0x6f, 0x6d, 0x43, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x51, 0x0a, 0x0a, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, - 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, - 0x08, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x6f, - 0x6f, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x6f, - 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x09, 0x4c, 0x65, - 0x61, 0x76, 0x65, 0x52, 0x6f, 0x6f, 0x6d, 0x12, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x52, 0x6f, 0x6f, - 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x52, 0x6f, - 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x05, 0x4d, 0x69, - 0x63, 0x55, 0x70, 0x12, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, - 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x63, 0x55, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1c, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x4d, 0x69, 0x63, 0x55, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, - 0x0a, 0x07, 0x4d, 0x69, 0x63, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x63, 0x44, 0x6f, 0x77, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x69, 0x63, 0x44, 0x6f, 0x77, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4d, 0x69, 0x63, 0x53, 0x65, 0x61, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x4d, 0x75, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, - 0x12, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x4d, 0x75, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, - 0x2e, 0x4d, 0x75, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x4b, 0x0a, 0x08, 0x4b, 0x69, 0x63, 0x6b, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1e, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x69, - 0x63, 0x6b, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x69, - 0x63, 0x6b, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, - 0x0a, 0x08, 0x53, 0x65, 0x6e, 0x64, 0x47, 0x69, 0x66, 0x74, 0x12, 0x1e, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x47, - 0x69, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x47, - 0x69, 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xee, 0x01, 0x0a, 0x10, - 0x52, 0x6f, 0x6f, 0x6d, 0x47, 0x75, 0x61, 0x72, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x6f, 0x0a, 0x14, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x70, 0x65, 0x61, 0x6b, 0x50, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x70, - 0x65, 0x61, 0x6b, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, - 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x70, 0x65, 0x61, 0x6b, 0x50, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x69, 0x0a, 0x12, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x50, - 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x6f, - 0x6f, 0x6d, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x6d, 0x2e, 0x76, - 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x6f, 0x6f, 0x6d, 0x50, 0x72, 0x65, 0x73, - 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x20, 0x5a, 0x1e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, - 0x72, 0x6f, 0x6f, 0x6d, 0x2f, 0x76, 0x31, 0x3b, 0x72, 0x6f, 0x6f, 0x6d, 0x76, 0x31, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_api_proto_room_v1_room_proto_rawDesc = "" + + "\n" + + "\x1capi/proto/room/v1/room.proto\x12\rhyapp.room.v1\"\xed\x01\n" + + "\vRequestMeta\x12\x1d\n" + + "\n" + + "request_id\x18\x01 \x01(\tR\trequestId\x12\x1d\n" + + "\n" + + "command_id\x18\x02 \x01(\tR\tcommandId\x12\"\n" + + "\ractor_user_id\x18\x03 \x01(\x03R\vactorUserId\x12\x17\n" + + "\aroom_id\x18\x04 \x01(\tR\x06roomId\x12&\n" + + "\x0fgateway_node_id\x18\x05 \x01(\tR\rgatewayNodeId\x12\x1d\n" + + "\n" + + "session_id\x18\x06 \x01(\tR\tsessionId\x12\x1c\n" + + "\n" + + "sent_at_ms\x18\a \x01(\x03R\bsentAtMs\"r\n" + + "\rCommandResult\x12\x18\n" + + "\aapplied\x18\x01 \x01(\bR\aapplied\x12!\n" + + "\froom_version\x18\x02 \x01(\x03R\vroomVersion\x12$\n" + + "\x0eserver_time_ms\x18\x03 \x01(\x03R\fserverTimeMs\"\x80\x01\n" + + "\bRoomUser\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x12\n" + + "\x04role\x18\x02 \x01(\tR\x04role\x12 \n" + + "\fjoined_at_ms\x18\x03 \x01(\x03R\n" + + "joinedAtMs\x12%\n" + + "\x0flast_seen_at_ms\x18\x04 \x01(\x03R\flastSeenAtMs\"\xc5\x02\n" + + "\tSeatState\x12\x17\n" + + "\aseat_no\x18\x01 \x01(\x05R\x06seatNo\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x16\n" + + "\x06locked\x18\x03 \x01(\bR\x06locked\x12#\n" + + "\rpublish_state\x18\x04 \x01(\tR\fpublishState\x12$\n" + + "\x0emic_session_id\x18\x05 \x01(\tR\fmicSessionId\x12.\n" + + "\x13publish_deadline_ms\x18\x06 \x01(\x03R\x11publishDeadlineMs\x127\n" + + "\x18mic_session_room_version\x18\a \x01(\x03R\x15micSessionRoomVersion\x12:\n" + + "\x1alast_publish_event_time_ms\x18\b \x01(\x03R\x16lastPublishEventTimeMs\"|\n" + + "\bRankItem\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x14\n" + + "\x05score\x18\x02 \x01(\x03R\x05score\x12\x1d\n" + + "\n" + + "gift_value\x18\x03 \x01(\x03R\tgiftValue\x12\"\n" + + "\rupdated_at_ms\x18\x04 \x01(\x03R\vupdatedAtMs\"\x80\x05\n" + + "\fRoomSnapshot\x12\x17\n" + + "\aroom_id\x18\x01 \x01(\tR\x06roomId\x12\"\n" + + "\rowner_user_id\x18\x02 \x01(\x03R\vownerUserId\x12 \n" + + "\fhost_user_id\x18\x03 \x01(\x03R\n" + + "hostUserId\x12\x12\n" + + "\x04mode\x18\x04 \x01(\tR\x04mode\x12\x16\n" + + "\x06status\x18\x05 \x01(\tR\x06status\x12!\n" + + "\fchat_enabled\x18\x06 \x01(\bR\vchatEnabled\x125\n" + + "\tmic_seats\x18\a \x03(\v2\x18.hyapp.room.v1.SeatStateR\bmicSeats\x12:\n" + + "\fonline_users\x18\b \x03(\v2\x17.hyapp.room.v1.RoomUserR\vonlineUsers\x12$\n" + + "\x0eadmin_user_ids\x18\t \x03(\x03R\fadminUserIds\x12 \n" + + "\fban_user_ids\x18\n" + + " \x03(\x03R\n" + + "banUserIds\x12\"\n" + + "\rmute_user_ids\x18\v \x03(\x03R\vmuteUserIds\x124\n" + + "\tgift_rank\x18\f \x03(\v2\x17.hyapp.room.v1.RankItemR\bgiftRank\x12C\n" + + "\broom_ext\x18\r \x03(\v2(.hyapp.room.v1.RoomSnapshot.RoomExtEntryR\aroomExt\x12\x12\n" + + "\x04heat\x18\x0e \x01(\x03R\x04heat\x12\x18\n" + + "\aversion\x18\x0f \x01(\x03R\aversion\x1a:\n" + + "\fRoomExtEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"v\n" + + "\x11CreateRoomRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.room.v1.RequestMetaR\x04meta\x12\x1d\n" + + "\n" + + "seat_count\x18\x02 \x01(\x05R\tseatCount\x12\x12\n" + + "\x04mode\x18\x03 \x01(\tR\x04mode\"{\n" + + "\x12CreateRoomResponse\x124\n" + + "\x06result\x18\x01 \x01(\v2\x1c.hyapp.room.v1.CommandResultR\x06result\x12/\n" + + "\x04room\x18\x02 \x01(\v2\x1b.hyapp.room.v1.RoomSnapshotR\x04room\"U\n" + + "\x0fJoinRoomRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.room.v1.RequestMetaR\x04meta\x12\x12\n" + + "\x04role\x18\x02 \x01(\tR\x04role\"\xa6\x01\n" + + "\x10JoinRoomResponse\x124\n" + + "\x06result\x18\x01 \x01(\v2\x1c.hyapp.room.v1.CommandResultR\x06result\x12+\n" + + "\x04user\x18\x02 \x01(\v2\x17.hyapp.room.v1.RoomUserR\x04user\x12/\n" + + "\x04room\x18\x03 \x01(\v2\x1b.hyapp.room.v1.RoomSnapshotR\x04room\"B\n" + + "\x10LeaveRoomRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.room.v1.RequestMetaR\x04meta\"z\n" + + "\x11LeaveRoomResponse\x124\n" + + "\x06result\x18\x01 \x01(\v2\x1c.hyapp.room.v1.CommandResultR\x06result\x12/\n" + + "\x04room\x18\x02 \x01(\v2\x1b.hyapp.room.v1.RoomSnapshotR\x04room\"W\n" + + "\fMicUpRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.room.v1.RequestMetaR\x04meta\x12\x17\n" + + "\aseat_no\x18\x02 \x01(\x05R\x06seatNo\"\xe5\x01\n" + + "\rMicUpResponse\x124\n" + + "\x06result\x18\x01 \x01(\v2\x1c.hyapp.room.v1.CommandResultR\x06result\x12\x17\n" + + "\aseat_no\x18\x02 \x01(\x05R\x06seatNo\x12/\n" + + "\x04room\x18\x03 \x01(\v2\x1b.hyapp.room.v1.RoomSnapshotR\x04room\x12$\n" + + "\x0emic_session_id\x18\x04 \x01(\tR\fmicSessionId\x12.\n" + + "\x13publish_deadline_ms\x18\x05 \x01(\x03R\x11publishDeadlineMs\"~\n" + + "\x0eMicDownRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.room.v1.RequestMetaR\x04meta\x12$\n" + + "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\x12\x16\n" + + "\x06reason\x18\x03 \x01(\tR\x06reason\"\x91\x01\n" + + "\x0fMicDownResponse\x124\n" + + "\x06result\x18\x01 \x01(\v2\x1c.hyapp.room.v1.CommandResultR\x06result\x12\x17\n" + + "\aseat_no\x18\x02 \x01(\x05R\x06seatNo\x12/\n" + + "\x04room\x18\x03 \x01(\v2\x1b.hyapp.room.v1.RoomSnapshotR\x04room\"\x85\x01\n" + + "\x14ChangeMicSeatRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.room.v1.RequestMetaR\x04meta\x12$\n" + + "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\x12\x17\n" + + "\aseat_no\x18\x03 \x01(\x05R\x06seatNo\"~\n" + + "\x15ChangeMicSeatResponse\x124\n" + + "\x06result\x18\x01 \x01(\v2\x1c.hyapp.room.v1.CommandResultR\x06result\x12/\n" + + "\x04room\x18\x02 \x01(\v2\x1b.hyapp.room.v1.RoomSnapshotR\x04room\"\xf8\x01\n" + + "\x1bConfirmMicPublishingRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.room.v1.RequestMetaR\x04meta\x12$\n" + + "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\x12$\n" + + "\x0emic_session_id\x18\x03 \x01(\tR\fmicSessionId\x12!\n" + + "\froom_version\x18\x04 \x01(\x03R\vroomVersion\x12\"\n" + + "\revent_time_ms\x18\x05 \x01(\x03R\veventTimeMs\x12\x16\n" + + "\x06source\x18\x06 \x01(\tR\x06source\"\x9e\x01\n" + + "\x1cConfirmMicPublishingResponse\x124\n" + + "\x06result\x18\x01 \x01(\v2\x1c.hyapp.room.v1.CommandResultR\x06result\x12\x17\n" + + "\aseat_no\x18\x02 \x01(\x05R\x06seatNo\x12/\n" + + "\x04room\x18\x03 \x01(\v2\x1b.hyapp.room.v1.RoomSnapshotR\x04room\"x\n" + + "\x15SetMicSeatLockRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.room.v1.RequestMetaR\x04meta\x12\x17\n" + + "\aseat_no\x18\x02 \x01(\x05R\x06seatNo\x12\x16\n" + + "\x06locked\x18\x03 \x01(\bR\x06locked\"\x7f\n" + + "\x16SetMicSeatLockResponse\x124\n" + + "\x06result\x18\x01 \x01(\v2\x1c.hyapp.room.v1.CommandResultR\x06result\x12/\n" + + "\x04room\x18\x02 \x01(\v2\x1b.hyapp.room.v1.RoomSnapshotR\x04room\"a\n" + + "\x15SetChatEnabledRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.room.v1.RequestMetaR\x04meta\x12\x18\n" + + "\aenabled\x18\x02 \x01(\bR\aenabled\"\x7f\n" + + "\x16SetChatEnabledResponse\x124\n" + + "\x06result\x18\x01 \x01(\v2\x1c.hyapp.room.v1.CommandResultR\x06result\x12/\n" + + "\x04room\x18\x02 \x01(\v2\x1b.hyapp.room.v1.RoomSnapshotR\x04room\"\x85\x01\n" + + "\x13SetRoomAdminRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.room.v1.RequestMetaR\x04meta\x12$\n" + + "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\x12\x18\n" + + "\aenabled\x18\x03 \x01(\bR\aenabled\"}\n" + + "\x14SetRoomAdminResponse\x124\n" + + "\x06result\x18\x01 \x01(\v2\x1c.hyapp.room.v1.CommandResultR\x06result\x12/\n" + + "\x04room\x18\x02 \x01(\v2\x1b.hyapp.room.v1.RoomSnapshotR\x04room\"o\n" + + "\x17TransferRoomHostRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.room.v1.RequestMetaR\x04meta\x12$\n" + + "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\"\x81\x01\n" + + "\x18TransferRoomHostResponse\x124\n" + + "\x06result\x18\x01 \x01(\v2\x1c.hyapp.room.v1.CommandResultR\x06result\x12/\n" + + "\x04room\x18\x02 \x01(\v2\x1b.hyapp.room.v1.RoomSnapshotR\x04room\"}\n" + + "\x0fMuteUserRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.room.v1.RequestMetaR\x04meta\x12$\n" + + "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\x12\x14\n" + + "\x05muted\x18\x03 \x01(\bR\x05muted\"y\n" + + "\x10MuteUserResponse\x124\n" + + "\x06result\x18\x01 \x01(\v2\x1c.hyapp.room.v1.CommandResultR\x06result\x12/\n" + + "\x04room\x18\x02 \x01(\v2\x1b.hyapp.room.v1.RoomSnapshotR\x04room\"g\n" + + "\x0fKickUserRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.room.v1.RequestMetaR\x04meta\x12$\n" + + "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\"y\n" + + "\x10KickUserResponse\x124\n" + + "\x06result\x18\x01 \x01(\v2\x1c.hyapp.room.v1.CommandResultR\x06result\x12/\n" + + "\x04room\x18\x02 \x01(\v2\x1b.hyapp.room.v1.RoomSnapshotR\x04room\"h\n" + + "\x10UnbanUserRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.room.v1.RequestMetaR\x04meta\x12$\n" + + "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\"z\n" + + "\x11UnbanUserResponse\x124\n" + + "\x06result\x18\x01 \x01(\v2\x1c.hyapp.room.v1.CommandResultR\x06result\x12/\n" + + "\x04room\x18\x02 \x01(\v2\x1b.hyapp.room.v1.RoomSnapshotR\x04room\"\x9f\x01\n" + + "\x0fSendGiftRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.room.v1.RequestMetaR\x04meta\x12$\n" + + "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\x12\x17\n" + + "\agift_id\x18\x03 \x01(\tR\x06giftId\x12\x1d\n" + + "\n" + + "gift_count\x18\x04 \x01(\x05R\tgiftCount\"\xfa\x01\n" + + "\x10SendGiftResponse\x124\n" + + "\x06result\x18\x01 \x01(\v2\x1c.hyapp.room.v1.CommandResultR\x06result\x12,\n" + + "\x12billing_receipt_id\x18\x02 \x01(\tR\x10billingReceiptId\x12\x1b\n" + + "\troom_heat\x18\x03 \x01(\x03R\broomHeat\x124\n" + + "\tgift_rank\x18\x04 \x03(\v2\x17.hyapp.room.v1.RankItemR\bgiftRank\x12/\n" + + "\x04room\x18\x05 \x01(\v2\x1b.hyapp.room.v1.RoomSnapshotR\x04room\"O\n" + + "\x1bCheckSpeakPermissionRequest\x12\x17\n" + + "\aroom_id\x18\x01 \x01(\tR\x06roomId\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\"s\n" + + "\x1cCheckSpeakPermissionResponse\x12\x18\n" + + "\aallowed\x18\x01 \x01(\bR\aallowed\x12\x16\n" + + "\x06reason\x18\x02 \x01(\tR\x06reason\x12!\n" + + "\froom_version\x18\x03 \x01(\x03R\vroomVersion\"\x8b\x01\n" + + "\x19VerifyRoomPresenceRequest\x12\x17\n" + + "\aroom_id\x18\x01 \x01(\tR\x06roomId\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x1d\n" + + "\n" + + "session_id\x18\x03 \x01(\tR\tsessionId\x12\x1d\n" + + "\n" + + "request_id\x18\x04 \x01(\tR\trequestId\"q\n" + + "\x1aVerifyRoomPresenceResponse\x12\x18\n" + + "\apresent\x18\x01 \x01(\bR\apresent\x12\x16\n" + + "\x06reason\x18\x02 \x01(\tR\x06reason\x12!\n" + + "\froom_version\x18\x03 \x01(\x03R\vroomVersion2\x92\n" + + "\n" + + "\x12RoomCommandService\x12Q\n" + + "\n" + + "CreateRoom\x12 .hyapp.room.v1.CreateRoomRequest\x1a!.hyapp.room.v1.CreateRoomResponse\x12K\n" + + "\bJoinRoom\x12\x1e.hyapp.room.v1.JoinRoomRequest\x1a\x1f.hyapp.room.v1.JoinRoomResponse\x12N\n" + + "\tLeaveRoom\x12\x1f.hyapp.room.v1.LeaveRoomRequest\x1a .hyapp.room.v1.LeaveRoomResponse\x12B\n" + + "\x05MicUp\x12\x1b.hyapp.room.v1.MicUpRequest\x1a\x1c.hyapp.room.v1.MicUpResponse\x12H\n" + + "\aMicDown\x12\x1d.hyapp.room.v1.MicDownRequest\x1a\x1e.hyapp.room.v1.MicDownResponse\x12Z\n" + + "\rChangeMicSeat\x12#.hyapp.room.v1.ChangeMicSeatRequest\x1a$.hyapp.room.v1.ChangeMicSeatResponse\x12o\n" + + "\x14ConfirmMicPublishing\x12*.hyapp.room.v1.ConfirmMicPublishingRequest\x1a+.hyapp.room.v1.ConfirmMicPublishingResponse\x12]\n" + + "\x0eSetMicSeatLock\x12$.hyapp.room.v1.SetMicSeatLockRequest\x1a%.hyapp.room.v1.SetMicSeatLockResponse\x12]\n" + + "\x0eSetChatEnabled\x12$.hyapp.room.v1.SetChatEnabledRequest\x1a%.hyapp.room.v1.SetChatEnabledResponse\x12W\n" + + "\fSetRoomAdmin\x12\".hyapp.room.v1.SetRoomAdminRequest\x1a#.hyapp.room.v1.SetRoomAdminResponse\x12c\n" + + "\x10TransferRoomHost\x12&.hyapp.room.v1.TransferRoomHostRequest\x1a'.hyapp.room.v1.TransferRoomHostResponse\x12K\n" + + "\bMuteUser\x12\x1e.hyapp.room.v1.MuteUserRequest\x1a\x1f.hyapp.room.v1.MuteUserResponse\x12K\n" + + "\bKickUser\x12\x1e.hyapp.room.v1.KickUserRequest\x1a\x1f.hyapp.room.v1.KickUserResponse\x12N\n" + + "\tUnbanUser\x12\x1f.hyapp.room.v1.UnbanUserRequest\x1a .hyapp.room.v1.UnbanUserResponse\x12K\n" + + "\bSendGift\x12\x1e.hyapp.room.v1.SendGiftRequest\x1a\x1f.hyapp.room.v1.SendGiftResponse2\xee\x01\n" + + "\x10RoomGuardService\x12o\n" + + "\x14CheckSpeakPermission\x12*.hyapp.room.v1.CheckSpeakPermissionRequest\x1a+.hyapp.room.v1.CheckSpeakPermissionResponse\x12i\n" + + "\x12VerifyRoomPresence\x12(.hyapp.room.v1.VerifyRoomPresenceRequest\x1a).hyapp.room.v1.VerifyRoomPresenceResponseB Z\x1ehyapp/api/proto/room/v1;roomv1b\x06proto3" var ( file_api_proto_room_v1_room_proto_rawDescOnce sync.Once - file_api_proto_room_v1_room_proto_rawDescData = file_api_proto_room_v1_room_proto_rawDesc + file_api_proto_room_v1_room_proto_rawDescData []byte ) func file_api_proto_room_v1_room_proto_rawDescGZIP() []byte { file_api_proto_room_v1_room_proto_rawDescOnce.Do(func() { - file_api_proto_room_v1_room_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_room_v1_room_proto_rawDescData) + file_api_proto_room_v1_room_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_api_proto_room_v1_room_proto_rawDesc), len(file_api_proto_room_v1_room_proto_rawDesc))) }) return file_api_proto_room_v1_room_proto_rawDescData } -var file_api_proto_room_v1_room_proto_msgTypes = make([]protoimpl.MessageInfo, 29) +var file_api_proto_room_v1_room_proto_msgTypes = make([]protoimpl.MessageInfo, 41) var file_api_proto_room_v1_room_proto_goTypes = []any{ (*RequestMeta)(nil), // 0: hyapp.room.v1.RequestMeta (*CommandResult)(nil), // 1: hyapp.room.v1.CommandResult @@ -2292,23 +2827,35 @@ var file_api_proto_room_v1_room_proto_goTypes = []any{ (*MicDownResponse)(nil), // 15: hyapp.room.v1.MicDownResponse (*ChangeMicSeatRequest)(nil), // 16: hyapp.room.v1.ChangeMicSeatRequest (*ChangeMicSeatResponse)(nil), // 17: hyapp.room.v1.ChangeMicSeatResponse - (*MuteUserRequest)(nil), // 18: hyapp.room.v1.MuteUserRequest - (*MuteUserResponse)(nil), // 19: hyapp.room.v1.MuteUserResponse - (*KickUserRequest)(nil), // 20: hyapp.room.v1.KickUserRequest - (*KickUserResponse)(nil), // 21: hyapp.room.v1.KickUserResponse - (*SendGiftRequest)(nil), // 22: hyapp.room.v1.SendGiftRequest - (*SendGiftResponse)(nil), // 23: hyapp.room.v1.SendGiftResponse - (*CheckSpeakPermissionRequest)(nil), // 24: hyapp.room.v1.CheckSpeakPermissionRequest - (*CheckSpeakPermissionResponse)(nil), // 25: hyapp.room.v1.CheckSpeakPermissionResponse - (*VerifyRoomPresenceRequest)(nil), // 26: hyapp.room.v1.VerifyRoomPresenceRequest - (*VerifyRoomPresenceResponse)(nil), // 27: hyapp.room.v1.VerifyRoomPresenceResponse - nil, // 28: hyapp.room.v1.RoomSnapshot.RoomExtEntry + (*ConfirmMicPublishingRequest)(nil), // 18: hyapp.room.v1.ConfirmMicPublishingRequest + (*ConfirmMicPublishingResponse)(nil), // 19: hyapp.room.v1.ConfirmMicPublishingResponse + (*SetMicSeatLockRequest)(nil), // 20: hyapp.room.v1.SetMicSeatLockRequest + (*SetMicSeatLockResponse)(nil), // 21: hyapp.room.v1.SetMicSeatLockResponse + (*SetChatEnabledRequest)(nil), // 22: hyapp.room.v1.SetChatEnabledRequest + (*SetChatEnabledResponse)(nil), // 23: hyapp.room.v1.SetChatEnabledResponse + (*SetRoomAdminRequest)(nil), // 24: hyapp.room.v1.SetRoomAdminRequest + (*SetRoomAdminResponse)(nil), // 25: hyapp.room.v1.SetRoomAdminResponse + (*TransferRoomHostRequest)(nil), // 26: hyapp.room.v1.TransferRoomHostRequest + (*TransferRoomHostResponse)(nil), // 27: hyapp.room.v1.TransferRoomHostResponse + (*MuteUserRequest)(nil), // 28: hyapp.room.v1.MuteUserRequest + (*MuteUserResponse)(nil), // 29: hyapp.room.v1.MuteUserResponse + (*KickUserRequest)(nil), // 30: hyapp.room.v1.KickUserRequest + (*KickUserResponse)(nil), // 31: hyapp.room.v1.KickUserResponse + (*UnbanUserRequest)(nil), // 32: hyapp.room.v1.UnbanUserRequest + (*UnbanUserResponse)(nil), // 33: hyapp.room.v1.UnbanUserResponse + (*SendGiftRequest)(nil), // 34: hyapp.room.v1.SendGiftRequest + (*SendGiftResponse)(nil), // 35: hyapp.room.v1.SendGiftResponse + (*CheckSpeakPermissionRequest)(nil), // 36: hyapp.room.v1.CheckSpeakPermissionRequest + (*CheckSpeakPermissionResponse)(nil), // 37: hyapp.room.v1.CheckSpeakPermissionResponse + (*VerifyRoomPresenceRequest)(nil), // 38: hyapp.room.v1.VerifyRoomPresenceRequest + (*VerifyRoomPresenceResponse)(nil), // 39: hyapp.room.v1.VerifyRoomPresenceResponse + nil, // 40: hyapp.room.v1.RoomSnapshot.RoomExtEntry } var file_api_proto_room_v1_room_proto_depIdxs = []int32{ 3, // 0: hyapp.room.v1.RoomSnapshot.mic_seats:type_name -> hyapp.room.v1.SeatState 2, // 1: hyapp.room.v1.RoomSnapshot.online_users:type_name -> hyapp.room.v1.RoomUser 4, // 2: hyapp.room.v1.RoomSnapshot.gift_rank:type_name -> hyapp.room.v1.RankItem - 28, // 3: hyapp.room.v1.RoomSnapshot.room_ext:type_name -> hyapp.room.v1.RoomSnapshot.RoomExtEntry + 40, // 3: hyapp.room.v1.RoomSnapshot.room_ext:type_name -> hyapp.room.v1.RoomSnapshot.RoomExtEntry 0, // 4: hyapp.room.v1.CreateRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta 1, // 5: hyapp.room.v1.CreateRoomResponse.result:type_name -> hyapp.room.v1.CommandResult 5, // 6: hyapp.room.v1.CreateRoomResponse.room:type_name -> hyapp.room.v1.RoomSnapshot @@ -2328,43 +2875,73 @@ var file_api_proto_room_v1_room_proto_depIdxs = []int32{ 0, // 20: hyapp.room.v1.ChangeMicSeatRequest.meta:type_name -> hyapp.room.v1.RequestMeta 1, // 21: hyapp.room.v1.ChangeMicSeatResponse.result:type_name -> hyapp.room.v1.CommandResult 5, // 22: hyapp.room.v1.ChangeMicSeatResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 23: hyapp.room.v1.MuteUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 24: hyapp.room.v1.MuteUserResponse.result:type_name -> hyapp.room.v1.CommandResult - 5, // 25: hyapp.room.v1.MuteUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 26: hyapp.room.v1.KickUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 27: hyapp.room.v1.KickUserResponse.result:type_name -> hyapp.room.v1.CommandResult - 5, // 28: hyapp.room.v1.KickUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 29: hyapp.room.v1.SendGiftRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 1, // 30: hyapp.room.v1.SendGiftResponse.result:type_name -> hyapp.room.v1.CommandResult - 4, // 31: hyapp.room.v1.SendGiftResponse.gift_rank:type_name -> hyapp.room.v1.RankItem - 5, // 32: hyapp.room.v1.SendGiftResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 6, // 33: hyapp.room.v1.RoomCommandService.CreateRoom:input_type -> hyapp.room.v1.CreateRoomRequest - 8, // 34: hyapp.room.v1.RoomCommandService.JoinRoom:input_type -> hyapp.room.v1.JoinRoomRequest - 10, // 35: hyapp.room.v1.RoomCommandService.LeaveRoom:input_type -> hyapp.room.v1.LeaveRoomRequest - 12, // 36: hyapp.room.v1.RoomCommandService.MicUp:input_type -> hyapp.room.v1.MicUpRequest - 14, // 37: hyapp.room.v1.RoomCommandService.MicDown:input_type -> hyapp.room.v1.MicDownRequest - 16, // 38: hyapp.room.v1.RoomCommandService.ChangeMicSeat:input_type -> hyapp.room.v1.ChangeMicSeatRequest - 18, // 39: hyapp.room.v1.RoomCommandService.MuteUser:input_type -> hyapp.room.v1.MuteUserRequest - 20, // 40: hyapp.room.v1.RoomCommandService.KickUser:input_type -> hyapp.room.v1.KickUserRequest - 22, // 41: hyapp.room.v1.RoomCommandService.SendGift:input_type -> hyapp.room.v1.SendGiftRequest - 24, // 42: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:input_type -> hyapp.room.v1.CheckSpeakPermissionRequest - 26, // 43: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:input_type -> hyapp.room.v1.VerifyRoomPresenceRequest - 7, // 44: hyapp.room.v1.RoomCommandService.CreateRoom:output_type -> hyapp.room.v1.CreateRoomResponse - 9, // 45: hyapp.room.v1.RoomCommandService.JoinRoom:output_type -> hyapp.room.v1.JoinRoomResponse - 11, // 46: hyapp.room.v1.RoomCommandService.LeaveRoom:output_type -> hyapp.room.v1.LeaveRoomResponse - 13, // 47: hyapp.room.v1.RoomCommandService.MicUp:output_type -> hyapp.room.v1.MicUpResponse - 15, // 48: hyapp.room.v1.RoomCommandService.MicDown:output_type -> hyapp.room.v1.MicDownResponse - 17, // 49: hyapp.room.v1.RoomCommandService.ChangeMicSeat:output_type -> hyapp.room.v1.ChangeMicSeatResponse - 19, // 50: hyapp.room.v1.RoomCommandService.MuteUser:output_type -> hyapp.room.v1.MuteUserResponse - 21, // 51: hyapp.room.v1.RoomCommandService.KickUser:output_type -> hyapp.room.v1.KickUserResponse - 23, // 52: hyapp.room.v1.RoomCommandService.SendGift:output_type -> hyapp.room.v1.SendGiftResponse - 25, // 53: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:output_type -> hyapp.room.v1.CheckSpeakPermissionResponse - 27, // 54: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:output_type -> hyapp.room.v1.VerifyRoomPresenceResponse - 44, // [44:55] is the sub-list for method output_type - 33, // [33:44] is the sub-list for method input_type - 33, // [33:33] is the sub-list for extension type_name - 33, // [33:33] is the sub-list for extension extendee - 0, // [0:33] is the sub-list for field type_name + 0, // 23: hyapp.room.v1.ConfirmMicPublishingRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 24: hyapp.room.v1.ConfirmMicPublishingResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 25: hyapp.room.v1.ConfirmMicPublishingResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 26: hyapp.room.v1.SetMicSeatLockRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 27: hyapp.room.v1.SetMicSeatLockResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 28: hyapp.room.v1.SetMicSeatLockResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 29: hyapp.room.v1.SetChatEnabledRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 30: hyapp.room.v1.SetChatEnabledResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 31: hyapp.room.v1.SetChatEnabledResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 32: hyapp.room.v1.SetRoomAdminRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 33: hyapp.room.v1.SetRoomAdminResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 34: hyapp.room.v1.SetRoomAdminResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 35: hyapp.room.v1.TransferRoomHostRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 36: hyapp.room.v1.TransferRoomHostResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 37: hyapp.room.v1.TransferRoomHostResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 38: hyapp.room.v1.MuteUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 39: hyapp.room.v1.MuteUserResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 40: hyapp.room.v1.MuteUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 41: hyapp.room.v1.KickUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 42: hyapp.room.v1.KickUserResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 43: hyapp.room.v1.KickUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 44: hyapp.room.v1.UnbanUserRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 45: hyapp.room.v1.UnbanUserResponse.result:type_name -> hyapp.room.v1.CommandResult + 5, // 46: hyapp.room.v1.UnbanUserResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 47: hyapp.room.v1.SendGiftRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 1, // 48: hyapp.room.v1.SendGiftResponse.result:type_name -> hyapp.room.v1.CommandResult + 4, // 49: hyapp.room.v1.SendGiftResponse.gift_rank:type_name -> hyapp.room.v1.RankItem + 5, // 50: hyapp.room.v1.SendGiftResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 6, // 51: hyapp.room.v1.RoomCommandService.CreateRoom:input_type -> hyapp.room.v1.CreateRoomRequest + 8, // 52: hyapp.room.v1.RoomCommandService.JoinRoom:input_type -> hyapp.room.v1.JoinRoomRequest + 10, // 53: hyapp.room.v1.RoomCommandService.LeaveRoom:input_type -> hyapp.room.v1.LeaveRoomRequest + 12, // 54: hyapp.room.v1.RoomCommandService.MicUp:input_type -> hyapp.room.v1.MicUpRequest + 14, // 55: hyapp.room.v1.RoomCommandService.MicDown:input_type -> hyapp.room.v1.MicDownRequest + 16, // 56: hyapp.room.v1.RoomCommandService.ChangeMicSeat:input_type -> hyapp.room.v1.ChangeMicSeatRequest + 18, // 57: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:input_type -> hyapp.room.v1.ConfirmMicPublishingRequest + 20, // 58: hyapp.room.v1.RoomCommandService.SetMicSeatLock:input_type -> hyapp.room.v1.SetMicSeatLockRequest + 22, // 59: hyapp.room.v1.RoomCommandService.SetChatEnabled:input_type -> hyapp.room.v1.SetChatEnabledRequest + 24, // 60: hyapp.room.v1.RoomCommandService.SetRoomAdmin:input_type -> hyapp.room.v1.SetRoomAdminRequest + 26, // 61: hyapp.room.v1.RoomCommandService.TransferRoomHost:input_type -> hyapp.room.v1.TransferRoomHostRequest + 28, // 62: hyapp.room.v1.RoomCommandService.MuteUser:input_type -> hyapp.room.v1.MuteUserRequest + 30, // 63: hyapp.room.v1.RoomCommandService.KickUser:input_type -> hyapp.room.v1.KickUserRequest + 32, // 64: hyapp.room.v1.RoomCommandService.UnbanUser:input_type -> hyapp.room.v1.UnbanUserRequest + 34, // 65: hyapp.room.v1.RoomCommandService.SendGift:input_type -> hyapp.room.v1.SendGiftRequest + 36, // 66: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:input_type -> hyapp.room.v1.CheckSpeakPermissionRequest + 38, // 67: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:input_type -> hyapp.room.v1.VerifyRoomPresenceRequest + 7, // 68: hyapp.room.v1.RoomCommandService.CreateRoom:output_type -> hyapp.room.v1.CreateRoomResponse + 9, // 69: hyapp.room.v1.RoomCommandService.JoinRoom:output_type -> hyapp.room.v1.JoinRoomResponse + 11, // 70: hyapp.room.v1.RoomCommandService.LeaveRoom:output_type -> hyapp.room.v1.LeaveRoomResponse + 13, // 71: hyapp.room.v1.RoomCommandService.MicUp:output_type -> hyapp.room.v1.MicUpResponse + 15, // 72: hyapp.room.v1.RoomCommandService.MicDown:output_type -> hyapp.room.v1.MicDownResponse + 17, // 73: hyapp.room.v1.RoomCommandService.ChangeMicSeat:output_type -> hyapp.room.v1.ChangeMicSeatResponse + 19, // 74: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:output_type -> hyapp.room.v1.ConfirmMicPublishingResponse + 21, // 75: hyapp.room.v1.RoomCommandService.SetMicSeatLock:output_type -> hyapp.room.v1.SetMicSeatLockResponse + 23, // 76: hyapp.room.v1.RoomCommandService.SetChatEnabled:output_type -> hyapp.room.v1.SetChatEnabledResponse + 25, // 77: hyapp.room.v1.RoomCommandService.SetRoomAdmin:output_type -> hyapp.room.v1.SetRoomAdminResponse + 27, // 78: hyapp.room.v1.RoomCommandService.TransferRoomHost:output_type -> hyapp.room.v1.TransferRoomHostResponse + 29, // 79: hyapp.room.v1.RoomCommandService.MuteUser:output_type -> hyapp.room.v1.MuteUserResponse + 31, // 80: hyapp.room.v1.RoomCommandService.KickUser:output_type -> hyapp.room.v1.KickUserResponse + 33, // 81: hyapp.room.v1.RoomCommandService.UnbanUser:output_type -> hyapp.room.v1.UnbanUserResponse + 35, // 82: hyapp.room.v1.RoomCommandService.SendGift:output_type -> hyapp.room.v1.SendGiftResponse + 37, // 83: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:output_type -> hyapp.room.v1.CheckSpeakPermissionResponse + 39, // 84: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:output_type -> hyapp.room.v1.VerifyRoomPresenceResponse + 68, // [68:85] is the sub-list for method output_type + 51, // [51:68] is the sub-list for method input_type + 51, // [51:51] is the sub-list for extension type_name + 51, // [51:51] is the sub-list for extension extendee + 0, // [0:51] is the sub-list for field type_name } func init() { file_api_proto_room_v1_room_proto_init() } @@ -2372,351 +2949,13 @@ func file_api_proto_room_v1_room_proto_init() { if File_api_proto_room_v1_room_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_api_proto_room_v1_room_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*RequestMeta); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*CommandResult); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*RoomUser); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[3].Exporter = func(v any, i int) any { - switch v := v.(*SeatState); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[4].Exporter = func(v any, i int) any { - switch v := v.(*RankItem); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[5].Exporter = func(v any, i int) any { - switch v := v.(*RoomSnapshot); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[6].Exporter = func(v any, i int) any { - switch v := v.(*CreateRoomRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[7].Exporter = func(v any, i int) any { - switch v := v.(*CreateRoomResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[8].Exporter = func(v any, i int) any { - switch v := v.(*JoinRoomRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[9].Exporter = func(v any, i int) any { - switch v := v.(*JoinRoomResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[10].Exporter = func(v any, i int) any { - switch v := v.(*LeaveRoomRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[11].Exporter = func(v any, i int) any { - switch v := v.(*LeaveRoomResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[12].Exporter = func(v any, i int) any { - switch v := v.(*MicUpRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[13].Exporter = func(v any, i int) any { - switch v := v.(*MicUpResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[14].Exporter = func(v any, i int) any { - switch v := v.(*MicDownRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[15].Exporter = func(v any, i int) any { - switch v := v.(*MicDownResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[16].Exporter = func(v any, i int) any { - switch v := v.(*ChangeMicSeatRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[17].Exporter = func(v any, i int) any { - switch v := v.(*ChangeMicSeatResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[18].Exporter = func(v any, i int) any { - switch v := v.(*MuteUserRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[19].Exporter = func(v any, i int) any { - switch v := v.(*MuteUserResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[20].Exporter = func(v any, i int) any { - switch v := v.(*KickUserRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[21].Exporter = func(v any, i int) any { - switch v := v.(*KickUserResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[22].Exporter = func(v any, i int) any { - switch v := v.(*SendGiftRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[23].Exporter = func(v any, i int) any { - switch v := v.(*SendGiftResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[24].Exporter = func(v any, i int) any { - switch v := v.(*CheckSpeakPermissionRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[25].Exporter = func(v any, i int) any { - switch v := v.(*CheckSpeakPermissionResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[26].Exporter = func(v any, i int) any { - switch v := v.(*VerifyRoomPresenceRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_room_v1_room_proto_msgTypes[27].Exporter = func(v any, i int) any { - switch v := v.(*VerifyRoomPresenceResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_api_proto_room_v1_room_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_proto_room_v1_room_proto_rawDesc), len(file_api_proto_room_v1_room_proto_rawDesc)), NumEnums: 0, - NumMessages: 29, + NumMessages: 41, NumExtensions: 0, NumServices: 2, }, @@ -2725,7 +2964,6 @@ func file_api_proto_room_v1_room_proto_init() { MessageInfos: file_api_proto_room_v1_room_proto_msgTypes, }.Build() File_api_proto_room_v1_room_proto = out.File - file_api_proto_room_v1_room_proto_rawDesc = nil file_api_proto_room_v1_room_proto_goTypes = nil file_api_proto_room_v1_room_proto_depIdxs = nil } diff --git a/api/proto/room/v1/room.proto b/api/proto/room/v1/room.proto index 1af568c4..2e4e60df 100644 --- a/api/proto/room/v1/room.proto +++ b/api/proto/room/v1/room.proto @@ -33,11 +33,21 @@ message RoomUser { int64 last_seen_at_ms = 4; } -// SeatState 表达单个麦位当前占用关系。 +// SeatState 表达单个麦位当前占用和 RTC 发流确认状态。 message SeatState { int32 seat_no = 1; int64 user_id = 2; bool locked = 3; + // publish_state 为空表示空麦或未进入发流流程;上麦后先进入 pending_publish,确认后进入 publishing。 + string publish_state = 4; + // mic_session_id 是单次上麦发流会话 ID;所有客户端确认或 RTC webhook 必须带回它。 + string mic_session_id = 5; + // publish_deadline_ms 是 pending_publish 必须确认发流的截止时间。 + int64 publish_deadline_ms = 6; + // mic_session_room_version 是创建本次 mic_session 的房间版本,用于丢弃旧版本 RTC 事件。 + int64 mic_session_room_version = 7; + // last_publish_event_time_ms 记录已接受的最新 RTC/客户端发流事件时间。 + int64 last_publish_event_time_ms = 8; } // RankItem 表达房间内本地礼物榜项目。 @@ -69,15 +79,12 @@ message RoomSnapshot { // CreateRoomRequest 创建一个新的房间执行单元。 // 必填语义:meta.room_id、meta.command_id、meta.actor_user_id、seat_count 和 mode。 -// owner_user_id/host_user_id 是内部兼容字段,外部 gateway 创建路径不接收客户端自报值。 message CreateRoomRequest { RequestMeta meta = 1; - int64 owner_user_id = 2; - int64 host_user_id = 3; // seat_count 必须大于 0;没有麦位的语音房不进入 Room Cell。 - int32 seat_count = 4; + int32 seat_count = 2; // mode 必须非空;当前只作为房间状态字段保存和透出。 - string mode = 5; + string mode = 3; } // CreateRoomResponse 返回新建后的房间快照。 @@ -121,12 +128,16 @@ message MicUpResponse { CommandResult result = 1; int32 seat_no = 2; RoomSnapshot room = 3; + string mic_session_id = 4; + int64 publish_deadline_ms = 5; } // MicDownRequest 把用户从当前麦位移下。 message MicDownRequest { RequestMeta meta = 1; int64 target_user_id = 2; + // reason 为空表示主动下麦;系统自动释放麦位时会写入稳定原因,例如 publish_timeout。 + string reason = 3; } // MicDownResponse 返回最新房间快照与目标麦位。 @@ -149,6 +160,75 @@ message ChangeMicSeatResponse { RoomSnapshot room = 2; } +// ConfirmMicPublishingRequest 确认当前 mic_session 已经在 RTC 侧成功发布音频。 +// 客户端 SDK 回调和后续 RTC webhook 都必须带 mic_session_id、room_version 和 event_time_ms。 +message ConfirmMicPublishingRequest { + RequestMeta meta = 1; + // target_user_id 为空时默认确认 actor_user_id;RTC webhook 入口可显式指定目标用户。 + int64 target_user_id = 2; + string mic_session_id = 3; + int64 room_version = 4; + int64 event_time_ms = 5; + string source = 6; +} + +// ConfirmMicPublishingResponse 返回确认后的最新房间快照。 +message ConfirmMicPublishingResponse { + CommandResult result = 1; + int32 seat_no = 2; + RoomSnapshot room = 3; +} + +// SetMicSeatLockRequest 锁定或解锁指定麦位。 +message SetMicSeatLockRequest { + RequestMeta meta = 1; + int32 seat_no = 2; + bool locked = 3; +} + +// SetMicSeatLockResponse 返回锁麦后的最新房间快照。 +message SetMicSeatLockResponse { + CommandResult result = 1; + RoomSnapshot room = 2; +} + +// SetChatEnabledRequest 开启或关闭房间公屏。 +message SetChatEnabledRequest { + RequestMeta meta = 1; + bool enabled = 2; +} + +// SetChatEnabledResponse 返回聊天开关变更后的最新房间快照。 +message SetChatEnabledResponse { + CommandResult result = 1; + RoomSnapshot room = 2; +} + +// SetRoomAdminRequest 添加或移除房间管理员。 +message SetRoomAdminRequest { + RequestMeta meta = 1; + int64 target_user_id = 2; + bool enabled = 3; +} + +// SetRoomAdminResponse 返回管理员集合变更后的最新房间快照。 +message SetRoomAdminResponse { + CommandResult result = 1; + RoomSnapshot room = 2; +} + +// TransferRoomHostRequest 把主持人身份转移给房间内用户。 +message TransferRoomHostRequest { + RequestMeta meta = 1; + int64 target_user_id = 2; +} + +// TransferRoomHostResponse 返回主持人转移后的最新房间快照。 +message TransferRoomHostResponse { + CommandResult result = 1; + RoomSnapshot room = 2; +} + // MuteUserRequest 修改房间内禁言态。 message MuteUserRequest { RequestMeta meta = 1; @@ -174,13 +254,24 @@ message KickUserResponse { RoomSnapshot room = 2; } +// UnbanUserRequest 解除房间内 ban,用户仍需重新 JoinRoom。 +message UnbanUserRequest { + RequestMeta meta = 1; + int64 target_user_id = 2; +} + +// UnbanUserResponse 返回解封后的最新房间快照。 +message UnbanUserResponse { + CommandResult result = 1; + RoomSnapshot room = 2; +} + // SendGiftRequest 是首版房间内最重要的变现命令。 message SendGiftRequest { RequestMeta meta = 1; int64 target_user_id = 2; string gift_id = 3; int32 gift_count = 4; - int64 gift_unit_value = 5; } // SendGiftResponse 返回扣费成功并落到房间后的状态结果。 @@ -228,8 +319,14 @@ service RoomCommandService { rpc MicUp(MicUpRequest) returns (MicUpResponse); rpc MicDown(MicDownRequest) returns (MicDownResponse); rpc ChangeMicSeat(ChangeMicSeatRequest) returns (ChangeMicSeatResponse); + rpc ConfirmMicPublishing(ConfirmMicPublishingRequest) returns (ConfirmMicPublishingResponse); + rpc SetMicSeatLock(SetMicSeatLockRequest) returns (SetMicSeatLockResponse); + rpc SetChatEnabled(SetChatEnabledRequest) returns (SetChatEnabledResponse); + rpc SetRoomAdmin(SetRoomAdminRequest) returns (SetRoomAdminResponse); + rpc TransferRoomHost(TransferRoomHostRequest) returns (TransferRoomHostResponse); rpc MuteUser(MuteUserRequest) returns (MuteUserResponse); rpc KickUser(KickUserRequest) returns (KickUserResponse); + rpc UnbanUser(UnbanUserRequest) returns (UnbanUserResponse); rpc SendGift(SendGiftRequest) returns (SendGiftResponse); } diff --git a/api/proto/room/v1/room_grpc.pb.go b/api/proto/room/v1/room_grpc.pb.go index 829afa2a..251de909 100644 --- a/api/proto/room/v1/room_grpc.pb.go +++ b/api/proto/room/v1/room_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v5.29.2 +// - protoc v5.27.3 // source: api/proto/room/v1/room.proto package roomv1 @@ -19,15 +19,21 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - RoomCommandService_CreateRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/CreateRoom" - RoomCommandService_JoinRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/JoinRoom" - RoomCommandService_LeaveRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/LeaveRoom" - RoomCommandService_MicUp_FullMethodName = "/hyapp.room.v1.RoomCommandService/MicUp" - RoomCommandService_MicDown_FullMethodName = "/hyapp.room.v1.RoomCommandService/MicDown" - RoomCommandService_ChangeMicSeat_FullMethodName = "/hyapp.room.v1.RoomCommandService/ChangeMicSeat" - RoomCommandService_MuteUser_FullMethodName = "/hyapp.room.v1.RoomCommandService/MuteUser" - RoomCommandService_KickUser_FullMethodName = "/hyapp.room.v1.RoomCommandService/KickUser" - RoomCommandService_SendGift_FullMethodName = "/hyapp.room.v1.RoomCommandService/SendGift" + RoomCommandService_CreateRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/CreateRoom" + RoomCommandService_JoinRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/JoinRoom" + RoomCommandService_LeaveRoom_FullMethodName = "/hyapp.room.v1.RoomCommandService/LeaveRoom" + RoomCommandService_MicUp_FullMethodName = "/hyapp.room.v1.RoomCommandService/MicUp" + RoomCommandService_MicDown_FullMethodName = "/hyapp.room.v1.RoomCommandService/MicDown" + RoomCommandService_ChangeMicSeat_FullMethodName = "/hyapp.room.v1.RoomCommandService/ChangeMicSeat" + RoomCommandService_ConfirmMicPublishing_FullMethodName = "/hyapp.room.v1.RoomCommandService/ConfirmMicPublishing" + RoomCommandService_SetMicSeatLock_FullMethodName = "/hyapp.room.v1.RoomCommandService/SetMicSeatLock" + RoomCommandService_SetChatEnabled_FullMethodName = "/hyapp.room.v1.RoomCommandService/SetChatEnabled" + RoomCommandService_SetRoomAdmin_FullMethodName = "/hyapp.room.v1.RoomCommandService/SetRoomAdmin" + RoomCommandService_TransferRoomHost_FullMethodName = "/hyapp.room.v1.RoomCommandService/TransferRoomHost" + RoomCommandService_MuteUser_FullMethodName = "/hyapp.room.v1.RoomCommandService/MuteUser" + RoomCommandService_KickUser_FullMethodName = "/hyapp.room.v1.RoomCommandService/KickUser" + RoomCommandService_UnbanUser_FullMethodName = "/hyapp.room.v1.RoomCommandService/UnbanUser" + RoomCommandService_SendGift_FullMethodName = "/hyapp.room.v1.RoomCommandService/SendGift" ) // RoomCommandServiceClient is the client API for RoomCommandService service. @@ -42,8 +48,14 @@ type RoomCommandServiceClient interface { MicUp(ctx context.Context, in *MicUpRequest, opts ...grpc.CallOption) (*MicUpResponse, error) MicDown(ctx context.Context, in *MicDownRequest, opts ...grpc.CallOption) (*MicDownResponse, error) ChangeMicSeat(ctx context.Context, in *ChangeMicSeatRequest, opts ...grpc.CallOption) (*ChangeMicSeatResponse, error) + ConfirmMicPublishing(ctx context.Context, in *ConfirmMicPublishingRequest, opts ...grpc.CallOption) (*ConfirmMicPublishingResponse, error) + SetMicSeatLock(ctx context.Context, in *SetMicSeatLockRequest, opts ...grpc.CallOption) (*SetMicSeatLockResponse, error) + SetChatEnabled(ctx context.Context, in *SetChatEnabledRequest, opts ...grpc.CallOption) (*SetChatEnabledResponse, error) + SetRoomAdmin(ctx context.Context, in *SetRoomAdminRequest, opts ...grpc.CallOption) (*SetRoomAdminResponse, error) + TransferRoomHost(ctx context.Context, in *TransferRoomHostRequest, opts ...grpc.CallOption) (*TransferRoomHostResponse, error) MuteUser(ctx context.Context, in *MuteUserRequest, opts ...grpc.CallOption) (*MuteUserResponse, error) KickUser(ctx context.Context, in *KickUserRequest, opts ...grpc.CallOption) (*KickUserResponse, error) + UnbanUser(ctx context.Context, in *UnbanUserRequest, opts ...grpc.CallOption) (*UnbanUserResponse, error) SendGift(ctx context.Context, in *SendGiftRequest, opts ...grpc.CallOption) (*SendGiftResponse, error) } @@ -115,6 +127,56 @@ func (c *roomCommandServiceClient) ChangeMicSeat(ctx context.Context, in *Change return out, nil } +func (c *roomCommandServiceClient) ConfirmMicPublishing(ctx context.Context, in *ConfirmMicPublishingRequest, opts ...grpc.CallOption) (*ConfirmMicPublishingResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ConfirmMicPublishingResponse) + err := c.cc.Invoke(ctx, RoomCommandService_ConfirmMicPublishing_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *roomCommandServiceClient) SetMicSeatLock(ctx context.Context, in *SetMicSeatLockRequest, opts ...grpc.CallOption) (*SetMicSeatLockResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SetMicSeatLockResponse) + err := c.cc.Invoke(ctx, RoomCommandService_SetMicSeatLock_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *roomCommandServiceClient) SetChatEnabled(ctx context.Context, in *SetChatEnabledRequest, opts ...grpc.CallOption) (*SetChatEnabledResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SetChatEnabledResponse) + err := c.cc.Invoke(ctx, RoomCommandService_SetChatEnabled_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *roomCommandServiceClient) SetRoomAdmin(ctx context.Context, in *SetRoomAdminRequest, opts ...grpc.CallOption) (*SetRoomAdminResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SetRoomAdminResponse) + err := c.cc.Invoke(ctx, RoomCommandService_SetRoomAdmin_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *roomCommandServiceClient) TransferRoomHost(ctx context.Context, in *TransferRoomHostRequest, opts ...grpc.CallOption) (*TransferRoomHostResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TransferRoomHostResponse) + err := c.cc.Invoke(ctx, RoomCommandService_TransferRoomHost_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *roomCommandServiceClient) MuteUser(ctx context.Context, in *MuteUserRequest, opts ...grpc.CallOption) (*MuteUserResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(MuteUserResponse) @@ -135,6 +197,16 @@ func (c *roomCommandServiceClient) KickUser(ctx context.Context, in *KickUserReq return out, nil } +func (c *roomCommandServiceClient) UnbanUser(ctx context.Context, in *UnbanUserRequest, opts ...grpc.CallOption) (*UnbanUserResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UnbanUserResponse) + err := c.cc.Invoke(ctx, RoomCommandService_UnbanUser_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *roomCommandServiceClient) SendGift(ctx context.Context, in *SendGiftRequest, opts ...grpc.CallOption) (*SendGiftResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(SendGiftResponse) @@ -157,8 +229,14 @@ type RoomCommandServiceServer interface { MicUp(context.Context, *MicUpRequest) (*MicUpResponse, error) MicDown(context.Context, *MicDownRequest) (*MicDownResponse, error) ChangeMicSeat(context.Context, *ChangeMicSeatRequest) (*ChangeMicSeatResponse, error) + ConfirmMicPublishing(context.Context, *ConfirmMicPublishingRequest) (*ConfirmMicPublishingResponse, error) + SetMicSeatLock(context.Context, *SetMicSeatLockRequest) (*SetMicSeatLockResponse, error) + SetChatEnabled(context.Context, *SetChatEnabledRequest) (*SetChatEnabledResponse, error) + SetRoomAdmin(context.Context, *SetRoomAdminRequest) (*SetRoomAdminResponse, error) + TransferRoomHost(context.Context, *TransferRoomHostRequest) (*TransferRoomHostResponse, error) MuteUser(context.Context, *MuteUserRequest) (*MuteUserResponse, error) KickUser(context.Context, *KickUserRequest) (*KickUserResponse, error) + UnbanUser(context.Context, *UnbanUserRequest) (*UnbanUserResponse, error) SendGift(context.Context, *SendGiftRequest) (*SendGiftResponse, error) mustEmbedUnimplementedRoomCommandServiceServer() } @@ -188,12 +266,30 @@ func (UnimplementedRoomCommandServiceServer) MicDown(context.Context, *MicDownRe func (UnimplementedRoomCommandServiceServer) ChangeMicSeat(context.Context, *ChangeMicSeatRequest) (*ChangeMicSeatResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ChangeMicSeat not implemented") } +func (UnimplementedRoomCommandServiceServer) ConfirmMicPublishing(context.Context, *ConfirmMicPublishingRequest) (*ConfirmMicPublishingResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ConfirmMicPublishing not implemented") +} +func (UnimplementedRoomCommandServiceServer) SetMicSeatLock(context.Context, *SetMicSeatLockRequest) (*SetMicSeatLockResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetMicSeatLock not implemented") +} +func (UnimplementedRoomCommandServiceServer) SetChatEnabled(context.Context, *SetChatEnabledRequest) (*SetChatEnabledResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetChatEnabled not implemented") +} +func (UnimplementedRoomCommandServiceServer) SetRoomAdmin(context.Context, *SetRoomAdminRequest) (*SetRoomAdminResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetRoomAdmin not implemented") +} +func (UnimplementedRoomCommandServiceServer) TransferRoomHost(context.Context, *TransferRoomHostRequest) (*TransferRoomHostResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method TransferRoomHost not implemented") +} func (UnimplementedRoomCommandServiceServer) MuteUser(context.Context, *MuteUserRequest) (*MuteUserResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method MuteUser not implemented") } func (UnimplementedRoomCommandServiceServer) KickUser(context.Context, *KickUserRequest) (*KickUserResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method KickUser not implemented") } +func (UnimplementedRoomCommandServiceServer) UnbanUser(context.Context, *UnbanUserRequest) (*UnbanUserResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UnbanUser not implemented") +} func (UnimplementedRoomCommandServiceServer) SendGift(context.Context, *SendGiftRequest) (*SendGiftResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SendGift not implemented") } @@ -326,6 +422,96 @@ func _RoomCommandService_ChangeMicSeat_Handler(srv interface{}, ctx context.Cont return interceptor(ctx, in, info, handler) } +func _RoomCommandService_ConfirmMicPublishing_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ConfirmMicPublishingRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoomCommandServiceServer).ConfirmMicPublishing(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoomCommandService_ConfirmMicPublishing_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoomCommandServiceServer).ConfirmMicPublishing(ctx, req.(*ConfirmMicPublishingRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _RoomCommandService_SetMicSeatLock_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetMicSeatLockRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoomCommandServiceServer).SetMicSeatLock(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoomCommandService_SetMicSeatLock_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoomCommandServiceServer).SetMicSeatLock(ctx, req.(*SetMicSeatLockRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _RoomCommandService_SetChatEnabled_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetChatEnabledRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoomCommandServiceServer).SetChatEnabled(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoomCommandService_SetChatEnabled_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoomCommandServiceServer).SetChatEnabled(ctx, req.(*SetChatEnabledRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _RoomCommandService_SetRoomAdmin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetRoomAdminRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoomCommandServiceServer).SetRoomAdmin(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoomCommandService_SetRoomAdmin_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoomCommandServiceServer).SetRoomAdmin(ctx, req.(*SetRoomAdminRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _RoomCommandService_TransferRoomHost_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TransferRoomHostRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoomCommandServiceServer).TransferRoomHost(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoomCommandService_TransferRoomHost_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoomCommandServiceServer).TransferRoomHost(ctx, req.(*TransferRoomHostRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _RoomCommandService_MuteUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MuteUserRequest) if err := dec(in); err != nil { @@ -362,6 +548,24 @@ func _RoomCommandService_KickUser_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +func _RoomCommandService_UnbanUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UnbanUserRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RoomCommandServiceServer).UnbanUser(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RoomCommandService_UnbanUser_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RoomCommandServiceServer).UnbanUser(ctx, req.(*UnbanUserRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _RoomCommandService_SendGift_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SendGiftRequest) if err := dec(in); err != nil { @@ -411,6 +615,26 @@ var RoomCommandService_ServiceDesc = grpc.ServiceDesc{ MethodName: "ChangeMicSeat", Handler: _RoomCommandService_ChangeMicSeat_Handler, }, + { + MethodName: "ConfirmMicPublishing", + Handler: _RoomCommandService_ConfirmMicPublishing_Handler, + }, + { + MethodName: "SetMicSeatLock", + Handler: _RoomCommandService_SetMicSeatLock_Handler, + }, + { + MethodName: "SetChatEnabled", + Handler: _RoomCommandService_SetChatEnabled_Handler, + }, + { + MethodName: "SetRoomAdmin", + Handler: _RoomCommandService_SetRoomAdmin_Handler, + }, + { + MethodName: "TransferRoomHost", + Handler: _RoomCommandService_TransferRoomHost_Handler, + }, { MethodName: "MuteUser", Handler: _RoomCommandService_MuteUser_Handler, @@ -419,6 +643,10 @@ var RoomCommandService_ServiceDesc = grpc.ServiceDesc{ MethodName: "KickUser", Handler: _RoomCommandService_KickUser_Handler, }, + { + MethodName: "UnbanUser", + Handler: _RoomCommandService_UnbanUser_Handler, + }, { MethodName: "SendGift", Handler: _RoomCommandService_SendGift_Handler, diff --git a/api/proto/user/v1/auth.pb.go b/api/proto/user/v1/auth.pb.go index 0acc4867..38bd39b9 100644 --- a/api/proto/user/v1/auth.pb.go +++ b/api/proto/user/v1/auth.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 -// protoc v5.29.2 +// protoc-gen-go v1.36.6 +// protoc v5.27.3 // source: api/proto/user/v1/auth.proto package userv1 @@ -11,6 +11,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -22,29 +23,26 @@ const ( // AuthToken 是登录和刷新后返回给 gateway 的令牌集合。 type AuthToken struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` - AccessToken string `protobuf:"bytes,3,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` - RefreshToken string `protobuf:"bytes,4,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` - ExpiresInSec int64 `protobuf:"varint,5,opt,name=expires_in_sec,json=expiresInSec,proto3" json:"expires_in_sec,omitempty"` - TokenType string `protobuf:"bytes,6,opt,name=token_type,json=tokenType,proto3" json:"token_type,omitempty"` - DisplayUserId string `protobuf:"bytes,7,opt,name=display_user_id,json=displayUserId,proto3" json:"display_user_id,omitempty"` - DefaultDisplayUserId string `protobuf:"bytes,8,opt,name=default_display_user_id,json=defaultDisplayUserId,proto3" json:"default_display_user_id,omitempty"` - DisplayUserIdKind string `protobuf:"bytes,9,opt,name=display_user_id_kind,json=displayUserIdKind,proto3" json:"display_user_id_kind,omitempty"` - DisplayUserIdExpiresAtMs int64 `protobuf:"varint,10,opt,name=display_user_id_expires_at_ms,json=displayUserIdExpiresAtMs,proto3" json:"display_user_id_expires_at_ms,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + AccessToken string `protobuf:"bytes,3,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` + RefreshToken string `protobuf:"bytes,4,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` + ExpiresInSec int64 `protobuf:"varint,5,opt,name=expires_in_sec,json=expiresInSec,proto3" json:"expires_in_sec,omitempty"` + TokenType string `protobuf:"bytes,6,opt,name=token_type,json=tokenType,proto3" json:"token_type,omitempty"` + DisplayUserId string `protobuf:"bytes,7,opt,name=display_user_id,json=displayUserId,proto3" json:"display_user_id,omitempty"` + DefaultDisplayUserId string `protobuf:"bytes,8,opt,name=default_display_user_id,json=defaultDisplayUserId,proto3" json:"default_display_user_id,omitempty"` + DisplayUserIdKind string `protobuf:"bytes,9,opt,name=display_user_id_kind,json=displayUserIdKind,proto3" json:"display_user_id_kind,omitempty"` + DisplayUserIdExpiresAtMs int64 `protobuf:"varint,10,opt,name=display_user_id_expires_at_ms,json=displayUserIdExpiresAtMs,proto3" json:"display_user_id_expires_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AuthToken) Reset() { *x = AuthToken{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_auth_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_auth_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AuthToken) String() string { @@ -55,7 +53,7 @@ func (*AuthToken) ProtoMessage() {} func (x *AuthToken) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_auth_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -142,22 +140,19 @@ func (x *AuthToken) GetDisplayUserIdExpiresAtMs() int64 { // LoginPasswordRequest 使用当前 display_user_id 和已设置密码登录。 type LoginPasswordRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + DisplayUserId string `protobuf:"bytes,2,opt,name=display_user_id,json=displayUserId,proto3" json:"display_user_id,omitempty"` + Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - DisplayUserId string `protobuf:"bytes,2,opt,name=display_user_id,json=displayUserId,proto3" json:"display_user_id,omitempty"` - Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` + sizeCache protoimpl.SizeCache } func (x *LoginPasswordRequest) Reset() { *x = LoginPasswordRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_auth_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_auth_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *LoginPasswordRequest) String() string { @@ -168,7 +163,7 @@ func (*LoginPasswordRequest) ProtoMessage() {} func (x *LoginPasswordRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_auth_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -206,41 +201,36 @@ func (x *LoginPasswordRequest) GetPassword() string { // LoginThirdPartyRequest 使用三方凭证登录或注册。 type LoginThirdPartyRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` - Credential string `protobuf:"bytes,3,opt,name=credential,proto3" json:"credential,omitempty"` - Username string `protobuf:"bytes,4,opt,name=username,proto3" json:"username,omitempty"` - Gender string `protobuf:"bytes,5,opt,name=gender,proto3" json:"gender,omitempty"` - Country string `protobuf:"bytes,6,opt,name=country,proto3" json:"country,omitempty"` - InviteCode string `protobuf:"bytes,7,opt,name=invite_code,json=inviteCode,proto3" json:"invite_code,omitempty"` - // Deprecated: Marked as deprecated in api/proto/user/v1/auth.proto. - Ip string `protobuf:"bytes,8,opt,name=ip,proto3" json:"ip,omitempty"` - DeviceId string `protobuf:"bytes,9,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"` - Device string `protobuf:"bytes,10,opt,name=device,proto3" json:"device,omitempty"` - Avatar string `protobuf:"bytes,11,opt,name=avatar,proto3" json:"avatar,omitempty"` - Birth string `protobuf:"bytes,12,opt,name=birth,proto3" json:"birth,omitempty"` - AppVersion string `protobuf:"bytes,13,opt,name=app_version,json=appVersion,proto3" json:"app_version,omitempty"` - Source string `protobuf:"bytes,14,opt,name=source,proto3" json:"source,omitempty"` - Platform string `protobuf:"bytes,15,opt,name=platform,proto3" json:"platform,omitempty"` - Language string `protobuf:"bytes,16,opt,name=language,proto3" json:"language,omitempty"` - OsVersion string `protobuf:"bytes,17,opt,name=os_version,json=osVersion,proto3" json:"os_version,omitempty"` - BuildNumber string `protobuf:"bytes,18,opt,name=build_number,json=buildNumber,proto3" json:"build_number,omitempty"` - Timezone string `protobuf:"bytes,19,opt,name=timezone,proto3" json:"timezone,omitempty"` - InstallChannel string `protobuf:"bytes,20,opt,name=install_channel,json=installChannel,proto3" json:"install_channel,omitempty"` - Campaign string `protobuf:"bytes,21,opt,name=campaign,proto3" json:"campaign,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` + Credential string `protobuf:"bytes,3,opt,name=credential,proto3" json:"credential,omitempty"` + Username string `protobuf:"bytes,4,opt,name=username,proto3" json:"username,omitempty"` + Gender string `protobuf:"bytes,5,opt,name=gender,proto3" json:"gender,omitempty"` + Country string `protobuf:"bytes,6,opt,name=country,proto3" json:"country,omitempty"` + InviteCode string `protobuf:"bytes,7,opt,name=invite_code,json=inviteCode,proto3" json:"invite_code,omitempty"` + DeviceId string `protobuf:"bytes,8,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"` + Device string `protobuf:"bytes,9,opt,name=device,proto3" json:"device,omitempty"` + Avatar string `protobuf:"bytes,10,opt,name=avatar,proto3" json:"avatar,omitempty"` + Birth string `protobuf:"bytes,11,opt,name=birth,proto3" json:"birth,omitempty"` + AppVersion string `protobuf:"bytes,12,opt,name=app_version,json=appVersion,proto3" json:"app_version,omitempty"` + Source string `protobuf:"bytes,13,opt,name=source,proto3" json:"source,omitempty"` + Platform string `protobuf:"bytes,14,opt,name=platform,proto3" json:"platform,omitempty"` + Language string `protobuf:"bytes,15,opt,name=language,proto3" json:"language,omitempty"` + OsVersion string `protobuf:"bytes,16,opt,name=os_version,json=osVersion,proto3" json:"os_version,omitempty"` + BuildNumber string `protobuf:"bytes,17,opt,name=build_number,json=buildNumber,proto3" json:"build_number,omitempty"` + Timezone string `protobuf:"bytes,18,opt,name=timezone,proto3" json:"timezone,omitempty"` + InstallChannel string `protobuf:"bytes,19,opt,name=install_channel,json=installChannel,proto3" json:"install_channel,omitempty"` + Campaign string `protobuf:"bytes,20,opt,name=campaign,proto3" json:"campaign,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *LoginThirdPartyRequest) Reset() { *x = LoginThirdPartyRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_auth_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_auth_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *LoginThirdPartyRequest) String() string { @@ -251,7 +241,7 @@ func (*LoginThirdPartyRequest) ProtoMessage() {} func (x *LoginThirdPartyRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_auth_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -315,14 +305,6 @@ func (x *LoginThirdPartyRequest) GetInviteCode() string { return "" } -// Deprecated: Marked as deprecated in api/proto/user/v1/auth.proto. -func (x *LoginThirdPartyRequest) GetIp() string { - if x != nil { - return x.Ip - } - return "" -} - func (x *LoginThirdPartyRequest) GetDeviceId() string { if x != nil { return x.DeviceId @@ -416,21 +398,18 @@ func (x *LoginThirdPartyRequest) GetCampaign() string { // AuthResponse 返回标准认证令牌。 type AuthResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Token *AuthToken `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + IsNewUser bool `protobuf:"varint,2,opt,name=is_new_user,json=isNewUser,proto3" json:"is_new_user,omitempty"` unknownFields protoimpl.UnknownFields - - Token *AuthToken `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` - IsNewUser bool `protobuf:"varint,2,opt,name=is_new_user,json=isNewUser,proto3" json:"is_new_user,omitempty"` + sizeCache protoimpl.SizeCache } func (x *AuthResponse) Reset() { *x = AuthResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_auth_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_auth_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AuthResponse) String() string { @@ -441,7 +420,7 @@ func (*AuthResponse) ProtoMessage() {} func (x *AuthResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_auth_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -472,22 +451,19 @@ func (x *AuthResponse) GetIsNewUser() bool { // SetPasswordRequest 为已经三方登录的用户首次设置密码。 type SetPasswordRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` + sizeCache protoimpl.SizeCache } func (x *SetPasswordRequest) Reset() { *x = SetPasswordRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_auth_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_auth_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SetPasswordRequest) String() string { @@ -498,7 +474,7 @@ func (*SetPasswordRequest) ProtoMessage() {} func (x *SetPasswordRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_auth_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -536,20 +512,17 @@ func (x *SetPasswordRequest) GetPassword() string { // SetPasswordResponse 返回密码是否已经成功设置。 type SetPasswordResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + PasswordSet bool `protobuf:"varint,1,opt,name=password_set,json=passwordSet,proto3" json:"password_set,omitempty"` unknownFields protoimpl.UnknownFields - - PasswordSet bool `protobuf:"varint,1,opt,name=password_set,json=passwordSet,proto3" json:"password_set,omitempty"` + sizeCache protoimpl.SizeCache } func (x *SetPasswordResponse) Reset() { *x = SetPasswordResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_auth_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_auth_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *SetPasswordResponse) String() string { @@ -560,7 +533,7 @@ func (*SetPasswordResponse) ProtoMessage() {} func (x *SetPasswordResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_auth_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -584,21 +557,18 @@ func (x *SetPasswordResponse) GetPasswordSet() bool { // RefreshTokenRequest 使用 refresh token 换取新 token。 type RefreshTokenRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + RefreshToken string `protobuf:"bytes,2,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - RefreshToken string `protobuf:"bytes,2,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` + sizeCache protoimpl.SizeCache } func (x *RefreshTokenRequest) Reset() { *x = RefreshTokenRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_auth_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_auth_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RefreshTokenRequest) String() string { @@ -609,7 +579,7 @@ func (*RefreshTokenRequest) ProtoMessage() {} func (x *RefreshTokenRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_auth_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -640,20 +610,17 @@ func (x *RefreshTokenRequest) GetRefreshToken() string { // RefreshTokenResponse 返回轮换后的令牌。 type RefreshTokenResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Token *AuthToken `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` unknownFields protoimpl.UnknownFields - - Token *AuthToken `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + sizeCache protoimpl.SizeCache } func (x *RefreshTokenResponse) Reset() { *x = RefreshTokenResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_auth_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_auth_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RefreshTokenResponse) String() string { @@ -664,7 +631,7 @@ func (*RefreshTokenResponse) ProtoMessage() {} func (x *RefreshTokenResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_auth_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -688,22 +655,19 @@ func (x *RefreshTokenResponse) GetToken() *AuthToken { // LogoutRequest 失效指定会话或 refresh token。 type LogoutRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + RefreshToken string `protobuf:"bytes,3,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` - RefreshToken string `protobuf:"bytes,3,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` + sizeCache protoimpl.SizeCache } func (x *LogoutRequest) Reset() { *x = LogoutRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_auth_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_auth_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *LogoutRequest) String() string { @@ -714,7 +678,7 @@ func (*LogoutRequest) ProtoMessage() {} func (x *LogoutRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_auth_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -752,20 +716,17 @@ func (x *LogoutRequest) GetRefreshToken() string { // LogoutResponse 返回会话是否被失效。 type LogoutResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Revoked bool `protobuf:"varint,1,opt,name=revoked,proto3" json:"revoked,omitempty"` unknownFields protoimpl.UnknownFields - - Revoked bool `protobuf:"varint,1,opt,name=revoked,proto3" json:"revoked,omitempty"` + sizeCache protoimpl.SizeCache } func (x *LogoutResponse) Reset() { *x = LogoutResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_auth_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_auth_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *LogoutResponse) String() string { @@ -776,7 +737,7 @@ func (*LogoutResponse) ProtoMessage() {} func (x *LogoutResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_auth_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -800,167 +761,90 @@ func (x *LogoutResponse) GetRevoked() bool { var File_api_proto_user_v1_auth_proto protoreflect.FileDescriptor -var file_api_proto_user_v1_auth_proto_rawDesc = []byte{ - 0x0a, 0x1c, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x75, 0x73, 0x65, 0x72, - 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x1a, 0x1c, 0x61, - 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, - 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa1, 0x03, 0x0a, 0x09, - 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, - 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, - 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, - 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, - 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x65, 0x78, 0x70, - 0x69, 0x72, 0x65, 0x73, 0x5f, 0x69, 0x6e, 0x5f, 0x73, 0x65, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0c, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x49, 0x6e, 0x53, 0x65, 0x63, 0x12, - 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x26, - 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x17, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, - 0x74, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2f, 0x0a, - 0x14, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x64, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x3f, - 0x0a, 0x1d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x22, - 0x8a, 0x01, 0x0a, 0x14, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, - 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, - 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0xfe, 0x04, 0x0a, - 0x16, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, - 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, - 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x61, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x16, 0x0a, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x76, 0x69, 0x74, 0x65, 0x43, 0x6f, - 0x64, 0x65, 0x12, 0x12, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, - 0x18, 0x01, 0x52, 0x02, 0x69, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, - 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, - 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, - 0x74, 0x61, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x69, 0x72, 0x74, 0x68, 0x18, 0x0c, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x62, 0x69, 0x72, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x70, - 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x61, 0x70, 0x70, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x0f, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1a, - 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x73, - 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x75, 0x69, - 0x6c, 0x64, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, - 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e, 0x73, 0x74, - 0x61, 0x6c, 0x6c, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x14, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x6d, 0x70, 0x61, 0x69, 0x67, 0x6e, 0x18, 0x15, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x61, 0x6d, 0x70, 0x61, 0x69, 0x67, 0x6e, 0x22, 0x5e, 0x0a, - 0x0c, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, - 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, - 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1e, 0x0a, - 0x0b, 0x69, 0x73, 0x5f, 0x6e, 0x65, 0x77, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x4e, 0x65, 0x77, 0x55, 0x73, 0x65, 0x72, 0x22, 0x79, 0x0a, - 0x12, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, - 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x38, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x50, - 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x73, 0x65, 0x74, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x53, - 0x65, 0x74, 0x22, 0x6a, 0x0a, 0x13, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, - 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, - 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x46, - 0x0a, 0x14, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, - 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x83, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x6f, 0x75, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, - 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x2a, 0x0a, 0x0e, - 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, - 0x0a, 0x07, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x07, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x32, 0xad, 0x03, 0x0a, 0x0b, 0x41, 0x75, 0x74, - 0x68, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x51, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, - 0x6e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x50, - 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, - 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x0f, 0x4c, - 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x12, 0x25, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x68, 0x69, 0x72, 0x64, 0x50, 0x61, 0x72, 0x74, 0x79, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0b, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, - 0x64, 0x12, 0x21, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x57, 0x0a, 0x0c, 0x52, 0x65, 0x66, 0x72, - 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x66, - 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x45, 0x0a, 0x06, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x12, 0x1c, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, - 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x20, 0x5a, 0x1e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x75, 0x73, 0x65, 0x72, - 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, -} +const file_api_proto_user_v1_auth_proto_rawDesc = "" + + "\n" + + "\x1capi/proto/user/v1/auth.proto\x12\rhyapp.user.v1\x1a\x1capi/proto/user/v1/user.proto\"\xa1\x03\n" + + "\tAuthToken\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x1d\n" + + "\n" + + "session_id\x18\x02 \x01(\tR\tsessionId\x12!\n" + + "\faccess_token\x18\x03 \x01(\tR\vaccessToken\x12#\n" + + "\rrefresh_token\x18\x04 \x01(\tR\frefreshToken\x12$\n" + + "\x0eexpires_in_sec\x18\x05 \x01(\x03R\fexpiresInSec\x12\x1d\n" + + "\n" + + "token_type\x18\x06 \x01(\tR\ttokenType\x12&\n" + + "\x0fdisplay_user_id\x18\a \x01(\tR\rdisplayUserId\x125\n" + + "\x17default_display_user_id\x18\b \x01(\tR\x14defaultDisplayUserId\x12/\n" + + "\x14display_user_id_kind\x18\t \x01(\tR\x11displayUserIdKind\x12?\n" + + "\x1ddisplay_user_id_expires_at_ms\x18\n" + + " \x01(\x03R\x18displayUserIdExpiresAtMs\"\x8a\x01\n" + + "\x14LoginPasswordRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12&\n" + + "\x0fdisplay_user_id\x18\x02 \x01(\tR\rdisplayUserId\x12\x1a\n" + + "\bpassword\x18\x03 \x01(\tR\bpassword\"\xea\x04\n" + + "\x16LoginThirdPartyRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x1a\n" + + "\bprovider\x18\x02 \x01(\tR\bprovider\x12\x1e\n" + + "\n" + + "credential\x18\x03 \x01(\tR\n" + + "credential\x12\x1a\n" + + "\busername\x18\x04 \x01(\tR\busername\x12\x16\n" + + "\x06gender\x18\x05 \x01(\tR\x06gender\x12\x18\n" + + "\acountry\x18\x06 \x01(\tR\acountry\x12\x1f\n" + + "\vinvite_code\x18\a \x01(\tR\n" + + "inviteCode\x12\x1b\n" + + "\tdevice_id\x18\b \x01(\tR\bdeviceId\x12\x16\n" + + "\x06device\x18\t \x01(\tR\x06device\x12\x16\n" + + "\x06avatar\x18\n" + + " \x01(\tR\x06avatar\x12\x14\n" + + "\x05birth\x18\v \x01(\tR\x05birth\x12\x1f\n" + + "\vapp_version\x18\f \x01(\tR\n" + + "appVersion\x12\x16\n" + + "\x06source\x18\r \x01(\tR\x06source\x12\x1a\n" + + "\bplatform\x18\x0e \x01(\tR\bplatform\x12\x1a\n" + + "\blanguage\x18\x0f \x01(\tR\blanguage\x12\x1d\n" + + "\n" + + "os_version\x18\x10 \x01(\tR\tosVersion\x12!\n" + + "\fbuild_number\x18\x11 \x01(\tR\vbuildNumber\x12\x1a\n" + + "\btimezone\x18\x12 \x01(\tR\btimezone\x12'\n" + + "\x0finstall_channel\x18\x13 \x01(\tR\x0einstallChannel\x12\x1a\n" + + "\bcampaign\x18\x14 \x01(\tR\bcampaign\"^\n" + + "\fAuthResponse\x12.\n" + + "\x05token\x18\x01 \x01(\v2\x18.hyapp.user.v1.AuthTokenR\x05token\x12\x1e\n" + + "\vis_new_user\x18\x02 \x01(\bR\tisNewUser\"y\n" + + "\x12SetPasswordRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x1a\n" + + "\bpassword\x18\x03 \x01(\tR\bpassword\"8\n" + + "\x13SetPasswordResponse\x12!\n" + + "\fpassword_set\x18\x01 \x01(\bR\vpasswordSet\"j\n" + + "\x13RefreshTokenRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12#\n" + + "\rrefresh_token\x18\x02 \x01(\tR\frefreshToken\"F\n" + + "\x14RefreshTokenResponse\x12.\n" + + "\x05token\x18\x01 \x01(\v2\x18.hyapp.user.v1.AuthTokenR\x05token\"\x83\x01\n" + + "\rLogoutRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x1d\n" + + "\n" + + "session_id\x18\x02 \x01(\tR\tsessionId\x12#\n" + + "\rrefresh_token\x18\x03 \x01(\tR\frefreshToken\"*\n" + + "\x0eLogoutResponse\x12\x18\n" + + "\arevoked\x18\x01 \x01(\bR\arevoked2\xad\x03\n" + + "\vAuthService\x12Q\n" + + "\rLoginPassword\x12#.hyapp.user.v1.LoginPasswordRequest\x1a\x1b.hyapp.user.v1.AuthResponse\x12U\n" + + "\x0fLoginThirdParty\x12%.hyapp.user.v1.LoginThirdPartyRequest\x1a\x1b.hyapp.user.v1.AuthResponse\x12T\n" + + "\vSetPassword\x12!.hyapp.user.v1.SetPasswordRequest\x1a\".hyapp.user.v1.SetPasswordResponse\x12W\n" + + "\fRefreshToken\x12\".hyapp.user.v1.RefreshTokenRequest\x1a#.hyapp.user.v1.RefreshTokenResponse\x12E\n" + + "\x06Logout\x12\x1c.hyapp.user.v1.LogoutRequest\x1a\x1d.hyapp.user.v1.LogoutResponseB Z\x1ehyapp/api/proto/user/v1;userv1b\x06proto3" var ( file_api_proto_user_v1_auth_proto_rawDescOnce sync.Once - file_api_proto_user_v1_auth_proto_rawDescData = file_api_proto_user_v1_auth_proto_rawDesc + file_api_proto_user_v1_auth_proto_rawDescData []byte ) func file_api_proto_user_v1_auth_proto_rawDescGZIP() []byte { file_api_proto_user_v1_auth_proto_rawDescOnce.Do(func() { - file_api_proto_user_v1_auth_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_user_v1_auth_proto_rawDescData) + file_api_proto_user_v1_auth_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_api_proto_user_v1_auth_proto_rawDesc), len(file_api_proto_user_v1_auth_proto_rawDesc))) }) return file_api_proto_user_v1_auth_proto_rawDescData } @@ -1010,133 +894,11 @@ func file_api_proto_user_v1_auth_proto_init() { return } file_api_proto_user_v1_user_proto_init() - if !protoimpl.UnsafeEnabled { - file_api_proto_user_v1_auth_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*AuthToken); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_auth_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*LoginPasswordRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_auth_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*LoginThirdPartyRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_auth_proto_msgTypes[3].Exporter = func(v any, i int) any { - switch v := v.(*AuthResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_auth_proto_msgTypes[4].Exporter = func(v any, i int) any { - switch v := v.(*SetPasswordRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_auth_proto_msgTypes[5].Exporter = func(v any, i int) any { - switch v := v.(*SetPasswordResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_auth_proto_msgTypes[6].Exporter = func(v any, i int) any { - switch v := v.(*RefreshTokenRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_auth_proto_msgTypes[7].Exporter = func(v any, i int) any { - switch v := v.(*RefreshTokenResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_auth_proto_msgTypes[8].Exporter = func(v any, i int) any { - switch v := v.(*LogoutRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_auth_proto_msgTypes[9].Exporter = func(v any, i int) any { - switch v := v.(*LogoutResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_api_proto_user_v1_auth_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_proto_user_v1_auth_proto_rawDesc), len(file_api_proto_user_v1_auth_proto_rawDesc)), NumEnums: 0, NumMessages: 10, NumExtensions: 0, @@ -1147,7 +909,6 @@ func file_api_proto_user_v1_auth_proto_init() { MessageInfos: file_api_proto_user_v1_auth_proto_msgTypes, }.Build() File_api_proto_user_v1_auth_proto = out.File - file_api_proto_user_v1_auth_proto_rawDesc = nil file_api_proto_user_v1_auth_proto_goTypes = nil file_api_proto_user_v1_auth_proto_depIdxs = nil } diff --git a/api/proto/user/v1/auth.proto b/api/proto/user/v1/auth.proto index 966de201..ad8b4937 100644 --- a/api/proto/user/v1/auth.proto +++ b/api/proto/user/v1/auth.proto @@ -36,20 +36,19 @@ message LoginThirdPartyRequest { string gender = 5; string country = 6; string invite_code = 7; - string ip = 8 [deprecated = true]; - string device_id = 9; - string device = 10; - string avatar = 11; - string birth = 12; - string app_version = 13; - string source = 14; - string platform = 15; - string language = 16; - string os_version = 17; - string build_number = 18; - string timezone = 19; - string install_channel = 20; - string campaign = 21; + string device_id = 8; + string device = 9; + string avatar = 10; + string birth = 11; + string app_version = 12; + string source = 13; + string platform = 14; + string language = 15; + string os_version = 16; + string build_number = 17; + string timezone = 18; + string install_channel = 19; + string campaign = 20; } // AuthResponse 返回标准认证令牌。 diff --git a/api/proto/user/v1/auth_grpc.pb.go b/api/proto/user/v1/auth_grpc.pb.go index 426d0b03..0ce0c30e 100644 --- a/api/proto/user/v1/auth_grpc.pb.go +++ b/api/proto/user/v1/auth_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v5.29.2 +// - protoc v5.27.3 // source: api/proto/user/v1/auth.proto package userv1 diff --git a/api/proto/user/v1/user.pb.go b/api/proto/user/v1/user.pb.go index 3f767259..2eb10857 100644 --- a/api/proto/user/v1/user.pb.go +++ b/api/proto/user/v1/user.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 -// protoc v5.29.2 +// protoc-gen-go v1.36.6 +// protoc v5.27.3 // source: api/proto/user/v1/user.proto package userv1 @@ -11,6 +11,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -75,27 +76,24 @@ func (UserStatus) EnumDescriptor() ([]byte, []int) { // RequestMeta 是 user-service 所有内部 RPC 的最小追踪元信息。 type RequestMeta struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + Caller string `protobuf:"bytes,2,opt,name=caller,proto3" json:"caller,omitempty"` + GatewayNodeId string `protobuf:"bytes,3,opt,name=gateway_node_id,json=gatewayNodeId,proto3" json:"gateway_node_id,omitempty"` + SentAtMs int64 `protobuf:"varint,4,opt,name=sent_at_ms,json=sentAtMs,proto3" json:"sent_at_ms,omitempty"` + DeviceId string `protobuf:"bytes,5,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"` + ClientIp string `protobuf:"bytes,6,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"` + UserAgent string `protobuf:"bytes,7,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"` + CountryByIp string `protobuf:"bytes,8,opt,name=country_by_ip,json=countryByIp,proto3" json:"country_by_ip,omitempty"` unknownFields protoimpl.UnknownFields - - RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` - Caller string `protobuf:"bytes,2,opt,name=caller,proto3" json:"caller,omitempty"` - GatewayNodeId string `protobuf:"bytes,3,opt,name=gateway_node_id,json=gatewayNodeId,proto3" json:"gateway_node_id,omitempty"` - SentAtMs int64 `protobuf:"varint,4,opt,name=sent_at_ms,json=sentAtMs,proto3" json:"sent_at_ms,omitempty"` - DeviceId string `protobuf:"bytes,5,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"` - ClientIp string `protobuf:"bytes,6,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"` - UserAgent string `protobuf:"bytes,7,opt,name=user_agent,json=userAgent,proto3" json:"user_agent,omitempty"` - CountryByIp string `protobuf:"bytes,8,opt,name=country_by_ip,json=countryByIp,proto3" json:"country_by_ip,omitempty"` + sizeCache protoimpl.SizeCache } func (x *RequestMeta) Reset() { *x = RequestMeta{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RequestMeta) String() string { @@ -106,7 +104,7 @@ func (*RequestMeta) ProtoMessage() {} func (x *RequestMeta) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -179,39 +177,36 @@ func (x *RequestMeta) GetCountryByIp() string { // User 是用户主数据投影,包含稳定身份、当前短号和基础资料。 type User struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - Status UserStatus `protobuf:"varint,2,opt,name=status,proto3,enum=hyapp.user.v1.UserStatus" json:"status,omitempty"` - CreatedAtMs int64 `protobuf:"varint,3,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` - UpdatedAtMs int64 `protobuf:"varint,4,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` - DisplayUserId string `protobuf:"bytes,5,opt,name=display_user_id,json=displayUserId,proto3" json:"display_user_id,omitempty"` - DefaultDisplayUserId string `protobuf:"bytes,6,opt,name=default_display_user_id,json=defaultDisplayUserId,proto3" json:"default_display_user_id,omitempty"` - DisplayUserIdKind string `protobuf:"bytes,7,opt,name=display_user_id_kind,json=displayUserIdKind,proto3" json:"display_user_id_kind,omitempty"` - DisplayUserIdExpiresAtMs int64 `protobuf:"varint,8,opt,name=display_user_id_expires_at_ms,json=displayUserIdExpiresAtMs,proto3" json:"display_user_id_expires_at_ms,omitempty"` - Username string `protobuf:"bytes,9,opt,name=username,proto3" json:"username,omitempty"` - Gender string `protobuf:"bytes,10,opt,name=gender,proto3" json:"gender,omitempty"` - Country string `protobuf:"bytes,11,opt,name=country,proto3" json:"country,omitempty"` - Avatar string `protobuf:"bytes,12,opt,name=avatar,proto3" json:"avatar,omitempty"` - Birth string `protobuf:"bytes,13,opt,name=birth,proto3" json:"birth,omitempty"` - Language string `protobuf:"bytes,14,opt,name=language,proto3" json:"language,omitempty"` - CountryId int64 `protobuf:"varint,15,opt,name=country_id,json=countryId,proto3" json:"country_id,omitempty"` - CountryName string `protobuf:"bytes,16,opt,name=country_name,json=countryName,proto3" json:"country_name,omitempty"` - CountryDisplayName string `protobuf:"bytes,17,opt,name=country_display_name,json=countryDisplayName,proto3" json:"country_display_name,omitempty"` - RegionId int64 `protobuf:"varint,18,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` - RegionCode string `protobuf:"bytes,19,opt,name=region_code,json=regionCode,proto3" json:"region_code,omitempty"` - RegionName string `protobuf:"bytes,20,opt,name=region_name,json=regionName,proto3" json:"region_name,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Status UserStatus `protobuf:"varint,2,opt,name=status,proto3,enum=hyapp.user.v1.UserStatus" json:"status,omitempty"` + CreatedAtMs int64 `protobuf:"varint,3,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,4,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + DisplayUserId string `protobuf:"bytes,5,opt,name=display_user_id,json=displayUserId,proto3" json:"display_user_id,omitempty"` + DefaultDisplayUserId string `protobuf:"bytes,6,opt,name=default_display_user_id,json=defaultDisplayUserId,proto3" json:"default_display_user_id,omitempty"` + DisplayUserIdKind string `protobuf:"bytes,7,opt,name=display_user_id_kind,json=displayUserIdKind,proto3" json:"display_user_id_kind,omitempty"` + DisplayUserIdExpiresAtMs int64 `protobuf:"varint,8,opt,name=display_user_id_expires_at_ms,json=displayUserIdExpiresAtMs,proto3" json:"display_user_id_expires_at_ms,omitempty"` + Username string `protobuf:"bytes,9,opt,name=username,proto3" json:"username,omitempty"` + Gender string `protobuf:"bytes,10,opt,name=gender,proto3" json:"gender,omitempty"` + Country string `protobuf:"bytes,11,opt,name=country,proto3" json:"country,omitempty"` + Avatar string `protobuf:"bytes,12,opt,name=avatar,proto3" json:"avatar,omitempty"` + Birth string `protobuf:"bytes,13,opt,name=birth,proto3" json:"birth,omitempty"` + Language string `protobuf:"bytes,14,opt,name=language,proto3" json:"language,omitempty"` + CountryId int64 `protobuf:"varint,15,opt,name=country_id,json=countryId,proto3" json:"country_id,omitempty"` + CountryName string `protobuf:"bytes,16,opt,name=country_name,json=countryName,proto3" json:"country_name,omitempty"` + CountryDisplayName string `protobuf:"bytes,17,opt,name=country_display_name,json=countryDisplayName,proto3" json:"country_display_name,omitempty"` + RegionId int64 `protobuf:"varint,18,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` + RegionCode string `protobuf:"bytes,19,opt,name=region_code,json=regionCode,proto3" json:"region_code,omitempty"` + RegionName string `protobuf:"bytes,20,opt,name=region_name,json=regionName,proto3" json:"region_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *User) Reset() { *x = User{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *User) String() string { @@ -222,7 +217,7 @@ func (*User) ProtoMessage() {} func (x *User) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -379,21 +374,18 @@ func (x *User) GetRegionName() string { // GetUserRequest 查询单个用户主状态。 type GetUserRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *GetUserRequest) Reset() { *x = GetUserRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetUserRequest) String() string { @@ -404,7 +396,7 @@ func (*GetUserRequest) ProtoMessage() {} func (x *GetUserRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -435,20 +427,17 @@ func (x *GetUserRequest) GetUserId() int64 { // GetUserResponse 返回单个用户主状态。 type GetUserResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` unknownFields protoimpl.UnknownFields - - User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` + sizeCache protoimpl.SizeCache } func (x *GetUserResponse) Reset() { *x = GetUserResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetUserResponse) String() string { @@ -459,7 +448,7 @@ func (*GetUserResponse) ProtoMessage() {} func (x *GetUserResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -483,21 +472,18 @@ func (x *GetUserResponse) GetUser() *User { // BatchGetUsersRequest 批量查询用户主状态。 type BatchGetUsersRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + UserIds []int64 `protobuf:"varint,2,rep,packed,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - UserIds []int64 `protobuf:"varint,2,rep,packed,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"` + sizeCache protoimpl.SizeCache } func (x *BatchGetUsersRequest) Reset() { *x = BatchGetUsersRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *BatchGetUsersRequest) String() string { @@ -508,7 +494,7 @@ func (*BatchGetUsersRequest) ProtoMessage() {} func (x *BatchGetUsersRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -539,20 +525,17 @@ func (x *BatchGetUsersRequest) GetUserIds() []int64 { // BatchGetUsersResponse 返回按 user_id 索引的用户主状态。 type BatchGetUsersResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Users map[int64]*User `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields - - Users map[int64]*User `protobuf:"bytes,1,rep,name=users,proto3" json:"users,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + sizeCache protoimpl.SizeCache } func (x *BatchGetUsersResponse) Reset() { *x = BatchGetUsersResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *BatchGetUsersResponse) String() string { @@ -563,7 +546,7 @@ func (*BatchGetUsersResponse) ProtoMessage() {} func (x *BatchGetUsersResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -587,24 +570,21 @@ func (x *BatchGetUsersResponse) GetUsers() map[int64]*User { // UpdateUserProfileRequest 修改用户可自助维护的基础资料。 type UpdateUserProfileRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Username *string `protobuf:"bytes,3,opt,name=username,proto3,oneof" json:"username,omitempty"` + Avatar *string `protobuf:"bytes,4,opt,name=avatar,proto3,oneof" json:"avatar,omitempty"` + Birth *string `protobuf:"bytes,5,opt,name=birth,proto3,oneof" json:"birth,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - Username *string `protobuf:"bytes,3,opt,name=username,proto3,oneof" json:"username,omitempty"` - Avatar *string `protobuf:"bytes,4,opt,name=avatar,proto3,oneof" json:"avatar,omitempty"` - Birth *string `protobuf:"bytes,5,opt,name=birth,proto3,oneof" json:"birth,omitempty"` + sizeCache protoimpl.SizeCache } func (x *UpdateUserProfileRequest) Reset() { *x = UpdateUserProfileRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UpdateUserProfileRequest) String() string { @@ -615,7 +595,7 @@ func (*UpdateUserProfileRequest) ProtoMessage() {} func (x *UpdateUserProfileRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -667,20 +647,17 @@ func (x *UpdateUserProfileRequest) GetBirth() string { // UpdateUserProfileResponse 返回更新后的用户资料投影。 type UpdateUserProfileResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` unknownFields protoimpl.UnknownFields - - User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` + sizeCache protoimpl.SizeCache } func (x *UpdateUserProfileResponse) Reset() { *x = UpdateUserProfileResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UpdateUserProfileResponse) String() string { @@ -691,7 +668,7 @@ func (*UpdateUserProfileResponse) ProtoMessage() {} func (x *UpdateUserProfileResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -715,22 +692,19 @@ func (x *UpdateUserProfileResponse) GetUser() *User { // ChangeUserCountryRequest 单独修改用户国家;该字段有冷却期和变更日志。 type ChangeUserCountryRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Country string `protobuf:"bytes,3,opt,name=country,proto3" json:"country,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - Country string `protobuf:"bytes,3,opt,name=country,proto3" json:"country,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ChangeUserCountryRequest) Reset() { *x = ChangeUserCountryRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ChangeUserCountryRequest) String() string { @@ -741,7 +715,7 @@ func (*ChangeUserCountryRequest) ProtoMessage() {} func (x *ChangeUserCountryRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -779,21 +753,18 @@ func (x *ChangeUserCountryRequest) GetCountry() string { // ChangeUserCountryResponse 返回更新后的用户资料和下一次可修改时间。 type ChangeUserCountryResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` - NextChangeAllowedAtMs int64 `protobuf:"varint,2,opt,name=next_change_allowed_at_ms,json=nextChangeAllowedAtMs,proto3" json:"next_change_allowed_at_ms,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + User *User `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"` + NextChangeAllowedAtMs int64 `protobuf:"varint,2,opt,name=next_change_allowed_at_ms,json=nextChangeAllowedAtMs,proto3" json:"next_change_allowed_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ChangeUserCountryResponse) Reset() { *x = ChangeUserCountryResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ChangeUserCountryResponse) String() string { @@ -804,7 +775,7 @@ func (*ChangeUserCountryResponse) ProtoMessage() {} func (x *ChangeUserCountryResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -835,27 +806,24 @@ func (x *ChangeUserCountryResponse) GetNextChangeAllowedAtMs() int64 { // Country 是国家主数据投影,客户端和普通用户流程只提交 country_code。 type Country struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - CountryId int64 `protobuf:"varint,1,opt,name=country_id,json=countryId,proto3" json:"country_id,omitempty"` - CountryName string `protobuf:"bytes,2,opt,name=country_name,json=countryName,proto3" json:"country_name,omitempty"` - CountryCode string `protobuf:"bytes,3,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"` - CountryDisplayName string `protobuf:"bytes,4,opt,name=country_display_name,json=countryDisplayName,proto3" json:"country_display_name,omitempty"` - Status string `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"` - SortOrder int32 `protobuf:"varint,6,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` - CreatedAtMs int64 `protobuf:"varint,7,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` - UpdatedAtMs int64 `protobuf:"varint,8,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + CountryId int64 `protobuf:"varint,1,opt,name=country_id,json=countryId,proto3" json:"country_id,omitempty"` + CountryName string `protobuf:"bytes,2,opt,name=country_name,json=countryName,proto3" json:"country_name,omitempty"` + CountryCode string `protobuf:"bytes,3,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"` + CountryDisplayName string `protobuf:"bytes,4,opt,name=country_display_name,json=countryDisplayName,proto3" json:"country_display_name,omitempty"` + Status string `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"` + SortOrder int32 `protobuf:"varint,6,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + CreatedAtMs int64 `protobuf:"varint,7,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,8,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Country) Reset() { *x = Country{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Country) String() string { @@ -866,7 +834,7 @@ func (*Country) ProtoMessage() {} func (x *Country) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -939,27 +907,24 @@ func (x *Country) GetUpdatedAtMs() int64 { // Region 是区域主数据投影,countries 只包含当前 active 国家码。 type Region struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + RegionId int64 `protobuf:"varint,1,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` + RegionCode string `protobuf:"bytes,2,opt,name=region_code,json=regionCode,proto3" json:"region_code,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` + Countries []string `protobuf:"bytes,5,rep,name=countries,proto3" json:"countries,omitempty"` + SortOrder int32 `protobuf:"varint,6,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + CreatedAtMs int64 `protobuf:"varint,7,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,8,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` unknownFields protoimpl.UnknownFields - - RegionId int64 `protobuf:"varint,1,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` - RegionCode string `protobuf:"bytes,2,opt,name=region_code,json=regionCode,proto3" json:"region_code,omitempty"` - Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` - Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty"` - Countries []string `protobuf:"bytes,5,rep,name=countries,proto3" json:"countries,omitempty"` - SortOrder int32 `protobuf:"varint,6,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` - CreatedAtMs int64 `protobuf:"varint,7,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` - UpdatedAtMs int64 `protobuf:"varint,8,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + sizeCache protoimpl.SizeCache } func (x *Region) Reset() { *x = Region{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Region) String() string { @@ -970,7 +935,7 @@ func (*Region) ProtoMessage() {} func (x *Region) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1043,25 +1008,22 @@ func (x *Region) GetUpdatedAtMs() int64 { // CreateCountryRequest 创建国家主数据;country_code 创建后不可变。 type CreateCountryRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - CountryName string `protobuf:"bytes,2,opt,name=country_name,json=countryName,proto3" json:"country_name,omitempty"` - CountryCode string `protobuf:"bytes,3,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"` - CountryDisplayName string `protobuf:"bytes,4,opt,name=country_display_name,json=countryDisplayName,proto3" json:"country_display_name,omitempty"` - SortOrder int32 `protobuf:"varint,5,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` - OperatorUserId int64 `protobuf:"varint,6,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + CountryName string `protobuf:"bytes,2,opt,name=country_name,json=countryName,proto3" json:"country_name,omitempty"` + CountryCode string `protobuf:"bytes,3,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"` + CountryDisplayName string `protobuf:"bytes,4,opt,name=country_display_name,json=countryDisplayName,proto3" json:"country_display_name,omitempty"` + SortOrder int32 `protobuf:"varint,5,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + OperatorUserId int64 `protobuf:"varint,6,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *CreateCountryRequest) Reset() { *x = CreateCountryRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CreateCountryRequest) String() string { @@ -1072,7 +1034,7 @@ func (*CreateCountryRequest) ProtoMessage() {} func (x *CreateCountryRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1130,21 +1092,18 @@ func (x *CreateCountryRequest) GetOperatorUserId() int64 { } type ListCountriesRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ListCountriesRequest) Reset() { *x = ListCountriesRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListCountriesRequest) String() string { @@ -1155,7 +1114,7 @@ func (*ListCountriesRequest) ProtoMessage() {} func (x *ListCountriesRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[13] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1185,20 +1144,17 @@ func (x *ListCountriesRequest) GetStatus() string { } type ListCountriesResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Countries []*Country `protobuf:"bytes,1,rep,name=countries,proto3" json:"countries,omitempty"` unknownFields protoimpl.UnknownFields - - Countries []*Country `protobuf:"bytes,1,rep,name=countries,proto3" json:"countries,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ListCountriesResponse) Reset() { *x = ListCountriesResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListCountriesResponse) String() string { @@ -1209,7 +1165,7 @@ func (*ListCountriesResponse) ProtoMessage() {} func (x *ListCountriesResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[14] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1233,25 +1189,22 @@ func (x *ListCountriesResponse) GetCountries() []*Country { // UpdateCountryRequest 只修改国家展示字段,不修改 country_code。 type UpdateCountryRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - CountryId int64 `protobuf:"varint,2,opt,name=country_id,json=countryId,proto3" json:"country_id,omitempty"` - CountryName string `protobuf:"bytes,3,opt,name=country_name,json=countryName,proto3" json:"country_name,omitempty"` - CountryDisplayName string `protobuf:"bytes,4,opt,name=country_display_name,json=countryDisplayName,proto3" json:"country_display_name,omitempty"` - SortOrder int32 `protobuf:"varint,5,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` - OperatorUserId int64 `protobuf:"varint,6,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + CountryId int64 `protobuf:"varint,2,opt,name=country_id,json=countryId,proto3" json:"country_id,omitempty"` + CountryName string `protobuf:"bytes,3,opt,name=country_name,json=countryName,proto3" json:"country_name,omitempty"` + CountryDisplayName string `protobuf:"bytes,4,opt,name=country_display_name,json=countryDisplayName,proto3" json:"country_display_name,omitempty"` + SortOrder int32 `protobuf:"varint,5,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + OperatorUserId int64 `protobuf:"varint,6,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *UpdateCountryRequest) Reset() { *x = UpdateCountryRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UpdateCountryRequest) String() string { @@ -1262,7 +1215,7 @@ func (*UpdateCountryRequest) ProtoMessage() {} func (x *UpdateCountryRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[15] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1320,22 +1273,19 @@ func (x *UpdateCountryRequest) GetOperatorUserId() int64 { } type DisableCountryRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - CountryId int64 `protobuf:"varint,2,opt,name=country_id,json=countryId,proto3" json:"country_id,omitempty"` - OperatorUserId int64 `protobuf:"varint,3,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + CountryId int64 `protobuf:"varint,2,opt,name=country_id,json=countryId,proto3" json:"country_id,omitempty"` + OperatorUserId int64 `protobuf:"varint,3,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *DisableCountryRequest) Reset() { *x = DisableCountryRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DisableCountryRequest) String() string { @@ -1346,7 +1296,7 @@ func (*DisableCountryRequest) ProtoMessage() {} func (x *DisableCountryRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[16] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1383,20 +1333,17 @@ func (x *DisableCountryRequest) GetOperatorUserId() int64 { } type CountryResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Country *Country `protobuf:"bytes,1,opt,name=country,proto3" json:"country,omitempty"` unknownFields protoimpl.UnknownFields - - Country *Country `protobuf:"bytes,1,opt,name=country,proto3" json:"country,omitempty"` + sizeCache protoimpl.SizeCache } func (x *CountryResponse) Reset() { *x = CountryResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CountryResponse) String() string { @@ -1407,7 +1354,7 @@ func (*CountryResponse) ProtoMessage() {} func (x *CountryResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[17] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1431,25 +1378,22 @@ func (x *CountryResponse) GetCountry() *Country { // CreateRegionRequest 创建区域并配置 active 国家列表。 type CreateRegionRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - RegionCode string `protobuf:"bytes,2,opt,name=region_code,json=regionCode,proto3" json:"region_code,omitempty"` - Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` - Countries []string `protobuf:"bytes,4,rep,name=countries,proto3" json:"countries,omitempty"` - SortOrder int32 `protobuf:"varint,5,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` - OperatorUserId int64 `protobuf:"varint,6,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + RegionCode string `protobuf:"bytes,2,opt,name=region_code,json=regionCode,proto3" json:"region_code,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Countries []string `protobuf:"bytes,4,rep,name=countries,proto3" json:"countries,omitempty"` + SortOrder int32 `protobuf:"varint,5,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + OperatorUserId int64 `protobuf:"varint,6,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *CreateRegionRequest) Reset() { *x = CreateRegionRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *CreateRegionRequest) String() string { @@ -1460,7 +1404,7 @@ func (*CreateRegionRequest) ProtoMessage() {} func (x *CreateRegionRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[18] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1518,21 +1462,18 @@ func (x *CreateRegionRequest) GetOperatorUserId() int64 { } type ListRegionsRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ListRegionsRequest) Reset() { *x = ListRegionsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListRegionsRequest) String() string { @@ -1543,7 +1484,7 @@ func (*ListRegionsRequest) ProtoMessage() {} func (x *ListRegionsRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[19] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1573,20 +1514,17 @@ func (x *ListRegionsRequest) GetStatus() string { } type ListRegionsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Regions []*Region `protobuf:"bytes,1,rep,name=regions,proto3" json:"regions,omitempty"` unknownFields protoimpl.UnknownFields - - Regions []*Region `protobuf:"bytes,1,rep,name=regions,proto3" json:"regions,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ListRegionsResponse) Reset() { *x = ListRegionsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[20] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ListRegionsResponse) String() string { @@ -1597,7 +1535,7 @@ func (*ListRegionsResponse) ProtoMessage() {} func (x *ListRegionsResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[20] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1620,21 +1558,18 @@ func (x *ListRegionsResponse) GetRegions() []*Region { } type GetRegionRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + RegionId int64 `protobuf:"varint,2,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - RegionId int64 `protobuf:"varint,2,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *GetRegionRequest) Reset() { *x = GetRegionRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[21] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetRegionRequest) String() string { @@ -1645,7 +1580,7 @@ func (*GetRegionRequest) ProtoMessage() {} func (x *GetRegionRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[21] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1676,25 +1611,22 @@ func (x *GetRegionRequest) GetRegionId() int64 { // UpdateRegionRequest 只修改区域展示字段,不修改 countries。 type UpdateRegionRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - RegionId int64 `protobuf:"varint,2,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` - RegionCode string `protobuf:"bytes,3,opt,name=region_code,json=regionCode,proto3" json:"region_code,omitempty"` - Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` - SortOrder int32 `protobuf:"varint,5,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` - OperatorUserId int64 `protobuf:"varint,6,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + RegionId int64 `protobuf:"varint,2,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` + RegionCode string `protobuf:"bytes,3,opt,name=region_code,json=regionCode,proto3" json:"region_code,omitempty"` + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + SortOrder int32 `protobuf:"varint,5,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + OperatorUserId int64 `protobuf:"varint,6,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *UpdateRegionRequest) Reset() { *x = UpdateRegionRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[22] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UpdateRegionRequest) String() string { @@ -1705,7 +1637,7 @@ func (*UpdateRegionRequest) ProtoMessage() {} func (x *UpdateRegionRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[22] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1763,23 +1695,20 @@ func (x *UpdateRegionRequest) GetOperatorUserId() int64 { } type ReplaceRegionCountriesRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - RegionId int64 `protobuf:"varint,2,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` - Countries []string `protobuf:"bytes,3,rep,name=countries,proto3" json:"countries,omitempty"` - OperatorUserId int64 `protobuf:"varint,4,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + RegionId int64 `protobuf:"varint,2,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` + Countries []string `protobuf:"bytes,3,rep,name=countries,proto3" json:"countries,omitempty"` + OperatorUserId int64 `protobuf:"varint,4,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ReplaceRegionCountriesRequest) Reset() { *x = ReplaceRegionCountriesRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[23] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ReplaceRegionCountriesRequest) String() string { @@ -1790,7 +1719,7 @@ func (*ReplaceRegionCountriesRequest) ProtoMessage() {} func (x *ReplaceRegionCountriesRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[23] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1834,22 +1763,19 @@ func (x *ReplaceRegionCountriesRequest) GetOperatorUserId() int64 { } type DisableRegionRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - RegionId int64 `protobuf:"varint,2,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` - OperatorUserId int64 `protobuf:"varint,3,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + RegionId int64 `protobuf:"varint,2,opt,name=region_id,json=regionId,proto3" json:"region_id,omitempty"` + OperatorUserId int64 `protobuf:"varint,3,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *DisableRegionRequest) Reset() { *x = DisableRegionRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[24] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DisableRegionRequest) String() string { @@ -1860,7 +1786,7 @@ func (*DisableRegionRequest) ProtoMessage() {} func (x *DisableRegionRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[24] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1897,20 +1823,17 @@ func (x *DisableRegionRequest) GetOperatorUserId() int64 { } type RegionResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Region *Region `protobuf:"bytes,1,opt,name=region,proto3" json:"region,omitempty"` unknownFields protoimpl.UnknownFields - - Region *Region `protobuf:"bytes,1,opt,name=region,proto3" json:"region,omitempty"` + sizeCache protoimpl.SizeCache } func (x *RegionResponse) Reset() { *x = RegionResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[25] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RegionResponse) String() string { @@ -1921,7 +1844,7 @@ func (*RegionResponse) ProtoMessage() {} func (x *RegionResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[25] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1945,25 +1868,22 @@ func (x *RegionResponse) GetRegion() *Region { // UserIdentity 是 user_id 和当前 active display_user_id 的绑定投影。 type UserIdentity struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - DisplayUserId string `protobuf:"bytes,2,opt,name=display_user_id,json=displayUserId,proto3" json:"display_user_id,omitempty"` - Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` - DefaultDisplayUserId string `protobuf:"bytes,4,opt,name=default_display_user_id,json=defaultDisplayUserId,proto3" json:"default_display_user_id,omitempty"` - DisplayUserIdKind string `protobuf:"bytes,5,opt,name=display_user_id_kind,json=displayUserIdKind,proto3" json:"display_user_id_kind,omitempty"` - DisplayUserIdExpiresAtMs int64 `protobuf:"varint,6,opt,name=display_user_id_expires_at_ms,json=displayUserIdExpiresAtMs,proto3" json:"display_user_id_expires_at_ms,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + DisplayUserId string `protobuf:"bytes,2,opt,name=display_user_id,json=displayUserId,proto3" json:"display_user_id,omitempty"` + Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` + DefaultDisplayUserId string `protobuf:"bytes,4,opt,name=default_display_user_id,json=defaultDisplayUserId,proto3" json:"default_display_user_id,omitempty"` + DisplayUserIdKind string `protobuf:"bytes,5,opt,name=display_user_id_kind,json=displayUserIdKind,proto3" json:"display_user_id_kind,omitempty"` + DisplayUserIdExpiresAtMs int64 `protobuf:"varint,6,opt,name=display_user_id_expires_at_ms,json=displayUserIdExpiresAtMs,proto3" json:"display_user_id_expires_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *UserIdentity) Reset() { *x = UserIdentity{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[26] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UserIdentity) String() string { @@ -1974,7 +1894,7 @@ func (*UserIdentity) ProtoMessage() {} func (x *UserIdentity) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[26] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2033,21 +1953,18 @@ func (x *UserIdentity) GetDisplayUserIdExpiresAtMs() int64 { // GetUserIdentityRequest 按 user_id 查询当前短号。 type GetUserIdentityRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *GetUserIdentityRequest) Reset() { *x = GetUserIdentityRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[27] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetUserIdentityRequest) String() string { @@ -2058,7 +1975,7 @@ func (*GetUserIdentityRequest) ProtoMessage() {} func (x *GetUserIdentityRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[27] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2089,20 +2006,17 @@ func (x *GetUserIdentityRequest) GetUserId() int64 { // GetUserIdentityResponse 返回当前短号绑定。 type GetUserIdentityResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Identity *UserIdentity `protobuf:"bytes,1,opt,name=identity,proto3" json:"identity,omitempty"` unknownFields protoimpl.UnknownFields - - Identity *UserIdentity `protobuf:"bytes,1,opt,name=identity,proto3" json:"identity,omitempty"` + sizeCache protoimpl.SizeCache } func (x *GetUserIdentityResponse) Reset() { *x = GetUserIdentityResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[28] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *GetUserIdentityResponse) String() string { @@ -2113,7 +2027,7 @@ func (*GetUserIdentityResponse) ProtoMessage() {} func (x *GetUserIdentityResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[28] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2137,21 +2051,18 @@ func (x *GetUserIdentityResponse) GetIdentity() *UserIdentity { // ResolveDisplayUserIDRequest 把客户端展示短号解析成系统 user_id。 type ResolveDisplayUserIDRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + DisplayUserId string `protobuf:"bytes,2,opt,name=display_user_id,json=displayUserId,proto3" json:"display_user_id,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - DisplayUserId string `protobuf:"bytes,2,opt,name=display_user_id,json=displayUserId,proto3" json:"display_user_id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ResolveDisplayUserIDRequest) Reset() { *x = ResolveDisplayUserIDRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[29] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ResolveDisplayUserIDRequest) String() string { @@ -2162,7 +2073,7 @@ func (*ResolveDisplayUserIDRequest) ProtoMessage() {} func (x *ResolveDisplayUserIDRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[29] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2193,20 +2104,17 @@ func (x *ResolveDisplayUserIDRequest) GetDisplayUserId() string { // ResolveDisplayUserIDResponse 返回 active 短号归属。 type ResolveDisplayUserIDResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Identity *UserIdentity `protobuf:"bytes,1,opt,name=identity,proto3" json:"identity,omitempty"` unknownFields protoimpl.UnknownFields - - Identity *UserIdentity `protobuf:"bytes,1,opt,name=identity,proto3" json:"identity,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ResolveDisplayUserIDResponse) Reset() { *x = ResolveDisplayUserIDResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[30] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ResolveDisplayUserIDResponse) String() string { @@ -2217,7 +2125,7 @@ func (*ResolveDisplayUserIDResponse) ProtoMessage() {} func (x *ResolveDisplayUserIDResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[30] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2241,24 +2149,21 @@ func (x *ResolveDisplayUserIDResponse) GetIdentity() *UserIdentity { // ChangeDisplayUserIDRequest 修改用户短号,必须由 user-service 保证事务一致。 type ChangeDisplayUserIDRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - NewDisplayUserId string `protobuf:"bytes,3,opt,name=new_display_user_id,json=newDisplayUserId,proto3" json:"new_display_user_id,omitempty"` - Reason string `protobuf:"bytes,4,opt,name=reason,proto3" json:"reason,omitempty"` - OperatorUserId int64 `protobuf:"varint,5,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + NewDisplayUserId string `protobuf:"bytes,3,opt,name=new_display_user_id,json=newDisplayUserId,proto3" json:"new_display_user_id,omitempty"` + Reason string `protobuf:"bytes,4,opt,name=reason,proto3" json:"reason,omitempty"` + OperatorUserId int64 `protobuf:"varint,5,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ChangeDisplayUserIDRequest) Reset() { *x = ChangeDisplayUserIDRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[31] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ChangeDisplayUserIDRequest) String() string { @@ -2269,7 +2174,7 @@ func (*ChangeDisplayUserIDRequest) ProtoMessage() {} func (x *ChangeDisplayUserIDRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[31] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2321,20 +2226,17 @@ func (x *ChangeDisplayUserIDRequest) GetOperatorUserId() int64 { // ChangeDisplayUserIDResponse 返回修改后的 active 绑定。 type ChangeDisplayUserIDResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Identity *UserIdentity `protobuf:"bytes,1,opt,name=identity,proto3" json:"identity,omitempty"` unknownFields protoimpl.UnknownFields - - Identity *UserIdentity `protobuf:"bytes,1,opt,name=identity,proto3" json:"identity,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ChangeDisplayUserIDResponse) Reset() { *x = ChangeDisplayUserIDResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[32] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ChangeDisplayUserIDResponse) String() string { @@ -2345,7 +2247,7 @@ func (*ChangeDisplayUserIDResponse) ProtoMessage() {} func (x *ChangeDisplayUserIDResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[32] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2369,24 +2271,21 @@ func (x *ChangeDisplayUserIDResponse) GetIdentity() *UserIdentity { // ApplyPrettyDisplayUserIDRequest 申请临时靓号并覆盖当前展示号。 type ApplyPrettyDisplayUserIDRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - PrettyDisplayUserId string `protobuf:"bytes,3,opt,name=pretty_display_user_id,json=prettyDisplayUserId,proto3" json:"pretty_display_user_id,omitempty"` - LeaseDurationMs int64 `protobuf:"varint,4,opt,name=lease_duration_ms,json=leaseDurationMs,proto3" json:"lease_duration_ms,omitempty"` - PaymentReceiptId string `protobuf:"bytes,5,opt,name=payment_receipt_id,json=paymentReceiptId,proto3" json:"payment_receipt_id,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + PrettyDisplayUserId string `protobuf:"bytes,3,opt,name=pretty_display_user_id,json=prettyDisplayUserId,proto3" json:"pretty_display_user_id,omitempty"` + LeaseDurationMs int64 `protobuf:"varint,4,opt,name=lease_duration_ms,json=leaseDurationMs,proto3" json:"lease_duration_ms,omitempty"` + PaymentReceiptId string `protobuf:"bytes,5,opt,name=payment_receipt_id,json=paymentReceiptId,proto3" json:"payment_receipt_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ApplyPrettyDisplayUserIDRequest) Reset() { *x = ApplyPrettyDisplayUserIDRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[33] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ApplyPrettyDisplayUserIDRequest) String() string { @@ -2397,7 +2296,7 @@ func (*ApplyPrettyDisplayUserIDRequest) ProtoMessage() {} func (x *ApplyPrettyDisplayUserIDRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[33] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2449,21 +2348,18 @@ func (x *ApplyPrettyDisplayUserIDRequest) GetPaymentReceiptId() string { // ApplyPrettyDisplayUserIDResponse 返回申请成功后的当前展示号和租约。 type ApplyPrettyDisplayUserIDResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Identity *UserIdentity `protobuf:"bytes,1,opt,name=identity,proto3" json:"identity,omitempty"` + LeaseId string `protobuf:"bytes,2,opt,name=lease_id,json=leaseId,proto3" json:"lease_id,omitempty"` unknownFields protoimpl.UnknownFields - - Identity *UserIdentity `protobuf:"bytes,1,opt,name=identity,proto3" json:"identity,omitempty"` - LeaseId string `protobuf:"bytes,2,opt,name=lease_id,json=leaseId,proto3" json:"lease_id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ApplyPrettyDisplayUserIDResponse) Reset() { *x = ApplyPrettyDisplayUserIDResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[34] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ApplyPrettyDisplayUserIDResponse) String() string { @@ -2474,7 +2370,7 @@ func (*ApplyPrettyDisplayUserIDResponse) ProtoMessage() {} func (x *ApplyPrettyDisplayUserIDResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[34] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2505,22 +2401,19 @@ func (x *ApplyPrettyDisplayUserIDResponse) GetLeaseId() string { // ExpirePrettyDisplayUserIDRequest 主动触发靓号过期恢复。 type ExpirePrettyDisplayUserIDRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + LeaseId string `protobuf:"bytes,3,opt,name=lease_id,json=leaseId,proto3" json:"lease_id,omitempty"` unknownFields protoimpl.UnknownFields - - Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` - UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` - LeaseId string `protobuf:"bytes,3,opt,name=lease_id,json=leaseId,proto3" json:"lease_id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ExpirePrettyDisplayUserIDRequest) Reset() { *x = ExpirePrettyDisplayUserIDRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[35] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ExpirePrettyDisplayUserIDRequest) String() string { @@ -2531,7 +2424,7 @@ func (*ExpirePrettyDisplayUserIDRequest) ProtoMessage() {} func (x *ExpirePrettyDisplayUserIDRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[35] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2569,20 +2462,17 @@ func (x *ExpirePrettyDisplayUserIDRequest) GetLeaseId() string { // ExpirePrettyDisplayUserIDResponse 返回恢复后的默认短号身份。 type ExpirePrettyDisplayUserIDResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Identity *UserIdentity `protobuf:"bytes,1,opt,name=identity,proto3" json:"identity,omitempty"` unknownFields protoimpl.UnknownFields - - Identity *UserIdentity `protobuf:"bytes,1,opt,name=identity,proto3" json:"identity,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ExpirePrettyDisplayUserIDResponse) Reset() { *x = ExpirePrettyDisplayUserIDResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_user_v1_user_proto_msgTypes[36] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_user_v1_user_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ExpirePrettyDisplayUserIDResponse) String() string { @@ -2593,7 +2483,7 @@ func (*ExpirePrettyDisplayUserIDResponse) ProtoMessage() {} func (x *ExpirePrettyDisplayUserIDResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_user_v1_user_proto_msgTypes[36] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2617,531 +2507,244 @@ func (x *ExpirePrettyDisplayUserIDResponse) GetIdentity() *UserIdentity { var File_api_proto_user_v1_user_proto protoreflect.FileDescriptor -var file_api_proto_user_v1_user_proto_rawDesc = []byte{ - 0x0a, 0x1c, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x75, 0x73, 0x65, 0x72, - 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x22, 0x87, 0x02, - 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, - 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, - 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x61, - 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x0f, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, - 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x67, - 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x0a, - 0x73, 0x65, 0x6e, 0x74, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x08, 0x73, 0x65, 0x6e, 0x74, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x5f, 0x69, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x49, 0x70, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x61, 0x67, 0x65, - 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x73, 0x65, 0x72, 0x41, 0x67, - 0x65, 0x6e, 0x74, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x62, - 0x79, 0x5f, 0x69, 0x70, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x72, 0x79, 0x42, 0x79, 0x49, 0x70, 0x22, 0xd6, 0x05, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, - 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x31, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x22, 0x0a, 0x0d, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, - 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, - 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x41, 0x74, 0x4d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x17, - 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x64, - 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x2f, 0x0a, 0x14, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x11, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x3f, 0x0a, 0x1d, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, - 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, - 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x64, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, - 0x73, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x16, 0x0a, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x72, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x0c, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x62, - 0x69, 0x72, 0x74, 0x68, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x62, 0x69, 0x72, 0x74, - 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x18, 0x0e, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x0a, - 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x10, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, - 0x30, 0x0a, 0x14, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x12, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, - 0x0a, 0x0b, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x13, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x14, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, - 0x22, 0x59, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x3a, 0x0a, 0x0f, 0x47, - 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, - 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, - 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x61, 0x0a, 0x14, 0x42, 0x61, 0x74, 0x63, 0x68, - 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, - 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x03, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x73, 0x22, 0xad, 0x01, 0x0a, 0x15, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x1a, 0x4d, 0x0a, 0x0a, 0x55, - 0x73, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xde, 0x01, 0x0a, 0x18, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, - 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x1f, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, - 0x01, 0x12, 0x1b, 0x0a, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x01, 0x52, 0x06, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x88, 0x01, 0x01, 0x12, 0x19, - 0x0a, 0x05, 0x62, 0x69, 0x72, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, - 0x05, 0x62, 0x69, 0x72, 0x74, 0x68, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x61, 0x76, 0x61, 0x74, 0x61, - 0x72, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x62, 0x69, 0x72, 0x74, 0x68, 0x22, 0x44, 0x0a, 0x19, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, - 0x72, 0x22, 0x7d, 0x0a, 0x18, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, - 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, - 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, - 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, - 0x22, 0x7e, 0x0a, 0x19, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, - 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, - 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x38, 0x0a, 0x19, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x61, 0x74, - 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x6e, 0x65, 0x78, 0x74, 0x43, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, - 0x22, 0x9f, 0x02, 0x0a, 0x07, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x1d, 0x0a, 0x0a, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, - 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, - 0x65, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x64, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x12, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, - 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, - 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, - 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x4d, 0x73, 0x22, 0xf7, 0x01, 0x0a, 0x06, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, - 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, - 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x69, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, - 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x87, 0x02, 0x0a, - 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x72, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, - 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x28, 0x0a, 0x10, - 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x5e, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, - 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x16, - 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x4d, 0x0a, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x34, 0x0a, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0x83, 0x02, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, - 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, - 0x0a, 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x49, 0x64, 0x12, 0x21, 0x0a, - 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x4e, 0x61, 0x6d, 0x65, - 0x12, 0x30, 0x0a, 0x14, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, - 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, - 0x72, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x90, 0x01, 0x0a, 0x15, - 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x79, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, - 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x43, - 0x0a, 0x0f, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x30, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x72, 0x79, 0x22, 0xe1, 0x01, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, - 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1f, 0x0a, 0x0b, 0x72, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x1d, - 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x28, 0x0a, - 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, - 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x5c, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x16, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x46, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x07, - 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x5f, 0x0a, - 0x10, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0xe0, - 0x01, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, - 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, - 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x43, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x6f, 0x72, 0x74, - 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x6f, - 0x72, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, - 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x22, 0xb4, 0x01, 0x0a, 0x1d, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x52, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, - 0x65, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, - 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x28, - 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x8d, 0x01, 0x0a, 0x14, 0x44, 0x69, 0x73, - 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, - 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x28, - 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x3f, 0x0a, 0x0e, 0x52, 0x65, 0x67, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x72, 0x65, - 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, - 0x6e, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x22, 0x90, 0x02, 0x0a, 0x0c, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x35, 0x0a, 0x17, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x64, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x44, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2f, 0x0a, 0x14, 0x64, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x5f, 0x6b, 0x69, - 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, - 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x3f, 0x0a, 0x1d, 0x64, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x5f, 0x65, - 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x18, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x41, 0x74, 0x4d, 0x73, 0x22, 0x61, 0x0a, 0x16, - 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, - 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, - 0x52, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x69, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x22, 0x75, 0x0a, 0x1b, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x44, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x64, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x57, 0x0a, 0x1c, 0x52, 0x65, - 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, - 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x69, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x22, 0xd6, 0x01, 0x0a, 0x1a, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x44, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, - 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x13, 0x6e, - 0x65, 0x77, 0x5f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x6e, 0x65, 0x77, 0x44, 0x69, 0x73, - 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, - 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, - 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x75, - 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x56, 0x0a, 0x1b, - 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x69, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x69, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x22, 0xf9, 0x01, 0x0a, 0x1f, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, - 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, - 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x33, 0x0a, 0x16, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x5f, 0x64, 0x69, 0x73, 0x70, - 0x6c, 0x61, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x13, 0x70, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, - 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0f, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x4d, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, - 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x49, 0x64, - 0x22, 0x76, 0x0a, 0x20, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x52, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x19, 0x0a, - 0x08, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x49, 0x64, 0x22, 0x86, 0x01, 0x0a, 0x20, 0x45, 0x78, 0x70, - 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, - 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, - 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, - 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x49, - 0x64, 0x22, 0x5c, 0x0a, 0x21, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, - 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2a, - 0x73, 0x0a, 0x0a, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, - 0x17, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, - 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x55, 0x53, - 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, - 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, - 0x53, 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x02, 0x12, 0x16, 0x0a, 0x12, - 0x55, 0x53, 0x45, 0x52, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x42, 0x41, 0x4e, 0x4e, - 0x45, 0x44, 0x10, 0x03, 0x32, 0x83, 0x03, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x48, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, - 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, - 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, - 0x0a, 0x0d, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x12, - 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x66, 0x0a, 0x11, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x12, - 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, - 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x66, 0x0a, 0x11, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x55, 0x73, 0x65, 0x72, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x27, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, - 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x55, 0x73, - 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xf5, 0x02, 0x0a, 0x13, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x12, 0x54, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, - 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5a, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, - 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0e, 0x44, 0x69, - 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x24, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, - 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x32, 0x99, 0x04, 0x0a, 0x12, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x6d, - 0x69, 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x51, 0x0a, 0x0c, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0b, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x21, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, - 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, - 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x51, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, - 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x65, 0x0a, 0x16, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x52, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x12, 0x2c, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, - 0x6c, 0x61, 0x63, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x72, - 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0d, 0x44, 0x69, 0x73, - 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x2e, 0x68, 0x79, 0x61, - 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x61, 0x62, - 0x6c, 0x65, 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xd3, - 0x04, 0x0a, 0x13, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x25, 0x2e, 0x68, 0x79, 0x61, 0x70, - 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x26, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x6f, - 0x6c, 0x76, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, - 0x12, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, - 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x68, - 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, - 0x6f, 0x6c, 0x76, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6c, 0x0a, 0x13, 0x43, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, - 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, - 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, - 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7b, 0x0a, 0x18, 0x41, 0x70, 0x70, 0x6c, 0x79, - 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, - 0x72, 0x49, 0x44, 0x12, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7e, 0x0a, 0x19, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, - 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x44, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, - 0x76, 0x31, 0x2e, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x50, 0x72, 0x65, 0x74, 0x74, 0x79, 0x44, - 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x20, 0x5a, 0x1e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2f, 0x61, 0x70, - 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x3b, - 0x75, 0x73, 0x65, 0x72, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_api_proto_user_v1_user_proto_rawDesc = "" + + "\n" + + "\x1capi/proto/user/v1/user.proto\x12\rhyapp.user.v1\"\x87\x02\n" + + "\vRequestMeta\x12\x1d\n" + + "\n" + + "request_id\x18\x01 \x01(\tR\trequestId\x12\x16\n" + + "\x06caller\x18\x02 \x01(\tR\x06caller\x12&\n" + + "\x0fgateway_node_id\x18\x03 \x01(\tR\rgatewayNodeId\x12\x1c\n" + + "\n" + + "sent_at_ms\x18\x04 \x01(\x03R\bsentAtMs\x12\x1b\n" + + "\tdevice_id\x18\x05 \x01(\tR\bdeviceId\x12\x1b\n" + + "\tclient_ip\x18\x06 \x01(\tR\bclientIp\x12\x1d\n" + + "\n" + + "user_agent\x18\a \x01(\tR\tuserAgent\x12\"\n" + + "\rcountry_by_ip\x18\b \x01(\tR\vcountryByIp\"\xd6\x05\n" + + "\x04User\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x03R\x06userId\x121\n" + + "\x06status\x18\x02 \x01(\x0e2\x19.hyapp.user.v1.UserStatusR\x06status\x12\"\n" + + "\rcreated_at_ms\x18\x03 \x01(\x03R\vcreatedAtMs\x12\"\n" + + "\rupdated_at_ms\x18\x04 \x01(\x03R\vupdatedAtMs\x12&\n" + + "\x0fdisplay_user_id\x18\x05 \x01(\tR\rdisplayUserId\x125\n" + + "\x17default_display_user_id\x18\x06 \x01(\tR\x14defaultDisplayUserId\x12/\n" + + "\x14display_user_id_kind\x18\a \x01(\tR\x11displayUserIdKind\x12?\n" + + "\x1ddisplay_user_id_expires_at_ms\x18\b \x01(\x03R\x18displayUserIdExpiresAtMs\x12\x1a\n" + + "\busername\x18\t \x01(\tR\busername\x12\x16\n" + + "\x06gender\x18\n" + + " \x01(\tR\x06gender\x12\x18\n" + + "\acountry\x18\v \x01(\tR\acountry\x12\x16\n" + + "\x06avatar\x18\f \x01(\tR\x06avatar\x12\x14\n" + + "\x05birth\x18\r \x01(\tR\x05birth\x12\x1a\n" + + "\blanguage\x18\x0e \x01(\tR\blanguage\x12\x1d\n" + + "\n" + + "country_id\x18\x0f \x01(\x03R\tcountryId\x12!\n" + + "\fcountry_name\x18\x10 \x01(\tR\vcountryName\x120\n" + + "\x14country_display_name\x18\x11 \x01(\tR\x12countryDisplayName\x12\x1b\n" + + "\tregion_id\x18\x12 \x01(\x03R\bregionId\x12\x1f\n" + + "\vregion_code\x18\x13 \x01(\tR\n" + + "regionCode\x12\x1f\n" + + "\vregion_name\x18\x14 \x01(\tR\n" + + "regionName\"Y\n" + + "\x0eGetUserRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\":\n" + + "\x0fGetUserResponse\x12'\n" + + "\x04user\x18\x01 \x01(\v2\x13.hyapp.user.v1.UserR\x04user\"a\n" + + "\x14BatchGetUsersRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x19\n" + + "\buser_ids\x18\x02 \x03(\x03R\auserIds\"\xad\x01\n" + + "\x15BatchGetUsersResponse\x12E\n" + + "\x05users\x18\x01 \x03(\v2/.hyapp.user.v1.BatchGetUsersResponse.UsersEntryR\x05users\x1aM\n" + + "\n" + + "UsersEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\x03R\x03key\x12)\n" + + "\x05value\x18\x02 \x01(\v2\x13.hyapp.user.v1.UserR\x05value:\x028\x01\"\xde\x01\n" + + "\x18UpdateUserProfileRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x1f\n" + + "\busername\x18\x03 \x01(\tH\x00R\busername\x88\x01\x01\x12\x1b\n" + + "\x06avatar\x18\x04 \x01(\tH\x01R\x06avatar\x88\x01\x01\x12\x19\n" + + "\x05birth\x18\x05 \x01(\tH\x02R\x05birth\x88\x01\x01B\v\n" + + "\t_usernameB\t\n" + + "\a_avatarB\b\n" + + "\x06_birth\"D\n" + + "\x19UpdateUserProfileResponse\x12'\n" + + "\x04user\x18\x01 \x01(\v2\x13.hyapp.user.v1.UserR\x04user\"}\n" + + "\x18ChangeUserCountryRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x18\n" + + "\acountry\x18\x03 \x01(\tR\acountry\"~\n" + + "\x19ChangeUserCountryResponse\x12'\n" + + "\x04user\x18\x01 \x01(\v2\x13.hyapp.user.v1.UserR\x04user\x128\n" + + "\x19next_change_allowed_at_ms\x18\x02 \x01(\x03R\x15nextChangeAllowedAtMs\"\x9f\x02\n" + + "\aCountry\x12\x1d\n" + + "\n" + + "country_id\x18\x01 \x01(\x03R\tcountryId\x12!\n" + + "\fcountry_name\x18\x02 \x01(\tR\vcountryName\x12!\n" + + "\fcountry_code\x18\x03 \x01(\tR\vcountryCode\x120\n" + + "\x14country_display_name\x18\x04 \x01(\tR\x12countryDisplayName\x12\x16\n" + + "\x06status\x18\x05 \x01(\tR\x06status\x12\x1d\n" + + "\n" + + "sort_order\x18\x06 \x01(\x05R\tsortOrder\x12\"\n" + + "\rcreated_at_ms\x18\a \x01(\x03R\vcreatedAtMs\x12\"\n" + + "\rupdated_at_ms\x18\b \x01(\x03R\vupdatedAtMs\"\xf7\x01\n" + + "\x06Region\x12\x1b\n" + + "\tregion_id\x18\x01 \x01(\x03R\bregionId\x12\x1f\n" + + "\vregion_code\x18\x02 \x01(\tR\n" + + "regionCode\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12\x16\n" + + "\x06status\x18\x04 \x01(\tR\x06status\x12\x1c\n" + + "\tcountries\x18\x05 \x03(\tR\tcountries\x12\x1d\n" + + "\n" + + "sort_order\x18\x06 \x01(\x05R\tsortOrder\x12\"\n" + + "\rcreated_at_ms\x18\a \x01(\x03R\vcreatedAtMs\x12\"\n" + + "\rupdated_at_ms\x18\b \x01(\x03R\vupdatedAtMs\"\x87\x02\n" + + "\x14CreateCountryRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12!\n" + + "\fcountry_name\x18\x02 \x01(\tR\vcountryName\x12!\n" + + "\fcountry_code\x18\x03 \x01(\tR\vcountryCode\x120\n" + + "\x14country_display_name\x18\x04 \x01(\tR\x12countryDisplayName\x12\x1d\n" + + "\n" + + "sort_order\x18\x05 \x01(\x05R\tsortOrder\x12(\n" + + "\x10operator_user_id\x18\x06 \x01(\x03R\x0eoperatorUserId\"^\n" + + "\x14ListCountriesRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x16\n" + + "\x06status\x18\x02 \x01(\tR\x06status\"M\n" + + "\x15ListCountriesResponse\x124\n" + + "\tcountries\x18\x01 \x03(\v2\x16.hyapp.user.v1.CountryR\tcountries\"\x83\x02\n" + + "\x14UpdateCountryRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x1d\n" + + "\n" + + "country_id\x18\x02 \x01(\x03R\tcountryId\x12!\n" + + "\fcountry_name\x18\x03 \x01(\tR\vcountryName\x120\n" + + "\x14country_display_name\x18\x04 \x01(\tR\x12countryDisplayName\x12\x1d\n" + + "\n" + + "sort_order\x18\x05 \x01(\x05R\tsortOrder\x12(\n" + + "\x10operator_user_id\x18\x06 \x01(\x03R\x0eoperatorUserId\"\x90\x01\n" + + "\x15DisableCountryRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x1d\n" + + "\n" + + "country_id\x18\x02 \x01(\x03R\tcountryId\x12(\n" + + "\x10operator_user_id\x18\x03 \x01(\x03R\x0eoperatorUserId\"C\n" + + "\x0fCountryResponse\x120\n" + + "\acountry\x18\x01 \x01(\v2\x16.hyapp.user.v1.CountryR\acountry\"\xe1\x01\n" + + "\x13CreateRegionRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x1f\n" + + "\vregion_code\x18\x02 \x01(\tR\n" + + "regionCode\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12\x1c\n" + + "\tcountries\x18\x04 \x03(\tR\tcountries\x12\x1d\n" + + "\n" + + "sort_order\x18\x05 \x01(\x05R\tsortOrder\x12(\n" + + "\x10operator_user_id\x18\x06 \x01(\x03R\x0eoperatorUserId\"\\\n" + + "\x12ListRegionsRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x16\n" + + "\x06status\x18\x02 \x01(\tR\x06status\"F\n" + + "\x13ListRegionsResponse\x12/\n" + + "\aregions\x18\x01 \x03(\v2\x15.hyapp.user.v1.RegionR\aregions\"_\n" + + "\x10GetRegionRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x1b\n" + + "\tregion_id\x18\x02 \x01(\x03R\bregionId\"\xe0\x01\n" + + "\x13UpdateRegionRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x1b\n" + + "\tregion_id\x18\x02 \x01(\x03R\bregionId\x12\x1f\n" + + "\vregion_code\x18\x03 \x01(\tR\n" + + "regionCode\x12\x12\n" + + "\x04name\x18\x04 \x01(\tR\x04name\x12\x1d\n" + + "\n" + + "sort_order\x18\x05 \x01(\x05R\tsortOrder\x12(\n" + + "\x10operator_user_id\x18\x06 \x01(\x03R\x0eoperatorUserId\"\xb4\x01\n" + + "\x1dReplaceRegionCountriesRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x1b\n" + + "\tregion_id\x18\x02 \x01(\x03R\bregionId\x12\x1c\n" + + "\tcountries\x18\x03 \x03(\tR\tcountries\x12(\n" + + "\x10operator_user_id\x18\x04 \x01(\x03R\x0eoperatorUserId\"\x8d\x01\n" + + "\x14DisableRegionRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x1b\n" + + "\tregion_id\x18\x02 \x01(\x03R\bregionId\x12(\n" + + "\x10operator_user_id\x18\x03 \x01(\x03R\x0eoperatorUserId\"?\n" + + "\x0eRegionResponse\x12-\n" + + "\x06region\x18\x01 \x01(\v2\x15.hyapp.user.v1.RegionR\x06region\"\x90\x02\n" + + "\fUserIdentity\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12&\n" + + "\x0fdisplay_user_id\x18\x02 \x01(\tR\rdisplayUserId\x12\x16\n" + + "\x06status\x18\x03 \x01(\tR\x06status\x125\n" + + "\x17default_display_user_id\x18\x04 \x01(\tR\x14defaultDisplayUserId\x12/\n" + + "\x14display_user_id_kind\x18\x05 \x01(\tR\x11displayUserIdKind\x12?\n" + + "\x1ddisplay_user_id_expires_at_ms\x18\x06 \x01(\x03R\x18displayUserIdExpiresAtMs\"a\n" + + "\x16GetUserIdentityRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\"R\n" + + "\x17GetUserIdentityResponse\x127\n" + + "\bidentity\x18\x01 \x01(\v2\x1b.hyapp.user.v1.UserIdentityR\bidentity\"u\n" + + "\x1bResolveDisplayUserIDRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12&\n" + + "\x0fdisplay_user_id\x18\x02 \x01(\tR\rdisplayUserId\"W\n" + + "\x1cResolveDisplayUserIDResponse\x127\n" + + "\bidentity\x18\x01 \x01(\v2\x1b.hyapp.user.v1.UserIdentityR\bidentity\"\xd6\x01\n" + + "\x1aChangeDisplayUserIDRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12-\n" + + "\x13new_display_user_id\x18\x03 \x01(\tR\x10newDisplayUserId\x12\x16\n" + + "\x06reason\x18\x04 \x01(\tR\x06reason\x12(\n" + + "\x10operator_user_id\x18\x05 \x01(\x03R\x0eoperatorUserId\"V\n" + + "\x1bChangeDisplayUserIDResponse\x127\n" + + "\bidentity\x18\x01 \x01(\v2\x1b.hyapp.user.v1.UserIdentityR\bidentity\"\xf9\x01\n" + + "\x1fApplyPrettyDisplayUserIDRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x123\n" + + "\x16pretty_display_user_id\x18\x03 \x01(\tR\x13prettyDisplayUserId\x12*\n" + + "\x11lease_duration_ms\x18\x04 \x01(\x03R\x0fleaseDurationMs\x12,\n" + + "\x12payment_receipt_id\x18\x05 \x01(\tR\x10paymentReceiptId\"v\n" + + " ApplyPrettyDisplayUserIDResponse\x127\n" + + "\bidentity\x18\x01 \x01(\v2\x1b.hyapp.user.v1.UserIdentityR\bidentity\x12\x19\n" + + "\blease_id\x18\x02 \x01(\tR\aleaseId\"\x86\x01\n" + + " ExpirePrettyDisplayUserIDRequest\x12.\n" + + "\x04meta\x18\x01 \x01(\v2\x1a.hyapp.user.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x19\n" + + "\blease_id\x18\x03 \x01(\tR\aleaseId\"\\\n" + + "!ExpirePrettyDisplayUserIDResponse\x127\n" + + "\bidentity\x18\x01 \x01(\v2\x1b.hyapp.user.v1.UserIdentityR\bidentity*s\n" + + "\n" + + "UserStatus\x12\x1b\n" + + "\x17USER_STATUS_UNSPECIFIED\x10\x00\x12\x16\n" + + "\x12USER_STATUS_ACTIVE\x10\x01\x12\x18\n" + + "\x14USER_STATUS_DISABLED\x10\x02\x12\x16\n" + + "\x12USER_STATUS_BANNED\x10\x032\x83\x03\n" + + "\vUserService\x12H\n" + + "\aGetUser\x12\x1d.hyapp.user.v1.GetUserRequest\x1a\x1e.hyapp.user.v1.GetUserResponse\x12Z\n" + + "\rBatchGetUsers\x12#.hyapp.user.v1.BatchGetUsersRequest\x1a$.hyapp.user.v1.BatchGetUsersResponse\x12f\n" + + "\x11UpdateUserProfile\x12'.hyapp.user.v1.UpdateUserProfileRequest\x1a(.hyapp.user.v1.UpdateUserProfileResponse\x12f\n" + + "\x11ChangeUserCountry\x12'.hyapp.user.v1.ChangeUserCountryRequest\x1a(.hyapp.user.v1.ChangeUserCountryResponse2\xf5\x02\n" + + "\x13CountryAdminService\x12T\n" + + "\rCreateCountry\x12#.hyapp.user.v1.CreateCountryRequest\x1a\x1e.hyapp.user.v1.CountryResponse\x12Z\n" + + "\rListCountries\x12#.hyapp.user.v1.ListCountriesRequest\x1a$.hyapp.user.v1.ListCountriesResponse\x12T\n" + + "\rUpdateCountry\x12#.hyapp.user.v1.UpdateCountryRequest\x1a\x1e.hyapp.user.v1.CountryResponse\x12V\n" + + "\x0eDisableCountry\x12$.hyapp.user.v1.DisableCountryRequest\x1a\x1e.hyapp.user.v1.CountryResponse2\x99\x04\n" + + "\x12RegionAdminService\x12Q\n" + + "\fCreateRegion\x12\".hyapp.user.v1.CreateRegionRequest\x1a\x1d.hyapp.user.v1.RegionResponse\x12T\n" + + "\vListRegions\x12!.hyapp.user.v1.ListRegionsRequest\x1a\".hyapp.user.v1.ListRegionsResponse\x12K\n" + + "\tGetRegion\x12\x1f.hyapp.user.v1.GetRegionRequest\x1a\x1d.hyapp.user.v1.RegionResponse\x12Q\n" + + "\fUpdateRegion\x12\".hyapp.user.v1.UpdateRegionRequest\x1a\x1d.hyapp.user.v1.RegionResponse\x12e\n" + + "\x16ReplaceRegionCountries\x12,.hyapp.user.v1.ReplaceRegionCountriesRequest\x1a\x1d.hyapp.user.v1.RegionResponse\x12S\n" + + "\rDisableRegion\x12#.hyapp.user.v1.DisableRegionRequest\x1a\x1d.hyapp.user.v1.RegionResponse2\xd3\x04\n" + + "\x13UserIdentityService\x12`\n" + + "\x0fGetUserIdentity\x12%.hyapp.user.v1.GetUserIdentityRequest\x1a&.hyapp.user.v1.GetUserIdentityResponse\x12o\n" + + "\x14ResolveDisplayUserID\x12*.hyapp.user.v1.ResolveDisplayUserIDRequest\x1a+.hyapp.user.v1.ResolveDisplayUserIDResponse\x12l\n" + + "\x13ChangeDisplayUserID\x12).hyapp.user.v1.ChangeDisplayUserIDRequest\x1a*.hyapp.user.v1.ChangeDisplayUserIDResponse\x12{\n" + + "\x18ApplyPrettyDisplayUserID\x12..hyapp.user.v1.ApplyPrettyDisplayUserIDRequest\x1a/.hyapp.user.v1.ApplyPrettyDisplayUserIDResponse\x12~\n" + + "\x19ExpirePrettyDisplayUserID\x12/.hyapp.user.v1.ExpirePrettyDisplayUserIDRequest\x1a0.hyapp.user.v1.ExpirePrettyDisplayUserIDResponseB Z\x1ehyapp/api/proto/user/v1;userv1b\x06proto3" var ( file_api_proto_user_v1_user_proto_rawDescOnce sync.Once - file_api_proto_user_v1_user_proto_rawDescData = file_api_proto_user_v1_user_proto_rawDesc + file_api_proto_user_v1_user_proto_rawDescData []byte ) func file_api_proto_user_v1_user_proto_rawDescGZIP() []byte { file_api_proto_user_v1_user_proto_rawDescOnce.Do(func() { - file_api_proto_user_v1_user_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_user_v1_user_proto_rawDescData) + file_api_proto_user_v1_user_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_api_proto_user_v1_user_proto_rawDesc), len(file_api_proto_user_v1_user_proto_rawDesc))) }) return file_api_proto_user_v1_user_proto_rawDescData } @@ -3274,458 +2877,12 @@ func file_api_proto_user_v1_user_proto_init() { if File_api_proto_user_v1_user_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_api_proto_user_v1_user_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*RequestMeta); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*User); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[2].Exporter = func(v any, i int) any { - switch v := v.(*GetUserRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[3].Exporter = func(v any, i int) any { - switch v := v.(*GetUserResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[4].Exporter = func(v any, i int) any { - switch v := v.(*BatchGetUsersRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[5].Exporter = func(v any, i int) any { - switch v := v.(*BatchGetUsersResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[6].Exporter = func(v any, i int) any { - switch v := v.(*UpdateUserProfileRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[7].Exporter = func(v any, i int) any { - switch v := v.(*UpdateUserProfileResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[8].Exporter = func(v any, i int) any { - switch v := v.(*ChangeUserCountryRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[9].Exporter = func(v any, i int) any { - switch v := v.(*ChangeUserCountryResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[10].Exporter = func(v any, i int) any { - switch v := v.(*Country); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[11].Exporter = func(v any, i int) any { - switch v := v.(*Region); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[12].Exporter = func(v any, i int) any { - switch v := v.(*CreateCountryRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[13].Exporter = func(v any, i int) any { - switch v := v.(*ListCountriesRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[14].Exporter = func(v any, i int) any { - switch v := v.(*ListCountriesResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[15].Exporter = func(v any, i int) any { - switch v := v.(*UpdateCountryRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[16].Exporter = func(v any, i int) any { - switch v := v.(*DisableCountryRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[17].Exporter = func(v any, i int) any { - switch v := v.(*CountryResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[18].Exporter = func(v any, i int) any { - switch v := v.(*CreateRegionRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[19].Exporter = func(v any, i int) any { - switch v := v.(*ListRegionsRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[20].Exporter = func(v any, i int) any { - switch v := v.(*ListRegionsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[21].Exporter = func(v any, i int) any { - switch v := v.(*GetRegionRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[22].Exporter = func(v any, i int) any { - switch v := v.(*UpdateRegionRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[23].Exporter = func(v any, i int) any { - switch v := v.(*ReplaceRegionCountriesRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[24].Exporter = func(v any, i int) any { - switch v := v.(*DisableRegionRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[25].Exporter = func(v any, i int) any { - switch v := v.(*RegionResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[26].Exporter = func(v any, i int) any { - switch v := v.(*UserIdentity); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[27].Exporter = func(v any, i int) any { - switch v := v.(*GetUserIdentityRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[28].Exporter = func(v any, i int) any { - switch v := v.(*GetUserIdentityResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[29].Exporter = func(v any, i int) any { - switch v := v.(*ResolveDisplayUserIDRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[30].Exporter = func(v any, i int) any { - switch v := v.(*ResolveDisplayUserIDResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[31].Exporter = func(v any, i int) any { - switch v := v.(*ChangeDisplayUserIDRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[32].Exporter = func(v any, i int) any { - switch v := v.(*ChangeDisplayUserIDResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[33].Exporter = func(v any, i int) any { - switch v := v.(*ApplyPrettyDisplayUserIDRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[34].Exporter = func(v any, i int) any { - switch v := v.(*ApplyPrettyDisplayUserIDResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[35].Exporter = func(v any, i int) any { - switch v := v.(*ExpirePrettyDisplayUserIDRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_user_v1_user_proto_msgTypes[36].Exporter = func(v any, i int) any { - switch v := v.(*ExpirePrettyDisplayUserIDResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } file_api_proto_user_v1_user_proto_msgTypes[6].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_api_proto_user_v1_user_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_proto_user_v1_user_proto_rawDesc), len(file_api_proto_user_v1_user_proto_rawDesc)), NumEnums: 1, NumMessages: 38, NumExtensions: 0, @@ -3737,7 +2894,6 @@ func file_api_proto_user_v1_user_proto_init() { MessageInfos: file_api_proto_user_v1_user_proto_msgTypes, }.Build() File_api_proto_user_v1_user_proto = out.File - file_api_proto_user_v1_user_proto_rawDesc = nil file_api_proto_user_v1_user_proto_goTypes = nil file_api_proto_user_v1_user_proto_depIdxs = nil } diff --git a/api/proto/user/v1/user_grpc.pb.go b/api/proto/user/v1/user_grpc.pb.go index 09b935d6..38d55260 100644 --- a/api/proto/user/v1/user_grpc.pb.go +++ b/api/proto/user/v1/user_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v5.29.2 +// - protoc v5.27.3 // source: api/proto/user/v1/user.proto package userv1 diff --git a/api/proto/wallet/v1/wallet.pb.go b/api/proto/wallet/v1/wallet.pb.go index 96443b61..d985b21f 100644 --- a/api/proto/wallet/v1/wallet.pb.go +++ b/api/proto/wallet/v1/wallet.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.2 -// protoc v5.29.2 +// protoc-gen-go v1.36.6 +// protoc v5.27.3 // source: api/proto/wallet/v1/wallet.proto package walletv1 @@ -11,6 +11,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" + unsafe "unsafe" ) const ( @@ -22,26 +23,24 @@ const ( // DebitGiftRequest 是 room-service 送礼同步扣费的最小账务输入。 type DebitGiftRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + CommandId string `protobuf:"bytes,1,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + RoomId string `protobuf:"bytes,2,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` + SenderUserId int64 `protobuf:"varint,3,opt,name=sender_user_id,json=senderUserId,proto3" json:"sender_user_id,omitempty"` + TargetUserId int64 `protobuf:"varint,4,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + GiftId string `protobuf:"bytes,5,opt,name=gift_id,json=giftId,proto3" json:"gift_id,omitempty"` + GiftCount int32 `protobuf:"varint,6,opt,name=gift_count,json=giftCount,proto3" json:"gift_count,omitempty"` + // price_version 为空时使用当前生效价格;非空时必须命中 active 价格版本。 + PriceVersion string `protobuf:"bytes,7,opt,name=price_version,json=priceVersion,proto3" json:"price_version,omitempty"` unknownFields protoimpl.UnknownFields - - CommandId string `protobuf:"bytes,1,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` - RoomId string `protobuf:"bytes,2,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` - SenderUserId int64 `protobuf:"varint,3,opt,name=sender_user_id,json=senderUserId,proto3" json:"sender_user_id,omitempty"` - TargetUserId int64 `protobuf:"varint,4,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` - GiftId string `protobuf:"bytes,5,opt,name=gift_id,json=giftId,proto3" json:"gift_id,omitempty"` - GiftCount int32 `protobuf:"varint,6,opt,name=gift_count,json=giftCount,proto3" json:"gift_count,omitempty"` - GiftUnitValue int64 `protobuf:"varint,7,opt,name=gift_unit_value,json=giftUnitValue,proto3" json:"gift_unit_value,omitempty"` + sizeCache protoimpl.SizeCache } func (x *DebitGiftRequest) Reset() { *x = DebitGiftRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_wallet_v1_wallet_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_wallet_v1_wallet_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DebitGiftRequest) String() string { @@ -52,7 +51,7 @@ func (*DebitGiftRequest) ProtoMessage() {} func (x *DebitGiftRequest) ProtoReflect() protoreflect.Message { mi := &file_api_proto_wallet_v1_wallet_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -109,31 +108,33 @@ func (x *DebitGiftRequest) GetGiftCount() int32 { return 0 } -func (x *DebitGiftRequest) GetGiftUnitValue() int64 { +func (x *DebitGiftRequest) GetPriceVersion() string { if x != nil { - return x.GiftUnitValue + return x.PriceVersion } - return 0 + return "" } -// DebitGiftResponse 返回扣费流水和账后余额。 +// DebitGiftResponse 返回扣费流水、服务端结算值和 sender COIN 账后余额。 type DebitGiftResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + BillingReceiptId string `protobuf:"bytes,1,opt,name=billing_receipt_id,json=billingReceiptId,proto3" json:"billing_receipt_id,omitempty"` + TransactionId string `protobuf:"bytes,2,opt,name=transaction_id,json=transactionId,proto3" json:"transaction_id,omitempty"` + CoinSpent int64 `protobuf:"varint,3,opt,name=coin_spent,json=coinSpent,proto3" json:"coin_spent,omitempty"` + GiftPointAdded int64 `protobuf:"varint,4,opt,name=gift_point_added,json=giftPointAdded,proto3" json:"gift_point_added,omitempty"` + HeatValue int64 `protobuf:"varint,5,opt,name=heat_value,json=heatValue,proto3" json:"heat_value,omitempty"` + PriceVersion string `protobuf:"bytes,6,opt,name=price_version,json=priceVersion,proto3" json:"price_version,omitempty"` + // balance_after 是 sender COIN available_amount 账后余额。 + BalanceAfter int64 `protobuf:"varint,7,opt,name=balance_after,json=balanceAfter,proto3" json:"balance_after,omitempty"` unknownFields protoimpl.UnknownFields - - BillingReceiptId string `protobuf:"bytes,1,opt,name=billing_receipt_id,json=billingReceiptId,proto3" json:"billing_receipt_id,omitempty"` - TotalGiftValue int64 `protobuf:"varint,2,opt,name=total_gift_value,json=totalGiftValue,proto3" json:"total_gift_value,omitempty"` - BalanceAfter int64 `protobuf:"varint,3,opt,name=balance_after,json=balanceAfter,proto3" json:"balance_after,omitempty"` + sizeCache protoimpl.SizeCache } func (x *DebitGiftResponse) Reset() { *x = DebitGiftResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_api_proto_wallet_v1_wallet_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_api_proto_wallet_v1_wallet_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DebitGiftResponse) String() string { @@ -144,7 +145,7 @@ func (*DebitGiftResponse) ProtoMessage() {} func (x *DebitGiftResponse) ProtoReflect() protoreflect.Message { mi := &file_api_proto_wallet_v1_wallet_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -166,13 +167,41 @@ func (x *DebitGiftResponse) GetBillingReceiptId() string { return "" } -func (x *DebitGiftResponse) GetTotalGiftValue() int64 { +func (x *DebitGiftResponse) GetTransactionId() string { if x != nil { - return x.TotalGiftValue + return x.TransactionId + } + return "" +} + +func (x *DebitGiftResponse) GetCoinSpent() int64 { + if x != nil { + return x.CoinSpent } return 0 } +func (x *DebitGiftResponse) GetGiftPointAdded() int64 { + if x != nil { + return x.GiftPointAdded + } + return 0 +} + +func (x *DebitGiftResponse) GetHeatValue() int64 { + if x != nil { + return x.HeatValue + } + return 0 +} + +func (x *DebitGiftResponse) GetPriceVersion() string { + if x != nil { + return x.PriceVersion + } + return "" +} + func (x *DebitGiftResponse) GetBalanceAfter() int64 { if x != nil { return x.BalanceAfter @@ -180,74 +209,418 @@ func (x *DebitGiftResponse) GetBalanceAfter() int64 { return 0 } +// AssetBalance 是用户某类资产的余额投影。 +type AssetBalance struct { + state protoimpl.MessageState `protogen:"open.v1"` + AssetType string `protobuf:"bytes,1,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + AvailableAmount int64 `protobuf:"varint,2,opt,name=available_amount,json=availableAmount,proto3" json:"available_amount,omitempty"` + FrozenAmount int64 `protobuf:"varint,3,opt,name=frozen_amount,json=frozenAmount,proto3" json:"frozen_amount,omitempty"` + Version int64 `protobuf:"varint,4,opt,name=version,proto3" json:"version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AssetBalance) Reset() { + *x = AssetBalance{} + mi := &file_api_proto_wallet_v1_wallet_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AssetBalance) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AssetBalance) ProtoMessage() {} + +func (x *AssetBalance) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_wallet_v1_wallet_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AssetBalance.ProtoReflect.Descriptor instead. +func (*AssetBalance) Descriptor() ([]byte, []int) { + return file_api_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{2} +} + +func (x *AssetBalance) GetAssetType() string { + if x != nil { + return x.AssetType + } + return "" +} + +func (x *AssetBalance) GetAvailableAmount() int64 { + if x != nil { + return x.AvailableAmount + } + return 0 +} + +func (x *AssetBalance) GetFrozenAmount() int64 { + if x != nil { + return x.FrozenAmount + } + return 0 +} + +func (x *AssetBalance) GetVersion() int64 { + if x != nil { + return x.Version + } + return 0 +} + +// GetBalancesRequest 查询用户多资产余额;内部调用方负责鉴权和用户范围控制。 +type GetBalancesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + AssetTypes []string `protobuf:"bytes,3,rep,name=asset_types,json=assetTypes,proto3" json:"asset_types,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetBalancesRequest) Reset() { + *x = GetBalancesRequest{} + mi := &file_api_proto_wallet_v1_wallet_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetBalancesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBalancesRequest) ProtoMessage() {} + +func (x *GetBalancesRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_wallet_v1_wallet_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetBalancesRequest.ProtoReflect.Descriptor instead. +func (*GetBalancesRequest) Descriptor() ([]byte, []int) { + return file_api_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{3} +} + +func (x *GetBalancesRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *GetBalancesRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *GetBalancesRequest) GetAssetTypes() []string { + if x != nil { + return x.AssetTypes + } + return nil +} + +type GetBalancesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Balances []*AssetBalance `protobuf:"bytes,1,rep,name=balances,proto3" json:"balances,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetBalancesResponse) Reset() { + *x = GetBalancesResponse{} + mi := &file_api_proto_wallet_v1_wallet_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetBalancesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBalancesResponse) ProtoMessage() {} + +func (x *GetBalancesResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_wallet_v1_wallet_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetBalancesResponse.ProtoReflect.Descriptor instead. +func (*GetBalancesResponse) Descriptor() ([]byte, []int) { + return file_api_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{4} +} + +func (x *GetBalancesResponse) GetBalances() []*AssetBalance { + if x != nil { + return x.Balances + } + return nil +} + +// AdminCreditAssetRequest 是后台手动入账/调账的最小审计命令。 +type AdminCreditAssetRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + CommandId string `protobuf:"bytes,1,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + TargetUserId int64 `protobuf:"varint,2,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + AssetType string `protobuf:"bytes,3,opt,name=asset_type,json=assetType,proto3" json:"asset_type,omitempty"` + Amount int64 `protobuf:"varint,4,opt,name=amount,proto3" json:"amount,omitempty"` + OperatorUserId int64 `protobuf:"varint,5,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + Reason string `protobuf:"bytes,6,opt,name=reason,proto3" json:"reason,omitempty"` + EvidenceRef string `protobuf:"bytes,7,opt,name=evidence_ref,json=evidenceRef,proto3" json:"evidence_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AdminCreditAssetRequest) Reset() { + *x = AdminCreditAssetRequest{} + mi := &file_api_proto_wallet_v1_wallet_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdminCreditAssetRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdminCreditAssetRequest) ProtoMessage() {} + +func (x *AdminCreditAssetRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_wallet_v1_wallet_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdminCreditAssetRequest.ProtoReflect.Descriptor instead. +func (*AdminCreditAssetRequest) Descriptor() ([]byte, []int) { + return file_api_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{5} +} + +func (x *AdminCreditAssetRequest) GetCommandId() string { + if x != nil { + return x.CommandId + } + return "" +} + +func (x *AdminCreditAssetRequest) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *AdminCreditAssetRequest) GetAssetType() string { + if x != nil { + return x.AssetType + } + return "" +} + +func (x *AdminCreditAssetRequest) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *AdminCreditAssetRequest) GetOperatorUserId() int64 { + if x != nil { + return x.OperatorUserId + } + return 0 +} + +func (x *AdminCreditAssetRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *AdminCreditAssetRequest) GetEvidenceRef() string { + if x != nil { + return x.EvidenceRef + } + return "" +} + +type AdminCreditAssetResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + TransactionId string `protobuf:"bytes,1,opt,name=transaction_id,json=transactionId,proto3" json:"transaction_id,omitempty"` + Balance *AssetBalance `protobuf:"bytes,2,opt,name=balance,proto3" json:"balance,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AdminCreditAssetResponse) Reset() { + *x = AdminCreditAssetResponse{} + mi := &file_api_proto_wallet_v1_wallet_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdminCreditAssetResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdminCreditAssetResponse) ProtoMessage() {} + +func (x *AdminCreditAssetResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_wallet_v1_wallet_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdminCreditAssetResponse.ProtoReflect.Descriptor instead. +func (*AdminCreditAssetResponse) Descriptor() ([]byte, []int) { + return file_api_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{6} +} + +func (x *AdminCreditAssetResponse) GetTransactionId() string { + if x != nil { + return x.TransactionId + } + return "" +} + +func (x *AdminCreditAssetResponse) GetBalance() *AssetBalance { + if x != nil { + return x.Balance + } + return nil +} + var File_api_proto_wallet_v1_wallet_proto protoreflect.FileDescriptor -var file_api_proto_wallet_v1_wallet_proto_rawDesc = []byte{ - 0x0a, 0x20, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x12, 0x0f, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x2e, 0x76, 0x31, 0x22, 0xf6, 0x01, 0x0a, 0x10, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, - 0x12, 0x24, 0x0a, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, - 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, - 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, - 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x75, 0x6e, 0x69, - 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x67, - 0x69, 0x66, 0x74, 0x55, 0x6e, 0x69, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x90, 0x01, 0x0a, - 0x11, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, - 0x62, 0x69, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x49, 0x64, - 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x47, 0x69, 0x66, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x61, - 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0c, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x32, - 0x63, 0x0a, 0x0d, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x52, 0x0a, 0x09, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x12, 0x21, 0x2e, - 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, 0x76, 0x31, 0x2e, - 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x22, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x44, 0x65, 0x62, 0x69, 0x74, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x24, 0x5a, 0x22, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2f, 0x61, 0x70, - 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2f, 0x76, - 0x31, 0x3b, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, -} +const file_api_proto_wallet_v1_wallet_proto_rawDesc = "" + + "\n" + + " api/proto/wallet/v1/wallet.proto\x12\x0fhyapp.wallet.v1\"\xf3\x01\n" + + "\x10DebitGiftRequest\x12\x1d\n" + + "\n" + + "command_id\x18\x01 \x01(\tR\tcommandId\x12\x17\n" + + "\aroom_id\x18\x02 \x01(\tR\x06roomId\x12$\n" + + "\x0esender_user_id\x18\x03 \x01(\x03R\fsenderUserId\x12$\n" + + "\x0etarget_user_id\x18\x04 \x01(\x03R\ftargetUserId\x12\x17\n" + + "\agift_id\x18\x05 \x01(\tR\x06giftId\x12\x1d\n" + + "\n" + + "gift_count\x18\x06 \x01(\x05R\tgiftCount\x12#\n" + + "\rprice_version\x18\a \x01(\tR\fpriceVersion\"\x9a\x02\n" + + "\x11DebitGiftResponse\x12,\n" + + "\x12billing_receipt_id\x18\x01 \x01(\tR\x10billingReceiptId\x12%\n" + + "\x0etransaction_id\x18\x02 \x01(\tR\rtransactionId\x12\x1d\n" + + "\n" + + "coin_spent\x18\x03 \x01(\x03R\tcoinSpent\x12(\n" + + "\x10gift_point_added\x18\x04 \x01(\x03R\x0egiftPointAdded\x12\x1d\n" + + "\n" + + "heat_value\x18\x05 \x01(\x03R\theatValue\x12#\n" + + "\rprice_version\x18\x06 \x01(\tR\fpriceVersion\x12#\n" + + "\rbalance_after\x18\a \x01(\x03R\fbalanceAfter\"\x97\x01\n" + + "\fAssetBalance\x12\x1d\n" + + "\n" + + "asset_type\x18\x01 \x01(\tR\tassetType\x12)\n" + + "\x10available_amount\x18\x02 \x01(\x03R\x0favailableAmount\x12#\n" + + "\rfrozen_amount\x18\x03 \x01(\x03R\ffrozenAmount\x12\x18\n" + + "\aversion\x18\x04 \x01(\x03R\aversion\"m\n" + + "\x12GetBalancesRequest\x12\x1d\n" + + "\n" + + "request_id\x18\x01 \x01(\tR\trequestId\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x1f\n" + + "\vasset_types\x18\x03 \x03(\tR\n" + + "assetTypes\"P\n" + + "\x13GetBalancesResponse\x129\n" + + "\bbalances\x18\x01 \x03(\v2\x1d.hyapp.wallet.v1.AssetBalanceR\bbalances\"\xfa\x01\n" + + "\x17AdminCreditAssetRequest\x12\x1d\n" + + "\n" + + "command_id\x18\x01 \x01(\tR\tcommandId\x12$\n" + + "\x0etarget_user_id\x18\x02 \x01(\x03R\ftargetUserId\x12\x1d\n" + + "\n" + + "asset_type\x18\x03 \x01(\tR\tassetType\x12\x16\n" + + "\x06amount\x18\x04 \x01(\x03R\x06amount\x12(\n" + + "\x10operator_user_id\x18\x05 \x01(\x03R\x0eoperatorUserId\x12\x16\n" + + "\x06reason\x18\x06 \x01(\tR\x06reason\x12!\n" + + "\fevidence_ref\x18\a \x01(\tR\vevidenceRef\"z\n" + + "\x18AdminCreditAssetResponse\x12%\n" + + "\x0etransaction_id\x18\x01 \x01(\tR\rtransactionId\x127\n" + + "\abalance\x18\x02 \x01(\v2\x1d.hyapp.wallet.v1.AssetBalanceR\abalance2\xa6\x02\n" + + "\rWalletService\x12R\n" + + "\tDebitGift\x12!.hyapp.wallet.v1.DebitGiftRequest\x1a\".hyapp.wallet.v1.DebitGiftResponse\x12X\n" + + "\vGetBalances\x12#.hyapp.wallet.v1.GetBalancesRequest\x1a$.hyapp.wallet.v1.GetBalancesResponse\x12g\n" + + "\x10AdminCreditAsset\x12(.hyapp.wallet.v1.AdminCreditAssetRequest\x1a).hyapp.wallet.v1.AdminCreditAssetResponseB$Z\"hyapp/api/proto/wallet/v1;walletv1b\x06proto3" var ( file_api_proto_wallet_v1_wallet_proto_rawDescOnce sync.Once - file_api_proto_wallet_v1_wallet_proto_rawDescData = file_api_proto_wallet_v1_wallet_proto_rawDesc + file_api_proto_wallet_v1_wallet_proto_rawDescData []byte ) func file_api_proto_wallet_v1_wallet_proto_rawDescGZIP() []byte { file_api_proto_wallet_v1_wallet_proto_rawDescOnce.Do(func() { - file_api_proto_wallet_v1_wallet_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_wallet_v1_wallet_proto_rawDescData) + file_api_proto_wallet_v1_wallet_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_api_proto_wallet_v1_wallet_proto_rawDesc), len(file_api_proto_wallet_v1_wallet_proto_rawDesc))) }) return file_api_proto_wallet_v1_wallet_proto_rawDescData } -var file_api_proto_wallet_v1_wallet_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_api_proto_wallet_v1_wallet_proto_msgTypes = make([]protoimpl.MessageInfo, 7) var file_api_proto_wallet_v1_wallet_proto_goTypes = []any{ - (*DebitGiftRequest)(nil), // 0: hyapp.wallet.v1.DebitGiftRequest - (*DebitGiftResponse)(nil), // 1: hyapp.wallet.v1.DebitGiftResponse + (*DebitGiftRequest)(nil), // 0: hyapp.wallet.v1.DebitGiftRequest + (*DebitGiftResponse)(nil), // 1: hyapp.wallet.v1.DebitGiftResponse + (*AssetBalance)(nil), // 2: hyapp.wallet.v1.AssetBalance + (*GetBalancesRequest)(nil), // 3: hyapp.wallet.v1.GetBalancesRequest + (*GetBalancesResponse)(nil), // 4: hyapp.wallet.v1.GetBalancesResponse + (*AdminCreditAssetRequest)(nil), // 5: hyapp.wallet.v1.AdminCreditAssetRequest + (*AdminCreditAssetResponse)(nil), // 6: hyapp.wallet.v1.AdminCreditAssetResponse } var file_api_proto_wallet_v1_wallet_proto_depIdxs = []int32{ - 0, // 0: hyapp.wallet.v1.WalletService.DebitGift:input_type -> hyapp.wallet.v1.DebitGiftRequest - 1, // 1: hyapp.wallet.v1.WalletService.DebitGift:output_type -> hyapp.wallet.v1.DebitGiftResponse - 1, // [1:2] is the sub-list for method output_type - 0, // [0:1] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 2, // 0: hyapp.wallet.v1.GetBalancesResponse.balances:type_name -> hyapp.wallet.v1.AssetBalance + 2, // 1: hyapp.wallet.v1.AdminCreditAssetResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance + 0, // 2: hyapp.wallet.v1.WalletService.DebitGift:input_type -> hyapp.wallet.v1.DebitGiftRequest + 3, // 3: hyapp.wallet.v1.WalletService.GetBalances:input_type -> hyapp.wallet.v1.GetBalancesRequest + 5, // 4: hyapp.wallet.v1.WalletService.AdminCreditAsset:input_type -> hyapp.wallet.v1.AdminCreditAssetRequest + 1, // 5: hyapp.wallet.v1.WalletService.DebitGift:output_type -> hyapp.wallet.v1.DebitGiftResponse + 4, // 6: hyapp.wallet.v1.WalletService.GetBalances:output_type -> hyapp.wallet.v1.GetBalancesResponse + 6, // 7: hyapp.wallet.v1.WalletService.AdminCreditAsset:output_type -> hyapp.wallet.v1.AdminCreditAssetResponse + 5, // [5:8] is the sub-list for method output_type + 2, // [2:5] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_api_proto_wallet_v1_wallet_proto_init() } @@ -255,39 +628,13 @@ func file_api_proto_wallet_v1_wallet_proto_init() { if File_api_proto_wallet_v1_wallet_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_api_proto_wallet_v1_wallet_proto_msgTypes[0].Exporter = func(v any, i int) any { - switch v := v.(*DebitGiftRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_api_proto_wallet_v1_wallet_proto_msgTypes[1].Exporter = func(v any, i int) any { - switch v := v.(*DebitGiftResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_api_proto_wallet_v1_wallet_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_proto_wallet_v1_wallet_proto_rawDesc), len(file_api_proto_wallet_v1_wallet_proto_rawDesc)), NumEnums: 0, - NumMessages: 2, + NumMessages: 7, NumExtensions: 0, NumServices: 1, }, @@ -296,7 +643,6 @@ func file_api_proto_wallet_v1_wallet_proto_init() { MessageInfos: file_api_proto_wallet_v1_wallet_proto_msgTypes, }.Build() File_api_proto_wallet_v1_wallet_proto = out.File - file_api_proto_wallet_v1_wallet_proto_rawDesc = nil file_api_proto_wallet_v1_wallet_proto_goTypes = nil file_api_proto_wallet_v1_wallet_proto_depIdxs = nil } diff --git a/api/proto/wallet/v1/wallet.proto b/api/proto/wallet/v1/wallet.proto index f7ec4f1f..58133cdd 100644 --- a/api/proto/wallet/v1/wallet.proto +++ b/api/proto/wallet/v1/wallet.proto @@ -12,18 +12,60 @@ message DebitGiftRequest { int64 target_user_id = 4; string gift_id = 5; int32 gift_count = 6; - int64 gift_unit_value = 7; + // price_version 为空时使用当前生效价格;非空时必须命中 active 价格版本。 + string price_version = 7; } -// DebitGiftResponse 返回扣费流水和账后余额。 +// DebitGiftResponse 返回扣费流水、服务端结算值和 sender COIN 账后余额。 message DebitGiftResponse { string billing_receipt_id = 1; - int64 total_gift_value = 2; - int64 balance_after = 3; + string transaction_id = 2; + int64 coin_spent = 3; + int64 gift_point_added = 4; + int64 heat_value = 5; + string price_version = 6; + // balance_after 是 sender COIN available_amount 账后余额。 + int64 balance_after = 7; } -// WalletService 首版只暴露送礼扣费能力。 +// AssetBalance 是用户某类资产的余额投影。 +message AssetBalance { + string asset_type = 1; + int64 available_amount = 2; + int64 frozen_amount = 3; + int64 version = 4; +} + +// GetBalancesRequest 查询用户多资产余额;内部调用方负责鉴权和用户范围控制。 +message GetBalancesRequest { + string request_id = 1; + int64 user_id = 2; + repeated string asset_types = 3; +} + +message GetBalancesResponse { + repeated AssetBalance balances = 1; +} + +// AdminCreditAssetRequest 是后台手动入账/调账的最小审计命令。 +message AdminCreditAssetRequest { + string command_id = 1; + int64 target_user_id = 2; + string asset_type = 3; + int64 amount = 4; + int64 operator_user_id = 5; + string reason = 6; + string evidence_ref = 7; +} + +message AdminCreditAssetResponse { + string transaction_id = 1; + AssetBalance balance = 2; +} + +// WalletService 是内部账务 gRPC 边界,外部 HTTP 入口仍由 gateway-service 承载。 service WalletService { rpc DebitGift(DebitGiftRequest) returns (DebitGiftResponse); + rpc GetBalances(GetBalancesRequest) returns (GetBalancesResponse); + rpc AdminCreditAsset(AdminCreditAssetRequest) returns (AdminCreditAssetResponse); } - diff --git a/api/proto/wallet/v1/wallet_grpc.pb.go b/api/proto/wallet/v1/wallet_grpc.pb.go index 434e007c..e610c9f7 100644 --- a/api/proto/wallet/v1/wallet_grpc.pb.go +++ b/api/proto/wallet/v1/wallet_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 -// - protoc v5.29.2 +// - protoc v5.27.3 // source: api/proto/wallet/v1/wallet.proto package walletv1 @@ -19,16 +19,20 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - WalletService_DebitGift_FullMethodName = "/hyapp.wallet.v1.WalletService/DebitGift" + WalletService_DebitGift_FullMethodName = "/hyapp.wallet.v1.WalletService/DebitGift" + WalletService_GetBalances_FullMethodName = "/hyapp.wallet.v1.WalletService/GetBalances" + WalletService_AdminCreditAsset_FullMethodName = "/hyapp.wallet.v1.WalletService/AdminCreditAsset" ) // WalletServiceClient is the client API for WalletService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. // -// WalletService 首版只暴露送礼扣费能力。 +// WalletService 是内部账务 gRPC 边界,外部 HTTP 入口仍由 gateway-service 承载。 type WalletServiceClient interface { DebitGift(ctx context.Context, in *DebitGiftRequest, opts ...grpc.CallOption) (*DebitGiftResponse, error) + GetBalances(ctx context.Context, in *GetBalancesRequest, opts ...grpc.CallOption) (*GetBalancesResponse, error) + AdminCreditAsset(ctx context.Context, in *AdminCreditAssetRequest, opts ...grpc.CallOption) (*AdminCreditAssetResponse, error) } type walletServiceClient struct { @@ -49,13 +53,35 @@ func (c *walletServiceClient) DebitGift(ctx context.Context, in *DebitGiftReques return out, nil } +func (c *walletServiceClient) GetBalances(ctx context.Context, in *GetBalancesRequest, opts ...grpc.CallOption) (*GetBalancesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetBalancesResponse) + err := c.cc.Invoke(ctx, WalletService_GetBalances_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *walletServiceClient) AdminCreditAsset(ctx context.Context, in *AdminCreditAssetRequest, opts ...grpc.CallOption) (*AdminCreditAssetResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AdminCreditAssetResponse) + err := c.cc.Invoke(ctx, WalletService_AdminCreditAsset_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // WalletServiceServer is the server API for WalletService service. // All implementations must embed UnimplementedWalletServiceServer // for forward compatibility. // -// WalletService 首版只暴露送礼扣费能力。 +// WalletService 是内部账务 gRPC 边界,外部 HTTP 入口仍由 gateway-service 承载。 type WalletServiceServer interface { DebitGift(context.Context, *DebitGiftRequest) (*DebitGiftResponse, error) + GetBalances(context.Context, *GetBalancesRequest) (*GetBalancesResponse, error) + AdminCreditAsset(context.Context, *AdminCreditAssetRequest) (*AdminCreditAssetResponse, error) mustEmbedUnimplementedWalletServiceServer() } @@ -69,6 +95,12 @@ type UnimplementedWalletServiceServer struct{} func (UnimplementedWalletServiceServer) DebitGift(context.Context, *DebitGiftRequest) (*DebitGiftResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method DebitGift not implemented") } +func (UnimplementedWalletServiceServer) GetBalances(context.Context, *GetBalancesRequest) (*GetBalancesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetBalances not implemented") +} +func (UnimplementedWalletServiceServer) AdminCreditAsset(context.Context, *AdminCreditAssetRequest) (*AdminCreditAssetResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AdminCreditAsset not implemented") +} func (UnimplementedWalletServiceServer) mustEmbedUnimplementedWalletServiceServer() {} func (UnimplementedWalletServiceServer) testEmbeddedByValue() {} @@ -108,6 +140,42 @@ func _WalletService_DebitGift_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _WalletService_GetBalances_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetBalancesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).GetBalances(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_GetBalances_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).GetBalances(ctx, req.(*GetBalancesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _WalletService_AdminCreditAsset_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AdminCreditAssetRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).AdminCreditAsset(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_AdminCreditAsset_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).AdminCreditAsset(ctx, req.(*AdminCreditAssetRequest)) + } + return interceptor(ctx, in, info, handler) +} + // WalletService_ServiceDesc is the grpc.ServiceDesc for WalletService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -119,6 +187,14 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{ MethodName: "DebitGift", Handler: _WalletService_DebitGift_Handler, }, + { + MethodName: "GetBalances", + Handler: _WalletService_GetBalances_Handler, + }, + { + MethodName: "AdminCreditAsset", + Handler: _WalletService_AdminCreditAsset_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "api/proto/wallet/v1/wallet.proto", diff --git a/docker-compose.yml b/docker-compose.yml index fadffa35..7d2033cd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -101,7 +101,7 @@ services: MYSQL_USER: hyapp MYSQL_PASSWORD: hyapp ports: - - "13306:3306" + - "23306:3306" volumes: - mysql-data:/var/lib/mysql - ./services/room-service/deploy/mysql/initdb/001_room_service.sql:/docker-entrypoint-initdb.d/001_room_service.sql:ro diff --git a/docs/auth-login-register-technical-design.md b/docs/auth-login-register-technical-design.md index 684a8343..cd233254 100644 --- a/docs/auth-login-register-technical-design.md +++ b/docs/auth-login-register-technical-design.md @@ -108,7 +108,7 @@ Response `data`: } ``` -三方接口不拆注册和登录。新用户不会默认生成密码;注册资料和来源快照写入 `users`,同时 `user_display_user_ids`、`third_party_identities` 和首个 `auth_sessions` 会在同一事务中创建。`birth` 必须是 `yyyy-mm-dd`;`platform` 当前只接受 `android` 或 `ios`;gateway 兼容客户端误拼的 `app_verison`,进入内部 RPC 时统一为 `app_version`。客户端不能提交注册 IP、UA 或 IP 国家,`register_ip` 和 `register_user_agent` 来自 gateway 请求上下文,`country_by_ip` 来自服务端/可信边缘层根据入口 IP 形成的国家快照。`provider_subject` 只允许由 user-service 校验 provider credential 后得到,不能信任客户端直传,也不能把客户端传入的 `credential` 原样当成 subject。 +三方接口不拆注册和登录。新用户不会默认生成密码;注册资料和来源快照写入 `users`,同时 `user_display_user_ids`、`third_party_identities` 和首个 `auth_sessions` 会在同一事务中创建。`birth` 必须是 `yyyy-mm-dd`;`platform` 当前只接受 `android` 或 `ios`;gateway 只接收 `app_version`,不会读取误拼字段。客户端不能提交注册 IP、UA 或 IP 国家,`register_ip` 和 `register_user_agent` 来自 gateway 请求上下文,`country_by_ip` 来自服务端/可信边缘层根据入口 IP 形成的国家快照。`provider_subject` 只允许由 user-service 校验 provider credential 后得到,不能信任客户端直传,也不能把客户端传入的 `credential` 原样当成 subject。 注册字段必须在 user-service 写库前完成校验,失败统一返回 `INVALID_ARGUMENT`,不能让 MySQL `VARCHAR` 或 `DATE` 约束错误泄漏成 gateway `UPSTREAM_ERROR`。 @@ -116,7 +116,7 @@ Response `data`: | --- | ---: | --- | | `username` | 64 | trimmed display name | | `gender` | 32 | client enum/string snapshot | -| `country` | 2 | ISO 3166-1 alpha-2, normalized uppercase | +| `country` | 3 | ISO 3166-1 alpha-2/alpha-3 canonical code, normalized uppercase | | `invite_code` | 64 | optional invite code | | `register_ip` | 64 | gateway observed IP only | | `register_user_agent` | 512 | gateway observed User-Agent only | @@ -347,20 +347,19 @@ message LoginThirdPartyRequest { string gender = 5; string country = 6; string invite_code = 7; - string ip = 8 [deprecated = true]; - string device_id = 9; - string device = 10; - string avatar = 11; - string birth = 12; - string app_version = 13; - string source = 14; - string platform = 15; - string language = 16; - string os_version = 17; - string build_number = 18; - string timezone = 19; - string install_channel = 20; - string campaign = 21; + string device_id = 8; + string device = 9; + string avatar = 10; + string birth = 11; + string app_version = 12; + string source = 13; + string platform = 14; + string language = 15; + string os_version = 16; + string build_number = 17; + string timezone = 18; + string install_channel = 19; + string campaign = 20; } message SetPasswordRequest { @@ -394,13 +393,13 @@ message AuthToken { | Column | Type | Rule | | --- | --- | --- | | `user_id` | bigint | primary key | -| `default_display_user_id` | varchar | user's permanent fallback short id | +| `default_display_user_id` | varchar | user's permanent default short id | | `current_display_user_id` | varchar | current effective short id; pretty id can cover default id | | `current_display_user_id_kind` | varchar | default or pretty | | `current_display_user_id_expires_at_ms` | bigint | null for default id | | `username` | varchar | registration display name snapshot | | `gender` | varchar | client submitted gender value | -| `country` | varchar | ISO 3166-1 alpha-2 country or region code | +| `country` | varchar | ISO 3166-1 alpha-2/alpha-3 canonical country code | | `invite_code` | varchar | optional invite code | | `register_ip` | varchar | gateway observed request ip snapshot | | `register_user_agent` | varchar | gateway observed User-Agent snapshot | @@ -653,7 +652,7 @@ gateway HTTP envelope 的 `code` 不使用数字。 | `AUTH_FAILED` | 401 | display_user_id missing, password wrong, no password set, or provider rejected | | `UNAUTHORIZED` | 401 | missing or invalid access token | | `PASSWORD_ALREADY_SET` | 409 | user already has password identity | -| `COUNTRY_CHANGE_COOLDOWN` | 409 | country change is within rolling 30-day cooldown | +| `COUNTRY_CHANGE_COOLDOWN` | 409 | country change is within the 30-day cooldown window | | `USER_DISABLED` | 403 | user is disabled or banned | | `SESSION_EXPIRED` | 401 | refresh token expired | | `SESSION_REVOKED` | 401 | refresh token revoked | @@ -690,17 +689,17 @@ Redis backend 使用 Lua 脚本一次性检查并递增同一请求命中的所 - 设置密码必须从 access token 取 `user_id`,不接受客户端 body 指定用户。 - access token 中的 `display_user_id` 不能作为权限、账务、房间或 IM 身份依据。 -## Compatibility Rules +## Development Contract -当前项目仍在架构阶段,没有外部调用方,本次契约直接删除了旧账号注册接口。后续一旦进入联调或灰度,必须恢复兼容演进纪律: +当前项目处于开发阶段,没有线上旧客户端或旧服务版本需要保留;HTTP、protobuf 和数据库契约按当前代码直接收敛: -- HTTP JSON 字段只能兼容扩展,不能重命名或删除已发布字段。 -- protobuf 字段只能追加,不能复用字段编号。 +- HTTP JSON 只接受当前文档字段,旧拼写和废弃字段不再读取。 +- protobuf 按当前字段编号生成,删除的旧字段不保留解析分支。 - 新增 provider 必须通过配置 allowlist 控制,不能让客户端任意传 provider 后触发未知逻辑。 - 新增 provider 必须同时新增专用 verifier、配置项和测试;不能只把 provider 名加入静态 allowlist。 - 三方登录创建用户必须使用事务,保证 `users` 注册资料、`user_display_user_ids` 和 `third_party_identities` 不出现半绑定。 - 设置密码必须使用 `user_id` 主键幂等判断,不能引入独立账号唯一键。 -- 登录响应可以追加默认短号、当前短号来源和靓号过期时间;已发布后不能改变 `display_user_id` 表示当前有效短号的语义。 +- 登录响应中的 `display_user_id` 表示当前有效短号,服务端权限、账务、房间和 IM 身份一律使用长 `user_id`。 ## Test Matrix diff --git a/docs/graceful-shutdown.md b/docs/graceful-shutdown.md index 9c2c704c..f3fc5426 100644 --- a/docs/graceful-shutdown.md +++ b/docs/graceful-shutdown.md @@ -1,6 +1,6 @@ # Graceful Shutdown Development Guide -本文档定义服务下线、重启、滚动发布时必须遵守的进程生命周期规则。目标是让实例退出时不再接新流量,不截断关键请求,不留下错误 lease,不制造重复消费。 +本文档定义服务下线、重启和重新部署时必须遵守的进程生命周期规则。目标是让实例退出时不再接新流量,不截断关键请求,不留下错误 lease,不制造重复消费。 ## Scope diff --git a/docs/health-checks.md b/docs/health-checks.md index bef228e1..167fa213 100644 --- a/docs/health-checks.md +++ b/docs/health-checks.md @@ -299,7 +299,7 @@ running -> draining -> stopped 5. lease 释放或自然过期。 6. 新节点接到请求后通过 snapshot + command log 恢复。 -禁止在 `ready=false` 之后继续接管新房间。否则滚动发布时会出现旧节点持续获得新 room lease,导致下线时间不可控。 +禁止在 `ready=false` 之后继续接管新房间。否则节点下线期间会持续获得新 room lease,导致退出时间不可控。 ## Observability Fields diff --git a/docs/lucky-gift-activity-service-development.md b/docs/lucky-gift-activity-service-development.md index 72168976..e79377a3 100644 --- a/docs/lucky-gift-activity-service-development.md +++ b/docs/lucky-gift-activity-service-development.md @@ -76,7 +76,8 @@ message CheckLuckyGiftRequest { LuckyGiftMeta meta = 1; string gift_id = 2; int32 gift_count = 3; - int64 gift_unit_price = 4; + int64 coin_spent = 4; + int64 heat_value = 5; } message CheckLuckyGiftResponse { @@ -89,8 +90,8 @@ message ExecuteLuckyGiftDrawRequest { LuckyGiftMeta meta = 1; string gift_id = 2; int32 gift_count = 3; - int64 gift_unit_price = 4; - int64 total_gift_value = 5; + int64 coin_spent = 4; + int64 heat_value = 5; string billing_receipt_id = 6; string rule_version = 7; } @@ -209,7 +210,8 @@ The primary key is `(scope_type, scope_id)`. | `target_user_id` | bigint | gift receiver | | `gift_id` | varchar | gift id | | `gift_count` | int | count | -| `total_gift_value` | bigint | paid coins | +| `coin_spent` | bigint | paid coins | +| `heat_value` | bigint | room heat contributed by this gift | | `rule_version` | varchar | immutable rule version | | `candidate_tiers_json` | json | tiers after filtering | | `selected_tier_id` | varchar | final selected tier | @@ -259,7 +261,7 @@ The primary key is `(scope_type, scope_id)`. All money-like values use integer coins. Percentages use ppm to avoid floating point drift. ```text -gift_value = gift_unit_price * gift_count +gift_value = heat_value pool_in = floor(gift_value * pool_rate_ppm / 1000000) platform_in = floor(pool_in * platform_weight_ppm / 1000000) room_in = floor(pool_in * room_weight_ppm / 1000000) diff --git a/docs/openapi/activity.swagger.yaml b/docs/openapi/activity.swagger.yaml new file mode 100644 index 00000000..03f4ae1c --- /dev/null +++ b/docs/openapi/activity.swagger.yaml @@ -0,0 +1,177 @@ +swagger: "2.0" +info: + title: HY Activity Service API + version: "1.0.0" + description: | + activity-service 当前只暴露内部 gRPC + protobuf 同步查询接口,没有 HTTP JSON router。 + 房间事件消费通过 consumer 边界接入,不伪装成同步 RPC。 + 本文件用 OpenAPI 2.0 记录 gRPC 合约,`paths` 使用 gRPC full method 作为文档路径,不代表真实 HTTP endpoint。 +host: localhost:13006 +basePath: / +schemes: + - http +consumes: + - application/grpc+proto +produces: + - application/grpc+proto +tags: + - name: activity + description: ActivityService,活动服务同步查询接口。 + - name: health + description: 标准 grpc.health.v1.Health。 +responses: + GRPCError: + description: gRPC status error;稳定业务原因放在 google.rpc.ErrorInfo.reason。 + schema: + $ref: "#/definitions/GRPCError" +paths: + /hyapp.activity.v1.ActivityService/PingActivity: + post: + tags: + - activity + summary: 活动服务连通性探测 + operationId: activityPingActivity + x-grpc-full-method: /hyapp.activity.v1.ActivityService/PingActivity + parameters: + - name: body + in: body + required: true + schema: + $ref: "#/definitions/PingActivityRequest" + responses: + "200": + description: 服务当前可接受同步 RPC。 + schema: + $ref: "#/definitions/PingActivityResponse" + default: + $ref: "#/responses/GRPCError" + /hyapp.activity.v1.ActivityService/GetActivityStatus: + post: + tags: + - activity + summary: 查询活动状态 + operationId: activityGetActivityStatus + x-grpc-full-method: /hyapp.activity.v1.ActivityService/GetActivityStatus + parameters: + - name: body + in: body + required: true + schema: + $ref: "#/definitions/GetActivityStatusRequest" + responses: + "200": + description: 查询成功。 + schema: + $ref: "#/definitions/GetActivityStatusResponse" + default: + $ref: "#/responses/GRPCError" + /grpc.health.v1.Health/Check: + post: + tags: + - health + summary: 标准 gRPC health check + operationId: activityHealthCheck + x-grpc-full-method: /grpc.health.v1.Health/Check + parameters: + - name: body + in: body + required: true + schema: + $ref: "#/definitions/HealthCheckRequest" + responses: + "200": + description: 标准 health check 响应。 + schema: + $ref: "#/definitions/HealthCheckResponse" + default: + $ref: "#/responses/GRPCError" + /grpc.health.v1.Health/Watch: + post: + tags: + - health + summary: 标准 gRPC health watch + operationId: activityHealthWatch + x-grpc-full-method: /grpc.health.v1.Health/Watch + parameters: + - name: body + in: body + required: true + schema: + $ref: "#/definitions/HealthCheckRequest" + responses: + "200": + description: 标准 health watch 流式响应;OpenAPI 只记录单条消息结构。 + schema: + $ref: "#/definitions/HealthCheckResponse" + default: + $ref: "#/responses/GRPCError" +definitions: + RequestMeta: + type: object + properties: + request_id: + type: string + description: 链路追踪 ID,不作为幂等键。 + caller: + type: string + gateway_node_id: + type: string + sent_at_ms: + type: integer + format: int64 + PingActivityRequest: + type: object + properties: + meta: + $ref: "#/definitions/RequestMeta" + PingActivityResponse: + type: object + properties: + ok: + type: boolean + node_id: + type: string + GetActivityStatusRequest: + type: object + required: + - activity_id + properties: + meta: + $ref: "#/definitions/RequestMeta" + activity_id: + type: string + GetActivityStatusResponse: + type: object + properties: + activity_id: + type: string + status: + type: string + description: 当前活动状态;具体业务活动会扩展自己的状态枚举。 + HealthCheckRequest: + type: object + properties: + service: + type: string + description: gRPC health service name;activity-service 使用 `activity-service`。 + HealthCheckResponse: + type: object + properties: + status: + type: string + enum: + - UNKNOWN + - SERVING + - NOT_SERVING + - SERVICE_UNKNOWN + GRPCError: + type: object + properties: + code: + type: string + description: gRPC status code,例如 INVALID_ARGUMENT、NOT_FOUND、UNAVAILABLE。 + message: + type: string + error_info_reason: + type: string + description: google.rpc.ErrorInfo.reason;活动常见值包括 RULE_NOT_ACTIVE、EVENT_ALREADY_CONSUMED、REWARD_PENDING。 diff --git a/docs/openapi/gateway.swagger.yaml b/docs/openapi/gateway.swagger.yaml index 57d94f26..6896bd5e 100644 --- a/docs/openapi/gateway.swagger.yaml +++ b/docs/openapi/gateway.swagger.yaml @@ -52,7 +52,7 @@ parameters: required: true type: integer format: int64 - description: 腾讯云 IM 回调携带的 SDKAppID;也兼容 `SDKAppID` 查询名。 + description: 腾讯云 IM 回调携带的 SDKAppID;当前入口接受 `SdkAppid` 查询名。 TencentIMCallbackCommand: name: CallbackCommand in: query @@ -749,6 +749,176 @@ paths: $ref: "#/responses/Internal" "502": $ref: "#/responses/UpstreamError" + /api/v1/rooms/mic/publishing/confirm: + post: + tags: + - rooms + summary: 确认 RTC 音频发布成功 + operationId: confirmMicPublishing + security: + - BearerAuth: [] + parameters: + - $ref: "#/parameters/RequestIDHeader" + - name: body + in: body + required: true + schema: + $ref: "#/definitions/ConfirmMicPublishingRequest" + responses: + "200": + description: 确认成功或旧事件被安全忽略,`data` 返回最新房间快照。 + schema: + $ref: "#/definitions/ConfirmMicPublishingEnvelope" + "400": + $ref: "#/responses/BadRequest" + "401": + $ref: "#/responses/Unauthorized" + "403": + $ref: "#/responses/Forbidden" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "500": + $ref: "#/responses/Internal" + "502": + $ref: "#/responses/UpstreamError" + /api/v1/rooms/mic/lock: + post: + tags: + - rooms + summary: 锁定或解锁麦位 + operationId: setMicSeatLock + security: + - BearerAuth: [] + parameters: + - $ref: "#/parameters/RequestIDHeader" + - name: body + in: body + required: true + schema: + $ref: "#/definitions/SetMicSeatLockRequest" + responses: + "200": + description: 锁麦状态修改成功,`data` 返回最新房间快照。 + schema: + $ref: "#/definitions/SetMicSeatLockEnvelope" + "400": + $ref: "#/responses/BadRequest" + "401": + $ref: "#/responses/Unauthorized" + "403": + $ref: "#/responses/Forbidden" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "500": + $ref: "#/responses/Internal" + "502": + $ref: "#/responses/UpstreamError" + /api/v1/rooms/chat/enabled: + post: + tags: + - rooms + summary: 开启或关闭房间公屏 + operationId: setChatEnabled + security: + - BearerAuth: [] + parameters: + - $ref: "#/parameters/RequestIDHeader" + - name: body + in: body + required: true + schema: + $ref: "#/definitions/SetChatEnabledRequest" + responses: + "200": + description: 公屏开关修改成功,`data` 返回最新房间快照。 + schema: + $ref: "#/definitions/SetChatEnabledEnvelope" + "400": + $ref: "#/responses/BadRequest" + "401": + $ref: "#/responses/Unauthorized" + "403": + $ref: "#/responses/Forbidden" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "500": + $ref: "#/responses/Internal" + "502": + $ref: "#/responses/UpstreamError" + /api/v1/rooms/admin/set: + post: + tags: + - rooms + summary: 添加或移除房间管理员 + operationId: setRoomAdmin + security: + - BearerAuth: [] + parameters: + - $ref: "#/parameters/RequestIDHeader" + - name: body + in: body + required: true + schema: + $ref: "#/definitions/SetRoomAdminRequest" + responses: + "200": + description: 管理员集合修改成功,`data` 返回最新房间快照。 + schema: + $ref: "#/definitions/SetRoomAdminEnvelope" + "400": + $ref: "#/responses/BadRequest" + "401": + $ref: "#/responses/Unauthorized" + "403": + $ref: "#/responses/Forbidden" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "500": + $ref: "#/responses/Internal" + "502": + $ref: "#/responses/UpstreamError" + /api/v1/rooms/host/transfer: + post: + tags: + - rooms + summary: 转移房间主持人 + operationId: transferRoomHost + security: + - BearerAuth: [] + parameters: + - $ref: "#/parameters/RequestIDHeader" + - name: body + in: body + required: true + schema: + $ref: "#/definitions/TransferRoomHostRequest" + responses: + "200": + description: 主持人转移成功,`data` 返回最新房间快照。 + schema: + $ref: "#/definitions/TransferRoomHostEnvelope" + "400": + $ref: "#/responses/BadRequest" + "401": + $ref: "#/responses/Unauthorized" + "403": + $ref: "#/responses/Forbidden" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "500": + $ref: "#/responses/Internal" + "502": + $ref: "#/responses/UpstreamError" /api/v1/rooms/user/mute: post: tags: @@ -817,6 +987,40 @@ paths: $ref: "#/responses/Internal" "502": $ref: "#/responses/UpstreamError" + /api/v1/rooms/user/unban: + post: + tags: + - rooms + summary: 解除房间 ban + operationId: unbanUser + security: + - BearerAuth: [] + parameters: + - $ref: "#/parameters/RequestIDHeader" + - name: body + in: body + required: true + schema: + $ref: "#/definitions/UnbanUserRequest" + responses: + "200": + description: 解封成功,`data` 返回最新房间快照,不自动恢复 presence、麦位或管理员身份。 + schema: + $ref: "#/definitions/UnbanUserEnvelope" + "400": + $ref: "#/responses/BadRequest" + "401": + $ref: "#/responses/Unauthorized" + "403": + $ref: "#/responses/Forbidden" + "404": + $ref: "#/responses/NotFound" + "409": + $ref: "#/responses/Conflict" + "500": + $ref: "#/responses/Internal" + "502": + $ref: "#/responses/UpstreamError" /api/v1/rooms/gift/send: post: tags: @@ -962,7 +1166,7 @@ definitions: type: string LoginThirdPartyRequest: type: object - description: 三方登录即注册;首次注册时资料字段写入 user-service users 主记录。`ip`、`user_agent` 和 `country_by_ip` 由 gateway/服务端上下文生成,不接受客户端 JSON。`app_verison` 拼写错误仅在 gateway 兼容,规范字段是 `app_version`。 + description: 三方登录即注册;首次注册时资料字段写入 user-service users 主记录。`ip`、`user_agent` 和 `country_by_ip` 由 gateway/服务端上下文生成,不接受客户端 JSON。 properties: provider: type: string @@ -1003,11 +1207,6 @@ definitions: app_version: type: string maxLength: 64 - app_verison: - type: string - maxLength: 64 - description: 拼写错误兼容字段;新客户端使用 app_version。 - x-deprecated: true build_number: type: string maxLength: 64 @@ -1177,7 +1376,7 @@ definitions: description: 发言用户;必须是内部长 `user_id` 的十进制字符串。 Operator_Account: type: string - description: 腾讯回调操作者字段,当前仅保留兼容。 + description: 腾讯云 IM 回调操作者账号字段。 TencentIMCallbackResponse: type: object properties: @@ -1311,7 +1510,7 @@ definitions: type: string pattern: "^[A-Za-z0-9_-]{1,48}$" maxLength: 48 - description: 兼容腾讯 IM GroupID 和 RTC strRoomId 的业务房间 ID。 + description: 同时满足腾讯 IM GroupID 和 RTC strRoomId 限制的业务房间 ID。 command_id: type: string description: 房间命令幂等键;客户端重试同一业务动作必须复用该值。未传时 gateway 自动生成新值,仅保证本次请求可执行,不保证后续 HTTP 重试幂等。 @@ -1364,6 +1563,9 @@ definitions: target_user_id: type: integer format: int64 + reason: + type: string + description: 主动下麦可为空;系统自动下麦会使用 publish_timeout 等稳定原因。 ChangeMicSeatRequest: type: object properties: @@ -1378,6 +1580,81 @@ definitions: seat_no: type: integer format: int32 + ConfirmMicPublishingRequest: + type: object + required: + - room_id + - mic_session_id + - room_version + - event_time_ms + properties: + room_id: + type: string + command_id: + type: string + description: 房间命令幂等键;客户端重试同一确认事件必须复用该值。 + target_user_id: + type: integer + format: int64 + description: 为空时默认确认当前登录用户;RTC webhook 编排可显式传目标用户。 + mic_session_id: + type: string + room_version: + type: integer + format: int64 + event_time_ms: + type: integer + format: int64 + source: + type: string + description: 例如 client 或 rtc_webhook。 + SetMicSeatLockRequest: + type: object + properties: + room_id: + type: string + command_id: + type: string + description: 房间命令幂等键;客户端重试同一业务动作必须复用该值。未传时 gateway 自动生成新值,仅保证本次请求可执行,不保证后续 HTTP 重试幂等。 + seat_no: + type: integer + format: int32 + locked: + type: boolean + SetChatEnabledRequest: + type: object + properties: + room_id: + type: string + command_id: + type: string + description: 房间命令幂等键;客户端重试同一业务动作必须复用该值。未传时 gateway 自动生成新值,仅保证本次请求可执行,不保证后续 HTTP 重试幂等。 + enabled: + type: boolean + SetRoomAdminRequest: + type: object + properties: + room_id: + type: string + command_id: + type: string + description: 房间命令幂等键;客户端重试同一业务动作必须复用该值。未传时 gateway 自动生成新值,仅保证本次请求可执行,不保证后续 HTTP 重试幂等。 + target_user_id: + type: integer + format: int64 + enabled: + type: boolean + TransferRoomHostRequest: + type: object + properties: + room_id: + type: string + command_id: + type: string + description: 房间命令幂等键;客户端重试同一业务动作必须复用该值。未传时 gateway 自动生成新值,仅保证本次请求可执行,不保证后续 HTTP 重试幂等。 + target_user_id: + type: integer + format: int64 MuteUserRequest: type: object properties: @@ -1402,6 +1679,17 @@ definitions: target_user_id: type: integer format: int64 + UnbanUserRequest: + type: object + properties: + room_id: + type: string + command_id: + type: string + description: 房间命令幂等键;客户端重试同一业务动作必须复用该值。未传时 gateway 自动生成新值,仅保证本次请求可执行,不保证后续 HTTP 重试幂等。 + target_user_id: + type: integer + format: int64 SendGiftRequest: type: object properties: @@ -1418,9 +1706,6 @@ definitions: gift_count: type: integer format: int32 - gift_unit_value: - type: integer - format: int64 CommandResult: type: object properties: @@ -1457,6 +1742,24 @@ definitions: format: int64 locked: type: boolean + publish_state: + type: string + enum: + - pending_publish + - publishing + - idle + description: pending_publish 表示业务占麦成功但 RTC 尚未确认发流;publishing 表示已确认发布音频。 + mic_session_id: + type: string + publish_deadline_ms: + type: integer + format: int64 + mic_session_room_version: + type: integer + format: int64 + last_publish_event_time_ms: + type: integer + format: int64 RankItem: type: object properties: @@ -1559,6 +1862,11 @@ definitions: format: int32 room: $ref: "#/definitions/RoomSnapshot" + mic_session_id: + type: string + publish_deadline_ms: + type: integer + format: int64 MicDownResponse: type: object properties: @@ -1576,6 +1884,44 @@ definitions: $ref: "#/definitions/CommandResult" room: $ref: "#/definitions/RoomSnapshot" + ConfirmMicPublishingResponse: + type: object + properties: + result: + $ref: "#/definitions/CommandResult" + seat_no: + type: integer + format: int32 + room: + $ref: "#/definitions/RoomSnapshot" + SetMicSeatLockResponse: + type: object + properties: + result: + $ref: "#/definitions/CommandResult" + room: + $ref: "#/definitions/RoomSnapshot" + SetChatEnabledResponse: + type: object + properties: + result: + $ref: "#/definitions/CommandResult" + room: + $ref: "#/definitions/RoomSnapshot" + SetRoomAdminResponse: + type: object + properties: + result: + $ref: "#/definitions/CommandResult" + room: + $ref: "#/definitions/RoomSnapshot" + TransferRoomHostResponse: + type: object + properties: + result: + $ref: "#/definitions/CommandResult" + room: + $ref: "#/definitions/RoomSnapshot" MuteUserResponse: type: object properties: @@ -1590,6 +1936,13 @@ definitions: $ref: "#/definitions/CommandResult" room: $ref: "#/definitions/RoomSnapshot" + UnbanUserResponse: + type: object + properties: + result: + $ref: "#/definitions/CommandResult" + room: + $ref: "#/definitions/RoomSnapshot" SendGiftResponse: type: object properties: @@ -1697,6 +2050,41 @@ definitions: properties: data: $ref: "#/definitions/ChangeMicSeatResponse" + ConfirmMicPublishingEnvelope: + allOf: + - $ref: "#/definitions/GatewayOKEnvelopeBase" + - type: object + properties: + data: + $ref: "#/definitions/ConfirmMicPublishingResponse" + SetMicSeatLockEnvelope: + allOf: + - $ref: "#/definitions/GatewayOKEnvelopeBase" + - type: object + properties: + data: + $ref: "#/definitions/SetMicSeatLockResponse" + SetChatEnabledEnvelope: + allOf: + - $ref: "#/definitions/GatewayOKEnvelopeBase" + - type: object + properties: + data: + $ref: "#/definitions/SetChatEnabledResponse" + SetRoomAdminEnvelope: + allOf: + - $ref: "#/definitions/GatewayOKEnvelopeBase" + - type: object + properties: + data: + $ref: "#/definitions/SetRoomAdminResponse" + TransferRoomHostEnvelope: + allOf: + - $ref: "#/definitions/GatewayOKEnvelopeBase" + - type: object + properties: + data: + $ref: "#/definitions/TransferRoomHostResponse" MuteUserEnvelope: allOf: - $ref: "#/definitions/GatewayOKEnvelopeBase" @@ -1711,6 +2099,13 @@ definitions: properties: data: $ref: "#/definitions/KickUserResponse" + UnbanUserEnvelope: + allOf: + - $ref: "#/definitions/GatewayOKEnvelopeBase" + - type: object + properties: + data: + $ref: "#/definitions/UnbanUserResponse" SendGiftEnvelope: allOf: - $ref: "#/definitions/GatewayOKEnvelopeBase" diff --git a/docs/openapi/room.swagger.yaml b/docs/openapi/room.swagger.yaml index 86a5539c..532d3a59 100644 --- a/docs/openapi/room.swagger.yaml +++ b/docs/openapi/room.swagger.yaml @@ -147,6 +147,106 @@ paths: $ref: "#/definitions/ChangeMicSeatResponse" default: $ref: "#/responses/GRPCError" + /hyapp.room.v1.RoomCommandService/ConfirmMicPublishing: + post: + tags: + - room-command + summary: 确认 RTC 音频发布成功 + operationId: roomConfirmMicPublishing + x-grpc-full-method: /hyapp.room.v1.RoomCommandService/ConfirmMicPublishing + parameters: + - name: body + in: body + required: true + schema: + $ref: "#/definitions/ConfirmMicPublishingRequest" + responses: + "200": + description: 当前 mic_session 确认成功;旧 session、旧 room_version 或旧 event_time 会被安全忽略。 + schema: + $ref: "#/definitions/ConfirmMicPublishingResponse" + default: + $ref: "#/responses/GRPCError" + /hyapp.room.v1.RoomCommandService/SetMicSeatLock: + post: + tags: + - room-command + summary: 锁定或解锁麦位 + operationId: roomSetMicSeatLock + x-grpc-full-method: /hyapp.room.v1.RoomCommandService/SetMicSeatLock + parameters: + - name: body + in: body + required: true + schema: + $ref: "#/definitions/SetMicSeatLockRequest" + responses: + "200": + description: 锁麦状态修改成功。 + schema: + $ref: "#/definitions/SetMicSeatLockResponse" + default: + $ref: "#/responses/GRPCError" + /hyapp.room.v1.RoomCommandService/SetChatEnabled: + post: + tags: + - room-command + summary: 开启或关闭公屏 + operationId: roomSetChatEnabled + x-grpc-full-method: /hyapp.room.v1.RoomCommandService/SetChatEnabled + parameters: + - name: body + in: body + required: true + schema: + $ref: "#/definitions/SetChatEnabledRequest" + responses: + "200": + description: 公屏开关修改成功;结果会影响 CheckSpeakPermission。 + schema: + $ref: "#/definitions/SetChatEnabledResponse" + default: + $ref: "#/responses/GRPCError" + /hyapp.room.v1.RoomCommandService/SetRoomAdmin: + post: + tags: + - room-command + summary: 添加或移除房间管理员 + operationId: roomSetRoomAdmin + x-grpc-full-method: /hyapp.room.v1.RoomCommandService/SetRoomAdmin + parameters: + - name: body + in: body + required: true + schema: + $ref: "#/definitions/SetRoomAdminRequest" + responses: + "200": + description: 管理员集合修改成功。 + schema: + $ref: "#/definitions/SetRoomAdminResponse" + default: + $ref: "#/responses/GRPCError" + /hyapp.room.v1.RoomCommandService/TransferRoomHost: + post: + tags: + - room-command + summary: 转移主持人 + operationId: roomTransferRoomHost + x-grpc-full-method: /hyapp.room.v1.RoomCommandService/TransferRoomHost + parameters: + - name: body + in: body + required: true + schema: + $ref: "#/definitions/TransferRoomHostRequest" + responses: + "200": + description: 主持人转移成功。 + schema: + $ref: "#/definitions/TransferRoomHostResponse" + default: + $ref: "#/responses/GRPCError" /hyapp.room.v1.RoomCommandService/MuteUser: post: tags: @@ -187,6 +287,26 @@ paths: $ref: "#/definitions/KickUserResponse" default: $ref: "#/responses/GRPCError" + /hyapp.room.v1.RoomCommandService/UnbanUser: + post: + tags: + - room-command + summary: 解除房间 ban + operationId: roomUnbanUser + x-grpc-full-method: /hyapp.room.v1.RoomCommandService/UnbanUser + parameters: + - name: body + in: body + required: true + schema: + $ref: "#/definitions/UnbanUserRequest" + responses: + "200": + description: 解封成功;不会自动恢复 presence、麦位或管理员身份。 + schema: + $ref: "#/definitions/UnbanUserResponse" + default: + $ref: "#/responses/GRPCError" /hyapp.room.v1.RoomCommandService/SendGift: post: tags: @@ -308,7 +428,7 @@ definitions: type: string pattern: "^[A-Za-z0-9_-]{1,48}$" maxLength: 48 - description: 命令所属房间 ID;CreateRoom 时必须兼容腾讯 IM GroupID 和 RTC strRoomId。 + description: 命令所属房间 ID;CreateRoom 时必须同时满足腾讯 IM GroupID 和 RTC strRoomId 限制。 gateway_node_id: type: string session_id: @@ -352,6 +472,28 @@ definitions: format: int64 locked: type: boolean + publish_state: + type: string + enum: + - pending_publish + - publishing + - idle + description: pending_publish 表示业务占麦成功但 RTC 尚未确认发流;publishing 表示已确认发布音频;空麦可为空或 idle。 + mic_session_id: + type: string + description: 单次上麦发流会话 ID;客户端确认和 RTC webhook 必须原样带回。 + publish_deadline_ms: + type: integer + format: int64 + description: pending_publish 必须完成确认的截止时间;超时会自动下麦。 + mic_session_room_version: + type: integer + format: int64 + description: 创建当前 mic_session 的房间版本,用于丢弃旧版本 RTC 事件。 + last_publish_event_time_ms: + type: integer + format: int64 + description: 当前麦位已接受的最新发布事件时间。 RankItem: type: object properties: @@ -430,14 +572,6 @@ definitions: properties: meta: $ref: "#/definitions/RequestMeta" - owner_user_id: - type: integer - format: int64 - description: 内部 gRPC 字段;缺省时 room-service 使用 `meta.actor_user_id` 作为 owner。外部 gateway HTTP 不接收该字段。 - host_user_id: - type: integer - format: int64 - description: 内部 gRPC 字段;缺省时 room-service 使用 owner 作为默认 host。外部 gateway HTTP 不接收该字段。 seat_count: type: integer format: int32 @@ -500,6 +634,11 @@ definitions: format: int32 room: $ref: "#/definitions/RoomSnapshot" + mic_session_id: + type: string + publish_deadline_ms: + type: integer + format: int64 MicDownRequest: type: object properties: @@ -508,6 +647,9 @@ definitions: target_user_id: type: integer format: int64 + reason: + type: string + description: 主动下麦可为空;系统自动下麦会使用 publish_timeout 等稳定原因。 MicDownResponse: type: object properties: @@ -518,6 +660,43 @@ definitions: format: int32 room: $ref: "#/definitions/RoomSnapshot" + ConfirmMicPublishingRequest: + type: object + required: + - meta + - mic_session_id + - room_version + - event_time_ms + properties: + meta: + $ref: "#/definitions/RequestMeta" + target_user_id: + type: integer + format: int64 + description: 为空时默认确认 actor_user_id;RTC webhook 编排可显式指定目标用户。 + mic_session_id: + type: string + room_version: + type: integer + format: int64 + description: 客户端传 MicUp 返回或快照中的 room_version;旧版本事件不会覆盖新会话。 + event_time_ms: + type: integer + format: int64 + description: 客户端 SDK 或 RTC webhook 事件发生时间;只接受当前会话更新的事件。 + source: + type: string + description: 例如 client 或 rtc_webhook。 + ConfirmMicPublishingResponse: + type: object + properties: + result: + $ref: "#/definitions/CommandResult" + seat_no: + type: integer + format: int32 + room: + $ref: "#/definitions/RoomSnapshot" ChangeMicSeatRequest: type: object properties: @@ -536,6 +715,69 @@ definitions: $ref: "#/definitions/CommandResult" room: $ref: "#/definitions/RoomSnapshot" + SetMicSeatLockRequest: + type: object + properties: + meta: + $ref: "#/definitions/RequestMeta" + seat_no: + type: integer + format: int32 + locked: + type: boolean + SetMicSeatLockResponse: + type: object + properties: + result: + $ref: "#/definitions/CommandResult" + room: + $ref: "#/definitions/RoomSnapshot" + SetChatEnabledRequest: + type: object + properties: + meta: + $ref: "#/definitions/RequestMeta" + enabled: + type: boolean + SetChatEnabledResponse: + type: object + properties: + result: + $ref: "#/definitions/CommandResult" + room: + $ref: "#/definitions/RoomSnapshot" + SetRoomAdminRequest: + type: object + properties: + meta: + $ref: "#/definitions/RequestMeta" + target_user_id: + type: integer + format: int64 + enabled: + type: boolean + SetRoomAdminResponse: + type: object + properties: + result: + $ref: "#/definitions/CommandResult" + room: + $ref: "#/definitions/RoomSnapshot" + TransferRoomHostRequest: + type: object + properties: + meta: + $ref: "#/definitions/RequestMeta" + target_user_id: + type: integer + format: int64 + TransferRoomHostResponse: + type: object + properties: + result: + $ref: "#/definitions/CommandResult" + room: + $ref: "#/definitions/RoomSnapshot" MuteUserRequest: type: object properties: @@ -568,6 +810,21 @@ definitions: $ref: "#/definitions/CommandResult" room: $ref: "#/definitions/RoomSnapshot" + UnbanUserRequest: + type: object + properties: + meta: + $ref: "#/definitions/RequestMeta" + target_user_id: + type: integer + format: int64 + UnbanUserResponse: + type: object + properties: + result: + $ref: "#/definitions/CommandResult" + room: + $ref: "#/definitions/RoomSnapshot" SendGiftRequest: type: object properties: @@ -581,9 +838,6 @@ definitions: gift_count: type: integer format: int32 - gift_unit_value: - type: integer - format: int64 SendGiftResponse: type: object properties: @@ -634,6 +888,9 @@ definitions: format: int64 session_id: type: string + request_id: + type: string + description: 外部守卫调用的追踪 ID;不作为幂等键。 VerifyRoomPresenceResponse: type: object properties: @@ -662,8 +919,13 @@ definitions: - room_user_joined - room_user_left - room_mic_changed + - room_mic_seat_locked + - room_chat_enabled_changed + - room_admin_changed + - room_host_transferred - room_user_muted - room_user_kicked + - room_user_unbanned - room_gift_sent - room_heat_changed - room_rank_changed diff --git a/docs/openapi/user.swagger.yaml b/docs/openapi/user.swagger.yaml index a3fee464..50f453b7 100644 --- a/docs/openapi/user.swagger.yaml +++ b/docs/openapi/user.swagger.yaml @@ -637,10 +637,6 @@ definitions: invite_code: type: string maxLength: 64 - ip: - type: string - description: 废弃字段;注册 IP 只能来自 meta.client_ip。 - x-deprecated: true device_id: type: string maxLength: 128 diff --git a/docs/openapi/wallet.swagger.yaml b/docs/openapi/wallet.swagger.yaml new file mode 100644 index 00000000..9e8fe725 --- /dev/null +++ b/docs/openapi/wallet.swagger.yaml @@ -0,0 +1,175 @@ +swagger: "2.0" +info: + title: HY Wallet Service API + version: "1.0.0" + description: | + wallet-service 当前只暴露内部 gRPC + protobuf 接口,没有 HTTP JSON router。 + 本文件用 OpenAPI 2.0 记录 gRPC 合约,`paths` 使用 gRPC full method 作为文档路径,不代表真实 HTTP endpoint。 +host: localhost:13004 +basePath: / +schemes: + - http +consumes: + - application/grpc+proto +produces: + - application/grpc+proto +tags: + - name: wallet + description: WalletService,账务扣费接口;当前房间送礼由 room-service 同步调用。 + - name: health + description: 标准 grpc.health.v1.Health。 +responses: + GRPCError: + description: gRPC status error;稳定业务原因放在 google.rpc.ErrorInfo.reason。 + schema: + $ref: "#/definitions/GRPCError" +paths: + /hyapp.wallet.v1.WalletService/DebitGift: + post: + tags: + - wallet + summary: 送礼扣费 + operationId: walletDebitGift + x-grpc-full-method: /hyapp.wallet.v1.WalletService/DebitGift + parameters: + - name: body + in: body + required: true + schema: + $ref: "#/definitions/DebitGiftRequest" + responses: + "200": + description: 扣费成功;重复 `command_id` 命中幂等时返回原始扣费回执。 + schema: + $ref: "#/definitions/DebitGiftResponse" + default: + $ref: "#/responses/GRPCError" + /grpc.health.v1.Health/Check: + post: + tags: + - health + summary: 标准 gRPC health check + operationId: walletHealthCheck + x-grpc-full-method: /grpc.health.v1.Health/Check + parameters: + - name: body + in: body + required: true + schema: + $ref: "#/definitions/HealthCheckRequest" + responses: + "200": + description: 标准 health check 响应。 + schema: + $ref: "#/definitions/HealthCheckResponse" + default: + $ref: "#/responses/GRPCError" + /grpc.health.v1.Health/Watch: + post: + tags: + - health + summary: 标准 gRPC health watch + operationId: walletHealthWatch + x-grpc-full-method: /grpc.health.v1.Health/Watch + parameters: + - name: body + in: body + required: true + schema: + $ref: "#/definitions/HealthCheckRequest" + responses: + "200": + description: 标准 health watch 流式响应;OpenAPI 只记录单条消息结构。 + schema: + $ref: "#/definitions/HealthCheckResponse" + default: + $ref: "#/responses/GRPCError" +definitions: + DebitGiftRequest: + type: object + required: + - command_id + - room_id + - sender_user_id + - target_user_id + - gift_id + - gift_count + properties: + command_id: + type: string + description: 账务幂等键;room-service 传入房间命令 `command_id`。 + room_id: + type: string + description: 礼物发生的房间 ID。 + sender_user_id: + type: integer + format: int64 + description: 扣费用户。 + target_user_id: + type: integer + format: int64 + description: 礼物接收用户。 + gift_id: + type: string + gift_count: + type: integer + format: int32 + minimum: 1 + price_version: + type: string + description: 指定礼物价格版本;为空时使用当前生效价格。 + DebitGiftResponse: + type: object + properties: + billing_receipt_id: + type: string + description: 扣费回执 ID,room-service 会写入房间礼物事件。 + transaction_id: + type: string + description: 钱包交易 ID。 + coin_spent: + type: integer + format: int64 + description: 本次实际扣减的 COIN 数量。 + gift_point_added: + type: integer + format: int64 + description: 本次实际增加的 GIFT_POINT 数量。 + heat_value: + type: integer + format: int64 + description: room-service 用于房间热度和榜单的结算值。 + price_version: + type: string + description: 本次命中的服务端价格版本。 + balance_after: + type: integer + format: int64 + description: 扣费后的发送方余额。 + HealthCheckRequest: + type: object + properties: + service: + type: string + description: gRPC health service name;wallet-service 使用 `wallet-service`。 + HealthCheckResponse: + type: object + properties: + status: + type: string + enum: + - UNKNOWN + - SERVING + - NOT_SERVING + - SERVICE_UNKNOWN + GRPCError: + type: object + properties: + code: + type: string + description: gRPC status code,例如 INVALID_ARGUMENT、FAILED_PRECONDITION、UNAVAILABLE。 + message: + type: string + error_info_reason: + type: string + description: google.rpc.ErrorInfo.reason;账务常见值包括 INSUFFICIENT_BALANCE、DUPLICATE_BILLING_COMMAND、LEDGER_CONFLICT。 diff --git a/docs/service-foundation-technical-design.md b/docs/service-foundation-technical-design.md index 70e37152..ee793a87 100644 --- a/docs/service-foundation-technical-design.md +++ b/docs/service-foundation-technical-design.md @@ -100,9 +100,8 @@ jwt: `wallet-service`: ```yaml -ledger: - currency: "coin" - idempotency_ttl_sec: 86400 +mysql_dsn: "hyapp:hyapp@tcp(127.0.0.1:23306)/hy_wallet?parseTime=true&charset=utf8mb4&loc=Local" +mysql_auto_migrate: false ``` `activity-service`: @@ -331,15 +330,18 @@ internal/transport/grpc ```text wallet_accounts -wallet_ledger -wallet_idempotency +wallet_transactions +wallet_entries +wallet_outbox +wallet_gift_prices ``` 账务规则: - 钱包余额不能只存在内存。 -- 每次扣费必须写 ledger。 -- 每个扣费命令必须幂等。 +- 每次账务动作必须写 transaction 和 entries。 +- 每个账务命令必须通过 `wallet_transactions.command_id` 幂等。 +- 跨服务事件必须先写 `wallet_outbox`,再由 worker 投递。 - 幂等命中必须返回原始 `billing_receipt_id`,不能重复扣费。 ### Readiness @@ -490,7 +492,7 @@ activity-service 额外需要: ### Contract Tests -覆盖 proto 兼容和 transport 行为: +覆盖 proto 契约和 transport 行为: - 成功 response 字段稳定。 - 业务错误返回正确 gRPC code 和 reason。 diff --git a/docs/tencent-rtc-token-uid-mapping-development.md b/docs/tencent-rtc-token-uid-mapping-development.md index ee711496..9deffaeb 100644 --- a/docs/tencent-rtc-token-uid-mapping-development.md +++ b/docs/tencent-rtc-token-uid-mapping-development.md @@ -112,8 +112,8 @@ RTC numeric roomId -> always 0 or omitted - 客户端进腾讯 RTC 房时只使用 `strRoomId`。 - 客户端不能把 `"123"` 转成数字 `123`;这在 TRTC 中是两个不同房间。 - 新建房间时的 `room_id` 必须满足 RTC 字符串房间 ID 限制;校验要前移到 `CreateRoom` HTTP/gRPC 入口或 room-service 领域层,不能创建出业务可用但永远拿不到 RTC token 的房间。 -- `/api/v1/rtc/token` 仍要重复校验 `room_id`,作为历史数据、内部调用和灰度期脏数据的 fail-closed 保护;历史房间 ID 不兼容时返回 `INVALID_ARGUMENT`。 -- 为兼容腾讯 IM 群组和 RTC 房间,建议收敛 `room_id` 格式到:`^[A-Za-z0-9_-]{1,48}$`。如果产品需要更丰富字符,必须同时确认 IM group id、RTC `strRoomId`、OpenAPI 和客户端 SDK 都接受。 +- `/api/v1/rtc/token` 仍要重复校验 `room_id`,作为内部调用和入口绕过场景的 fail-closed 保护;非法房间 ID 返回 `INVALID_ARGUMENT`。 +- 为同时满足腾讯 IM 群组和 RTC 房间限制,`room_id` 格式收敛到:`^[A-Za-z0-9_-]{1,48}$`。如果产品需要更丰富字符,必须同时确认 IM group id、RTC `strRoomId`、OpenAPI 和客户端 SDK 都接受。 ## External HTTP API @@ -164,7 +164,7 @@ Response `data`: | Case | HTTP | Code | Rule | | --- | ---: | --- | --- | | 未登录 | 401 | `UNAUTHORIZED` | middleware 拦截 | -| `room_id` 为空或不兼容 RTC | 400 | `INVALID_ARGUMENT` | gateway 本地校验 | +| `room_id` 为空或不符合 RTC 限制 | 400 | `INVALID_ARGUMENT` | gateway 本地校验 | | 不在房间 | 403 | `PERMISSION_DENIED` | `VerifyRoomPresence.present=false` | | 房间不存在 | 404 | `NOT_FOUND` | guard reason 为 `room_not_found` | | 用户被踢/封禁 | 403 | `PERMISSION_DENIED` | guard reason 为 `user_banned` | @@ -297,19 +297,31 @@ handler 装配规则: ## Room-Service Changes -首版不要求 room-service 新增 RTC 状态。 +room-service 不保存 RTC socket 或在线态,但必须保存“业务占麦后是否完成 RTC 发流确认”的麦位状态,避免把 `MicUp` 当成音频已经发布成功。 -需要确认: +当前麦位发流状态: + +| State | Meaning | Transition | +| --- | --- | --- | +| empty / `idle` | 空麦或没有发流会话 | `MicUp` 后进入 `pending_publish` | +| `pending_publish` | 业务麦位占用成功,等待客户端或 RTC webhook 确认音频已发布 | `ConfirmMicPublishing` 后进入 `publishing`;超时自动 `MicDown(reason=publish_timeout)` | +| `publishing` | 当前 `mic_session_id` 已确认发布音频 | `MicDown`、`LeaveRoom`、`KickUser` 后清空 | + +需要确认的稳定行为: - `VerifyRoomPresence` 能区分 `room_not_found/not_in_room/user_banned`。 - `JoinRoom` 重连刷新 `last_seen_at_ms`。 - `LeaveRoom` 后 `VerifyRoomPresence` 返回 false。 - Kick 后 `VerifyRoomPresence` 返回 `user_banned`。 +- `MicUp` 返回 `mic_session_id` 和 `publish_deadline_ms`,并在 `SeatState` 写入 `pending_publish`。 +- `ConfirmMicPublishing(mic_session_id, room_version, event_time_ms)` 只确认当前麦位会话。 +- pending 超过 `publish_deadline_ms` 未确认时,room-service 自动下麦并在事件 reason 中写 `publish_timeout`。 可选增强: - `RoomSnapshot.room_ext` 后续可加入 `rtc_enabled=true`,但首版不需要。 - 如果要根据麦位返回 `role=anchor/audience`,gateway 可以从 room-service 增加只读查询或扩展 guard response;不要让 gateway 自己缓存麦位状态。 +- 后续接 RTC webhook 时,webhook payload 必须带 `mic_session_id`、`room_version`、`event_time_ms`;room-service 只接受当前 session 且事件时间不旧于已接受事件的数据,旧 audience/leave 事件不能清掉新上麦状态。 ## Client Flow @@ -329,10 +341,13 @@ handler 装配规则: ```text 1. POST /api/v1/rooms/mic/up -2. Client receives MicUp success and latest RoomSnapshot +2. Client receives mic_session_id, publish_deadline_ms, and RoomSnapshot with publish_state=pending_publish 3. RTC SDK switchRole(anchor) 4. RTC SDK startLocalAudio -5. Client uses room-service mic seat state as UI source of truth +5. Client receives local publish success callback +6. POST /api/v1/rooms/mic/publishing/confirm with room_id, mic_session_id, room_version, event_time_ms, source=client +7. RoomSnapshot seat publish_state becomes publishing +8. If step 6 is not completed before publish_deadline_ms, room-service automatically MicDown(reason=publish_timeout) ``` 重连: @@ -363,6 +378,8 @@ handler 装配规则: - 超过 presence stale timeout 后,业务 presence 被清理,新的 RTC token 会被拒绝。 - 客户端收到 Kick 系统消息后必须 `stopLocalAudio`、退出 RTC 房并退出 IM 群;服务端后续 token 签发也会拒绝。 - 下麦成功后客户端必须 `stopLocalAudio`,并在 VoiceChatRoom 场景调用 `switchRole(audience)` 或按 SDK 推荐方式停止发布。 +- 客户端只能把 `publishing` 当成“服务端已接受音频发布确认”;`pending_publish` 只能展示为“正在连接麦克风/等待发流确认”。 +- 客户端重复确认同一 SDK 成功回调时必须复用同一个 `command_id`;如果没有复用,room-service 仍会用 `mic_session_id + room_version + event_time_ms` 防止旧事件覆盖新会话。 ## Security Rules @@ -388,6 +405,10 @@ handler 装配规则: | `room_id` | 内部房间 ID | | `str_room_id` | RTC 字符串房间 ID | | `room_version` | guard 返回的房间版本 | +| `mic_session_id` | 单次上麦发流会话 ID | +| `publish_state` | pending_publish / publishing | +| `publish_deadline_ms` | 发流确认截止时间 | +| `publish_event_time_ms` | 客户端 SDK 或 RTC webhook 事件时间 | | `result` | issued / denied / config_error / upstream_error | | `reason` | guard 或配置失败原因 | @@ -399,6 +420,9 @@ gateway_rtc_token_denied_total gateway_rtc_token_config_error_total gateway_rtc_token_upstream_error_total gateway_rtc_token_latency_ms +room_mic_publish_confirmed_total +room_mic_publish_timeout_total +room_mic_publish_stale_event_ignored_total ``` 腾讯 RTC 外部进房成功率由客户端 SDK 上报或日志采集,不放进 gateway readiness。 @@ -414,9 +438,12 @@ gateway_rtc_token_latency_ms 7. 新增 gateway `/api/v1/rtc/token` handler 和 route。 8. handler 调 `RoomGuardClient.VerifyRoomPresence`。 9. 在 `CreateRoom` 入口或 room-service 领域层补统一 `room_id` 格式校验,并保持 `/api/v1/rtc/token` 二次校验。 -10. 更新 `docs/openapi/gateway.swagger.yaml`。 -11. 增加 gateway transport 单测、config 单测和 room_id 建房校验单测。 -12. 跑 Phase 2 最小手工联调:JoinRoom 后签 RTC token,客户端用 `strRoomId` 进入语音房。 +10. 扩展 room proto:`SeatState.publish_state/mic_session_id/publish_deadline_ms`、`MicUpResponse`、`ConfirmMicPublishing`。 +11. room-service 在 `MicUp` 写入 `pending_publish`,确认后写 `publishing`,超时自动 `MicDown(reason=publish_timeout)`。 +12. gateway 增加 `/api/v1/rooms/mic/publishing/confirm` 并透传 request_id 到 room-service。 +13. 更新 `docs/openapi/gateway.swagger.yaml` 和 `docs/openapi/room.swagger.yaml`。 +14. 增加 gateway transport、room-service command、stale event 和 timeout 单测。 +15. 跑 Phase 2 最小手工联调:JoinRoom 后签 RTC token,客户端用 `strRoomId` 进入语音房,再完成 MicUp -> ConfirmMicPublishing -> MicDown。 ## Acceptance Tests @@ -430,13 +457,17 @@ gateway_rtc_token_latency_ms | 房间不存在 | HTTP 404 envelope | | 被踢用户请求 | HTTP 403 envelope | | RTC 配置缺失 | HTTP 500 envelope,不返回假 UserSig | -| `room_id` 为空或不兼容 | HTTP 400 envelope | -| CreateRoom 使用不兼容 RTC/IM 的 `room_id` | HTTP 400 envelope,房间不创建 | +| `room_id` 为空或格式非法 | HTTP 400 envelope | +| CreateRoom 使用不符合 RTC/IM 限制的 `room_id` | HTTP 400 envelope,房间不创建 | | UserID 映射 | `user_id=10001` 时 `rtc_user_id == "10001"` | | request_id 透传 | response envelope 和 guard RPC 使用同一个 `request_id` | | `tencent_rtc.enabled=false` | HTTP 500 envelope,不返回 UserSig | | `room_id_type` 非 `string` | handler fail-closed,不返回 UserSig | | IM UserSig 不回归 | 现有 `/api/v1/im/usersig` 测试仍通过 | +| MicUp 后未确认发布 | 麦位进入 `pending_publish`,响应返回 `mic_session_id/publish_deadline_ms` | +| ConfirmMicPublishing 当前 session | 麦位进入 `publishing` | +| ConfirmMicPublishing 旧 session/旧版本/旧 event_time | 不覆盖当前麦位状态 | +| publish deadline 超时 | 自动 `MicDown(reason=publish_timeout)` 并释放麦位 | 建议补充手工验证: @@ -449,10 +480,16 @@ Login -> Get RTC token -> RTC SDK enterRoom(strRoomId=room_id, userId=rtc_user_id) -> MicUp + -> SeatState.publish_state=pending_publish, client receives mic_session_id -> RTC SDK switchRole(anchor) -> Client starts publishing audio + -> ConfirmMicPublishing(mic_session_id, room_version, event_time_ms) + -> SeatState.publish_state=publishing + -> Retry stale ConfirmMicPublishing with old mic_session_id/event_time_ms and verify no state rollback -> MicDown -> Client stops publishing audio and switches to audience + -> MicUp again but do not confirm + -> publish_deadline_ms expires and room-service auto MicDown(reason=publish_timeout) -> LeaveRoom -> RTC token denied for same room ``` @@ -476,7 +513,7 @@ go test ./... docker compose config ``` -如果修改 protobuf,本功能首版不需要;若后续增加 room guard response 字段,则必须运行: +涉及 protobuf 时运行: ```bash make proto @@ -495,6 +532,10 @@ Authenticated user -> gateway maps room_id to RTC strRoomId -> gateway signs RTC UserSig on server -> client enters Tencent RTC room + -> MicUp creates pending_publish mic_session + -> client confirms publishing with mic_session_id, room_version, event_time_ms + -> stale RTC/client events cannot clear a newer mic_session + -> unconfirmed pending_publish session auto MicDowns after deadline -> LeaveRoom/Kick/stale cleanup makes later RTC token request fail ``` diff --git a/docs/user-region-country-development.md b/docs/user-region-country-development.md index bee6371e..839c5df2 100644 --- a/docs/user-region-country-development.md +++ b/docs/user-region-country-development.md @@ -55,11 +55,10 @@ 规则: -- `countries.country_code` 是本系统 canonical 国家码的权威来源。同一个真实国家只能有一条 canonical 记录,不能同时把 `US` 和 `USA` 当成两个国家主键。 -- `country_aliases.alias_code` 只用于输入兼容,例如 `USA -> US`。alias 解析后必须写回 canonical `country_code`。 -- `country_code` 创建后视为不可变。需要改码时新增 canonical 国家记录并通过迁移任务更新 `users.country`、`region_countries` 和审计口径。 -- 注册、改国家、区域国家配置都必须先把输入 trim、转大写,再校验 `^[A-Z]{2,3}$`,然后通过 alias/canonical 解析命中 active `countries`。 -- `users.country` 继续保存 canonical `country_code` 字符串,保持现有 proto 和 HTTP API 兼容;服务端展示时可以按 `country_code` 拼出 `country_id/country_name/country_display_name`。 +- `countries.country_code` 是本系统国家码权威来源。同一个真实国家只能有一条记录,运营配置必须避免同时把 `US` 和 `USA` 当成两个国家主键。 +- `country_code` 创建后视为不可变。需要改码时新增国家记录并通过重算任务更新 `users.country`、`region_countries` 和审计口径。 +- 注册、改国家、区域国家配置都必须先把输入 trim、转大写,再校验 `^[A-Z]{2,3}$`,然后命中 active `countries`。 +- `users.country` 保存 `country_code` 字符串;服务端展示时可以按 `country_code` 拼出 `country_id/country_name/country_display_name`。 - `country_by_ip` 只做审计和风控快照;如果边缘层给出的国家码不在 `countries` 中,user-service 应丢弃或记录为空,不能参与区域归属。 ### Region @@ -127,21 +126,7 @@ CREATE TABLE IF NOT EXISTS countries ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ``` -`country_code` 使用 `VARCHAR(3)` 而不是 `CHAR(2)`,因为 canonical code 保留两位或三位的产品选择空间。首版 seed 采用 alpha-2 作为 canonical;需要兼容 alpha-3 输入时写入 `country_aliases`,不能在 `countries` 里再建一个独立国家。 - -### `country_aliases` - -```sql -CREATE TABLE IF NOT EXISTS country_aliases ( - alias_code VARCHAR(3) NOT NULL PRIMARY KEY, - country_code VARCHAR(3) NOT NULL, - created_at_ms BIGINT NOT NULL, - updated_at_ms BIGINT NOT NULL, - KEY idx_country_aliases_country (country_code) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -``` - -alias 只参与解析输入。注册、改国家和区域配置收到 `USA` 时解析到 `US`;`users.country`、`region_countries.country_code` 和 rebuild task 都保存 `US`。 +`country_code` 使用 `VARCHAR(3)` 而不是 `CHAR(2)`,因为产品允许两位或三位国家码。服务端不维护国家码别名表;客户端和运营后台必须提交最终 `country_code`。 ### `regions` @@ -258,7 +243,7 @@ ALTER TABLE users ADD KEY idx_users_country_region (country, region_id); ``` -`users.country` 继续保存 `country_code`。首个迁移不强制把 `country VARCHAR(64)` 收窄到 `VARCHAR(3)`,避免滚动发布期间旧脏数据或旧服务写入失败;代码上线并清理历史数据后,可以单独评估收窄字段长度。 +`users.country` 保存 `country_code`。开发阶段直接按当前 schema 和测试收敛字段长度与校验规则,不保留旧结构分支。 不建议在 `users` 冗余 `country_name`、`country_display_name`、`region_code` 和 `region_name`。展示时由 `user-service` 按 `users.country` 和 `users.region_id` 查询国家/区域主数据,或由读模型拼装。 @@ -266,7 +251,7 @@ ALTER TABLE users ### Public User Projection -现有用户投影可以追加字段,保持 proto/API 向后兼容: +用户投影字段: ```proto message User { @@ -286,8 +271,7 @@ message User { 规则: -- 老客户端可以忽略新增字段。 -- `country` 仍然是 canonical `country_code`,例如 `CN`、`US`;客户端传入的 alias 不原样保存。 +- `country` 是 `country_code`,例如 `CN`、`US`。 - `country_id = 0` 表示当前 `country` 为空或未命中国家表。 - `country_display_name` 是中文展示名,面向需要中文国家描述的客户端或管理端。 - `region_id = 0` 表示无区域归属。 @@ -562,14 +546,12 @@ type RegionRepository interface { 首版为保证管理变更后新注册和改国家立即生效,优先不加缓存,直接 MySQL 查询。等注册量上来后再加缓存;一旦加缓存,必须使用版本校验、Redis pub/sub 或其他跨实例失效机制,不能只依赖 TTL。 -## Database Migration Plan +## Development Plan -生产迁移必须兼容滚动发布: - -1. 新增 `countries`、`country_aliases`、`regions`、`region_countries`、`region_change_logs`、`user_region_rebuild_tasks`。 +1. 新增 `countries`、`regions`、`region_countries`、`region_change_logs`、`user_region_rebuild_tasks`。 2. `users` 新增 nullable `region_id` 和索引,暂不收窄 `country` 字段长度。 -3. 初始化 canonical 国家表种子数据和 alias 映射;例如只 seed `US` 到 `countries`,把 `USA -> US` 写入 `country_aliases`。 -4. 代码读取时兼容 `country_id=0`、`region_id` 为空和国家表未命中的历史用户。 +3. 初始化国家表种子数据。 +4. 代码读取时允许 `country_id=0`、`region_id` 为空和国家表未命中的用户快照。 5. 上线国家管理、区域管理和注册/改国家写入逻辑。 6. 对历史 `users.country` 做一次 rebuild,把已有用户填充 `region_id`,并产出未命中国家表的异常列表。 7. 后续如果需要强制所有用户都有国家或区域,再单独评估数据质量和产品规则。 @@ -631,7 +613,7 @@ go test ./... | --- | --- | | 创建国家 `CN` | 国家表保存 `country_name/country_code/country_display_name` | | 创建三位 canonical 国家码 `TLA` | `country_code=TLA` 合法 | -| 创建 alias 国家码 `USA` | 返回 `CONFLICT`,因为 `USA` 是 `US` 的 alias | +| 创建重复国家码 | 返回 `CONFLICT` | | 创建小写国家码 `cn` | 归一为 `CN` | | 创建非法国家码 `C1` 或 `ABCD` | 返回 `INVALID_ARGUMENT` | | 注册国家命中区域 | 新用户 `Country=SG`,`RegionID=SEA` | @@ -640,7 +622,6 @@ go test ./... | 注册国家小写 | `sg` 归一为 `SG` 后命中区域 | | 已有三方登录再次传国家 | 不修改原国家和区域 | | 修改国家命中区域 | `ChangeUserCountry` 同步更新 `country` 和 `region_id` | -| 修改为 alias 国家码 | `ChangeUserCountry("USA")` 写入 canonical `US` | | 修改国家不存在或 disabled | 返回 `INVALID_ARGUMENT`,不写国家日志 | | 修改国家无映射 | `region_id` 清空 | | 修改相同国家 | 不写国家日志,不消耗冷却,允许修正 stale `region_id` | @@ -710,7 +691,7 @@ docker compose down - 历史用户可以通过 rebuild task 被重算到新区域。 - `country_by_ip` 不参与区域归属。 - gateway 不保存区域规则,room-service 不感知区域。 -- 所有新增 proto 字段向后兼容,老客户端忽略区域字段不受影响。 +- 当前处于开发阶段,proto 字段按当前实现直接调整。 ## Open Questions @@ -721,5 +702,5 @@ docker compose down - 管理端是否需要查看每个区域的用户数量。 - disabled 区域下历史用户是清空 `region_id`,还是保留旧 region 作为历史标签。 - 国家表的初始种子数据来源,是手工运营配置、ISO 导入,还是产品自定义列表。 -- 三位国家码如果作为输入兼容,应通过 `country_aliases` 映射到 canonical 国家;不要直接作为同一真实国家的第二条 `countries` 记录。 +- 两位或三位国家码都作为最终 `country_code` 处理,不做别名解析。 - `HK`、`MO`、`TW` 这类地区码的中文展示名和产品文案口径。 diff --git a/docs/voice-room-basic-loop-implementation.md b/docs/voice-room-basic-loop-implementation.md index b140be92..41fb8bc6 100644 --- a/docs/voice-room-basic-loop-implementation.md +++ b/docs/voice-room-basic-loop-implementation.md @@ -264,7 +264,7 @@ room-service 发给腾讯云 IM 的房间系统消息使用 `TIMCustomElem`。 - `event_id` 是客户端去重键。 - `room_version` 是房间状态排序参考,不替代腾讯云 IM 消息序列。 - `event_type` 使用 snake_case,不能直接暴露 protobuf 事件名。 -- 新增字段必须兼容旧客户端,不能修改已有字段语义。 +- 系统事件字段按当前 OpenAPI/proto 契约输出;新增字段必须同步 gateway、room-service 和客户端测试。 首版系统事件: diff --git a/docs/voice-room-feature-inventory.md b/docs/voice-room-feature-inventory.md index 796b7431..ba01d533 100644 --- a/docs/voice-room-feature-inventory.md +++ b/docs/voice-room-feature-inventory.md @@ -96,7 +96,7 @@ | --- | --- | --- | --- | | RTC UserSig 签发 | `DONE` | `pkg/tencentrtc` 和 `/api/v1/rtc/token` 已存在 | 真实 TRTC SDK enterRoom 联调 | | RTC user_id 映射 | `DONE` | 内部 `user_id` -> RTC `UserID` 十进制字符串 | 多端同房策略未做 | -| RTC room_id 映射 | `DONE` | 统一使用 TRTC `strRoomId`,校验 room_id 子集 | 历史非法 room_id 迁移策略 | +| RTC room_id 映射 | `DONE` | 统一使用 TRTC `strRoomId`,校验 room_id 子集 | 当前入口 fail-closed,非法 room_id 不创建房间 | | 进频道权限 | `DONE` | 签 RTC token 前调用 `VerifyRoomPresence` | 客户端必须按 JoinRoom 后再取 token | | RTC 角色 | `PARTIAL` | token 返回默认 `audience` | 上麦后切 `anchor` 或 SDK role 切换未闭环 | | 离频道 | `PARTIAL` | 服务端 LeaveRoom 会移除业务 presence | 客户端退出 TRTC 房间、异常重进策略未联调 | @@ -122,12 +122,12 @@ | Feature | Status | Current Evidence | Remaining Work | | --- | --- | --- | --- | -| wallet-service 账务表 | `DONE` | `wallet_accounts`、`wallet_ledger`、`wallet_idempotency` | 充值、退款、管理员调账未做 | -| 礼物扣费幂等 | `DONE` | `DebitGift` 使用 command_id 幂等,测试覆盖 | 幂等记录过期和冲突请求告警 | +| wallet-service 账务表 | `DONE` | `wallet_accounts`、`wallet_transactions`、`wallet_entries`、`wallet_outbox`、`wallet_gift_prices` | 充值、退款、管理员调账未做 | +| 礼物扣费幂等 | `DONE` | `DebitGift` 使用 `wallet_transactions.command_id` 幂等,测试覆盖 | 冲突请求告警 | | SendGift 房间命令 | `DONE` | room-service 先扣费,再更新 heat、gift_rank、outbox | 礼物目标、礼物表现和客户端动画联调 | | 房间热度 | `DONE` | `RoomSnapshot.heat` 和 `RoomHeatChanged` 事件 | 热度衰减/排行榜周期未做 | | 本地礼物榜 | `DONE` | `LocalRank` 和 `gift_rank` 快照 | 全局榜、跨房榜、榜单重置未做 | -| 礼物配置 | `TODO` | 当前请求直接传 `gift_id/gift_count/gift_unit_value` | 需要礼物表、价格、上下架、表现资源、版本 | +| 礼物配置 | `PARTIAL` | `wallet_gift_prices` 提供服务端价格,SendGift 请求只传 `gift_id/gift_count` | 后台价格、上下架、表现资源、版本管理未做 | | 背包/道具 | `TODO` | 当前只有金币扣费 | 背包扣减、免费礼物、道具有效期未做 | | 礼物消息补偿 | `DONE` | GiftSent/Heat/Rank 事件进入 outbox,主事件同步 IM | 客户端去重和展示策略未验收 | diff --git a/docs/voice-room-mic-room-management-development.md b/docs/voice-room-mic-room-management-development.md new file mode 100644 index 00000000..ea1c86a7 --- /dev/null +++ b/docs/voice-room-mic-room-management-development.md @@ -0,0 +1,747 @@ +# Voice Room Mic And Room Management Development Guide + +本文档定义语音房“麦位与房间管理”下一阶段开发设计。目标是在现有 `MicUp/MicDown/ChangeMicSeat/MuteUser/KickUser` 基础上补齐权限矩阵、锁麦、聊天开关、管理员管理、解封、时长型管控和 RTC 推流联动。 + +## Scope + +| Capability | Owner | Required Result | +| --- | --- | --- | +| 麦位状态 | `room-service` | Room Cell 串行维护麦位占用、锁定和换位 | +| 房间角色 | `room-service` | owner、host、admin、audience 权限可判定、可恢复 | +| 管理命令入口 | `gateway-service` | HTTP 只做鉴权和协议转换,actor 只能来自 access token | +| IM 发言守卫 | `gateway-service` + `room-service` | 腾讯 IM 发言前回查禁言、封禁、聊天开关和 presence | +| RTC 推流策略 | Client + Tencent RTC | 服务端给出麦位事实,客户端按麦位事实切换 TRTC role 或推流状态 | +| 房间系统事件 | `room-service` + Tencent IM | 管理动作进入 outbox,并同步推送房间系统消息 | + +## Non-Goals + +| Non-goal | Reason | +| --- | --- | +| 不在 gateway 判断房间权限 | gateway 不持有 Room Cell,不能复制权限逻辑 | +| 不把 RTC 在线态写进 Room Cell | RTC 连接态属于腾讯 RTC,Room Cell 只保存业务麦位事实 | +| 不做客户端 socket 管理 | 长连接和群消息继续由腾讯 IM 承担 | +| 不把全局封禁放进 room-service | 全局用户状态属于 user-service 或后台风控服务 | +| 不在本阶段做复杂主持排班 | 当前只补房间内即时管理能力 | + +## Current State + +现有能力: + +| Feature | Status | Current Behavior | +| --- | --- | --- | +| 上麦 | `DONE` | `MicUp` 校验用户在 presence、目标麦位存在、未锁、未占用 | +| 下麦 | `DONE` | `MicDown` 可把指定 target 从麦位移下 | +| 换麦 | `DONE` | `ChangeMicSeat` 原子清空源麦位并占用目标麦位 | +| 禁言 | `DONE` | `MuteUser` 写 `MuteUsers` 集合,`CheckSpeakPermission` 拒绝发言 | +| 踢人 | `DONE` | `KickUser` 移除 presence、释放麦位、写 `BanUsers` 集合 | +| 麦位锁定字段 | `PARTIAL` | `SeatState.locked` 和 `RoomState.MicSeat.Locked` 已存在,但没有命令 | +| 管理员集合 | `PARTIAL` | `RoomState.AdminUsers` 和 snapshot `admin_user_ids` 已存在,但没有管理命令 | +| 聊天开关字段 | `PARTIAL` | `chat_enabled` 已影响 `CheckSpeakPermission`,但没有命令 | +| RTC 发流确认 | `DONE` | `MicUp` 进入 `pending_publish`,客户端或 RTC webhook 确认后进入 `publishing`,超时自动下麦 | + +现有主要缺口: + +- 管理命令没有统一权限矩阵。 +- 管理员增删、主持转移、owner 转让未实现。 +- 锁麦、聊天开关、解封没有命令。 +- 禁言/封禁没有过期时间、原因、操作者审计字段。 +- RTC token 仍未按麦位事实动态返回 `anchor/audience`;当前由客户端在 `MicUp` 成功后切换角色并确认发流。 + +## Target State + +本阶段完成后,房间管理应该满足: + +- 普通用户只能自助上麦、主动下麦、主动离房和送礼。 +- owner/host/admin 可以管理麦位、禁言、踢人和聊天开关。 +- 只有 owner 可以增删 admin、转 host;转 owner 推迟到后续房间资产和后台审计阶段。 +- 锁麦位后任何角色都不能上麦或换入,必须先解锁。 +- 被禁言用户不能发公屏,但仍可收消息、上麦规则由权限策略决定。 +- 被踢/ban 用户不能 JoinRoom、不能 IM 进群、不能发言、不能拿 RTC token;已在腾讯 IM 群内的用户必须通过 IM bridge 触发踢出群副作用。 +- 所有管理动作写 command log、snapshot、outbox,并可恢复。 +- 客户端通过房间系统消息和最新 snapshot 修正本地 UI。 + +## Role Model + +### Roles + +| Role | Meaning | Source | +| --- | --- | --- | +| `owner` | 房间所有者,最高权限 | `RoomState.OwnerUserID` | +| `host` | 主持人,负责房间现场管理 | `RoomState.HostUserID` | +| `admin` | 房间管理员 | `RoomState.AdminUsers` 中除 owner/host 外的用户 | +| `audience` | 普通观众 | `OnlineUsers[user].Role` 或默认值 | + +角色判定不要只信 `RoomUser.role`。权威顺序: + +```text +if actor == owner_user_id -> owner +else if actor == host_user_id -> host +else if admin_users[actor] -> admin +else -> audience +``` + +`JoinRoom.role` 只能作为展示和默认入房角色,不能让客户端自报 `admin` 获得权限。 + +### Permission Matrix + +| Action | Owner | Host | Admin | Audience | +| --- | --- | --- | --- | --- | +| 自己上麦 `MicUp` | allow | allow | allow | allow | +| 自己下麦 `MicDown(self)` | allow | allow | allow | allow | +| 下别人麦 `MicDown(target)` | allow | allow | allow | deny | +| 换别人麦 `ChangeMicSeat(target)` | allow | allow | allow | deny | +| 锁/解锁麦位 | allow | allow | allow | deny | +| 禁言/解禁 | allow | allow | allow | deny | +| 踢人/ban | allow | allow | allow | deny | +| 解封/unban | allow | allow | deny | deny | +| 开关公屏 | allow | allow | allow | deny | +| 增删 admin | allow | deny | deny | deny | +| 转 host | allow | deny | deny | deny | +| 转 owner(后续批次) | allow | deny | deny | deny | +| 关房(后续批次) | allow | deny by default | deny | deny | + +补充约束: + +- owner 不能被 host/admin 踢、禁言、下麦或移出管理员集合。 +- host 不能被普通 admin 踢、禁言或降级。 +- actor 对自己执行 `MuteUser` 和 `KickUser` 默认拒绝,避免错误操作造成不可恢复状态。 +- 非 presence 用户不能被设为 host/admin,除非后续明确支持预设管理员。 +- 锁麦是硬锁,owner/host/admin 也不能直接上锁定麦位;管理员需要先解锁,或先下麦再锁麦。 +- 管理命令 actor 必须在当前房间 presence 中;离房后的 host/admin 不能继续远程管理,重新 JoinRoom 后恢复其持久角色。 + +## Command Design + +现有命令保留: + +- `MicUp` +- `MicDown` +- `ChangeMicSeat` +- `MuteUser` +- `KickUser` + +本阶段新增命令: + +| Command | Purpose | Notes | +| --- | --- | --- | +| `SetMicSeatLock` | 锁定或解锁指定麦位 | 首版只允许锁空麦位 | +| `SetChatEnabled` | 开启或关闭公屏 | 影响 `CheckSpeakPermission` | +| `SetRoomAdmin` | 添加或移除管理员 | 只有 owner 可执行 | +| `TransferRoomHost` | 转移主持人 | 只有 owner 可执行 | +| `UnbanUser` | 解除房间 ban | 用户可重新 JoinRoom | + +后续批次命令: + +| Command | Reason To Defer | +| --- | --- | +| `TransferRoomOwner` | 影响房间资产、收益、后台审计和纠纷处理,本阶段只做现场主持转移 | +| `CloseRoom` | 属于房间生命周期管理,需要和房间列表、搜索、推荐和恢复策略一起设计 | + +### `SetMicSeatLock` + +语义: + +- `locked=true` 锁麦。 +- `locked=false` 解锁。 +- 首版锁麦只允许目标麦位为空;如果麦位有人,返回 `CONFLICT`。 +- 解锁空麦位或已解锁麦位幂等返回当前快照,不重复写事件。 + +拒绝锁 occupied seat 的原因: + +- 避免一个命令同时产生“下麦”和“锁麦”两个 UI 事件。 +- 如果管理端要清空并锁定,应先 `MicDown(target)`,再 `SetMicSeatLock(seat_no,true)`。 + +### `SetChatEnabled` + +语义: + +- `enabled=false` 后 `CheckSpeakPermission` 返回 `chat_disabled`。 +- 关闭公屏对所有用户自发公屏消息生效,包括 owner/host/admin。 +- 系统消息和管理消息不受影响,因为它们由 room-service 通过腾讯 IM REST 发布。 +- 重复设置相同值幂等,不重复写事件。 + +### `SetRoomAdmin` + +语义: + +- `enabled=true` 添加管理员。 +- `enabled=false` 移除管理员。 +- owner/host 永远属于管理集合,不能通过该命令移除。 +- target 必须在当前房间 presence 中,避免后台误把不存在用户加入房间管理。 +- admin 离房后保留 admin 身份,重新 JoinRoom 后继续具备 admin 权限。 +- admin 被 KickUser/ban 后必须从 `AdminUsers` 删除;Unban 后不自动恢复 admin,必须 owner 重新添加。 + +### `TransferRoomHost` + +语义: + +- 新 host 必须在 room presence 中。 +- 新 host 自动进入 `AdminUsers`。 +- 旧 host 是否仍为 admin:首版保留为 admin,避免主持交接后失去管理权限造成现场中断。 +- host 主动离房后 `HostUserID` 不自动变化,但离房期间不能执行管理命令。 +- 当前 host 不能被 KickUser 直接踢出;owner 必须先 `TransferRoomHost`,再按普通 admin/audience 规则管理旧 host。 + +### Deferred `TransferRoomOwner` + +语义: + +- 本阶段不实现 HTTP/RPC,只保留后续语义边界。 +- 新 owner 必须在 room presence 中。 +- 新 owner 自动成为 owner、host 和 admin。 +- 旧 owner 是否降级:首版降级为 admin,不继续是 host,避免双 owner。 +- 转 owner 必须写强审计事件。 + +### `UnbanUser` + +语义: + +- 从 `BanUsers` 删除 target。 +- 不自动 JoinRoom;用户必须重新调用 `JoinRoom`。 +- 重复 unban 已不在 ban 集合的用户幂等返回当前快照。 +- 不恢复被 KickUser 时移除的 admin 身份、麦位或 presence。 + +### `KickUser` / Room Ban Extension + +`KickUser` 表达“踢出房间并写入房间 ban 集合”。如果本阶段要支持限时 ban,直接把请求和快照契约调整为: + +```proto +message KickUserRequest { + RequestMeta meta = 1; + int64 target_user_id = 2; + int64 duration_ms = 3; + string reason = 4; +} +``` + +语义: + +- `duration_ms=0` 表示永久 ban,直到 `UnbanUser`。 +- `duration_ms>0` 表示 ban 到 `now + duration_ms`;过期后 JoinRoom、IM 进群和 RTC token 可以重新通过。 +- KickUser 成功后必须原子移除 target 的 presence、麦位和 admin 身份,并写 `BanUsers[target]`。 +- KickUser 成功提交后,outbox/IM bridge 必须触发腾讯 IM 群成员踢出副作用;副作用失败不回滚 Room Cell,但必须重试。 +- 在 IM 踢出副作用成功前,`CheckSpeakPermission` 和 `VerifyRoomPresence` 仍必须拒绝 target,避免其继续发言或重新进群。 + +### `MuteUser` Extension + +如果需要限时禁言,`MuteUser` 请求和快照契约直接调整为: + +```proto +message MuteUserRequest { + RequestMeta meta = 1; + int64 target_user_id = 2; + bool muted = 3; + int64 duration_ms = 4; + string reason = 5; +} +``` + +语义: + +- `muted=true,duration_ms=0` 表示永久禁言,直到手动解除。 +- `muted=true,duration_ms>0` 表示禁言到 `now + duration_ms`。 +- `muted=false` 表示解除禁言,忽略 `duration_ms`。 +- 当前 snapshot 仍保留 `mute_user_ids`,后续可追加 `mute_states` 暴露过期时间。 +- MuteUser 只阻断公屏发言,不移除 presence、不影响收消息、不改变 RTC 麦位;如果需要离房必须使用 KickUser。 + +## Proto Changes + +只允许追加字段、消息和 RPC,不能改已有字段含义或编号。 + +本阶段固定追加: + +```proto +message SetMicSeatLockRequest { + RequestMeta meta = 1; + int32 seat_no = 2; + bool locked = 3; +} + +message SetMicSeatLockResponse { + CommandResult result = 1; + RoomSnapshot room = 2; +} + +message SetChatEnabledRequest { + RequestMeta meta = 1; + bool enabled = 2; +} + +message SetChatEnabledResponse { + CommandResult result = 1; + RoomSnapshot room = 2; +} + +message SetRoomAdminRequest { + RequestMeta meta = 1; + int64 target_user_id = 2; + bool enabled = 3; +} + +message SetRoomAdminResponse { + CommandResult result = 1; + RoomSnapshot room = 2; +} + +message TransferRoomHostRequest { + RequestMeta meta = 1; + int64 target_user_id = 2; +} + +message TransferRoomHostResponse { + CommandResult result = 1; + RoomSnapshot room = 2; +} + +message UnbanUserRequest { + RequestMeta meta = 1; + int64 target_user_id = 2; +} + +message UnbanUserResponse { + CommandResult result = 1; + RoomSnapshot room = 2; +} +``` + +如果本阶段启用限时 mute/ban,再追加 moderation state 和 `RoomSnapshot` 字段: + +```proto +message ModerationState { + int64 user_id = 1; + int64 expires_at_ms = 2; + int64 operator_user_id = 3; + string reason = 4; +} + +message RoomSnapshot { + ... + repeated ModerationState mute_states = 16; + repeated ModerationState ban_states = 17; +} +``` + +字段规则: + +- `mute_user_ids` / `ban_user_ids` 输出当前仍有效的 user_id,供只关心布尔状态的客户端使用。 +- `mute_states` / `ban_states` 输出过期时间、原因和操作者,用于完整管理面展示。 +- 快照和恢复链路必须使用同一套 state 结构,不能同时维护两套来源。 + +`RoomCommandService` 追加: + +```proto +rpc SetMicSeatLock(SetMicSeatLockRequest) returns (SetMicSeatLockResponse); +rpc SetChatEnabled(SetChatEnabledRequest) returns (SetChatEnabledResponse); +rpc SetRoomAdmin(SetRoomAdminRequest) returns (SetRoomAdminResponse); +rpc TransferRoomHost(TransferRoomHostRequest) returns (TransferRoomHostResponse); +rpc UnbanUser(UnbanUserRequest) returns (UnbanUserResponse); +``` + +`TransferRoomOwner` 和 `CloseRoom` 可以放到下一批,如果本阶段只做现场管理。 + +## HTTP API Design + +gateway 追加 HTTP 路由: + +```text +POST /api/v1/rooms/mic/lock +POST /api/v1/rooms/chat/enabled +POST /api/v1/rooms/admin/set +POST /api/v1/rooms/host/transfer +POST /api/v1/rooms/user/unban +``` + +所有请求都必须支持 `command_id`: + +```json +{ + "room_id": "room_1001", + "command_id": "cmd_room_1001_lock_seat_1_10001_1" +} +``` + +### Command Idempotency + +`command_id` 是房间命令幂等键,不是链路追踪 ID。 + +规则: + +- 幂等作用域为 `(room_id, command_id)`。 +- room-service 持久化 command log 时必须保存 `command_type` 和 payload hash。 +- 重复提交相同 `(room_id, command_id, command_type, payload_hash)` 返回幂等成功,不重复执行 mutate、不重复写 outbox。 +- 重复提交相同 `(room_id, command_id)` 但 `command_type` 或 payload hash 不一致,返回 `CONFLICT`。 +- 幂等返回可以携带当前最新 snapshot,但 `CommandResult.applied=false`,客户端按 snapshot `version` 修正 UI。 +- 业务 no-op 也必须写入幂等记录或 command log;例如重复 `SetChatEnabled(false)` 不能因为未递增版本而丢失幂等保护。 +- `request_id` 只用于追踪,可以每次重试不同;不能参与幂等判定。 + +### Lock Mic + +```json +{ + "room_id": "room_1001", + "command_id": "cmd_lock_1", + "seat_no": 3, + "locked": true +} +``` + +### Chat Enabled + +```json +{ + "room_id": "room_1001", + "command_id": "cmd_chat_off_1", + "enabled": false +} +``` + +### Set Admin + +```json +{ + "room_id": "room_1001", + "command_id": "cmd_admin_add_10002", + "target_user_id": 10002, + "enabled": true +} +``` + +### Transfer Host + +```json +{ + "room_id": "room_1001", + "command_id": "cmd_host_to_10002", + "target_user_id": 10002 +} +``` + +### Unban + +```json +{ + "room_id": "room_1001", + "command_id": "cmd_unban_10002", + "target_user_id": 10002 +} +``` + +## RoomState Changes + +短期最小变更: + +```go +type RoomState struct { + ... + HostUserID int64 + ChatEnabled bool + AdminUsers map[int64]bool + BanUsers map[int64]bool + MuteUsers map[int64]bool +} +``` + +这些字段已存在。 + +如果本阶段启用时长型禁言和封禁,内部状态替换为: + +```go +type UserModerationState struct { + UserID int64 + ExpiresAtMS int64 + OperatorUserID int64 + Reason string +} + +MuteUsers map[int64]UserModerationState +BanUsers map[int64]UserModerationState +``` + +开发策略: + +- snapshot 输出 `mute_user_ids`、`ban_user_ids`、`mute_states` 和 `ban_states` 时必须来自同一份状态。 +- 请求里没有 `duration_ms` 时按永久处理,不能依赖旧命令语义分支。 +- `CheckSpeakPermission`、`VerifyRoomPresence`、`JoinRoom` 和 `/api/v1/rtc/token` 都只看未过期的 ban/mute。 +- 如果本阶段不做限时 ban/mute,就不要追加 `duration_ms`、`mute_states`、`ban_states`;保留 bool set,避免半套协议。 + +## Authorization Design + +在 room-service 内新增统一权限 helper: + +```go +type roomRole string + +const ( + roleOwner roomRole = "owner" + roleHost roomRole = "host" + roleAdmin roomRole = "admin" + roleAudience roomRole = "audience" +) + +func actorRole(state *RoomState, actorUserID int64) roomRole +func canManageMic(state *RoomState, actorUserID int64, targetUserID int64) bool +func canManageModeration(state *RoomState, actorUserID int64, targetUserID int64) bool +func canManageAdmins(state *RoomState, actorUserID int64) bool +func canTransferHost(state *RoomState, actorUserID int64) bool +``` + +权限失败统一返回: + +```text +PERMISSION_DENIED +``` + +不要在错误信息里暴露过多角色细节,避免客户端据此枚举房间管理结构。服务端日志可以记录: + +```text +request_id, command_id, room_id, actor_user_id, target_user_id, action, actor_role, denied_reason +``` + +## RTC Push Linkage + +服务端不直接控制 TRTC 推流,但必须区分“业务占麦成功”和“RTC 已确认发布音频”: + +| Room State | Client RTC Behavior | +| --- | --- | +| 用户不在麦位 | TRTC role `audience`,不能发布音频 | +| `MicUp` 成功且 `SeatState.user_id == self`、`publish_state=pending_publish` | 客户端切 `anchor` 并 `startLocalAudio`,UI 展示为等待发流确认 | +| SDK 本地发流成功后调用 `ConfirmMicPublishing(mic_session_id, room_version, event_time_ms)` | 服务端把当前麦位切到 `publish_state=publishing` | +| `pending_publish` 超过 `publish_deadline_ms` 未确认 | 服务端自动 `MicDown(reason=publish_timeout)`,客户端停止上行并切回 `audience` | +| `MicDown`/`KickUser`/`LeaveRoom` 后麦位释放 | 客户端停止本地音频上行并切回 `audience` | +| `ChangeMicSeat` | 客户端只更新 UI,不需要重进 RTC | +| `SetMicSeatLock` | 客户端更新麦位 UI,不影响当前 RTC 连接 | + +后续接 RTC webhook 时,payload 必须带 `mic_session_id`、`room_version`、`event_time_ms`。room-service 只接受当前 session 且事件时间不旧于已接受事件的数据,旧 audience/leave 事件不能清理新的上麦会话。 + +`/api/v1/rtc/token` 首版仍返回 `audience`。后续如果要服务端返回 `anchor`,必须在签 token 时读取 Room Cell 麦位事实: + +```text +if user is on mic -> role=anchor +else -> role=audience +``` + +但不要把 RTC 在线态写入 Room Cell。 + +## Event Design + +新增或复用 room outbox 事件: + +| Event Type | Trigger | IM Event Type | +| --- | --- | --- | +| `RoomMicChanged` | MicUp/MicDown/ChangeMicSeat | `room_mic_up/down/change` | +| `RoomMicSeatLocked` | SetMicSeatLock | `room_mic_seat_locked` | +| `RoomChatEnabledChanged` | SetChatEnabled | `room_chat_enabled_changed` | +| `RoomAdminChanged` | SetRoomAdmin | `room_admin_changed` | +| `RoomHostTransferred` | TransferRoomHost | `room_host_transferred` | +| `RoomUserMuted` | MuteUser | `room_user_muted` | +| `RoomUserKicked` | KickUser | `room_user_kicked` | +| `RoomUserUnbanned` | UnbanUser | `room_user_unbanned` | + +### Event Payload Contract + +所有 room outbox 事件和腾讯 IM 系统消息必须使用同一套字段语义,避免客户端按不同来源写两套解析。 + +| Event Type | Required Payload | +| --- | --- | +| `RoomMicChanged` | `event_id`、`room_id`、`room_version`、`actor_user_id`、`target_user_id`、`from_seat`、`to_seat`、`action`、`mic_session_id`、`publish_state`、`reason`、`publish_deadline_ms`、`publish_event_time_ms` | +| `RoomMicSeatLocked` | `event_id`、`room_id`、`room_version`、`actor_user_id`、`seat_no`、`locked` | +| `RoomChatEnabledChanged` | `event_id`、`room_id`、`room_version`、`actor_user_id`、`enabled` | +| `RoomAdminChanged` | `event_id`、`room_id`、`room_version`、`actor_user_id`、`target_user_id`、`enabled` | +| `RoomHostTransferred` | `event_id`、`room_id`、`room_version`、`actor_user_id`、`old_host_user_id`、`new_host_user_id` | +| `RoomUserMuted` | `event_id`、`room_id`、`room_version`、`actor_user_id`、`target_user_id`、`muted`、`expires_at_ms`、`reason` | +| `RoomUserKicked` | `event_id`、`room_id`、`room_version`、`actor_user_id`、`target_user_id`、`expires_at_ms`、`reason` | +| `RoomUserUnbanned` | `event_id`、`room_id`、`room_version`、`actor_user_id`、`target_user_id` | + +事件要求: + +- `event_id` 由 outbox 生成。 +- `room_version` 必须等于命令提交后的版本。 +- 同步 IM publish 失败不回滚命令。 +- outbox worker 后续补投。 +- 客户端按 `event_id` 去重,按 `room_version` 判断是否需要拉最新 snapshot。 +- 客户端不能把 `room_mic_up` 当成音频已发布,只能在 `publish_state=publishing` 后展示为真正发流中。 +- IM 系统消息只是 UI 通知;KickUser 的群成员踢出是独立 side effect,必须由 outbox/IM bridge 可重试执行。 + +### IM Side Effects + +`KickUser`/ban 成功提交后,需要两个层面的外部效果: + +| Side Effect | Required Behavior | +| --- | --- | +| 房间系统消息 | 发布 `room_user_kicked`,让客户端立即关闭麦位、弹出提示、停止 RTC 上行 | +| 腾讯 IM 群成员移除 | 调用腾讯 IM REST 把 target 从房间群移出,避免继续收公屏消息 | +| 腾讯 IM 失败补偿 | 不回滚 Room Cell;outbox 保留待处理记录,worker 重试直到成功或进入人工告警 | +| 失败窗口守卫 | IM 移除成功前,`CheckSpeakPermission` 继续拒绝发言,`VerifyRoomPresence` 继续拒绝进群 | + +首版不在 room-service 管理客户端 socket;如果腾讯 IM REST 持续失败,服务端安全边界是“不能发言、不能重新进群、不能拿 RTC token”,但可能短时间继续收到群消息,需要告警和补偿。 + +## Recovery Replay + +每个新增命令都必须加入: + +- `command` 模型。 +- `command.Serialize/Deserialize`。 +- `replay` 分支。 +- command log 测试。 + +恢复语义: + +| Command | Replay Behavior | +| --- | --- | +| `SetMicSeatLock` | 设置 `MicSeats[index].Locked` | +| `SetChatEnabled` | 设置 `ChatEnabled` | +| `SetRoomAdmin` | 增删 `AdminUsers[target]`,但 owner/host 保持 admin | +| `TransferRoomHost` | 更新 `HostUserID`,新 host 加入 `AdminUsers` | +| `UnbanUser` | 删除 `BanUsers[target]` | +| `MuteUser` with duration | 如果恢复时已过期,清理;否则保留 | +| `KickUser` with duration | 恢复 presence/mic/admin 移除结果;ban 未过期时保留,已过期时不恢复 ban | + +如果支持时长型禁言/封禁,需要有清理 worker 或在 guard 查询时懒清理过期状态。首版建议 guard 查询时懒判断: + +```text +if expires_at_ms > 0 && now >= expires_at_ms -> treat as not muted/banned +``` + +是否立即写回清理结果可以后续由 moderation sweep worker 完成。 + +恢复时不要重新执行腾讯 IM 踢人副作用;IM side effect 只从 outbox 待投递记录补偿,不从 command replay 直接调用外部服务。 + +## Validation Rules + +| Input | Rule | Error | +| --- | --- | --- | +| `seat_no` | 必须存在于当前房间麦位 | `INVALID_ARGUMENT` | +| `target_user_id` | 必须大于 0 | `INVALID_ARGUMENT` | +| actor | 必须来自 `RequestMeta.actor_user_id` | `UNAUTHENTICATED` or `INVALID_ARGUMENT` | +| room | 必须 active | `FAILED_PRECONDITION` | +| locked occupied seat | 首版拒绝 | `CONFLICT` | +| permission denied | actor 权限不足 | `PERMISSION_DENIED` | +| target is owner | host/admin 不能管理 owner | `PERMISSION_DENIED` | +| self kick/mute | 默认拒绝 | `INVALID_ARGUMENT` or `PERMISSION_DENIED` | +| `command_id` duplicate with different payload | 同房间同 command_id 不能复用不同 payload | `CONFLICT` | +| `duration_ms` | `>=0`,上限按配置限制,首版建议不超过 30 天 | `INVALID_ARGUMENT` | +| `reason` | trim 后最多 128 字符,不写入客户端不可见敏感信息 | `INVALID_ARGUMENT` | +| target is current host | 首版不能直接 KickUser,owner 需先转 host | `PERMISSION_DENIED` | + +## Implementation Order + +1. 新增 room-service 权限 helper 和单测,先接入现有 `MicDown/ChangeMicSeat/MuteUser/KickUser`。 +2. 补齐 command_id payload hash 校验,确保重复 command_id 不会静默吞掉不同请求。 +3. 追加 proto:`SetMicSeatLock`、`SetChatEnabled`、`SetRoomAdmin`、`TransferRoomHost`、`UnbanUser`;如果本阶段启用限时管控,再追加 moderation state 和 duration 字段。 +4. 运行 `make proto`,提交生成的 `.pb.go` 和 `_grpc.pb.go`。 +5. 扩展 gateway room client、HTTP handler 和 OpenAPI。 +6. 新增 command 模型和 `Deserialize` 分支。 +7. 在 room-service 实现新增命令,全部走 `mutateRoom`。 +8. 新增 room events,接入 outbox 和腾讯 IM system event。 +9. 为 KickUser 接入腾讯 IM 群成员移除 side effect,失败不回滚命令但必须由 outbox worker 补偿。 +10. 扩展 recovery replay,确保 replay 不直接调用外部 IM。 +11. 补全单元测试、gateway transport 测试和 outbox/IM bridge 补偿测试。 +12. 客户端联调麦位 UI、系统消息和 RTC 推流切换。 + +## Test Plan + +### Permission Tests + +| Case | Expected | +| --- | --- | +| audience 下别人麦 | `PERMISSION_DENIED` | +| audience 自己下麦 | success | +| admin 下 audience 麦 | success | +| admin 下 owner 麦 | `PERMISSION_DENIED` | +| host 禁言 audience | success | +| admin 禁言 host | `PERMISSION_DENIED` | +| owner 添加 admin | success | +| host 添加 admin | `PERMISSION_DENIED` | +| admin 解封用户 | `PERMISSION_DENIED` | +| host 解封用户 | success | + +### Mic Tests + +| Case | Expected | +| --- | --- | +| 锁空麦 | `SeatState.locked=true`,写事件 | +| 重复锁同一空麦 | 幂等返回,不重复事件 | +| 锁 occupied 麦 | `CONFLICT` | +| 解锁已锁麦 | `SeatState.locked=false` | +| locked seat MicUp | `PERMISSION_DENIED` | +| locked seat ChangeMicSeat | `PERMISSION_DENIED` | +| owner 上 locked seat | `PERMISSION_DENIED`,必须先解锁 | + +### Chat Tests + +| Case | Expected | +| --- | --- | +| SetChatEnabled false | snapshot `chat_enabled=false` | +| chat disabled 后发言守卫 | `allowed=false, reason=chat_disabled` | +| SetChatEnabled true | 发言守卫恢复正常 | + +### Admin And Host Tests + +| Case | Expected | +| --- | --- | +| owner 添加 admin | `admin_user_ids` 包含 target | +| owner 移除 admin | `admin_user_ids` 不包含 target | +| 移除 owner admin | 拒绝 | +| TransferRoomHost | `host_user_id` 更新,新 host 在 admin 集合 | +| 新 host 管理麦位 | success | +| admin 离房后重进 | admin 权限保留 | +| Kick admin | admin 身份被移除,Unban 后不自动恢复 | +| Kick current host | `PERMISSION_DENIED`,必须先转 host | + +### Ban And Unban Tests + +| Case | Expected | +| --- | --- | +| KickUser 后 JoinRoom | `PERMISSION_DENIED` | +| KickUser 后 VerifyRoomPresence | `present=false, reason=user_banned` | +| KickUser 后 CheckSpeakPermission | `allowed=false, reason=user_banned` | +| KickUser 后 IM side effect | outbox/IM bridge 触发群成员移除 | +| IM side effect 失败 | Room Cell 不回滚,outbox 保留重试 | +| 限时 KickUser 过期后 JoinRoom | success | +| UnbanUser 后 JoinRoom | success | +| 重复 UnbanUser | 幂等 | + +### Idempotency Tests + +| Case | Expected | +| --- | --- | +| 相同 command_id 相同 payload 重试 | 不重复 mutate,不重复 outbox,返回幂等结果 | +| 相同 command_id 不同 payload | `CONFLICT` | +| no-op 命令重试 | 不因未递增版本而丢失幂等保护 | + +### Event Payload Tests + +| Case | Expected | +| --- | --- | +| 每类 room outbox event | 包含文档 payload 矩阵要求字段 | +| IM system event | 与 outbox payload 字段语义一致 | +| 客户端重复收到同一 event_id | 去重后 UI 不重复变更 | + +### Recovery Tests + +| Case | Expected | +| --- | --- | +| snapshot 后回放 SetMicSeatLock | locked 状态恢复 | +| snapshot 后回放 SetChatEnabled | chat_enabled 恢复 | +| snapshot 后回放 SetRoomAdmin | admin 集合恢复 | +| snapshot 后回放 TransferRoomHost | host 恢复 | +| snapshot 后回放 UnbanUser | ban 集合恢复 | + +## Acceptance Criteria + +- 权限矩阵覆盖所有麦位和房间管理命令。 +- 普通用户不能管理他人麦位、禁言、踢人、锁麦或开关公屏。 +- owner 可以添加管理员、转 host、管理所有麦位。 +- 锁麦命令可用,locked 麦位对所有角色都不能上麦或换入。 +- 关闭公屏后腾讯 IM 发言回调被拒绝,打开后恢复。 +- 踢人后用户不能 JoinRoom、不能 IM 进群、不能发言、不能拿 RTC token。 +- 踢人后 outbox/IM bridge 会补偿执行腾讯 IM 群成员移除;副作用失败不回滚 Room Cell。 +- 解封后用户可以重新 JoinRoom,但不会自动恢复 presence。 +- 解封后不会自动恢复 admin、host、麦位或 RTC 推流状态。 +- 相同 command_id 不同 payload 会被拒绝,no-op 管理命令也具备幂等保护。 +- 新增管理动作全部写 command log、snapshot、outbox,并能恢复。 +- 同步 IM 失败不回滚房间状态,outbox worker 后续补投。 +- 客户端可通过系统消息和 snapshot 正确更新麦位、禁言、踢人、聊天开关和管理员 UI。 + +## Remaining Decisions + +- 是否本阶段启用限时 mute/ban;如果不开启,就不要追加 `duration_ms` 和 `mute_states/ban_states`。 +- 上麦后 RTC role 首版由客户端自行切换;gateway RTC token 返回 `anchor` 需要后续读取 Room Cell 麦位事实后再做。 diff --git a/docs/wallet-service-architecture.md b/docs/wallet-service-architecture.md new file mode 100644 index 00000000..b431df73 --- /dev/null +++ b/docs/wallet-service-architecture.md @@ -0,0 +1,480 @@ +# Wallet Service Architecture + +本文定义钱包服务的目标架构、账务模型、核心流程和落地顺序。当前 `wallet-service` 只有单币种 `coin` 扣费能力,新需求需要升级为多资产账户、外部支付订单、币商充值、主播奖励和提现审核的一体化账务边界。 + +## Goals + +- `wallet-service` 是所有用户资产余额、流水、幂等和提现冻结的唯一 owner。 +- `room-service` 只同步调用钱包完成送礼扣费和主播积分入账,不直接修改余额。 +- MQ 只消费钱包 outbox 事件,不作为余额事实来源。 +- Redis 只能做读缓存、风控计数或短期状态,不能作为余额、充值或提现恢复来源。 +- 所有资金和虚拟资产使用整数最小单位,不能使用浮点数。 + +## Asset Model + +| Asset | Meaning | Spendable | Withdrawable | Owner | +| --- | --- | --- | --- | --- | +| `COIN` | 金币,用户送礼消费资产 | yes | no | wallet-service | +| `DIAMOND` | 钻石,不可直接消费,只能按政策兑换金币或美元余额 | no | no | wallet-service | +| `GIFT_POINT` | 主播礼物积分,达到政策后结算美元奖励 | no | no | wallet-service | +| `USD_BALANCE` | 主播可提现美元余额,建议单位为 cent 或 micro cent | no | yes | wallet-service | + +钻石和积分都不是直接消费资产。钻石兑换金币或美元余额时必须落兑换订单,记录政策版本、汇率和结果。主播积分达到政策后,由结算任务发起美元余额奖励入账,用户提现只能提现 `USD_BALANCE`。 + +## Current Implementation Snapshot + +| Capability | Status | Current Evidence | Gap | +| --- | --- | --- | --- | +| `DebitGift` RPC | `DONE` | `DebitGift` 只接收礼物 ID、数量和可选价格版本,返回 `transaction_id/coin_spent/gift_point_added/heat_value/price_version` | 充值、兑换、提现仍需新增 RPC 和状态机 | +| 多资产账户 | `PARTIAL` | `wallet_accounts(user_id,asset_type,available_amount,frozen_amount,version)` | 已能表达冻结余额;提现冻结/解冻/出账命令未落地 | +| 交易和分录 | `DONE` | 交易写 `wallet_transactions`、`wallet_entries`,不保留旧流水表 | 充值、兑换、提现的分录类型仍需补齐 | +| 命令幂等 | `DONE` | `wallet_transactions.command_id` + `request_hash` | 重复 provider receipt 也要幂等 | +| 余额不足保护 | `DONE` | MySQL 事务锁 sender account,余额不足失败 | 需要并发压测覆盖多资产和冻结余额 | +| 礼物价格 | `DONE` | `wallet_gift_prices` 提供服务端 `coin_price/gift_point_amount/heat_value`,客户端不提交礼物单价 | 后台礼物价格配置和上下架入口未落地 | +| 充值 | `TODO` | 无 payment order 和 provider 校验 | Apple、Google、线下、币商库存发放都未实现 | +| 币商转金币 | `TODO` | 无 merchant account/transfer | 需要币商余额、限额、审计和用户入账 | +| 钻石兑换 | `TODO` | 无 diamond exchange order | 需要政策快照、兑换状态机和原子出入账 | +| 主播积分/奖励 | `PARTIAL` | 送礼实时给收礼人加 `GIFT_POINT` | 周期结算任务和 `USD_BALANCE` 奖励入账未落地 | +| 提现 | `TODO` | 无 withdraw request | 需要冻结、审核、人工打款、失败回滚 | +| 钱包 outbox | `PARTIAL` | `wallet_outbox` 已和交易同事务写入 `WalletBalanceChanged/WalletGiftDebited` | outbox worker 和 MQ 投递适配未落地 | + +这张表是需求边界,不是上线状态承诺。后续开发必须先补账务事实层,再补 HTTP 管理入口和活动联动。 + +## Requirement Scope + +### P0: Gift Billing Foundation + +- `DebitGift` 只接收服务端定价所需字段,不保留历史兼容字段。 +- 钱包按服务端礼物价格结算 `coin_spent`、`gift_point_added` 和 `heat_value`,客户端不能提交礼物单价。 +- 同一个事务内完成 sender `COIN` 扣减、target `GIFT_POINT` 入账、交易记录、分录和 outbox。 +- `room-service` 只消费钱包返回的已结算值更新房间热度、本地榜和房间 outbox。 +- 扣费失败时 `room-service` 不进入 Room Cell 状态变更。 + +### P1: Balance And Admin Operations + +- 增加多资产余额查询,客户端只能看到自己的余额,后台可以按权限查询用户资产和流水。 +- 增加后台手动充值、扣减和调账命令,所有操作必须记录操作人、原因、凭证和审批来源。 +- 增加账务对账查询:按用户、资产、交易、外部订单和时间窗口查询 transaction/entry。 +- 任何后台命令都必须有独立 `command_id`,重复提交返回原结果,不允许重复入账。 + +### P2: Provider Recharge + +- Apple/Google 充值必须落 `payment_orders`,订单状态和账务入账不能只依赖客户端回调。 +- provider receipt、transaction id、purchase token 必须全局唯一,重复通知只能返回原订单结果。 +- provider 校验失败不能产生资产分录,校验成功和资产入账必须在同一个 MySQL 事务提交。 +- 商品配置必须是服务端资产包配置,客户端传入的商品 ID 只能用于匹配,不决定到账金额。 + +### P3: Merchant And Exchange + +- 币商库存是币商自己的 `COIN` 账户余额,币商给用户转币必须先扣币商再加用户。 +- 币商转账需要限额、风控状态、线下订单号、目标用户和审计日志。 +- 钻石兑换必须生成兑换订单,记录政策版本和汇率快照,兑换成功原子扣 `DIAMOND` 并加目标资产。 +- 兑换失败不能出现单边扣减,重复兑换命令必须返回同一兑换订单结果。 + +### P4: Anchor Reward And Withdraw + +- 送礼实时加主播 `GIFT_POINT`,但美元奖励由结算任务按政策转换,不能写死在送礼链路。 +- 奖励结算生成 `anchor_reward_settlements`,确认后加主播 `USD_BALANCE`。 +- 提现申请必须先冻结 `USD_BALANCE`,审核拒绝解冻,审核通过后进入人工打款流程。 +- 人工打款完成后从冻结余额出账;打款失败回到可重试状态,不能自动解冻后丢失审核上下文。 + +## Component Diagram + +```mermaid +graph LR + Client["Client"] --> Gateway["gateway-service"] + Gateway --> Room["room-service"] + Gateway --> Wallet["wallet-service"] + Gateway --> Admin["admin/ops API"] + + Room -->|"DebitGift gRPC"| Wallet + Admin -->|"manual recharge / withdraw review"| Wallet + Merchant["coin merchant"] -->|"merchant transfer API"| Gateway + Apple["Apple IAP"] -->|"receipt / callback"| Gateway + Google["Google Play Billing"] -->|"receipt / callback"| Gateway + + Wallet --> MySQL[("wallet MySQL")] + Wallet --> Outbox[("wallet_outbox")] + Outbox --> MQ["MQ / event bus"] + MQ --> Activity["activity-service / settlement jobs"] + MQ --> BI["risk / audit / analytics"] + + Wallet --> RoomEvent["billing result"] + Room --> TencentIM["Tencent IM room message"] +``` + +## Ledger Design + +钱包不要继续使用“单账户余额表 + 单条流水”承载所有场景。目标模型是账户表记录余额,交易表记录业务命令,分录表记录每个账户的资产变化。 + +核心表建议: + +```sql +wallet_accounts( + user_id BIGINT NOT NULL, + asset_type VARCHAR(32) NOT NULL, + available_amount BIGINT NOT NULL, + frozen_amount BIGINT NOT NULL, + version BIGINT NOT NULL, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (user_id, asset_type) +); + +wallet_transactions( + transaction_id VARCHAR(96) NOT NULL PRIMARY KEY, + command_id VARCHAR(128) NOT NULL, + biz_type VARCHAR(64) NOT NULL, + status VARCHAR(32) NOT NULL, + request_hash VARCHAR(128) NOT NULL, + external_ref VARCHAR(128) NOT NULL DEFAULT '', + created_at_ms BIGINT NOT NULL, + UNIQUE KEY uk_wallet_tx_command (command_id) +); + +wallet_entries( + entry_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + transaction_id VARCHAR(96) NOT NULL, + user_id BIGINT NOT NULL, + asset_type VARCHAR(32) NOT NULL, + available_delta BIGINT NOT NULL, + frozen_delta BIGINT NOT NULL, + available_after BIGINT NOT NULL, + frozen_after BIGINT NOT NULL, + counterparty_user_id BIGINT NOT NULL DEFAULT 0, + room_id VARCHAR(96) NOT NULL DEFAULT '', + created_at_ms BIGINT NOT NULL, + KEY idx_wallet_entries_user_time (user_id, created_at_ms), + KEY idx_wallet_entries_tx (transaction_id) +); +``` + +交易和分录的核心约束: + +- `wallet_transactions.command_id` 是钱包命令幂等键,同一个命令重复提交必须返回同一结果。 +- `wallet_transactions.request_hash` 覆盖业务语义,同一个 `command_id` 携带不同 payload 必须返回冲突。 +- `wallet_entries` 是资产事实的审计分录,原则上只追加不修改;余额修正也要通过新交易和新分录表达。 +- `wallet_entries.available_delta` 和 `frozen_delta` 必须同时表达一笔资产变化,提现冻结是 available 负数、frozen 正数,打款完成是 frozen 负数。 +- `wallet_accounts.version` 用于并发更新保护,写路径仍以 MySQL 行锁和事务为准。 +- `available_amount + frozen_amount` 是用户某资产的总负债,不能出现负数,不能由 Redis 恢复。 + +支付、兑换、提现使用独立状态表承载业务状态机: + +- `payment_orders`: Apple、Google、运营给币商发币和线下充值记录。 +- `merchant_coin_transfers`: 币商给用户转金币的交易申请和结果。 +- `diamond_exchange_orders`: 钻石兑换金币或美元余额的政策快照。 +- `anchor_reward_settlements`: 主播积分达到政策后的美元奖励结算单。 +- `withdraw_requests`: 主播提现申请、审核、驳回、人工打款和完成状态。 +- `wallet_outbox`: 余额变化、充值成功、提现状态变化等事件投递源。 + +业务状态表必须把外部唯一键落到数据库约束里。Apple/Google 使用 provider transaction id 或 purchase token 唯一约束,币商转账使用 merchant order id + merchant user id 唯一约束,提现使用 withdraw request id 唯一约束。 + +礼物价格需要单独的服务端账务配置,不能由房间命令携带价格决定: + +```sql +wallet_gift_prices( + gift_id VARCHAR(96) NOT NULL, + price_version VARCHAR(64) NOT NULL, + status VARCHAR(32) NOT NULL, + coin_price BIGINT NOT NULL, + gift_point_amount BIGINT NOT NULL, + heat_value BIGINT NOT NULL, + effective_at_ms BIGINT NOT NULL, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (gift_id, price_version), + KEY idx_wallet_gift_prices_effective (gift_id, status, effective_at_ms) +); +``` + +`wallet_gift_prices` 只承载账务结算字段。礼物名称、图标、动画、排序、客户端资源可以放在独立礼物配置或后台配置里,但钱包落账时必须记录 `gift_id`、`price_version`、`coin_price`、`gift_point_amount` 和 `heat_value` 快照。 + +## Send Gift Flow + +```mermaid +sequenceDiagram + participant C as Client + participant G as gateway-service + participant R as room-service + participant W as wallet-service + participant DB as wallet MySQL + participant IM as Tencent IM + + C->>G: send gift + G->>R: SendGift(command_id, room_id, gift_id, count) + R->>R: Room Cell validates room/user/target + R->>W: DebitGift(command_id, payer, anchor, gift) + W->>DB: tx lock payer COIN account + W->>DB: debit COIN, credit GIFT_POINT, write tx/entries/outbox + W-->>R: receipt, coin_spent, point_added, balance_after + R->>R: commit heat/rank/contribution/snapshot/outbox + R-->>G: room snapshot + R->>IM: gift room message +``` + +`DebitGift` 应该由钱包按服务端礼物配置计算最终金币扣费和主播积分。客户端不提交礼物单价,`room-service` 只能使用钱包返回的已结算值;房间恢复也只使用 command log 中保存的 `heat_value`,不能重新推导价格。 + +送礼账务要求: + +- `sender_user_id` 和 `target_user_id` 必须不同;如果产品允许给自己送礼,需要单独产品规则和风控开关。 +- `gift_count` 必须设置单次上限,防止大整数溢出和单请求绕过风控。 +- `coin_spent = coin_price * gift_count`,计算前要检查乘法溢出。 +- `gift_point_added = gift_point_amount * gift_count`,`heat_value = heat_value_per_gift * gift_count`。 +- 余额不足时不能写 `wallet_transactions` 的成功状态,不能写 `wallet_entries`,也不能写 outbox。 +- 扣费成功后必须返回 `billing_receipt_id`、`transaction_id`、`coin_spent`、`gift_point_added`、`heat_value`、`price_version` 和 sender `COIN` 账后余额。 + +## Recharge Flows + +Apple 和 Google: + +1. 客户端创建或购买商品,商品映射到固定资产包。 +2. 客户端提交 provider receipt 或 purchase token。 +3. `wallet-service` 调 provider 校验接口,确认未消费、金额、商品、环境和用户绑定。 +4. MySQL 事务内写 `payment_orders`、`wallet_transactions`、`wallet_entries` 和 `wallet_outbox`。 +5. 同一个 provider transaction id 必须全局唯一,重复回调只能返回原结果。 + +币商: + +1. 币商私下给平台转账后,运营后台创建 `merchant_recharge`,审核通过后给币商账户发放 `COIN`。 +2. 币商没有可提现余额入账,平台给币商发放的是可转给用户的金币库存。 +3. 用户私下给币商转账后,币商发起转金币命令,钱包在一个事务中扣币商 `COIN`,加用户 `COIN`。 +4. 币商给用户转多少 `COIN`,就代表这次用户通过币商完成了多少金币充值。 +5. 这类交易 `biz_type=merchant_transfer`,同时记录币商 ID、目标用户、线下订单号和操作来源。 +6. 币商不能直接改用户余额,所有发币和转币都必须走钱包命令和审计流水。 + +运营线下充值: + +1. 用户线下给平台转账后,运营后台创建 `manual_recharge`。 +2. 审核通过后钱包给用户加 `COIN`,并记录操作人、凭证、金额和备注。 +3. 手动充值必须有权限、二次确认和审计日志。 + +充值订单状态机: + +```mermaid +stateDiagram-v2 + [*] --> created + created --> verifying + verifying --> verified + verifying --> verify_failed + verified --> credited + credited --> [*] + verify_failed --> [*] +``` + +`credited` 是唯一代表资产已到账的终态。`verified` 只代表 provider 或后台审核通过,不代表余额已更新;从 `verified` 到 `credited` 必须和 `wallet_transactions`、`wallet_entries`、`wallet_outbox` 同事务提交。 + +## Diamond Exchange + +```mermaid +flowchart LR + A["User DIAMOND account"] -->|"debit diamonds"| X["diamond_exchange_order"] + X -->|"policy: diamond to coin"| C["User COIN account"] + X -->|"policy: diamond to USD"| U["User USD_BALANCE account"] +``` + +钻石兑换必须冻结政策版本。订单里至少记录 `from_diamond_amount`、`to_asset_type`、`to_amount`、`policy_id`、`rate_snapshot` 和 `status`。兑换成功后写分录和 outbox;兑换失败不能出现钻石扣了但目标资产没到账。 + +## Anchor Reward And Withdraw + +主播通过礼物获得 `GIFT_POINT`。积分政策不要写死在送礼链路里,建议由结算任务按周期计算奖励: + +1. 送礼时钱包给主播增加 `GIFT_POINT`。 +2. 结算任务读取积分和政策,生成 `anchor_reward_settlements`。 +3. 结算单确认后调用钱包 `CreditRewardBalance`,增加主播 `USD_BALANCE`。 +4. 主播提现时先冻结 `USD_BALANCE`,审核拒绝解冻,审核通过后等待人工转账。 +5. 人工转账完成后把冻结余额出账,状态改为 `paid`。 + +提现状态机: + +```mermaid +stateDiagram-v2 + [*] --> pending_review + pending_review --> rejected + rejected --> refunded + pending_review --> approved + approved --> paying + paying --> paid + paying --> pay_failed + pay_failed --> approved +``` + +提现申请时必须从 `available_amount` 转到 `frozen_amount`,不能只写一个待审核单。否则审核期间用户可以重复提现同一笔余额。 + +## RPC Surface + +当前开发阶段直接使用新的 wallet RPC 契约: + +- `GetBalances`: 查询用户多资产余额。 +- `DebitGift`: 扣金币、加主播积分,返回已结算值。 +- `CreatePaymentOrder`: 创建 Apple/Google 商品订单。 +- `ConfirmProviderPayment`: 校验 provider receipt 并入账。 +- `AdminCreditAsset`: 运营手动充值或调账,只开放后台。 +- `MerchantTransferCoin`: 币商转金币给用户。 +- `ExchangeDiamond`: 钻石兑换金币或美元余额。 +- `CreditRewardBalance`: 结算任务给主播发美元余额奖励。 +- `CreateWithdrawRequest`: 主播提现申请并冻结余额。 +- `ReviewWithdrawRequest`: 后台审核提现。 +- `MarkWithdrawPaid`: 人工打款后确认出账。 + +外部 HTTP 入口仍在 `gateway-service`,内部统一 gRPC + protobuf。当前处于开发阶段,不做历史兼容保留;修改 `api/proto` 后必须运行 `make proto` 和 `go test ./...`。 + +### RPC Field Requirements + +`DebitGift` 字段要求: + +- Request: `price_version` 可选;为空时钱包选择当前生效价格;传入时必须命中有效价格。 +- Response: `transaction_id`,用于内部账务查询。 +- Response: `coin_spent`,实际扣减的 `COIN`。 +- Response: `gift_point_added`,实际增加给收礼人的 `GIFT_POINT`。 +- Response: `heat_value`,room-service 更新房间热度的唯一可信值。 +- Response: `price_version`,这次账务使用的礼物价格版本。 +- Response: `balance_after`,语义限定为 sender `COIN available_amount`。 + +新 RPC 的共同字段要求: + +- Request 必须包含 `command_id`,后台命令还必须包含 `operator_user_id`、`reason`、`evidence_ref`。 +- Response 必须包含稳定业务 ID,例如 `transaction_id`、`payment_order_id`、`withdraw_request_id`。 +- 查询 RPC 可以不需要 `command_id`,但必须透传 gateway 的 `request_id` 用于追踪。 +- 错误码必须能被 gateway 映射成稳定 HTTP envelope,不能只返回自由文本。 + +## HTTP And Admin Requirements + +外部 HTTP 仍只放在 `gateway-service`,wallet-service 不直接暴露公网 HTTP。 + +客户端接口: + +- `GET /api/v1/wallet/balances`: 查询当前用户多资产余额。 +- `GET /api/v1/wallet/transactions`: 查询当前用户流水,支持 asset、biz_type、时间和游标分页。 +- `POST /api/v1/wallet/payments/apple/confirm`: 提交 Apple receipt 或 transaction id。 +- `POST /api/v1/wallet/payments/google/confirm`: 提交 Google purchase token。 +- `POST /api/v1/wallet/diamond/exchange`: 发起钻石兑换。 +- `POST /api/v1/wallet/withdrawals`: 主播发起提现。 +- `GET /api/v1/wallet/withdrawals`: 查询提现记录。 + +后台接口: + +- 用户资产查询和流水查询。 +- 手动充值、扣减和调账。 +- 币商账户开通、禁用、库存发放、转账查询。 +- 提现审核、打款标记、打款失败重试。 +- 礼物账务价格配置和上下架。 +- 兑换政策、奖励政策和限额配置。 + +后台所有写接口必须记录操作人、来源 IP、原因、工单或凭证引用。没有审计字段的后台写操作不允许落地。 + +## Error Semantics + +钱包错误要区分“用户可处理”和“系统需重试”: + +| Error | Meaning | Retry | +| --- | --- | --- | +| `INSUFFICIENT_BALANCE` | 可用余额不足 | no | +| `ACCOUNT_FROZEN` | 账户被风控冻结 | no | +| `GIFT_NOT_FOUND` | 礼物账务配置不存在 | no | +| `GIFT_OFF_SHELF` | 礼物不可购买 | no | +| `REQUEST_CONFLICT` | 同 `command_id` 不同 payload | no | +| `PAYMENT_VERIFY_FAILED` | provider 校验失败 | no | +| `PROVIDER_DUPLICATE` | provider 交易已处理 | return original | +| `WITHDRAW_AMOUNT_INVALID` | 提现金额或门槛不满足 | no | +| `LEDGER_BUSY` | 同账户并发冲突或锁等待超时 | yes | +| `LEDGER_INTERNAL` | 数据库或内部未知错误 | yes | + +`room-service` 调 `DebitGift` 时,只能在明确可重试错误上按上层策略重试。余额不足、礼物下架、请求冲突不能重试成重复扣费。 + +## Outbox Events + +`wallet_outbox` 必须和账务交易同事务写入。事件消费者失败不能回滚账务事实,只能通过 outbox worker 补投。 + +建议事件: + +- `WalletBalanceChanged`: 任意资产 available/frozen 变化。 +- `WalletGiftDebited`: 送礼扣费成功,包含 sender、target、room、gift、price_version、coin_spent、gift_point_added。 +- `PaymentCredited`: provider 或后台充值到账。 +- `MerchantCoinTransferred`: 币商给用户转金币成功。 +- `DiamondExchanged`: 钻石兑换成功。 +- `RewardBalanceCredited`: 主播奖励美元余额入账。 +- `WithdrawFrozen`: 提现申请冻结成功。 +- `WithdrawPaid`: 人工打款完成并出账。 +- `WithdrawRejected`: 审核拒绝并解冻。 + +事件 payload 必须包含 `event_id`、`transaction_id`、`command_id`、`user_id`、`asset_type`、`available_delta`、`frozen_delta`、`created_at_ms`。消费者必须按 `event_id` 或 `transaction_id + event_type` 幂等。 + +## Reconciliation And Observability + +- 每日对账任务按 `wallet_entries` 汇总校验 `wallet_accounts.available_amount/frozen_amount`。 +- Provider 充值需要按 provider 后台交易和本地 `payment_orders` 做差异对账。 +- 提现需要按 `withdraw_requests` 状态、冻结余额和人工打款记录做对账。 +- 关键指标:扣费成功率、余额不足数、幂等命中数、请求冲突数、provider 校验失败数、outbox pending 数、outbox 重试次数、提现冻结金额。 +- 关键日志必须带 `request_id`、`command_id`、`transaction_id`、`user_id`、`biz_type`,但不能输出 provider receipt、支付凭证全文和敏感证件信息。 + +## Development Plan + +1. 保持当前干净账本表:`wallet_accounts`、`wallet_transactions`、`wallet_entries`、`wallet_outbox`、`wallet_gift_prices`。 +2. `DebitGift` 只走 v2 账本和服务端礼物价格,不保留客户端单价字段。 +3. 增加后台礼物价格配置、上下架和审计入口。 +4. 增加充值、兑换、提现订单表和状态机。 +5. 增加钱包 outbox worker 和 MQ 投递适配。 + +## Acceptance Criteria + +账务基础: + +- 同一 `command_id`、同一 payload 重复调用 `DebitGift`,余额只变化一次,返回同一 `billing_receipt_id`。 +- 同一 `command_id`、不同 payload 调用 `DebitGift`,返回 `REQUEST_CONFLICT`,余额不变。 +- 并发多个扣费请求打同一个 sender 账户,不出现负数余额。 +- 余额不足时没有成功交易、没有分录、没有 outbox。 + +送礼: + +- 钱包按 `wallet_gift_prices` 计算扣费、积分和热度,客户端没有礼物单价输入。 +- 扣费成功后 sender `COIN` 减少,target `GIFT_POINT` 增加,room-service 使用钱包返回 `heat_value` 更新房间热度。 +- 腾讯 IM 或 room outbox 投递失败不影响钱包交易事实。 + +充值和兑换: + +- 同一个 Apple/Google provider transaction 重复确认,只返回原到账结果。 +- provider 校验失败不会产生资产分录。 +- 币商转金币在一个事务中扣币商、加用户,任一侧失败则整体失败。 +- 钻石兑换成功后扣 `DIAMOND` 和加目标资产是原子结果。 + +提现: + +- 提现申请成功后 `USD_BALANCE.available_amount` 减少,`frozen_amount` 增加。 +- 审核拒绝后冻结金额回到 available。 +- 打款完成后 frozen 减少并写出账分录。 +- 打款失败后状态可重试,不丢失冻结关系和审核上下文。 + +验证命令: + +```bash +make proto +go test ./services/wallet-service/... +go test ./services/room-service/... +go test ./services/gateway-service/... +docker compose config +``` + +涉及 Docker、配置或 provider mock 时再补 `docker compose build` 和实际 `docker compose up -d` 冒烟。 + +## Implementation Order + +1. 新增 v2 账本表:`wallet_accounts` 扩展为多资产 available/frozen,新增 `wallet_transactions`、`wallet_entries`、`wallet_outbox`。 +2. 重构 repository:提供通用 `ApplyTransaction`,在一个 MySQL 事务中完成幂等、锁账户、写分录、更新余额。 +3. 保持现有 `DebitGift` RPC 可用,内部改为 `COIN` 扣费和 `GIFT_POINT` 入账。 +4. 增加余额查询和管理端手动充值,先打通运营可测链路。 +5. 增加 Apple/Google 订单和 receipt 校验,provider transaction id 做唯一约束。 +6. 增加币商账户、币商转账和限额审计。 +7. 增加钻石兑换订单。 +8. 增加主播奖励结算和提现冻结/审核/人工打款闭环。 + +## Critical Rules + +- 任何入账、扣款、冻结、解冻、出账都必须写 transaction 和 entry,不能直接 update balance。 +- 幂等键必须覆盖业务语义,同一个 command_id 带不同 payload 必须返回冲突。 +- Apple/Google 充值以 provider 真实交易号为最终幂等源,不能只信客户端 request id。 +- 币商充值分两段:平台给币商发 `COIN` 库存,币商再把 `COIN` 转给用户;必须有币商金币余额、限额和审计。 +- `USD_BALANCE` 是可提现负债,调账和奖励发放需要更高权限和审计。 +- 兑换汇率和奖励政策必须记录快照,不能只存当前 policy id。 +- 提现人工打款前必须冻结余额,打款失败要回到可重新处理状态。 +- 钱包事件投递使用 outbox,MQ 投递失败不回滚账务事实。 +- 礼物价格和积分比例必须来自服务端配置,不能由客户端决定。 diff --git a/pkg/grpchealth/health.go b/pkg/grpchealth/health.go index 49aef4a0..2e7d45d7 100644 --- a/pkg/grpchealth/health.go +++ b/pkg/grpchealth/health.go @@ -51,7 +51,7 @@ func (s *Server) Check(ctx context.Context, req *healthpb.HealthCheckRequest) (* return &healthpb.HealthCheckResponse{Status: s.status(ctx)}, nil } -// Watch 定时推送 ready 状态变化,兼容支持 gRPC health watch 的服务发现系统。 +// Watch 定时推送 ready 状态变化,供支持 gRPC health watch 的服务发现系统消费。 func (s *Server) Watch(req *healthpb.HealthCheckRequest, stream healthgrpc.Health_WatchServer) error { if !s.supports(req.GetService()) { if err := stream.Send(&healthpb.HealthCheckResponse{Status: healthpb.HealthCheckResponse_SERVICE_UNKNOWN}); err != nil { diff --git a/pkg/roomid/roomid.go b/pkg/roomid/roomid.go index 0c30ac01..76882e35 100644 --- a/pkg/roomid/roomid.go +++ b/pkg/roomid/roomid.go @@ -2,11 +2,11 @@ package roomid const ( - // MaxStringIDLength keeps room_id compatible with Tencent IM GroupID and RTC strRoomId. + // MaxStringIDLength keeps room_id within both Tencent IM GroupID and RTC strRoomId limits. MaxStringIDLength = 48 ) -// ValidStringID reports whether value can be used as both internal room_id and RTC strRoomId. +// ValidStringID reports whether value can be used as the internal room_id and Tencent room identifier. func ValidStringID(value string) bool { if len(value) == 0 || len(value) > MaxStringIDLength { return false diff --git a/pkg/tencentim/rest_client.go b/pkg/tencentim/rest_client.go index 8a90221c..e9730f8c 100644 --- a/pkg/tencentim/rest_client.go +++ b/pkg/tencentim/rest_client.go @@ -24,6 +24,8 @@ const ( createGroupCommand = "v4/group_open_http_svc/create_group" // sendGroupMsgCommand is Tencent Cloud Chat REST command for group message sending. sendGroupMsgCommand = "v4/group_open_http_svc/send_group_msg" + // deleteGroupMemberCommand removes users from a Tencent Cloud Chat group. + deleteGroupMemberCommand = "v4/group_open_http_svc/delete_group_member" ) // RESTConfig contains Tencent Cloud Chat server-side REST API settings. @@ -156,6 +158,27 @@ func (c *RESTClient) PublishRoomEvent(ctx context.Context, event RoomEvent) erro return response.err() } +// DeleteRoomGroupMember removes a room user from the Tencent Cloud Chat group. +func (c *RESTClient) DeleteRoomGroupMember(ctx context.Context, roomID string, userID int64) error { + roomID = strings.TrimSpace(roomID) + if roomID == "" || userID <= 0 { + return fmt.Errorf("room group member removal is incomplete") + } + + request := deleteGroupMemberRequest{ + GroupID: roomID, + MemberToDelAccount: []string{FormatUserID(userID)}, + Silence: 1, + } + + var response restResponse + if err := c.post(ctx, deleteGroupMemberCommand, request, &response); err != nil { + return err + } + + return response.err() +} + // FormatUserID converts the internal immutable user_id to the Tencent Chat identifier. func FormatUserID(userID int64) string { // Decimal user_id is stable across services and avoids exposing mutable display_user_id. @@ -256,6 +279,13 @@ type sendGroupMsgRequest struct { FromAccount string `json:"From_Account,omitempty"` } +type deleteGroupMemberRequest struct { + GroupID string `json:"GroupId"` + MemberToDelAccount []string `json:"MemberToDel_Account"` + Silence int `json:"Silence,omitempty"` + Reason string `json:"Reason,omitempty"` +} + type messageElement struct { MsgType string `json:"MsgType"` MsgContent customMsgContent `json:"MsgContent"` diff --git a/pkg/tencentim/rest_client_test.go b/pkg/tencentim/rest_client_test.go index f15415bc..43154a31 100644 --- a/pkg/tencentim/rest_client_test.go +++ b/pkg/tencentim/rest_client_test.go @@ -93,6 +93,35 @@ func TestRESTClientPublishRoomEventBuildsCustomMessage(t *testing.T) { } } +// TestRESTClientDeleteRoomGroupMemberBuildsTencentRequest 锁定踢出 IM 群成员的 REST 请求。 +func TestRESTClientDeleteRoomGroupMemberBuildsTencentRequest(t *testing.T) { + var capturedPath string + var capturedBody map[string]any + client := newTestRESTClient(t, func(request *http.Request) (*http.Response, error) { + capturedPath = request.URL.Path + if err := json.NewDecoder(request.Body).Decode(&capturedBody); err != nil { + t.Fatalf("decode request body failed: %v", err) + } + + return okRESTResponse(), nil + }) + + if err := client.DeleteRoomGroupMember(context.Background(), "room-1001", 10002); err != nil { + t.Fatalf("DeleteRoomGroupMember failed: %v", err) + } + + if capturedPath != "/"+deleteGroupMemberCommand { + t.Fatalf("path mismatch: got %q", capturedPath) + } + members, ok := capturedBody["MemberToDel_Account"].([]any) + if !ok || len(members) != 1 || members[0] != "10002" { + t.Fatalf("unexpected member removal body: %+v", capturedBody) + } + if capturedBody["GroupId"] != "room-1001" || capturedBody["Silence"].(float64) != 1 { + t.Fatalf("unexpected group removal body: %+v", capturedBody) + } +} + func newTestRESTClient(t *testing.T, roundTrip roundTripFunc) *RESTClient { t.Helper() diff --git a/pkg/tencentrtc/token.go b/pkg/tencentrtc/token.go index cf0bc450..b2cdef08 100644 --- a/pkg/tencentrtc/token.go +++ b/pkg/tencentrtc/token.go @@ -67,7 +67,7 @@ func GenerateToken(cfg TokenConfig, userID int64, targetRoomID string, now time. } if !roomid.ValidStringID(targetRoomID) { // This guard protects package callers; gateway maps this case to INVALID_ARGUMENT first. - return TokenResult{}, fmt.Errorf("room_id is incompatible with tencent rtc") + return TokenResult{}, fmt.Errorf("room_id is invalid for tencent rtc") } rtcUserID := FormatUserID(userID) diff --git a/pkg/tencentrtc/token_test.go b/pkg/tencentrtc/token_test.go index d434b1a3..259bb61e 100644 --- a/pkg/tencentrtc/token_test.go +++ b/pkg/tencentrtc/token_test.go @@ -62,8 +62,7 @@ func TestGenerateTokenFailsClosed(t *testing.T) { } } -// TestGenerateTokenRejectsIncompatibleRoomID keeps historical dirty room IDs fail-closed. -func TestGenerateTokenRejectsIncompatibleRoomID(t *testing.T) { +func TestGenerateTokenRejectsInvalidRoomID(t *testing.T) { _, err := GenerateToken(TokenConfig{ Enabled: true, SDKAppID: 1400000000, @@ -73,6 +72,6 @@ func TestGenerateTokenRejectsIncompatibleRoomID(t *testing.T) { AppScene: AppSceneVoiceChatRoom, }, 10001, "room:1001", time.Unix(1_700_000_000, 0)) if err == nil { - t.Fatalf("GenerateToken should reject incompatible room_id") + t.Fatalf("GenerateToken should reject invalid room_id") } } diff --git a/scripts/print-compose-addresses.sh b/scripts/print-compose-addresses.sh index 41d9a373..cc425335 100755 --- a/scripts/print-compose-addresses.sh +++ b/scripts/print-compose-addresses.sh @@ -9,17 +9,17 @@ PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" cd "${PROJECT_ROOT}" # Docker Desktop and Linux Docker expose published ports differently. The -# fallback keeps the output useful before containers are fully healthy or when +# default keeps the output useful before containers are fully healthy or when # Docker is not running yet. published_port() { local service="$1" local container_port="$2" - local fallback_port="$3" + local default_port="$3" local published="" published="$(docker compose port "${service}" "${container_port}" 2>/dev/null | head -n 1 || true)" if [[ -z "${published}" ]]; then - printf '%s' "${fallback_port}" + printf '%s' "${default_port}" return fi @@ -71,14 +71,14 @@ print_row() { local scheme="$3" local container_host="$4" local container_port="$5" - local fallback_host_port="$6" + local default_host_port="$6" local path="${7:-}" local host_port="" local local_addr="" local lan_addr="" local docker_addr="" - host_port="$(published_port "${service}" "${container_port}" "${fallback_host_port}")" + host_port="$(published_port "${service}" "${container_port}" "${default_host_port}")" local_addr="$(format_address "${scheme}" "127.0.0.1" "${host_port}" "${path}")" lan_addr="$(format_address "${scheme}" "${LAN_IP}" "${host_port}" "${path}")" docker_addr="$(format_address "${scheme}" "${container_host}" "${container_port}" "${path}")" @@ -97,7 +97,7 @@ print_row "room-service" "gRPC" "grpc" "room-service" "13001" "13001" print_row "wallet-service" "gRPC" "grpc" "wallet-service" "13004" "13004" print_row "user-service" "gRPC" "grpc" "user-service" "13005" "13005" print_row "activity-service" "gRPC" "grpc" "activity-service" "13006" "13006" -print_row "mysql" "MySQL" "tcp" "mysql" "3306" "13306" +print_row "mysql" "MySQL" "tcp" "mysql" "3306" "23306" print_row "redis" "Redis" "tcp" "redis" "6379" "13379" printf '\nUse LOCAL from the host machine, HOST_LAN from another device on the same LAN, and DOCKER_NETWORK only between compose containers.\n' diff --git a/services/activity-service/configs/config.yaml b/services/activity-service/configs/config.yaml index 39274387..e03e085a 100644 --- a/services/activity-service/configs/config.yaml +++ b/services/activity-service/configs/config.yaml @@ -1,7 +1,7 @@ service_name: activity-service node_id: activity-local grpc_addr: ":13006" -mysql_dsn: "hyapp:hyapp@tcp(127.0.0.1:13306)/hy_activity?parseTime=true&charset=utf8mb4&loc=Local" +mysql_dsn: "hyapp:hyapp@tcp(127.0.0.1:23306)/hy_activity?parseTime=true&charset=utf8mb4&loc=Local" mysql_auto_migrate: false consumer: room_outbox_poll_interval_ms: 1000 diff --git a/services/activity-service/internal/config/config.go b/services/activity-service/internal/config/config.go index ccec7fb0..20fecccd 100644 --- a/services/activity-service/internal/config/config.go +++ b/services/activity-service/internal/config/config.go @@ -26,7 +26,7 @@ func Default() Config { ServiceName: "activity-service", NodeID: "activity-local", GRPCAddr: ":13006", - MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:13306)/hy_activity?parseTime=true&charset=utf8mb4&loc=Local", + MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hy_activity?parseTime=true&charset=utf8mb4&loc=Local", MySQLAutoMigrate: false, Consumer: ConsumerConfig{ RoomOutboxPollIntervalMs: 1000, diff --git a/services/gateway-service/internal/client/room_client.go b/services/gateway-service/internal/client/room_client.go index 12d9b22d..469ed462 100644 --- a/services/gateway-service/internal/client/room_client.go +++ b/services/gateway-service/internal/client/room_client.go @@ -15,8 +15,14 @@ type RoomClient interface { MicUp(ctx context.Context, req *roomv1.MicUpRequest) (*roomv1.MicUpResponse, error) MicDown(ctx context.Context, req *roomv1.MicDownRequest) (*roomv1.MicDownResponse, error) ChangeMicSeat(ctx context.Context, req *roomv1.ChangeMicSeatRequest) (*roomv1.ChangeMicSeatResponse, error) + ConfirmMicPublishing(ctx context.Context, req *roomv1.ConfirmMicPublishingRequest) (*roomv1.ConfirmMicPublishingResponse, error) + SetMicSeatLock(ctx context.Context, req *roomv1.SetMicSeatLockRequest) (*roomv1.SetMicSeatLockResponse, error) + SetChatEnabled(ctx context.Context, req *roomv1.SetChatEnabledRequest) (*roomv1.SetChatEnabledResponse, error) + SetRoomAdmin(ctx context.Context, req *roomv1.SetRoomAdminRequest) (*roomv1.SetRoomAdminResponse, error) + TransferRoomHost(ctx context.Context, req *roomv1.TransferRoomHostRequest) (*roomv1.TransferRoomHostResponse, error) MuteUser(ctx context.Context, req *roomv1.MuteUserRequest) (*roomv1.MuteUserResponse, error) KickUser(ctx context.Context, req *roomv1.KickUserRequest) (*roomv1.KickUserResponse, error) + UnbanUser(ctx context.Context, req *roomv1.UnbanUserRequest) (*roomv1.UnbanUserResponse, error) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*roomv1.SendGiftResponse, error) } @@ -73,6 +79,26 @@ func (c *grpcRoomClient) ChangeMicSeat(ctx context.Context, req *roomv1.ChangeMi return c.client.ChangeMicSeat(ctx, req) } +func (c *grpcRoomClient) ConfirmMicPublishing(ctx context.Context, req *roomv1.ConfirmMicPublishingRequest) (*roomv1.ConfirmMicPublishingResponse, error) { + return c.client.ConfirmMicPublishing(ctx, req) +} + +func (c *grpcRoomClient) SetMicSeatLock(ctx context.Context, req *roomv1.SetMicSeatLockRequest) (*roomv1.SetMicSeatLockResponse, error) { + return c.client.SetMicSeatLock(ctx, req) +} + +func (c *grpcRoomClient) SetChatEnabled(ctx context.Context, req *roomv1.SetChatEnabledRequest) (*roomv1.SetChatEnabledResponse, error) { + return c.client.SetChatEnabled(ctx, req) +} + +func (c *grpcRoomClient) SetRoomAdmin(ctx context.Context, req *roomv1.SetRoomAdminRequest) (*roomv1.SetRoomAdminResponse, error) { + return c.client.SetRoomAdmin(ctx, req) +} + +func (c *grpcRoomClient) TransferRoomHost(ctx context.Context, req *roomv1.TransferRoomHostRequest) (*roomv1.TransferRoomHostResponse, error) { + return c.client.TransferRoomHost(ctx, req) +} + func (c *grpcRoomClient) MuteUser(ctx context.Context, req *roomv1.MuteUserRequest) (*roomv1.MuteUserResponse, error) { return c.client.MuteUser(ctx, req) } @@ -81,6 +107,10 @@ func (c *grpcRoomClient) KickUser(ctx context.Context, req *roomv1.KickUserReque return c.client.KickUser(ctx, req) } +func (c *grpcRoomClient) UnbanUser(ctx context.Context, req *roomv1.UnbanUserRequest) (*roomv1.UnbanUserResponse, error) { + return c.client.UnbanUser(ctx, req) +} + func (c *grpcRoomClient) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*roomv1.SendGiftResponse, error) { return c.client.SendGift(ctx, req) } diff --git a/services/gateway-service/internal/transport/http/auth_handler.go b/services/gateway-service/internal/transport/http/auth_handler.go index f8c33582..eefe8225 100644 --- a/services/gateway-service/internal/transport/http/auth_handler.go +++ b/services/gateway-service/internal/transport/http/auth_handler.go @@ -75,7 +75,6 @@ func (h *Handler) loginThirdParty(writer http.ResponseWriter, request *http.Requ Avatar string `json:"avatar"` Birth string `json:"birth"` AppVersion string `json:"app_version"` - AppVersionTypo string `json:"app_verison"` BuildNumber string `json:"build_number"` Source string `json:"source"` InstallChannel string `json:"install_channel"` @@ -112,7 +111,7 @@ func (h *Handler) loginThirdParty(writer http.ResponseWriter, request *http.Requ OsVersion: body.OSVersion, Avatar: body.Avatar, Birth: body.Birth, - AppVersion: firstNonEmptyString(body.AppVersion, body.AppVersionTypo), + AppVersion: body.AppVersion, BuildNumber: body.BuildNumber, Source: body.Source, InstallChannel: body.InstallChannel, @@ -235,17 +234,6 @@ func authRequestMeta(request *http.Request, deviceID string) *userv1.RequestMeta } } -func firstNonEmptyString(values ...string) string { - for _, value := range values { - if trimmed := strings.TrimSpace(value); trimmed != "" { - // 只用于兼容 app_verison 拼写错误,进入 user-service 前仍使用规范 app_version 字段。 - return trimmed - } - } - - return "" -} - // authData 把 user-service 的 token response 转成外部 HTTP 扁平 data。 func authData(token *userv1.AuthToken, isNewUser *bool) authTokenData { if token == nil { diff --git a/services/gateway-service/internal/transport/http/handler.go b/services/gateway-service/internal/transport/http/handler.go index 6494f7b5..fbaeab27 100644 --- a/services/gateway-service/internal/transport/http/handler.go +++ b/services/gateway-service/internal/transport/http/handler.go @@ -55,8 +55,7 @@ func NewHandler(roomClient client.RoomClient, userClient ...client.UserAuthClien return handler } -// NewHandlerWithClients 装配完整 gateway HTTP handler。 -// 保留 NewHandler 是为了不破坏只测 room/auth transport 的旧测试构造。 +// NewHandlerWithClients 装配需要用户资料接口的 gateway HTTP handler。 func NewHandlerWithClients(roomClient client.RoomClient, userClient client.UserAuthClient, userIdentityClient client.UserIdentityClient, userProfileClient ...client.UserProfileClient) *Handler { handler := &Handler{ roomClient: roomClient, diff --git a/services/gateway-service/internal/transport/http/response.go b/services/gateway-service/internal/transport/http/response.go index 97e75649..e5041c3c 100644 --- a/services/gateway-service/internal/transport/http/response.go +++ b/services/gateway-service/internal/transport/http/response.go @@ -68,7 +68,6 @@ func decodeJSON(reader io.Reader, out any) error { } // writeOK 写成功 envelope。 -// request 允许为空,兼容少量历史 helper 调用;正常业务路径应始终传入 request。 func writeOK(writer http.ResponseWriter, request *http.Request, data any) { writeEnvelope(writer, http.StatusOK, responseEnvelope{ Code: codeOK, @@ -111,7 +110,7 @@ func mapReasonToHTTP(reason xerr.Code) (int, string, string) { return http.StatusUnauthorized, string(reason), "authentication failed" case xerr.Unauthorized, xerr.SessionExpired, xerr.SessionRevoked: return http.StatusUnauthorized, string(reason), "unauthorized" - case xerr.PasswordAlreadySet, xerr.DisplayUserIDExists: + case xerr.Conflict, xerr.PasswordAlreadySet, xerr.DisplayUserIDExists: return http.StatusConflict, string(reason), "conflict" case xerr.DisplayUserIDCooldown, xerr.DisplayUserIDPrettyNotAvailable, xerr.DisplayUserIDPrettyActive, xerr.DisplayUserIDLeaseExpired, xerr.DisplayUserIDPaymentRequired, xerr.CountryChangeCooldown, xerr.RegionCountryConflict, xerr.RegionDisabled: return http.StatusConflict, string(reason), "conflict" @@ -139,9 +138,5 @@ func writeEnvelope(writer http.ResponseWriter, statusCode int, response response // responseRequestID 统一确定响应中的 request_id。 func responseRequestID(request *http.Request) string { - if request == nil { - return idgen.New("req") - } - return requestIDFromContext(request.Context()) } diff --git a/services/gateway-service/internal/transport/http/response_test.go b/services/gateway-service/internal/transport/http/response_test.go index 4485c9ad..ed19932a 100644 --- a/services/gateway-service/internal/transport/http/response_test.go +++ b/services/gateway-service/internal/transport/http/response_test.go @@ -26,8 +26,14 @@ type fakeRoomClient struct { lastMicUp *roomv1.MicUpRequest lastMicDown *roomv1.MicDownRequest lastChangeMicSeat *roomv1.ChangeMicSeatRequest + lastConfirmMic *roomv1.ConfirmMicPublishingRequest + lastMicSeatLock *roomv1.SetMicSeatLockRequest + lastChatEnabled *roomv1.SetChatEnabledRequest + lastSetAdmin *roomv1.SetRoomAdminRequest + lastTransferHost *roomv1.TransferRoomHostRequest lastMute *roomv1.MuteUserRequest lastKick *roomv1.KickUserRequest + lastUnban *roomv1.UnbanUserRequest lastGift *roomv1.SendGiftRequest createErr error } @@ -68,19 +74,49 @@ func (f *fakeRoomClient) ChangeMicSeat(_ context.Context, req *roomv1.ChangeMicS return &roomv1.ChangeMicSeatResponse{Result: &roomv1.CommandResult{Applied: true, RoomVersion: 6}}, nil } +func (f *fakeRoomClient) ConfirmMicPublishing(_ context.Context, req *roomv1.ConfirmMicPublishingRequest) (*roomv1.ConfirmMicPublishingResponse, error) { + f.lastConfirmMic = req + return &roomv1.ConfirmMicPublishingResponse{Result: &roomv1.CommandResult{Applied: true, RoomVersion: 7}}, nil +} + +func (f *fakeRoomClient) SetMicSeatLock(_ context.Context, req *roomv1.SetMicSeatLockRequest) (*roomv1.SetMicSeatLockResponse, error) { + f.lastMicSeatLock = req + return &roomv1.SetMicSeatLockResponse{Result: &roomv1.CommandResult{Applied: true, RoomVersion: 8}}, nil +} + +func (f *fakeRoomClient) SetChatEnabled(_ context.Context, req *roomv1.SetChatEnabledRequest) (*roomv1.SetChatEnabledResponse, error) { + f.lastChatEnabled = req + return &roomv1.SetChatEnabledResponse{Result: &roomv1.CommandResult{Applied: true, RoomVersion: 8}}, nil +} + +func (f *fakeRoomClient) SetRoomAdmin(_ context.Context, req *roomv1.SetRoomAdminRequest) (*roomv1.SetRoomAdminResponse, error) { + f.lastSetAdmin = req + return &roomv1.SetRoomAdminResponse{Result: &roomv1.CommandResult{Applied: true, RoomVersion: 9}}, nil +} + +func (f *fakeRoomClient) TransferRoomHost(_ context.Context, req *roomv1.TransferRoomHostRequest) (*roomv1.TransferRoomHostResponse, error) { + f.lastTransferHost = req + return &roomv1.TransferRoomHostResponse{Result: &roomv1.CommandResult{Applied: true, RoomVersion: 10}}, nil +} + func (f *fakeRoomClient) MuteUser(_ context.Context, req *roomv1.MuteUserRequest) (*roomv1.MuteUserResponse, error) { f.lastMute = req - return &roomv1.MuteUserResponse{Result: &roomv1.CommandResult{Applied: true, RoomVersion: 7}}, nil + return &roomv1.MuteUserResponse{Result: &roomv1.CommandResult{Applied: true, RoomVersion: 11}}, nil } func (f *fakeRoomClient) KickUser(_ context.Context, req *roomv1.KickUserRequest) (*roomv1.KickUserResponse, error) { f.lastKick = req - return &roomv1.KickUserResponse{Result: &roomv1.CommandResult{Applied: true, RoomVersion: 8}}, nil + return &roomv1.KickUserResponse{Result: &roomv1.CommandResult{Applied: true, RoomVersion: 12}}, nil +} + +func (f *fakeRoomClient) UnbanUser(_ context.Context, req *roomv1.UnbanUserRequest) (*roomv1.UnbanUserResponse, error) { + f.lastUnban = req + return &roomv1.UnbanUserResponse{Result: &roomv1.CommandResult{Applied: true, RoomVersion: 13}}, nil } func (f *fakeRoomClient) SendGift(_ context.Context, req *roomv1.SendGiftRequest) (*roomv1.SendGiftResponse, error) { f.lastGift = req - return &roomv1.SendGiftResponse{Result: &roomv1.CommandResult{Applied: true, RoomVersion: 9}}, nil + return &roomv1.SendGiftResponse{Result: &roomv1.CommandResult{Applied: true, RoomVersion: 14}}, nil } type fakeUserAuthClient struct { @@ -215,7 +251,7 @@ func (f *fakeRoomGuardClient) VerifyRoomPresence(_ context.Context, req *roomv1. func TestRoutesWriteUnifiedEnvelopeAndReuseRequestID(t *testing.T) { client := &fakeRoomClient{} router := NewHandler(client).Routes(auth.NewVerifier("secret")) - body := []byte(`{"room_id":"room-1","owner_user_id":999,"host_user_id":1000,"seat_count":8,"mode":"voice"}`) + body := []byte(`{"room_id":"room-1","seat_count":8,"mode":"voice"}`) request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/create", bytes.NewReader(body)) request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) request.Header.Set("X-Request-ID", "req-test") @@ -243,9 +279,6 @@ func TestRoutesWriteUnifiedEnvelopeAndReuseRequestID(t *testing.T) { if client.lastCreate.GetMeta().GetActorUserId() != 42 { t.Fatalf("actor user mismatch: got %d", client.lastCreate.GetMeta().GetActorUserId()) } - if client.lastCreate.GetOwnerUserId() != 0 || client.lastCreate.GetHostUserId() != 0 { - t.Fatalf("gateway must not pass body owner/host to room-service, got owner=%d host=%d", client.lastCreate.GetOwnerUserId(), client.lastCreate.GetHostUserId()) - } if client.lastCreate.GetMeta().GetCommandId() == "" || client.lastCreate.GetMeta().GetCommandId() == "req-test" { t.Fatalf("gateway-generated command_id must be non-empty and separate from request_id: %+v", client.lastCreate.GetMeta()) } @@ -313,6 +346,51 @@ func TestRoomCommandsPropagateClientCommandID(t *testing.T) { return client.lastChangeMicSeat.GetMeta() }, }, + { + name: "confirm_mic_publishing", + path: "/api/v1/rooms/mic/publishing/confirm", + body: `{"room_id":"room-1","command_id":"cmd-confirm-mic-1","mic_session_id":"mic-1","room_version":4,"event_time_ms":1700000001000,"source":"client"}`, + commandID: "cmd-confirm-mic-1", + extractMeta: func(client *fakeRoomClient) *roomv1.RequestMeta { + return client.lastConfirmMic.GetMeta() + }, + }, + { + name: "mic_lock", + path: "/api/v1/rooms/mic/lock", + body: `{"room_id":"room-1","command_id":"cmd-mic-lock-1","seat_no":2,"locked":true}`, + commandID: "cmd-mic-lock-1", + extractMeta: func(client *fakeRoomClient) *roomv1.RequestMeta { + return client.lastMicSeatLock.GetMeta() + }, + }, + { + name: "chat_enabled", + path: "/api/v1/rooms/chat/enabled", + body: `{"room_id":"room-1","command_id":"cmd-chat-enabled-1","enabled":false}`, + commandID: "cmd-chat-enabled-1", + extractMeta: func(client *fakeRoomClient) *roomv1.RequestMeta { + return client.lastChatEnabled.GetMeta() + }, + }, + { + name: "set_admin", + path: "/api/v1/rooms/admin/set", + body: `{"room_id":"room-1","command_id":"cmd-admin-1","target_user_id":43,"enabled":true}`, + commandID: "cmd-admin-1", + extractMeta: func(client *fakeRoomClient) *roomv1.RequestMeta { + return client.lastSetAdmin.GetMeta() + }, + }, + { + name: "transfer_host", + path: "/api/v1/rooms/host/transfer", + body: `{"room_id":"room-1","command_id":"cmd-host-1","target_user_id":43}`, + commandID: "cmd-host-1", + extractMeta: func(client *fakeRoomClient) *roomv1.RequestMeta { + return client.lastTransferHost.GetMeta() + }, + }, { name: "mute", path: "/api/v1/rooms/user/mute", @@ -331,10 +409,19 @@ func TestRoomCommandsPropagateClientCommandID(t *testing.T) { return client.lastKick.GetMeta() }, }, + { + name: "unban", + path: "/api/v1/rooms/user/unban", + body: `{"room_id":"room-1","command_id":"cmd-unban-1","target_user_id":43}`, + commandID: "cmd-unban-1", + extractMeta: func(client *fakeRoomClient) *roomv1.RequestMeta { + return client.lastUnban.GetMeta() + }, + }, { name: "gift", path: "/api/v1/rooms/gift/send", - body: `{"room_id":"room-1","command_id":"cmd-gift-1","target_user_id":43,"gift_id":"rose","gift_count":1,"gift_unit_value":10}`, + body: `{"room_id":"room-1","command_id":"cmd-gift-1","target_user_id":43,"gift_id":"rose","gift_count":1}`, commandID: "cmd-gift-1", extractMeta: func(client *fakeRoomClient) *roomv1.RequestMeta { return client.lastGift.GetMeta() @@ -374,10 +461,34 @@ func TestRoomCommandsPropagateClientCommandID(t *testing.T) { } } +func TestConfirmMicPublishingForwardsSessionVersionAndEventTime(t *testing.T) { + client := &fakeRoomClient{} + router := NewHandler(client).Routes(auth.NewVerifier("secret")) + body := []byte(`{"room_id":"room-1","command_id":"cmd-confirm","target_user_id":43,"mic_session_id":"mic-session-1","room_version":12,"event_time_ms":1700000001234,"source":"client"}`) + request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/mic/publishing/confirm", bytes.NewReader(body)) + request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42)) + request.Header.Set("X-Request-ID", "req-confirm-mic") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, request) + + assertEnvelope(t, recorder, http.StatusOK, codeOK, "req-confirm-mic") + if client.lastConfirmMic == nil { + t.Fatal("ConfirmMicPublishing request was not sent") + } + if client.lastConfirmMic.GetTargetUserId() != 43 || + client.lastConfirmMic.GetMicSessionId() != "mic-session-1" || + client.lastConfirmMic.GetRoomVersion() != 12 || + client.lastConfirmMic.GetEventTimeMs() != 1700000001234 || + client.lastConfirmMic.GetSource() != "client" { + t.Fatalf("confirm fields mismatch: %+v", client.lastConfirmMic) + } +} + func TestThirdPartyLoginUsesPublicEnvelopeAndPropagatesRequestID(t *testing.T) { userClient := &fakeUserAuthClient{} router := NewHandler(&fakeRoomClient{}, userClient).Routes(auth.NewVerifier("secret")) - body := []byte(`{"provider":"google","credential":"id-token-1","username":"hy","gender":"male","country":"CN","invite_code":"INV-1","ip":"198.51.100.9","device_id":"dev-1","device":"iPhone 15","os_version":"iOS 18.1","avatar":"https://cdn.example/avatar.png","birth":"2000-01-02","app_verison":"1.2.3","build_number":"123","source":"campaign-a","install_channel":"app_store","campaign":"spring_launch","platform":"ios","language":"zh-CN","timezone":"Asia/Shanghai"}`) + body := []byte(`{"provider":"google","credential":"id-token-1","username":"hy","gender":"male","country":"CN","invite_code":"INV-1","device_id":"dev-1","device":"iPhone 15","os_version":"iOS 18.1","avatar":"https://cdn.example/avatar.png","birth":"2000-01-02","app_version":"1.2.3","build_number":"123","source":"campaign-a","install_channel":"app_store","campaign":"spring_launch","platform":"ios","language":"zh-CN","timezone":"Asia/Shanghai"}`) request := httptest.NewRequest(http.MethodPost, "/api/v1/auth/third-party/login", bytes.NewReader(body)) request.Header.Set("X-Request-ID", "req-auth-third") request.Header.Set("X-Forwarded-For", "203.0.113.10, 10.0.0.1") @@ -405,7 +516,7 @@ func TestThirdPartyLoginUsesPublicEnvelopeAndPropagatesRequestID(t *testing.T) { if req.GetMeta().GetRequestId() != "req-auth-third" || req.GetMeta().GetClientIp() != "203.0.113.10" || req.GetMeta().GetUserAgent() != "hyapp-test/1.0" || req.GetMeta().GetCountryByIp() != "SG" { t.Fatalf("auth meta was not propagated from gateway context: %+v", req.GetMeta()) } - if req.GetUsername() != "hy" || req.GetInviteCode() != "INV-1" || req.GetIp() != "" || req.GetDeviceId() != "dev-1" { + if req.GetUsername() != "hy" || req.GetInviteCode() != "INV-1" || req.GetDeviceId() != "dev-1" { t.Fatalf("third-party registration fields were not propagated: %+v", req) } if req.GetDevice() != "iPhone 15" || req.GetOsVersion() != "iOS 18.1" || req.GetAvatar() == "" || req.GetBirth() != "2000-01-02" || req.GetAppVersion() != "1.2.3" || req.GetBuildNumber() != "123" { @@ -619,6 +730,14 @@ func TestAuthLoginMapsGRPCReason(t *testing.T) { assertEnvelope(t, recorder, http.StatusUnauthorized, string(xerr.AuthFailed), "req-auth-failed") } +func TestMapReasonToHTTPMapsGenericConflict(t *testing.T) { + // room-service 的麦位占用、幂等 payload 冲突等通用业务冲突必须稳定透出 409。 + statusCode, code, message := mapReasonToHTTP(xerr.Conflict) + if statusCode != http.StatusConflict || code != string(xerr.Conflict) || message != "conflict" { + t.Fatalf("generic conflict mapping mismatch: status=%d code=%s message=%s", statusCode, code, message) + } +} + func TestRoutesWriteUnauthorizedEnvelope(t *testing.T) { router := NewHandler(&fakeRoomClient{}).Routes(auth.NewVerifier("secret")) request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/create", bytes.NewReader([]byte(`{}`))) @@ -642,7 +761,7 @@ func TestRoutesWriteInvalidJSONEnvelope(t *testing.T) { assertEnvelope(t, recorder, http.StatusBadRequest, codeInvalidJSON, "req-json") } -func TestCreateRoomRejectsIncompatibleRoomIDBeforeGRPC(t *testing.T) { +func TestCreateRoomRejectsInvalidRoomIDBeforeGRPC(t *testing.T) { client := &fakeRoomClient{} router := NewHandler(client).Routes(auth.NewVerifier("secret")) request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/create", bytes.NewReader([]byte(`{"room_id":"room:1","seat_count":8,"mode":"voice"}`))) diff --git a/services/gateway-service/internal/transport/http/room_handler.go b/services/gateway-service/internal/transport/http/room_handler.go index 3a8a9fd4..95c74690 100644 --- a/services/gateway-service/internal/transport/http/room_handler.go +++ b/services/gateway-service/internal/transport/http/room_handler.go @@ -101,6 +101,7 @@ func (h *Handler) micDown(writer http.ResponseWriter, request *http.Request) { RoomID string `json:"room_id"` CommandID string `json:"command_id"` TargetUserID int64 `json:"target_user_id"` + Reason string `json:"reason"` } if !decode(writer, request, &body) { @@ -110,6 +111,7 @@ func (h *Handler) micDown(writer http.ResponseWriter, request *http.Request) { resp, err := h.roomClient.MicDown(request.Context(), &roomv1.MicDownRequest{ Meta: meta(request, body.RoomID, body.CommandID), TargetUserId: body.TargetUserID, + Reason: body.Reason, }) write(writer, request, resp, err) } @@ -135,6 +137,113 @@ func (h *Handler) changeMicSeat(writer http.ResponseWriter, request *http.Reques write(writer, request, resp, err) } +// confirmMicPublishing 把客户端 SDK 发流成功回调转换为 room-service 发流确认命令。 +func (h *Handler) confirmMicPublishing(writer http.ResponseWriter, request *http.Request) { + var body struct { + RoomID string `json:"room_id"` + CommandID string `json:"command_id"` + TargetUserID int64 `json:"target_user_id"` + MicSessionID string `json:"mic_session_id"` + RoomVersion int64 `json:"room_version"` + EventTimeMS int64 `json:"event_time_ms"` + Source string `json:"source"` + } + + if !decode(writer, request, &body) { + return + } + + resp, err := h.roomClient.ConfirmMicPublishing(request.Context(), &roomv1.ConfirmMicPublishingRequest{ + Meta: meta(request, body.RoomID, body.CommandID), + TargetUserId: body.TargetUserID, + MicSessionId: body.MicSessionID, + RoomVersion: body.RoomVersion, + EventTimeMs: body.EventTimeMS, + Source: body.Source, + }) + write(writer, request, resp, err) +} + +// setMicSeatLock 把锁麦 HTTP JSON 请求转换为 room-service SetMicSeatLock 命令。 +func (h *Handler) setMicSeatLock(writer http.ResponseWriter, request *http.Request) { + var body struct { + RoomID string `json:"room_id"` + CommandID string `json:"command_id"` + SeatNo int32 `json:"seat_no"` + Locked bool `json:"locked"` + } + + if !decode(writer, request, &body) { + return + } + + resp, err := h.roomClient.SetMicSeatLock(request.Context(), &roomv1.SetMicSeatLockRequest{ + Meta: meta(request, body.RoomID, body.CommandID), + SeatNo: body.SeatNo, + Locked: body.Locked, + }) + write(writer, request, resp, err) +} + +// setChatEnabled 把公屏开关 HTTP JSON 请求转换为 room-service SetChatEnabled 命令。 +func (h *Handler) setChatEnabled(writer http.ResponseWriter, request *http.Request) { + var body struct { + RoomID string `json:"room_id"` + CommandID string `json:"command_id"` + Enabled bool `json:"enabled"` + } + + if !decode(writer, request, &body) { + return + } + + resp, err := h.roomClient.SetChatEnabled(request.Context(), &roomv1.SetChatEnabledRequest{ + Meta: meta(request, body.RoomID, body.CommandID), + Enabled: body.Enabled, + }) + write(writer, request, resp, err) +} + +// setRoomAdmin 把管理员增删 HTTP JSON 请求转换为 room-service SetRoomAdmin 命令。 +func (h *Handler) setRoomAdmin(writer http.ResponseWriter, request *http.Request) { + var body struct { + RoomID string `json:"room_id"` + CommandID string `json:"command_id"` + TargetUserID int64 `json:"target_user_id"` + Enabled bool `json:"enabled"` + } + + if !decode(writer, request, &body) { + return + } + + resp, err := h.roomClient.SetRoomAdmin(request.Context(), &roomv1.SetRoomAdminRequest{ + Meta: meta(request, body.RoomID, body.CommandID), + TargetUserId: body.TargetUserID, + Enabled: body.Enabled, + }) + write(writer, request, resp, err) +} + +// transferRoomHost 把主持转移 HTTP JSON 请求转换为 room-service TransferRoomHost 命令。 +func (h *Handler) transferRoomHost(writer http.ResponseWriter, request *http.Request) { + var body struct { + RoomID string `json:"room_id"` + CommandID string `json:"command_id"` + TargetUserID int64 `json:"target_user_id"` + } + + if !decode(writer, request, &body) { + return + } + + resp, err := h.roomClient.TransferRoomHost(request.Context(), &roomv1.TransferRoomHostRequest{ + Meta: meta(request, body.RoomID, body.CommandID), + TargetUserId: body.TargetUserID, + }) + write(writer, request, resp, err) +} + // muteUser 把禁言 HTTP JSON 请求转换为 room-service MuteUser 命令。 func (h *Handler) muteUser(writer http.ResponseWriter, request *http.Request) { var body struct { @@ -175,16 +284,34 @@ func (h *Handler) kickUser(writer http.ResponseWriter, request *http.Request) { write(writer, request, resp, err) } +// unbanUser 把解封 HTTP JSON 请求转换为 room-service UnbanUser 命令。 +func (h *Handler) unbanUser(writer http.ResponseWriter, request *http.Request) { + var body struct { + RoomID string `json:"room_id"` + CommandID string `json:"command_id"` + TargetUserID int64 `json:"target_user_id"` + } + + if !decode(writer, request, &body) { + return + } + + resp, err := h.roomClient.UnbanUser(request.Context(), &roomv1.UnbanUserRequest{ + Meta: meta(request, body.RoomID, body.CommandID), + TargetUserId: body.TargetUserID, + }) + write(writer, request, resp, err) +} + // sendGift 把送礼 HTTP JSON 请求转换为 room-service SendGift 命令。 // 扣费、幂等、房间表现和 outbox 仍由 room-service 的命令链路统一处理。 func (h *Handler) sendGift(writer http.ResponseWriter, request *http.Request) { var body struct { - RoomID string `json:"room_id"` - CommandID string `json:"command_id"` - TargetUserID int64 `json:"target_user_id"` - GiftID string `json:"gift_id"` - GiftCount int32 `json:"gift_count"` - GiftUnitValue int64 `json:"gift_unit_value"` + RoomID string `json:"room_id"` + CommandID string `json:"command_id"` + TargetUserID int64 `json:"target_user_id"` + GiftID string `json:"gift_id"` + GiftCount int32 `json:"gift_count"` } if !decode(writer, request, &body) { @@ -192,11 +319,10 @@ func (h *Handler) sendGift(writer http.ResponseWriter, request *http.Request) { } resp, err := h.roomClient.SendGift(request.Context(), &roomv1.SendGiftRequest{ - Meta: meta(request, body.RoomID, body.CommandID), - TargetUserId: body.TargetUserID, - GiftId: body.GiftID, - GiftCount: body.GiftCount, - GiftUnitValue: body.GiftUnitValue, + Meta: meta(request, body.RoomID, body.CommandID), + TargetUserId: body.TargetUserID, + GiftId: body.GiftID, + GiftCount: body.GiftCount, }) write(writer, request, resp, err) } diff --git a/services/gateway-service/internal/transport/http/router.go b/services/gateway-service/internal/transport/http/router.go index 69cdcbb1..269e8fbe 100644 --- a/services/gateway-service/internal/transport/http/router.go +++ b/services/gateway-service/internal/transport/http/router.go @@ -36,8 +36,14 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler { mux.Handle(apiV1Prefix+"/rooms/mic/up", apiHandler(jwtVerifier, h.micUp)) mux.Handle(apiV1Prefix+"/rooms/mic/down", apiHandler(jwtVerifier, h.micDown)) mux.Handle(apiV1Prefix+"/rooms/mic/change", apiHandler(jwtVerifier, h.changeMicSeat)) + mux.Handle(apiV1Prefix+"/rooms/mic/publishing/confirm", apiHandler(jwtVerifier, h.confirmMicPublishing)) + mux.Handle(apiV1Prefix+"/rooms/mic/lock", apiHandler(jwtVerifier, h.setMicSeatLock)) + mux.Handle(apiV1Prefix+"/rooms/chat/enabled", apiHandler(jwtVerifier, h.setChatEnabled)) + mux.Handle(apiV1Prefix+"/rooms/admin/set", apiHandler(jwtVerifier, h.setRoomAdmin)) + mux.Handle(apiV1Prefix+"/rooms/host/transfer", apiHandler(jwtVerifier, h.transferRoomHost)) mux.Handle(apiV1Prefix+"/rooms/user/mute", apiHandler(jwtVerifier, h.muteUser)) mux.Handle(apiV1Prefix+"/rooms/user/kick", apiHandler(jwtVerifier, h.kickUser)) + mux.Handle(apiV1Prefix+"/rooms/user/unban", apiHandler(jwtVerifier, h.unbanUser)) mux.Handle(apiV1Prefix+"/rooms/gift/send", apiHandler(jwtVerifier, h.sendGift)) return mux diff --git a/services/gateway-service/internal/transport/http/tencent_im_callback.go b/services/gateway-service/internal/transport/http/tencent_im_callback.go index b1774ff1..78e5055a 100644 --- a/services/gateway-service/internal/transport/http/tencent_im_callback.go +++ b/services/gateway-service/internal/transport/http/tencent_im_callback.go @@ -145,9 +145,6 @@ func (h *Handler) tencentIMSDKAppIDMatched(request *http.Request) bool { } raw := strings.TrimSpace(request.URL.Query().Get("SdkAppid")) - if raw == "" { - raw = strings.TrimSpace(request.URL.Query().Get("SDKAppID")) - } value, err := strconv.ParseInt(raw, 10, 64) return err == nil && value == h.tencentIM.SDKAppID } diff --git a/services/room-service/configs/config.docker.yaml b/services/room-service/configs/config.docker.yaml index d9f2639f..3acf087d 100644 --- a/services/room-service/configs/config.docker.yaml +++ b/services/room-service/configs/config.docker.yaml @@ -23,6 +23,8 @@ redis_password: "" redis_db: 0 presence_stale_after: "2m" presence_stale_scan_interval: "30s" +mic_publish_timeout: "15s" +mic_publish_scan_interval: "1s" outbox_worker: # Docker 本地默认启动补偿 worker,验证同步 IM 失败后的 room_outbox 补投闭环。 enabled: true diff --git a/services/room-service/configs/config.tencent.example.yaml b/services/room-service/configs/config.tencent.example.yaml index 5e55579a..f42874f1 100644 --- a/services/room-service/configs/config.tencent.example.yaml +++ b/services/room-service/configs/config.tencent.example.yaml @@ -30,6 +30,8 @@ redis_password: "TENCENT_REDIS_PASSWORD" redis_db: 0 presence_stale_after: "2m" presence_stale_scan_interval: "30s" +mic_publish_timeout: "15s" +mic_publish_scan_interval: "1s" outbox_worker: # 线上默认启动补偿 worker;腾讯云 IM 抖动时不回滚 Room Cell 状态。 enabled: true diff --git a/services/room-service/configs/config.yaml b/services/room-service/configs/config.yaml index da6a9cf5..22cc2529 100644 --- a/services/room-service/configs/config.yaml +++ b/services/room-service/configs/config.yaml @@ -17,7 +17,7 @@ tencent_im: group_type: "ChatRoom" # room-service 同步建群和发送房间系统消息的单次 REST 超时。 request_timeout: "5s" -mysql_dsn: "hyapp:hyapp@tcp(127.0.0.1:13306)/hyapp_room?parseTime=true&charset=utf8mb4&loc=Local" +mysql_dsn: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_room?parseTime=true&charset=utf8mb4&loc=Local" mysql_max_open_conns: 20 mysql_max_idle_conns: 10 mysql_auto_migrate: true @@ -26,6 +26,8 @@ redis_password: "" redis_db: 0 presence_stale_after: "2m" presence_stale_scan_interval: "30s" +mic_publish_timeout: "15s" +mic_publish_scan_interval: "1s" outbox_worker: # room_outbox 是腾讯云 IM 系统消息补偿通道;失败保持 pending 并固定间隔重试。 enabled: true diff --git a/services/room-service/internal/app/app.go b/services/room-service/internal/app/app.go index aac21f3c..0da434ee 100644 --- a/services/room-service/internal/app/app.go +++ b/services/room-service/internal/app/app.go @@ -116,6 +116,7 @@ func New(cfg config.Config) (*App, error) { RankLimit: cfg.RankLimit, SnapshotEveryN: cfg.SnapshotEveryN, PresenceStaleAfter: cfg.PresenceStaleAfter, + MicPublishTimeout: cfg.MicPublishTimeout, }, directory, repository, integration.NewGRPCWalletClient(walletConn), roomPublisher, outboxPublisher) server := grpc.NewServer() @@ -167,6 +168,14 @@ func (a *App) Run() error { a.service.RunPresenceStaleWorker(a.workerCtx, a.cfg.PresenceStaleScanInterval) }() } + if a.cfg.MicPublishScanInterval > 0 { + // MicUp 只代表业务占麦;后台 worker 负责释放未确认 RTC 发流的 pending_publish 麦位。 + a.workerWG.Add(1) + go func() { + defer a.workerWG.Done() + a.service.RunMicPublishTimeoutWorker(a.workerCtx, a.cfg.MicPublishScanInterval) + }() + } if a.cfg.OutboxWorker.Enabled { a.workerWG.Add(1) go func() { diff --git a/services/room-service/internal/config/config.go b/services/room-service/internal/config/config.go index 907c5f07..62297e06 100644 --- a/services/room-service/internal/config/config.go +++ b/services/room-service/internal/config/config.go @@ -45,6 +45,10 @@ type Config struct { PresenceStaleAfter time.Duration `yaml:"presence_stale_after"` // PresenceStaleScanInterval 是本节点扫描已装载 Room Cell 的周期。 PresenceStaleScanInterval time.Duration `yaml:"presence_stale_scan_interval"` + // MicPublishTimeout 是 MicUp 后等待客户端/RTC 发流确认的最长时间。 + MicPublishTimeout time.Duration `yaml:"mic_publish_timeout"` + // MicPublishScanInterval 是本节点扫描 pending_publish 超时麦位的周期。 + MicPublishScanInterval time.Duration `yaml:"mic_publish_scan_interval"` // OutboxWorker 控制 room_outbox 到腾讯云 IM 补偿投递 worker。 OutboxWorker OutboxWorkerConfig `yaml:"outbox_worker"` } @@ -120,7 +124,7 @@ func Default() Config { GroupType: tencentim.DefaultGroupType, RequestTimeout: 5 * time.Second, }, - MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:13306)/hyapp_room?parseTime=true&charset=utf8mb4&loc=Local", + MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_room?parseTime=true&charset=utf8mb4&loc=Local", MySQLMaxOpenConns: 20, MySQLMaxIdleConns: 10, MySQLAutoMigrate: true, @@ -128,11 +132,14 @@ func Default() Config { RedisDB: 0, PresenceStaleAfter: 2 * time.Minute, PresenceStaleScanInterval: 30 * time.Second, + MicPublishTimeout: 15 * time.Second, + MicPublishScanInterval: time.Second, OutboxWorker: defaultOutboxWorkerConfig(), } } func defaultOutboxWorkerConfig() OutboxWorkerConfig { + // 默认启动补偿 worker,保证同步腾讯云 IM 失败后 pending outbox 能自动重试。 return OutboxWorkerConfig{ Enabled: true, PollInterval: time.Second, @@ -144,6 +151,7 @@ func defaultOutboxWorkerConfig() OutboxWorkerConfig { // Normalize 补齐动态配置默认值,并拒绝会改变补偿语义的未知策略。 func Normalize(cfg Config) (Config, error) { + // 配置归一化只处理需要跨环境保持语义的字段,其他默认值由 Default 提供。 outboxWorker, err := normalizeOutboxWorkerConfig(cfg.OutboxWorker) if err != nil { return Config{}, err @@ -154,6 +162,7 @@ func Normalize(cfg Config) (Config, error) { } func normalizeOutboxWorkerConfig(cfg OutboxWorkerConfig) (OutboxWorkerConfig, error) { + // Outbox worker 的所有时间和批量参数都必须有安全下限,避免配置缺省导致 busy loop。 defaults := defaultOutboxWorkerConfig() if cfg.PollInterval <= 0 { cfg.PollInterval = defaults.PollInterval @@ -169,6 +178,7 @@ func normalizeOutboxWorkerConfig(cfg OutboxWorkerConfig) (OutboxWorkerConfig, er cfg.RetryStrategy = defaults.RetryStrategy } if cfg.RetryStrategy != defaults.RetryStrategy { + // V1 只实现 fixed_interval;未知策略直接启动失败,避免误以为退避或死信已生效。 return OutboxWorkerConfig{}, fmt.Errorf("unsupported outbox_worker retry_strategy %q", cfg.RetryStrategy) } diff --git a/services/room-service/internal/integration/tencent_im.go b/services/room-service/internal/integration/tencent_im.go index ece91e5b..636e8386 100644 --- a/services/room-service/internal/integration/tencent_im.go +++ b/services/room-service/internal/integration/tencent_im.go @@ -50,6 +50,12 @@ func (p *TencentIMPublisher) PublishOutboxEvent(ctx context.Context, envelope *r if err != nil || !publish { return err } + if envelope.GetEventType() == "RoomUserKicked" { + // KickUser 的安全边界除了系统消息,还必须补偿移除腾讯云 IM 群成员。 + if err := p.client.DeleteRoomGroupMember(ctx, envelope.GetRoomId(), event.TargetUserID); err != nil { + return err + } + } return p.client.PublishRoomEvent(ctx, event) } @@ -88,6 +94,7 @@ func (noopOutboxPublisher) PublishOutboxEvent(context.Context, *roomeventsv1.Eve } func roomEventFromEnvelope(envelope *roomeventsv1.EventEnvelope) (tencentim.RoomEvent, bool, error) { + // EventEnvelope 是 room_outbox 的稳定信封,补偿投递时再按 event_type 解出客户端字段。 base := tencentim.RoomEvent{ EventID: envelope.GetEventId(), RoomID: envelope.GetRoomId(), @@ -97,6 +104,7 @@ func roomEventFromEnvelope(envelope *roomeventsv1.EventEnvelope) (tencentim.Room switch envelope.GetEventType() { case "RoomUserJoined": + // 进房事件需要带 role,客户端可用于展示进房身份,但权限仍以后续 snapshot 为准。 var body roomeventsv1.RoomUserJoined if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil { return tencentim.RoomEvent{}, false, err @@ -106,6 +114,7 @@ func roomEventFromEnvelope(envelope *roomeventsv1.EventEnvelope) (tencentim.Room base.Attributes = map[string]string{"role": body.GetRole()} return base, true, nil case "RoomUserLeft": + // 离房事件只表达业务 presence 移除,不声明腾讯云 IM 连接是否已断开。 var body roomeventsv1.RoomUserLeft if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil { return tencentim.RoomEvent{}, false, err @@ -114,6 +123,7 @@ func roomEventFromEnvelope(envelope *roomeventsv1.EventEnvelope) (tencentim.Room base.TargetUserID = body.GetUserId() return base, true, nil case "RoomMicChanged": + // 麦位变更把 from/to 合并成一个客户端事件,避免换麦出现中间空位状态。 var body roomeventsv1.RoomMicChanged if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil { return tencentim.RoomEvent{}, false, err @@ -124,9 +134,59 @@ func roomEventFromEnvelope(envelope *roomeventsv1.EventEnvelope) (tencentim.Room if base.SeatNo == 0 { base.SeatNo = body.GetFromSeat() } - base.Attributes = map[string]string{"action": body.GetAction()} + base.Attributes = map[string]string{ + "action": body.GetAction(), + "mic_session_id": body.GetMicSessionId(), + "publish_state": body.GetPublishState(), + "reason": body.GetReason(), + "publish_deadline_ms": fmt.Sprintf("%d", body.GetPublishDeadlineMs()), + "publish_event_time_ms": fmt.Sprintf("%d", body.GetPublishEventTimeMs()), + } + return base, true, nil + case "RoomMicSeatLocked": + // 锁麦事件只广播锁状态;首版锁麦不会自动下掉已占用用户。 + var body roomeventsv1.RoomMicSeatLocked + if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil { + return tencentim.RoomEvent{}, false, err + } + base.ActorUserID = body.GetActorUserId() + base.SeatNo = body.GetSeatNo() + base.Attributes = map[string]string{"locked": fmt.Sprintf("%t", body.GetLocked())} + return base, true, nil + case "RoomChatEnabledChanged": + // 公屏开关事件提示客户端更新输入态,真正发言仍由 CheckSpeakPermission 拦截。 + var body roomeventsv1.RoomChatEnabledChanged + if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil { + return tencentim.RoomEvent{}, false, err + } + base.ActorUserID = body.GetActorUserId() + base.Attributes = map[string]string{"enabled": fmt.Sprintf("%t", body.GetEnabled())} + return base, true, nil + case "RoomAdminChanged": + // 管理员变更进入系统消息,客户端收到后仍应以最新 snapshot 修正本地权限 UI。 + var body roomeventsv1.RoomAdminChanged + if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil { + return tencentim.RoomEvent{}, false, err + } + base.ActorUserID = body.GetActorUserId() + base.TargetUserID = body.GetTargetUserId() + base.Attributes = map[string]string{"enabled": fmt.Sprintf("%t", body.GetEnabled())} + return base, true, nil + case "RoomHostTransferred": + // 主持人转移同时携带新旧 host,便于客户端展示交接提示。 + var body roomeventsv1.RoomHostTransferred + if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil { + return tencentim.RoomEvent{}, false, err + } + base.ActorUserID = body.GetActorUserId() + base.TargetUserID = body.GetNewHostUserId() + base.Attributes = map[string]string{ + "old_host_user_id": fmt.Sprintf("%d", body.GetOldHostUserId()), + "new_host_user_id": fmt.Sprintf("%d", body.GetNewHostUserId()), + } return base, true, nil case "RoomUserMuted": + // 禁言事件只影响公屏发言权限,不改变 presence、麦位或收消息资格。 var body roomeventsv1.RoomUserMuted if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil { return tencentim.RoomEvent{}, false, err @@ -136,6 +196,7 @@ func roomEventFromEnvelope(envelope *roomeventsv1.EventEnvelope) (tencentim.Room base.Attributes = map[string]string{"muted": fmt.Sprintf("%t", body.GetMuted())} return base, true, nil case "RoomUserKicked": + // KickUser 的即时系统消息和补偿踢群共用同一个 target_user_id。 var body roomeventsv1.RoomUserKicked if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil { return tencentim.RoomEvent{}, false, err @@ -143,7 +204,17 @@ func roomEventFromEnvelope(envelope *roomeventsv1.EventEnvelope) (tencentim.Room base.ActorUserID = body.GetActorUserId() base.TargetUserID = body.GetTargetUserId() return base, true, nil + case "RoomUserUnbanned": + // 解封只恢复再次 JoinRoom 的资格,不代表目标已经回到房间 presence。 + var body roomeventsv1.RoomUserUnbanned + if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil { + return tencentim.RoomEvent{}, false, err + } + base.ActorUserID = body.GetActorUserId() + base.TargetUserID = body.GetTargetUserId() + return base, true, nil case "RoomGiftSent": + // 送礼客户端消息只用 GiftSent 主事件承载,Heat/Rank 变化作为字段随同一条消息展示。 var body roomeventsv1.RoomGiftSent if err := proto.Unmarshal(envelope.GetBody(), &body); err != nil { return tencentim.RoomEvent{}, false, err @@ -164,6 +235,7 @@ func roomEventFromEnvelope(envelope *roomeventsv1.EventEnvelope) (tencentim.Room } func eventTypeForClient(eventType string) string { + // protobuf 事件名使用 PascalCase,客户端 TIMCustomElem 使用 snake_case 作为稳定协议字段。 switch eventType { case "RoomUserJoined": return "room_user_joined" @@ -171,10 +243,20 @@ func eventTypeForClient(eventType string) string { return "room_user_left" case "RoomMicChanged": return "room_mic_changed" + case "RoomMicSeatLocked": + return "room_mic_seat_locked" + case "RoomChatEnabledChanged": + return "room_chat_enabled_changed" + case "RoomAdminChanged": + return "room_admin_changed" + case "RoomHostTransferred": + return "room_host_transferred" case "RoomUserMuted": return "room_user_muted" case "RoomUserKicked": return "room_user_kicked" + case "RoomUserUnbanned": + return "room_user_unbanned" case "RoomGiftSent": return "room_gift_sent" default: diff --git a/services/room-service/internal/integration/tencent_im_test.go b/services/room-service/internal/integration/tencent_im_test.go index 977524f1..52632314 100644 --- a/services/room-service/internal/integration/tencent_im_test.go +++ b/services/room-service/internal/integration/tencent_im_test.go @@ -1,10 +1,15 @@ package integration import ( + "bytes" + "context" + "io" + "net/http" "testing" "time" roomeventsv1 "hyapp/api/proto/events/room/v1" + "hyapp/pkg/tencentim" "hyapp/services/room-service/internal/room/outbox" ) @@ -27,3 +32,46 @@ func TestRoomEventFromEnvelopeSkipsNonIMEvents(t *testing.T) { t.Fatalf("RoomCreated must not publish Tencent IM message: %+v", event) } } + +func TestTencentIMPublisherRemovesGroupMemberBeforeKickMessage(t *testing.T) { + paths := make([]string, 0, 2) + client, err := tencentim.NewRESTClient(tencentim.RESTConfig{ + SDKAppID: 1400000000, + SecretKey: "secret", + AdminIdentifier: "administrator", + AdminUserSigTTL: time.Hour, + HTTPClient: &http.Client{Transport: roundTripFunc(func(request *http.Request) (*http.Response, error) { + paths = append(paths, request.URL.Path) + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte(`{"ActionStatus":"OK","ErrorCode":0,"ErrorInfo":""}`))), + Header: make(http.Header), + }, nil + })}, + }) + if err != nil { + t.Fatalf("NewRESTClient failed: %v", err) + } + + record, err := outbox.Build("room-kick", "RoomUserKicked", 3, time.Now(), &roomeventsv1.RoomUserKicked{ + ActorUserId: 1, + TargetUserId: 2, + }) + if err != nil { + t.Fatalf("Build RoomUserKicked envelope failed: %v", err) + } + publisher := NewTencentIMPublisher(client) + if err := publisher.PublishOutboxEvent(context.Background(), record.Envelope); err != nil { + t.Fatalf("PublishOutboxEvent failed: %v", err) + } + + if len(paths) != 2 || paths[0] != "/v4/group_open_http_svc/delete_group_member" || paths[1] != "/v4/group_open_http_svc/send_group_msg" { + t.Fatalf("kick outbox should delete member before system message, paths=%+v", paths) + } +} + +type roundTripFunc func(*http.Request) (*http.Response, error) + +func (f roundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) { + return f(request) +} diff --git a/services/room-service/internal/room/command/command.go b/services/room-service/internal/room/command/command.go index db830796..8115ec66 100644 --- a/services/room-service/internal/room/command/command.go +++ b/services/room-service/internal/room/command/command.go @@ -1,7 +1,10 @@ // Package command 定义 room-service 可持久化、可回放的房间命令模型。 package command -import "encoding/json" +import ( + "encoding/json" + "fmt" +) // Command 统一表达可写入 command log 的房间命令。 type Command interface { @@ -87,6 +90,10 @@ type MicUp struct { Base // SeatNo 是目标麦位编号,从 RoomState 初始化的麦位集合中查找。 SeatNo int32 `json:"seat_no"` + // MicSessionID 是服务端为本次上麦生成的发流会话 ID,确认和超时释放都必须匹配它。 + MicSessionID string `json:"mic_session_id"` + // PublishDeadlineMS 是客户端必须确认 RTC 发流成功的截止时间。 + PublishDeadlineMS int64 `json:"publish_deadline_ms"` } // Type 返回命令类型。 @@ -97,11 +104,33 @@ type MicDown struct { Base // TargetUserID 是被下麦用户;操作者和目标用户可以不同。 TargetUserID int64 `json:"target_user_id"` + // MicSessionID 非空时只允许释放同一发流会话,防止旧超时任务清掉新上麦。 + MicSessionID string `json:"mic_session_id,omitempty"` + // Reason 记录系统触发下麦原因,例如 publish_timeout;主动下麦保持为空。 + Reason string `json:"reason,omitempty"` } // Type 返回命令类型。 func (MicDown) Type() string { return "mic_down" } +// ConfirmMicPublishing 定义 RTC 发流成功确认请求。 +type ConfirmMicPublishing struct { + Base + // TargetUserID 是被确认发布音频的用户;为空时由服务层使用 ActorUserID。 + TargetUserID int64 `json:"target_user_id"` + // MicSessionID 必须匹配当前麦位上的会话。 + MicSessionID string `json:"mic_session_id"` + // RoomVersion 是触发客户端/RTC 事件时看到的房间版本,旧版本事件会被忽略。 + RoomVersion int64 `json:"room_version"` + // EventTimeMS 是客户端 SDK 或 RTC webhook 的事件时间,必须晚于已接受事件。 + EventTimeMS int64 `json:"event_time_ms"` + // Source 记录确认来源,例如 client 或 rtc_webhook。 + Source string `json:"source,omitempty"` +} + +// Type 返回命令类型。 +func (ConfirmMicPublishing) Type() string { return "confirm_mic_publishing" } + // ChangeMicSeat 定义换麦位请求。 type ChangeMicSeat struct { Base @@ -114,6 +143,50 @@ type ChangeMicSeat struct { // Type 返回命令类型。 func (ChangeMicSeat) Type() string { return "change_mic_seat" } +// SetMicSeatLock 定义锁定或解锁麦位请求。 +type SetMicSeatLock struct { + Base + // SeatNo 是目标麦位编号。 + SeatNo int32 `json:"seat_no"` + // Locked 为 true 表示锁定,false 表示解锁。 + Locked bool `json:"locked"` +} + +// Type 返回命令类型。 +func (SetMicSeatLock) Type() string { return "set_mic_seat_lock" } + +// SetChatEnabled 定义房间公屏开关请求。 +type SetChatEnabled struct { + Base + // Enabled 为 false 时 CheckSpeakPermission 会拒绝所有普通公屏发言。 + Enabled bool `json:"enabled"` +} + +// Type 返回命令类型。 +func (SetChatEnabled) Type() string { return "set_chat_enabled" } + +// SetRoomAdmin 定义添加或移除管理员请求。 +type SetRoomAdmin struct { + Base + // TargetUserID 是被添加或移除管理员身份的用户。 + TargetUserID int64 `json:"target_user_id"` + // Enabled 为 true 表示添加管理员,false 表示移除管理员。 + Enabled bool `json:"enabled"` +} + +// Type 返回命令类型。 +func (SetRoomAdmin) Type() string { return "set_room_admin" } + +// TransferRoomHost 定义主持人转移请求。 +type TransferRoomHost struct { + Base + // TargetUserID 是新的主持人,必须在当前房间 presence 中。 + TargetUserID int64 `json:"target_user_id"` +} + +// Type 返回命令类型。 +func (TransferRoomHost) Type() string { return "transfer_room_host" } + // MuteUser 定义禁言请求。 type MuteUser struct { Base @@ -136,6 +209,16 @@ type KickUser struct { // Type 返回命令类型。 func (KickUser) Type() string { return "kick_user" } +// UnbanUser 定义解除房间 ban 请求。 +type UnbanUser struct { + Base + // TargetUserID 是被解除 ban 的用户。 + TargetUserID int64 `json:"target_user_id"` +} + +// Type 返回命令类型。 +func (UnbanUser) Type() string { return "unban_user" } + // SendGift 定义送礼请求。 type SendGift struct { Base @@ -145,8 +228,16 @@ type SendGift struct { GiftID string `json:"gift_id"` // GiftCount 是礼物数量,必须为正数。 GiftCount int32 `json:"gift_count"` - // GiftUnitValue 是单个礼物价值,必须为正数,账务扣费由 wallet-service 执行。 - GiftUnitValue int64 `json:"gift_unit_value"` + // BillingReceiptID 是 wallet-service 成功落账后的回执,用于排障和恢复审计。 + BillingReceiptID string `json:"billing_receipt_id,omitempty"` + // CoinSpent 是 sender COIN 实际扣减值,来自 wallet-service 服务端价格。 + CoinSpent int64 `json:"coin_spent,omitempty"` + // GiftPointAdded 是 target GIFT_POINT 实际入账值,来自 wallet-service。 + GiftPointAdded int64 `json:"gift_point_added,omitempty"` + // HeatValue 是 Room Cell 恢复时唯一可信的热度增量,不能重新用客户端价格推导。 + HeatValue int64 `json:"heat_value,omitempty"` + // PriceVersion 是本次钱包结算使用的服务端礼物价格版本。 + PriceVersion string `json:"price_version,omitempty"` } // Type 返回命令类型。 @@ -158,11 +249,42 @@ func Serialize(cmd Command) ([]byte, error) { return json.Marshal(cmd) } +// IdempotencyPayloadForCommand 生成用于 command_id 冲突判定的业务载荷。 +func IdempotencyPayloadForCommand(cmd Command) ([]byte, error) { + payload, err := Serialize(cmd) + if err != nil { + return nil, err + } + return IdempotencyPayload(payload) +} + +// IdempotencyPayload 去掉请求追踪和入口节点等易变字段,只保留业务命令语义。 +func IdempotencyPayload(payload []byte) ([]byte, error) { + var values map[string]any + if err := json.Unmarshal(payload, &values); err != nil { + return nil, err + } + delete(values, "request_id") + delete(values, "gateway_node_id") + delete(values, "session_id") + delete(values, "sent_at_ms") + // SendGift 的以下字段由 wallet-service 结算后写入 command log,不属于客户端请求幂等语义。 + delete(values, "billing_receipt_id") + delete(values, "coin_spent") + delete(values, "gift_point_added") + delete(values, "heat_value") + delete(values, "price_version") + // MicUp 的发流确认 deadline 由 room-service 生成,客户端重试同一 command_id 时不能因此冲突。 + delete(values, "publish_deadline_ms") + + return json.Marshal(values) +} + // Deserialize 按命令类型恢复命令载荷。 func Deserialize(commandType string, payload []byte) (Command, error) { var cmd Command - // command_type 是恢复时的分发键,未知命令返回 nil 让恢复流程安全跳过。 + // command_type 是恢复时的分发键;未知类型表示当前代码无法保证恢复语义。 switch commandType { case CreateRoom{}.Type(): cmd = &CreateRoom{} @@ -174,16 +296,28 @@ func Deserialize(commandType string, payload []byte) (Command, error) { cmd = &MicUp{} case MicDown{}.Type(): cmd = &MicDown{} + case ConfirmMicPublishing{}.Type(): + cmd = &ConfirmMicPublishing{} case ChangeMicSeat{}.Type(): cmd = &ChangeMicSeat{} + case SetMicSeatLock{}.Type(): + cmd = &SetMicSeatLock{} + case SetChatEnabled{}.Type(): + cmd = &SetChatEnabled{} + case SetRoomAdmin{}.Type(): + cmd = &SetRoomAdmin{} + case TransferRoomHost{}.Type(): + cmd = &TransferRoomHost{} case MuteUser{}.Type(): cmd = &MuteUser{} case KickUser{}.Type(): cmd = &KickUser{} + case UnbanUser{}.Type(): + cmd = &UnbanUser{} case SendGift{}.Type(): cmd = &SendGift{} default: - return nil, nil + return nil, fmt.Errorf("unknown room command type: %s", commandType) } if err := json.Unmarshal(payload, cmd); err != nil { diff --git a/services/room-service/internal/room/service/authorization.go b/services/room-service/internal/room/service/authorization.go new file mode 100644 index 00000000..b46d99db --- /dev/null +++ b/services/room-service/internal/room/service/authorization.go @@ -0,0 +1,117 @@ +package service + +import ( + "hyapp/pkg/xerr" + "hyapp/services/room-service/internal/room/state" +) + +// roomRole 是权限矩阵内部使用的收敛角色,不直接信任客户端 JoinRoom.role。 +type roomRole string + +const ( + // roleOwner 来自 RoomState.OwnerUserID,拥有房间内最高管理权限。 + roleOwner roomRole = "owner" + // roleHost 来自 RoomState.HostUserID,负责现场管理但不能越过 owner。 + roleHost roomRole = "host" + // roleAdmin 来自 RoomState.AdminUsers,权限低于 owner/host。 + roleAdmin roomRole = "admin" + // roleAudience 是默认兜底角色,包含未登录、未在房间和普通观众。 + roleAudience roomRole = "audience" +) + +func actorRole(current *state.RoomState, actorUserID int64) roomRole { + // 角色权威顺序按文档固定:owner > host > admin > audience,不能只看 RoomUser.role 展示字段。 + switch { + case current == nil || actorUserID <= 0: + return roleAudience + case actorUserID == current.OwnerUserID: + return roleOwner + case actorUserID == current.HostUserID: + return roleHost + case current.AdminUsers[actorUserID]: + return roleAdmin + default: + return roleAudience + } +} + +func requireActiveRoom(current *state.RoomState) error { + // 当前只有 active 房间可执行管理和麦位命令;CloseRoom 语义后续单独扩展。 + if current == nil || current.Status != "active" { + return xerr.New(xerr.Conflict, "room is not active") + } + return nil +} + +func requireActorPresent(current *state.RoomState, actorUserID int64) error { + // 管理动作必须来自房间业务 presence;离房后的 host/admin 不能远程管理房间。 + if _, exists := current.OnlineUsers[actorUserID]; !exists { + return xerr.New(xerr.PermissionDenied, "permission denied") + } + return nil +} + +func requireManagerPresent(current *state.RoomState, actorUserID int64) error { + // 先校验 presence,再校验角色,避免房间外用户凭持久 admin 身份直接操作。 + if err := requireActorPresent(current, actorUserID); err != nil { + return err + } + if !isManagerRole(actorRole(current, actorUserID)) { + return xerr.New(xerr.PermissionDenied, "permission denied") + } + return nil +} + +func isManagerRole(role roomRole) bool { + // 管理角色集合只包含 owner/host/admin,普通观众只能做自助动作。 + return role == roleOwner || role == roleHost || role == roleAdmin +} + +func canManageTarget(current *state.RoomState, actorUserID int64, targetUserID int64) error { + // 目标用户同样按权威状态判定角色,防止客户端伪造 role 绕过管理层级。 + actor := actorRole(current, actorUserID) + target := actorRole(current, targetUserID) + if !isManagerRole(actor) { + return xerr.New(xerr.PermissionDenied, "permission denied") + } + if target == roleOwner && actor != roleOwner { + // host/admin 不能管理 owner,owner 自操作由具体命令的 self check 再收紧。 + return xerr.New(xerr.PermissionDenied, "permission denied") + } + if target == roleHost && actor == roleAdmin { + // 普通 admin 不能管理 host;host 变更必须走 owner 的 TransferRoomHost。 + return xerr.New(xerr.PermissionDenied, "permission denied") + } + return nil +} + +func canSelfOrManageTarget(current *state.RoomState, actorUserID int64, targetUserID int64) error { + // 自助动作例如自己下麦允许 audience 执行,代操作才进入管理权限矩阵。 + if actorUserID == targetUserID { + return nil + } + return canManageTarget(current, actorUserID, targetUserID) +} + +func requireOwnerPresent(current *state.RoomState, actorUserID int64) error { + // owner 专属命令仍要求 owner 当前在房间业务 presence 内。 + if err := requireActorPresent(current, actorUserID); err != nil { + return err + } + if actorRole(current, actorUserID) != roleOwner { + return xerr.New(xerr.PermissionDenied, "permission denied") + } + return nil +} + +func requireOwnerOrHostPresent(current *state.RoomState, actorUserID int64) error { + // 解封等高风险操作限制在 owner/host,普通 admin 不能恢复被 ban 用户。 + if err := requireActorPresent(current, actorUserID); err != nil { + return err + } + role := actorRole(current, actorUserID) + if role != roleOwner && role != roleHost { + return xerr.New(xerr.PermissionDenied, "permission denied") + } + return nil +} diff --git a/services/room-service/internal/room/service/create_room.go b/services/room-service/internal/room/service/create_room.go index 4f9b71ea..15d040fe 100644 --- a/services/room-service/internal/room/service/create_room.go +++ b/services/room-service/internal/room/service/create_room.go @@ -24,34 +24,26 @@ func (s *Service) CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest) } if !roomid.ValidStringID(req.GetMeta().GetRoomId()) { // 新房间必须能同时映射到腾讯云 IM GroupID 和 RTC strRoomId,避免创建后无法进语音。 - return nil, xerr.New(xerr.InvalidArgument, "room_id format is incompatible") + return nil, xerr.New(xerr.InvalidArgument, "room_id format is invalid") } mode := strings.TrimSpace(req.GetMode()) if mode == "" { // mode 是房间状态对客户端和后续策略分发的基础维度,不能创建空模式房。 return nil, xerr.New(xerr.InvalidArgument, "mode is required") } - - // protobuf 请求先收敛为 command,后续幂等、持久化和恢复都只认识 command 模型。 - cmd := command.CreateRoom{ - Base: baseFromMeta(req.GetMeta()), - OwnerUserID: req.GetOwnerUserId(), - HostUserID: req.GetHostUserId(), - SeatCount: req.GetSeatCount(), - Mode: mode, - } - actorUserID := cmd.ActorUserID() + actorUserID := req.GetMeta().GetActorUserId() if actorUserID == 0 { // CreateRoom 的 owner/host 默认来自已鉴权调用方,不允许匿名内部命令造房间。 return nil, xerr.New(xerr.InvalidArgument, "actor_user_id is required") } - if cmd.OwnerUserID == 0 { - // gateway 不需要理解房主分配语义;缺省 owner 由 room-service 从 actor 收敛。 - cmd.OwnerUserID = actorUserID - } - if cmd.HostUserID == 0 { - // v1 创建房间默认房主即主持,后续如需转主持应走独立房间管理命令。 - cmd.HostUserID = cmd.OwnerUserID + + // protobuf 请求先收敛为 command,后续幂等、持久化和恢复都只认识 command 模型。 + cmd := command.CreateRoom{ + Base: baseFromMeta(req.GetMeta()), + OwnerUserID: actorUserID, + HostUserID: actorUserID, + SeatCount: req.GetSeatCount(), + Mode: mode, } if cmd.SeatCount <= 0 { @@ -59,18 +51,11 @@ func (s *Service) CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest) return nil, xerr.New(xerr.InvalidArgument, "seat_count must be positive") } - if cmd.OwnerUserID == 0 || cmd.HostUserID == 0 { - // owner/host 是默认管理员集合来源,不能缺失。 - return nil, xerr.New(xerr.InvalidArgument, "owner and host are required") - } - if cmd.OwnerUserID != actorUserID || cmd.HostUserID != actorUserID { - // 外部创建路径不能代替其他用户开房,显式内部字段也不能偏离 actor。 - return nil, xerr.New(xerr.PermissionDenied, "owner and host must match actor") - } - - if seen, err := s.repository.HasCommand(ctx, cmd.RoomID(), cmd.ID()); err != nil { + payload, seen, err := s.commandPayloadForIdempotency(ctx, cmd) + if err != nil { return nil, err - } else if seen { + } + if seen { // CreateRoom 幂等重试也要确保腾讯云 IM 群组存在,避免上次创建在外部桥接处失败。 if err := s.syncPublisher.EnsureRoomGroup(ctx, cmd.RoomID(), cmd.OwnerUserID); err != nil { return nil, err @@ -122,11 +107,6 @@ func (s *Service) CreateRoom(ctx context.Context, req *roomv1.CreateRoomRequest) LastSeenAtMS: now.UnixMilli(), } - payload, err := command.Serialize(cmd) - if err != nil { - return nil, err - } - // RoomCreated 进入 outbox,供 activity/audit 等房间外系统异步消费。 createdEvent, err := outbox.Build(cmd.RoomID(), "RoomCreated", roomState.Version, now, &roomeventsv1.RoomCreated{ OwnerUserId: cmd.OwnerUserID, diff --git a/services/room-service/internal/room/service/event_attrs.go b/services/room-service/internal/room/service/event_attrs.go new file mode 100644 index 00000000..3af77fb8 --- /dev/null +++ b/services/room-service/internal/room/service/event_attrs.go @@ -0,0 +1,16 @@ +package service + +import "strconv" + +func boolString(value bool) string { + // 腾讯云 IM 自定义消息 attributes 只放字符串,bool 在这里统一转成小写字面量。 + if value { + return "true" + } + return "false" +} + +func int64String(value int64) string { + // 用户 ID 等数值进入 attributes 时保持十进制字符串,和 IM identifier 映射一致。 + return strconv.FormatInt(value, 10) +} diff --git a/services/room-service/internal/room/service/gift.go b/services/room-service/internal/room/service/gift.go index 9c895beb..d8aac835 100644 --- a/services/room-service/internal/room/service/gift.go +++ b/services/room-service/internal/room/service/gift.go @@ -20,11 +20,10 @@ import ( func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*roomv1.SendGiftResponse, error) { // SendGift 的账务不在 room-service 内结算,但房间表现、热度和榜单由 Room Cell 同步更新。 cmd := command.SendGift{ - Base: baseFromMeta(req.GetMeta()), - TargetUserID: req.GetTargetUserId(), - GiftID: req.GetGiftId(), - GiftCount: req.GetGiftCount(), - GiftUnitValue: req.GetGiftUnitValue(), + Base: baseFromMeta(req.GetMeta()), + TargetUserID: req.GetTargetUserId(), + GiftID: req.GetGiftId(), + GiftCount: req.GetGiftCount(), } result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, localRank *rank.LocalRank) (mutationResult, []outbox.Record, error) { @@ -38,28 +37,41 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r return mutationResult{}, nil, xerr.New(xerr.NotFound, "target not in room") } - if cmd.GiftCount <= 0 || cmd.GiftUnitValue <= 0 { - // 非正数礼物会破坏账务和榜单累计语义。 - return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "gift_count and gift_unit_value must be positive") + if cmd.GiftID == "" || cmd.GiftCount <= 0 { + // 礼物 ID 和数量是钱包查服务端价格的最小输入,客户端不再提交礼物单价。 + return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "gift_id and gift_count must be valid") } // 钱包扣费在房间状态变更前完成;失败时不写 command log、不进入 Room Cell 提交态。 billing, err := s.wallet.DebitGift(ctx, &walletv1.DebitGiftRequest{ - CommandId: cmd.ID(), - RoomId: cmd.RoomID(), - SenderUserId: cmd.ActorUserID(), - TargetUserId: cmd.TargetUserID, - GiftId: cmd.GiftID, - GiftCount: cmd.GiftCount, - GiftUnitValue: cmd.GiftUnitValue, + CommandId: cmd.ID(), + RoomId: cmd.RoomID(), + SenderUserId: cmd.ActorUserID(), + TargetUserId: cmd.TargetUserID, + GiftId: cmd.GiftID, + GiftCount: cmd.GiftCount, }) if err != nil { return mutationResult{}, nil, err } + if billing == nil { + return mutationResult{}, nil, xerr.New(xerr.Unavailable, "wallet debit response is empty") + } + heatValue := billing.GetHeatValue() + settledCommand := cmd + settledCommand.BillingReceiptID = billing.GetBillingReceiptId() + settledCommand.CoinSpent = billing.GetCoinSpent() + settledCommand.GiftPointAdded = billing.GetGiftPointAdded() + settledCommand.HeatValue = heatValue + settledCommand.PriceVersion = billing.GetPriceVersion() + commandPayload, err := command.Serialize(settledCommand) + if err != nil { + return mutationResult{}, nil, err + } // 扣费成功后,Room Cell 同步更新热度和本地礼物榜。 - current.Heat += billing.GetTotalGiftValue() - current.GiftRank = localRank.ApplyGift(cmd.ActorUserID(), billing.GetTotalGiftValue(), now) + current.Heat += heatValue + current.GiftRank = localRank.ApplyGift(cmd.ActorUserID(), heatValue, now) current.Version++ // 送礼事件、热度事件和榜单事件使用同一个房间版本,方便消费者关联同一次命令。 @@ -68,7 +80,7 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r TargetUserId: cmd.TargetUserID, GiftId: cmd.GiftID, GiftCount: cmd.GiftCount, - GiftValue: billing.GetTotalGiftValue(), + GiftValue: heatValue, BillingReceiptId: billing.GetBillingReceiptId(), }) if err != nil { @@ -76,7 +88,7 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r } heatEvent, err := outbox.Build(current.RoomID, "RoomHeatChanged", current.Version, now, &roomeventsv1.RoomHeatChanged{ - Delta: billing.GetTotalGiftValue(), + Delta: heatValue, CurrentHeat: current.Heat, }) if err != nil { @@ -98,6 +110,7 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r billingReceiptID: billing.GetBillingReceiptId(), roomHeat: current.Heat, giftRank: cloneProtoRank(current.GiftRank), + commandPayload: commandPayload, syncEvent: &tencentim.RoomEvent{ // 同步广播只选 GiftSent 主事件作为客户端房间系统消息,Heat/Rank 通过字段携带。 EventID: giftEvent.EventID, @@ -105,13 +118,16 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r EventType: "room_gift_sent", ActorUserID: cmd.ActorUserID(), TargetUserID: cmd.TargetUserID, - GiftValue: billing.GetTotalGiftValue(), + GiftValue: heatValue, RoomHeat: current.Heat, RoomVersion: current.Version, Attributes: map[string]string{ "gift_id": cmd.GiftID, "gift_count": fmt.Sprintf("%d", cmd.GiftCount), "billing_receipt_id": billing.GetBillingReceiptId(), + "coin_spent": fmt.Sprintf("%d", settledCommand.CoinSpent), + "gift_point_added": fmt.Sprintf("%d", settledCommand.GiftPointAdded), + "price_version": settledCommand.PriceVersion, }, }, }, []outbox.Record{giftEvent, heatEvent, rankEvent}, nil diff --git a/services/room-service/internal/room/service/management.go b/services/room-service/internal/room/service/management.go new file mode 100644 index 00000000..0be650dc --- /dev/null +++ b/services/room-service/internal/room/service/management.go @@ -0,0 +1,288 @@ +package service + +import ( + "context" + "time" + + roomeventsv1 "hyapp/api/proto/events/room/v1" + roomv1 "hyapp/api/proto/room/v1" + "hyapp/pkg/tencentim" + "hyapp/pkg/xerr" + "hyapp/services/room-service/internal/room/command" + "hyapp/services/room-service/internal/room/outbox" + "hyapp/services/room-service/internal/room/rank" + "hyapp/services/room-service/internal/room/state" +) + +// SetMicSeatLock 锁定或解锁指定麦位;首版只允许锁空麦位。 +func (s *Service) SetMicSeatLock(ctx context.Context, req *roomv1.SetMicSeatLockRequest) (*roomv1.SetMicSeatLockResponse, error) { + // 锁麦是房间管理命令,客户端不能通过麦位字段直接绕过 Room Cell 权限矩阵。 + cmd := command.SetMicSeatLock{ + Base: baseFromMeta(req.GetMeta()), + SeatNo: req.GetSeatNo(), + Locked: req.GetLocked(), + } + + result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) { + if err := requireActiveRoom(current); err != nil { + return mutationResult{}, nil, err + } + if err := requireManagerPresent(current, cmd.ActorUserID()); err != nil { + return mutationResult{}, nil, err + } + + index := current.SeatIndex(cmd.SeatNo) + if index < 0 { + // 只能锁定房间初始化时存在的麦位编号。 + return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "seat does not exist") + } + if cmd.Locked && current.MicSeats[index].UserID != 0 { + // 首版不把“下麦”和“锁麦”合并成一个命令,避免客户端处理复合事件。 + return mutationResult{}, nil, xerr.New(xerr.Conflict, "seat is occupied") + } + if current.MicSeats[index].Locked == cmd.Locked { + // 重复锁定或重复解锁是 no-op,只写 command log 做幂等保护,不广播事件。 + return mutationResult{snapshot: current.ToProto()}, nil, nil + } + + // 锁状态属于 Room Cell 核心态,必须和版本递增一起提交。 + current.MicSeats[index].Locked = cmd.Locked + current.Version++ + + // 锁麦事件进入 outbox,客户端通过系统消息修正麦位 UI。 + lockEvent, err := outbox.Build(current.RoomID, "RoomMicSeatLocked", current.Version, now, &roomeventsv1.RoomMicSeatLocked{ + ActorUserId: cmd.ActorUserID(), + SeatNo: cmd.SeatNo, + Locked: cmd.Locked, + }) + if err != nil { + return mutationResult{}, nil, err + } + + return mutationResult{ + snapshot: current.ToProto(), + syncEvent: &tencentim.RoomEvent{ + EventID: lockEvent.EventID, + RoomID: current.RoomID, + EventType: "room_mic_seat_locked", + ActorUserID: cmd.ActorUserID(), + SeatNo: cmd.SeatNo, + RoomVersion: current.Version, + Attributes: map[string]string{ + "locked": boolString(cmd.Locked), + }, + }, + }, []outbox.Record{lockEvent}, nil + }) + if err != nil { + return nil, err + } + + return &roomv1.SetMicSeatLockResponse{ + Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()), + Room: result.snapshot, + }, nil +} + +// SetChatEnabled 开启或关闭公屏发言守卫。 +func (s *Service) SetChatEnabled(ctx context.Context, req *roomv1.SetChatEnabledRequest) (*roomv1.SetChatEnabledResponse, error) { + // 公屏开关只影响普通客户端发言守卫,不阻断 room-service 发送系统消息。 + cmd := command.SetChatEnabled{ + Base: baseFromMeta(req.GetMeta()), + Enabled: req.GetEnabled(), + } + + result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) { + if err := requireActiveRoom(current); err != nil { + return mutationResult{}, nil, err + } + if err := requireManagerPresent(current, cmd.ActorUserID()); err != nil { + return mutationResult{}, nil, err + } + if current.ChatEnabled == cmd.Enabled { + // 重复设置同一个开关值不产生事件,避免客户端收到无意义的状态变更。 + return mutationResult{snapshot: current.ToProto()}, nil, nil + } + + // CheckSpeakPermission 后续会以该字段作为 chat_disabled 的权威来源。 + current.ChatEnabled = cmd.Enabled + current.Version++ + + // 公屏开关变化需要进入 outbox,便于 IM 系统消息和审计消费。 + chatEvent, err := outbox.Build(current.RoomID, "RoomChatEnabledChanged", current.Version, now, &roomeventsv1.RoomChatEnabledChanged{ + ActorUserId: cmd.ActorUserID(), + Enabled: cmd.Enabled, + }) + if err != nil { + return mutationResult{}, nil, err + } + + return mutationResult{ + snapshot: current.ToProto(), + syncEvent: &tencentim.RoomEvent{ + EventID: chatEvent.EventID, + RoomID: current.RoomID, + EventType: "room_chat_enabled_changed", + ActorUserID: cmd.ActorUserID(), + RoomVersion: current.Version, + Attributes: map[string]string{ + "enabled": boolString(cmd.Enabled), + }, + }, + }, []outbox.Record{chatEvent}, nil + }) + if err != nil { + return nil, err + } + + return &roomv1.SetChatEnabledResponse{ + Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()), + Room: result.snapshot, + }, nil +} + +// SetRoomAdmin 添加或移除房间管理员;只有 owner 可以执行。 +func (s *Service) SetRoomAdmin(ctx context.Context, req *roomv1.SetRoomAdminRequest) (*roomv1.SetRoomAdminResponse, error) { + // 管理员身份是 RoomState 持久态,离房后保留,重新进房后继续生效。 + cmd := command.SetRoomAdmin{ + Base: baseFromMeta(req.GetMeta()), + TargetUserID: req.GetTargetUserId(), + Enabled: req.GetEnabled(), + } + + result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) { + if err := requireActiveRoom(current); err != nil { + return mutationResult{}, nil, err + } + if cmd.TargetUserID <= 0 { + return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "target_user_id is required") + } + if err := requireOwnerPresent(current, cmd.ActorUserID()); err != nil { + // 只有 owner 可以增删 admin,host/admin 不能扩张自己的权限。 + return mutationResult{}, nil, err + } + if _, exists := current.OnlineUsers[cmd.TargetUserID]; !exists { + // 首版不支持预设管理员,避免把不存在或已离房用户加入管理集合。 + return mutationResult{}, nil, xerr.New(xerr.NotFound, "target not in room") + } + if !cmd.Enabled && (cmd.TargetUserID == current.OwnerUserID || cmd.TargetUserID == current.HostUserID) { + // owner/host 的管理身份由专门字段决定,不能通过 admin 集合移除。 + return mutationResult{}, nil, xerr.New(xerr.PermissionDenied, "permission denied") + } + if current.AdminUsers[cmd.TargetUserID] == cmd.Enabled { + // 重复添加或重复移除是 no-op,避免重复发送 RoomAdminChanged。 + return mutationResult{snapshot: current.ToProto()}, nil, nil + } + + if cmd.Enabled { + // map set 是管理员集合的恢复来源,快照会导出为 admin_user_ids。 + current.AdminUsers[cmd.TargetUserID] = true + } else { + delete(current.AdminUsers, cmd.TargetUserID) + } + current.Version++ + + // 管理员变更事件同时服务客户端提示和后续房间审计。 + adminEvent, err := outbox.Build(current.RoomID, "RoomAdminChanged", current.Version, now, &roomeventsv1.RoomAdminChanged{ + ActorUserId: cmd.ActorUserID(), + TargetUserId: cmd.TargetUserID, + Enabled: cmd.Enabled, + }) + if err != nil { + return mutationResult{}, nil, err + } + + return mutationResult{ + snapshot: current.ToProto(), + syncEvent: &tencentim.RoomEvent{ + EventID: adminEvent.EventID, + RoomID: current.RoomID, + EventType: "room_admin_changed", + ActorUserID: cmd.ActorUserID(), + TargetUserID: cmd.TargetUserID, + RoomVersion: current.Version, + Attributes: map[string]string{ + "enabled": boolString(cmd.Enabled), + }, + }, + }, []outbox.Record{adminEvent}, nil + }) + if err != nil { + return nil, err + } + + return &roomv1.SetRoomAdminResponse{ + Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()), + Room: result.snapshot, + }, nil +} + +// TransferRoomHost 转移主持人;新 host 自动进入管理员集合。 +func (s *Service) TransferRoomHost(ctx context.Context, req *roomv1.TransferRoomHostRequest) (*roomv1.TransferRoomHostResponse, error) { + // 主持人是独立于 admin 集合的强角色,转移必须由 owner 发起。 + cmd := command.TransferRoomHost{ + Base: baseFromMeta(req.GetMeta()), + TargetUserID: req.GetTargetUserId(), + } + + result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) { + if err := requireActiveRoom(current); err != nil { + return mutationResult{}, nil, err + } + if cmd.TargetUserID <= 0 { + return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "target_user_id is required") + } + if err := requireOwnerPresent(current, cmd.ActorUserID()); err != nil { + // host 不能自转移或把主持权限授予他人。 + return mutationResult{}, nil, err + } + if _, exists := current.OnlineUsers[cmd.TargetUserID]; !exists { + // 新 host 必须当前在房间 presence 内,保证交接对象可立即管理现场。 + return mutationResult{}, nil, xerr.New(xerr.NotFound, "target not in room") + } + if current.HostUserID == cmd.TargetUserID { + // 转给当前 host 不改变状态,也不重复广播。 + return mutationResult{snapshot: current.ToProto()}, nil, nil + } + + oldHostUserID := current.HostUserID + // 新 host 自动进入 AdminUsers;旧 host 是否保留 admin 由当前实现保持不变。 + current.HostUserID = cmd.TargetUserID + current.AdminUsers[cmd.TargetUserID] = true + current.Version++ + + // 主持人转移是房间管理强事件,必须进入 outbox 便于客户端和审计同步。 + hostEvent, err := outbox.Build(current.RoomID, "RoomHostTransferred", current.Version, now, &roomeventsv1.RoomHostTransferred{ + ActorUserId: cmd.ActorUserID(), + OldHostUserId: oldHostUserID, + NewHostUserId: cmd.TargetUserID, + }) + if err != nil { + return mutationResult{}, nil, err + } + + return mutationResult{ + snapshot: current.ToProto(), + syncEvent: &tencentim.RoomEvent{ + EventID: hostEvent.EventID, + RoomID: current.RoomID, + EventType: "room_host_transferred", + ActorUserID: cmd.ActorUserID(), + TargetUserID: cmd.TargetUserID, + RoomVersion: current.Version, + Attributes: map[string]string{ + "old_host_user_id": int64String(oldHostUserID), + "new_host_user_id": int64String(cmd.TargetUserID), + }, + }, + }, []outbox.Record{hostEvent}, nil + }) + if err != nil { + return nil, err + } + + return &roomv1.TransferRoomHostResponse{ + Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()), + Room: result.snapshot, + }, nil +} diff --git a/services/room-service/internal/room/service/mic.go b/services/room-service/internal/room/service/mic.go index 914bb7d9..e1f726c0 100644 --- a/services/room-service/internal/room/service/mic.go +++ b/services/room-service/internal/room/service/mic.go @@ -2,6 +2,8 @@ package service import ( "context" + "crypto/sha256" + "encoding/hex" "time" roomeventsv1 "hyapp/api/proto/events/room/v1" @@ -21,8 +23,13 @@ func (s *Service) MicUp(ctx context.Context, req *roomv1.MicUpRequest) (*roomv1. Base: baseFromMeta(req.GetMeta()), SeatNo: req.GetSeatNo(), } + cmd.MicSessionID = micSessionIDForCommand(cmd.RoomID(), cmd.ID()) + cmd.PublishDeadlineMS = s.clock.Now().Add(s.micPublishTimeout).UnixMilli() result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) { + if err := requireActiveRoom(current); err != nil { + return mutationResult{}, nil, err + } if _, exists := current.OnlineUsers[cmd.ActorUserID()]; !exists { // 不在 room-service presence 的用户不能上麦。 return mutationResult{}, nil, xerr.New(xerr.NotFound, "user not in room") @@ -49,25 +56,37 @@ func (s *Service) MicUp(ctx context.Context, req *roomv1.MicUpRequest) (*roomv1. return mutationResult{}, nil, xerr.New(xerr.Conflict, "seat is occupied") } - // 麦位占用和房间版本必须在同一个 Room Cell 任务里更新。 - current.MicSeats[index].UserID = cmd.ActorUserID() + // MicUp 只代表业务占麦成功;RTC 发流必须再通过 ConfirmMicPublishing 确认。 current.Version++ + current.MicSeats[index].UserID = cmd.ActorUserID() + current.MicSeats[index].PublishState = state.MicPublishPending + current.MicSeats[index].MicSessionID = cmd.MicSessionID + current.MicSeats[index].PublishDeadlineMS = cmd.PublishDeadlineMS + current.MicSeats[index].MicSessionRoomVersion = current.Version + // 新 session 尚未接受任何 RTC/客户端事件;不能用服务端 MicUp 时间初始化, + // 否则客户端设备时间略慢时,合法的首次确认会被误判为旧事件。 + current.MicSeats[index].LastPublishEventTimeMS = 0 // RoomMicChanged 是房间外消费者和腾讯云 IM 共同使用的麦位事件源。 micEvent, err := outbox.Build(current.RoomID, "RoomMicChanged", current.Version, now, &roomeventsv1.RoomMicChanged{ - ActorUserId: cmd.ActorUserID(), - TargetUserId: cmd.ActorUserID(), - FromSeat: 0, - ToSeat: cmd.SeatNo, - Action: "up", + ActorUserId: cmd.ActorUserID(), + TargetUserId: cmd.ActorUserID(), + FromSeat: 0, + ToSeat: cmd.SeatNo, + Action: "up", + MicSessionId: cmd.MicSessionID, + PublishState: state.MicPublishPending, + PublishDeadlineMs: cmd.PublishDeadlineMS, }) if err != nil { return mutationResult{}, nil, err } return mutationResult{ - snapshot: current.ToProto(), - seatNo: cmd.SeatNo, + snapshot: current.ToProto(), + seatNo: cmd.SeatNo, + micSessionID: cmd.MicSessionID, + publishDeadlineMS: cmd.PublishDeadlineMS, syncEvent: &tencentim.RoomEvent{ EventID: micEvent.EventID, RoomID: current.RoomID, @@ -76,17 +95,33 @@ func (s *Service) MicUp(ctx context.Context, req *roomv1.MicUpRequest) (*roomv1. TargetUserID: cmd.ActorUserID(), SeatNo: cmd.SeatNo, RoomVersion: current.Version, + Attributes: map[string]string{ + "action": "up", + "publish_state": state.MicPublishPending, + "mic_session_id": cmd.MicSessionID, + "publish_deadline_ms": int64String(cmd.PublishDeadlineMS), + }, }, }, []outbox.Record{micEvent}, nil }) if err != nil { return nil, err } + if result.micSessionID == "" { + // 幂等重试不会重新执行 mutate,需要从当前快照补回首次 MicUp 生成的会话信息。 + if seat := protoSeatByUser(result.snapshot, cmd.ActorUserID()); seat != nil { + result.seatNo = seat.GetSeatNo() + result.micSessionID = seat.GetMicSessionId() + result.publishDeadlineMS = seat.GetPublishDeadlineMs() + } + } return &roomv1.MicUpResponse{ - Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()), - SeatNo: result.seatNo, - Room: result.snapshot, + Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()), + SeatNo: result.seatNo, + Room: result.snapshot, + MicSessionId: result.micSessionID, + PublishDeadlineMs: result.publishDeadlineMS, }, nil } @@ -96,18 +131,55 @@ func (s *Service) MicDown(ctx context.Context, req *roomv1.MicDownRequest) (*roo cmd := command.MicDown{ Base: baseFromMeta(req.GetMeta()), TargetUserID: req.GetTargetUserId(), + Reason: req.GetReason(), + } + if cmd.TargetUserID == 0 { + // target 缺省时按主动下麦处理;管理别人下麦必须显式传 target_user_id。 + cmd.TargetUserID = cmd.ActorUserID() } - result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) { + result, err := s.micDown(ctx, cmd) + if err != nil { + return nil, err + } + + return &roomv1.MicDownResponse{ + Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()), + SeatNo: result.seatNo, + Room: result.snapshot, + }, nil +} + +func (s *Service) micDown(ctx context.Context, cmd command.MicDown) (mutationResult, error) { + return s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) { + if err := requireActiveRoom(current); err != nil { + return mutationResult{}, nil, err + } + if cmd.TargetUserID <= 0 { + return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "target_user_id is required") + } + if err := requireActorPresent(current, cmd.ActorUserID()); err != nil { + return mutationResult{}, nil, err + } + if err := canSelfOrManageTarget(current, cmd.ActorUserID(), cmd.TargetUserID); err != nil { + return mutationResult{}, nil, err + } + seat, exists := current.SeatByUser(cmd.TargetUserID) if !exists { // 目标用户不在麦上时不能生成下麦事件。 return mutationResult{}, nil, xerr.New(xerr.NotFound, "target not on seat") } + if cmd.MicSessionID != "" && seat.MicSessionID != cmd.MicSessionID { + // 超时任务携带旧 mic_session_id 时只能成为 no-op,不能清掉用户新的上麦会话。 + return mutationResult{snapshot: current.ToProto(), seatNo: seat.SeatNo}, nil, nil + } + // SeatByUser 返回的是值拷贝,真正修改需要再定位切片下标。 index := current.SeatIndex(seat.SeatNo) - current.MicSeats[index].UserID = 0 + micSessionID := current.MicSeats[index].MicSessionID + current.ClearMicSession(index) current.Version++ micEvent, err := outbox.Build(current.RoomID, "RoomMicChanged", current.Version, now, &roomeventsv1.RoomMicChanged{ @@ -116,6 +188,9 @@ func (s *Service) MicDown(ctx context.Context, req *roomv1.MicDownRequest) (*roo FromSeat: seat.SeatNo, ToSeat: 0, Action: "down", + MicSessionId: micSessionID, + PublishState: state.MicPublishIdle, + Reason: cmd.Reason, }) if err != nil { return mutationResult{}, nil, err @@ -132,18 +207,15 @@ func (s *Service) MicDown(ctx context.Context, req *roomv1.MicDownRequest) (*roo TargetUserID: cmd.TargetUserID, SeatNo: seat.SeatNo, RoomVersion: current.Version, + Attributes: map[string]string{ + "action": "down", + "publish_state": state.MicPublishIdle, + "mic_session_id": micSessionID, + "reason": cmd.Reason, + }, }, }, []outbox.Record{micEvent}, nil }) - if err != nil { - return nil, err - } - - return &roomv1.MicDownResponse{ - Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()), - SeatNo: result.seatNo, - Room: result.snapshot, - }, nil } // ChangeMicSeat 调整指定用户所在麦位。 @@ -156,6 +228,19 @@ func (s *Service) ChangeMicSeat(ctx context.Context, req *roomv1.ChangeMicSeatRe } result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) { + if err := requireActiveRoom(current); err != nil { + return mutationResult{}, nil, err + } + if cmd.TargetUserID <= 0 { + return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "target_user_id is required") + } + if err := requireActorPresent(current, cmd.ActorUserID()); err != nil { + return mutationResult{}, nil, err + } + if err := canManageTarget(current, cmd.ActorUserID(), cmd.TargetUserID); err != nil { + return mutationResult{}, nil, err + } + fromSeat, exists := current.SeatByUser(cmd.TargetUserID) if !exists { // 未上麦用户没有可换出的源麦位。 @@ -180,16 +265,30 @@ func (s *Service) ChangeMicSeat(ctx context.Context, req *roomv1.ChangeMicSeatRe // 清空源麦位和占用目标麦位在同一 Cell 任务内完成,客户端不会看到中间态。 fromIndex := current.SeatIndex(fromSeat.SeatNo) + movedSeat := current.MicSeats[fromIndex] current.MicSeats[fromIndex].UserID = 0 + current.MicSeats[fromIndex].PublishState = "" + current.MicSeats[fromIndex].MicSessionID = "" + current.MicSeats[fromIndex].PublishDeadlineMS = 0 + current.MicSeats[fromIndex].MicSessionRoomVersion = 0 + current.MicSeats[fromIndex].LastPublishEventTimeMS = 0 current.MicSeats[toIndex].UserID = cmd.TargetUserID + current.MicSeats[toIndex].PublishState = movedSeat.PublishState + current.MicSeats[toIndex].MicSessionID = movedSeat.MicSessionID + current.MicSeats[toIndex].PublishDeadlineMS = movedSeat.PublishDeadlineMS + current.MicSeats[toIndex].MicSessionRoomVersion = movedSeat.MicSessionRoomVersion + current.MicSeats[toIndex].LastPublishEventTimeMS = movedSeat.LastPublishEventTimeMS current.Version++ micEvent, err := outbox.Build(current.RoomID, "RoomMicChanged", current.Version, now, &roomeventsv1.RoomMicChanged{ - ActorUserId: cmd.ActorUserID(), - TargetUserId: cmd.TargetUserID, - FromSeat: fromSeat.SeatNo, - ToSeat: cmd.SeatNo, - Action: "change", + ActorUserId: cmd.ActorUserID(), + TargetUserId: cmd.TargetUserID, + FromSeat: fromSeat.SeatNo, + ToSeat: cmd.SeatNo, + Action: "change", + MicSessionId: movedSeat.MicSessionID, + PublishState: movedSeat.PublishState, + PublishDeadlineMs: movedSeat.PublishDeadlineMS, }) if err != nil { return mutationResult{}, nil, err @@ -205,6 +304,12 @@ func (s *Service) ChangeMicSeat(ctx context.Context, req *roomv1.ChangeMicSeatRe TargetUserID: cmd.TargetUserID, SeatNo: cmd.SeatNo, RoomVersion: current.Version, + Attributes: map[string]string{ + "action": "change", + "publish_state": movedSeat.PublishState, + "mic_session_id": movedSeat.MicSessionID, + "publish_deadline_ms": int64String(movedSeat.PublishDeadlineMS), + }, }, }, []outbox.Record{micEvent}, nil }) @@ -217,3 +322,124 @@ func (s *Service) ChangeMicSeat(ctx context.Context, req *roomv1.ChangeMicSeatRe Room: result.snapshot, }, nil } + +// ConfirmMicPublishing 把 pending_publish 麦位推进到 publishing。 +func (s *Service) ConfirmMicPublishing(ctx context.Context, req *roomv1.ConfirmMicPublishingRequest) (*roomv1.ConfirmMicPublishingResponse, error) { + cmd := command.ConfirmMicPublishing{ + Base: baseFromMeta(req.GetMeta()), + TargetUserID: req.GetTargetUserId(), + MicSessionID: req.GetMicSessionId(), + RoomVersion: req.GetRoomVersion(), + EventTimeMS: req.GetEventTimeMs(), + Source: req.GetSource(), + } + if cmd.TargetUserID == 0 { + cmd.TargetUserID = cmd.ActorUserID() + } + + result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) { + if err := requireActiveRoom(current); err != nil { + return mutationResult{}, nil, err + } + if cmd.TargetUserID <= 0 || cmd.MicSessionID == "" || cmd.RoomVersion <= 0 || cmd.EventTimeMS <= 0 { + return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "mic publish confirmation is incomplete") + } + if err := requireActorPresent(current, cmd.ActorUserID()); err != nil { + return mutationResult{}, nil, err + } + if cmd.ActorUserID() != cmd.TargetUserID { + if err := canManageTarget(current, cmd.ActorUserID(), cmd.TargetUserID); err != nil { + return mutationResult{}, nil, err + } + } + + seat, exists := current.SeatByUser(cmd.TargetUserID) + if !exists { + // RTC webhook 可能晚于用户下麦到达;旧事件只忽略,不返回错误清掉新状态。 + return mutationResult{snapshot: current.ToProto()}, nil, nil + } + if seat.MicSessionID != cmd.MicSessionID || + cmd.RoomVersion < seat.MicSessionRoomVersion || + cmd.EventTimeMS <= seat.LastPublishEventTimeMS || + cmd.EventTimeMS > seat.PublishDeadlineMS { + return mutationResult{snapshot: current.ToProto(), seatNo: seat.SeatNo}, nil, nil + } + if seat.PublishState == state.MicPublishPublishing { + return mutationResult{snapshot: current.ToProto(), seatNo: seat.SeatNo}, nil, nil + } + if seat.PublishState != state.MicPublishPending { + return mutationResult{}, nil, xerr.New(xerr.Conflict, "mic is not waiting for publish confirmation") + } + + index := current.SeatIndex(seat.SeatNo) + current.MicSeats[index].PublishState = state.MicPublishPublishing + current.MicSeats[index].LastPublishEventTimeMS = cmd.EventTimeMS + current.Version++ + + micEvent, err := outbox.Build(current.RoomID, "RoomMicChanged", current.Version, now, &roomeventsv1.RoomMicChanged{ + ActorUserId: cmd.ActorUserID(), + TargetUserId: cmd.TargetUserID, + FromSeat: seat.SeatNo, + ToSeat: seat.SeatNo, + Action: "publish_confirmed", + MicSessionId: cmd.MicSessionID, + PublishState: state.MicPublishPublishing, + PublishDeadlineMs: seat.PublishDeadlineMS, + PublishEventTimeMs: cmd.EventTimeMS, + }) + if err != nil { + return mutationResult{}, nil, err + } + + return mutationResult{ + snapshot: current.ToProto(), + seatNo: seat.SeatNo, + micSessionID: cmd.MicSessionID, + publishDeadlineMS: seat.PublishDeadlineMS, + syncEvent: &tencentim.RoomEvent{ + EventID: micEvent.EventID, + RoomID: current.RoomID, + EventType: "room_mic_publish_confirmed", + ActorUserID: cmd.ActorUserID(), + TargetUserID: cmd.TargetUserID, + SeatNo: seat.SeatNo, + RoomVersion: current.Version, + Attributes: map[string]string{ + "action": "publish_confirmed", + "publish_state": state.MicPublishPublishing, + "mic_session_id": cmd.MicSessionID, + "publish_event_time_ms": int64String(cmd.EventTimeMS), + "source": cmd.Source, + }, + }, + }, []outbox.Record{micEvent}, nil + }) + if err != nil { + return nil, err + } + + return &roomv1.ConfirmMicPublishingResponse{ + Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()), + SeatNo: result.seatNo, + Room: result.snapshot, + }, nil +} + +func micSessionIDForCommand(roomID string, commandID string) string { + sum := sha256.Sum256([]byte(roomID + "\x00" + commandID)) + return "mic_" + hex.EncodeToString(sum[:16]) +} + +func protoSeatByUser(snapshot *roomv1.RoomSnapshot, userID int64) *roomv1.SeatState { + if snapshot == nil { + return nil + } + + for _, seat := range snapshot.GetMicSeats() { + if seat.GetUserId() == userID { + return seat + } + } + + return nil +} diff --git a/services/room-service/internal/room/service/mic_publish_timeout.go b/services/room-service/internal/room/service/mic_publish_timeout.go new file mode 100644 index 00000000..49d24210 --- /dev/null +++ b/services/room-service/internal/room/service/mic_publish_timeout.go @@ -0,0 +1,90 @@ +package service + +import ( + "context" + "time" + + "hyapp/pkg/idgen" + "hyapp/services/room-service/internal/room/command" + "hyapp/services/room-service/internal/room/state" +) + +const micDownReasonPublishTimeout = "publish_timeout" + +// RunMicPublishTimeoutWorker 周期性释放超过 deadline 仍未确认 RTC 发流的麦位。 +func (s *Service) RunMicPublishTimeoutWorker(ctx context.Context, interval time.Duration) { + if interval <= 0 || s.micPublishTimeout <= 0 { + // 非正数表示显式关闭,便于测试或临时止血。 + return + } + + ticker := time.NewTicker(interval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + _ = s.SweepMicPublishTimeouts(ctx) + } + } +} + +// SweepMicPublishTimeouts 扫描本节点已装载 Room Cell,自动下掉未确认发流的 pending_publish 麦位。 +func (s *Service) SweepMicPublishTimeouts(ctx context.Context) error { + now := s.clock.Now() + nowMs := now.UnixMilli() + for _, roomID := range s.loadedRoomIDs() { + roomCell := s.loadCell(roomID) + if roomCell == nil { + continue + } + + currentState, _, err := roomCell.Snapshot(ctx) + if err != nil { + return err + } + + for _, seat := range pendingPublishTimedOutSeats(currentState, nowMs) { + cmd := command.MicDown{ + Base: command.Base{ + RequestID: idgen.New("req_mic_publish_timeout"), + CommandID: idgen.New("cmd_mic_publish_timeout"), + ActorID: seat.UserID, + Room: roomID, + GatewayNodeID: s.nodeID, + SessionID: "mic-publish-timeout", + SentAtMS: nowMs, + }, + TargetUserID: seat.UserID, + MicSessionID: seat.MicSessionID, + Reason: micDownReasonPublishTimeout, + } + if _, err := s.micDown(ctx, cmd); err != nil { + return err + } + } + } + + return nil +} + +func pendingPublishTimedOutSeats(current *state.RoomState, nowMs int64) []state.MicSeat { + if current == nil { + return nil + } + + seats := make([]state.MicSeat, 0) + for _, seat := range current.MicSeats { + if seat.UserID <= 0 || seat.MicSessionID == "" { + continue + } + if seat.PublishState == state.MicPublishPending && seat.PublishDeadlineMS > 0 && seat.PublishDeadlineMS <= nowMs { + // 返回值拷贝,后续 MicDown 还会用 mic_session_id 在当前 Cell 状态中二次校验。 + seats = append(seats, seat) + } + } + + return seats +} diff --git a/services/room-service/internal/room/service/moderation.go b/services/room-service/internal/room/service/moderation.go index 0203423c..0e4a3a13 100644 --- a/services/room-service/internal/room/service/moderation.go +++ b/services/room-service/internal/room/service/moderation.go @@ -7,6 +7,7 @@ import ( roomeventsv1 "hyapp/api/proto/events/room/v1" roomv1 "hyapp/api/proto/room/v1" "hyapp/pkg/tencentim" + "hyapp/pkg/xerr" "hyapp/services/room-service/internal/room/command" "hyapp/services/room-service/internal/room/outbox" "hyapp/services/room-service/internal/room/rank" @@ -23,6 +24,26 @@ func (s *Service) MuteUser(ctx context.Context, req *roomv1.MuteUserRequest) (*r } result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) { + if err := requireActiveRoom(current); err != nil { + return mutationResult{}, nil, err + } + if cmd.TargetUserID <= 0 { + return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "target_user_id is required") + } + if cmd.TargetUserID == cmd.ActorUserID() { + return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "cannot mute self") + } + if err := requireActorPresent(current, cmd.ActorUserID()); err != nil { + return mutationResult{}, nil, err + } + if err := canManageTarget(current, cmd.ActorUserID(), cmd.TargetUserID); err != nil { + return mutationResult{}, nil, err + } + if current.MuteUsers[cmd.TargetUserID] == cmd.Muted { + // 重复设置同一禁言状态是业务 no-op,仍由 pipeline 写 command log 做幂等保护。 + return mutationResult{snapshot: current.ToProto()}, nil, nil + } + if cmd.Muted { // map set 表达禁言集合,true 才会被快照导出。 current.MuteUsers[cmd.TargetUserID] = true @@ -74,14 +95,41 @@ func (s *Service) KickUser(ctx context.Context, req *roomv1.KickUserRequest) (*r } result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) { + if err := requireActiveRoom(current); err != nil { + return mutationResult{}, nil, err + } + if cmd.TargetUserID <= 0 { + return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "target_user_id is required") + } + if cmd.TargetUserID == cmd.ActorUserID() { + return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "cannot kick self") + } + if cmd.TargetUserID == current.HostUserID { + return mutationResult{}, nil, xerr.New(xerr.PermissionDenied, "permission denied") + } + if err := requireActorPresent(current, cmd.ActorUserID()); err != nil { + return mutationResult{}, nil, err + } + if err := canManageTarget(current, cmd.ActorUserID(), cmd.TargetUserID); err != nil { + return mutationResult{}, nil, err + } + + _, inRoom := current.OnlineUsers[cmd.TargetUserID] + _, onSeat := current.SeatByUser(cmd.TargetUserID) + if current.BanUsers[cmd.TargetUserID] && !inRoom && !onSeat && !current.AdminUsers[cmd.TargetUserID] { + // 已处于 ban 且没有残留 presence、麦位或管理员身份时,重复 KickUser 不产生新事件。 + return mutationResult{snapshot: current.ToProto()}, nil, nil + } + if seat, exists := current.SeatByUser(cmd.TargetUserID); exists { // 被踢用户如果在麦上,必须先释放麦位,避免恢复后出现幽灵占位。 index := current.SeatIndex(seat.SeatNo) - current.MicSeats[index].UserID = 0 + current.ClearMicSession(index) } // ban 集合会让后续 JoinRoom 和 VerifyRoomPresence 都失败。 delete(current.OnlineUsers, cmd.TargetUserID) + delete(current.AdminUsers, cmd.TargetUserID) current.BanUsers[cmd.TargetUserID] = true current.Version++ @@ -114,3 +162,58 @@ func (s *Service) KickUser(ctx context.Context, req *roomv1.KickUserRequest) (*r Room: result.snapshot, }, nil } + +// UnbanUser 解除房间 ban;不会恢复 presence、管理员、麦位或 RTC 推流事实。 +func (s *Service) UnbanUser(ctx context.Context, req *roomv1.UnbanUserRequest) (*roomv1.UnbanUserResponse, error) { + cmd := command.UnbanUser{ + Base: baseFromMeta(req.GetMeta()), + TargetUserID: req.GetTargetUserId(), + } + + result, err := s.mutateRoom(ctx, cmd, true, func(now time.Time, current *state.RoomState, _ *rank.LocalRank) (mutationResult, []outbox.Record, error) { + if err := requireActiveRoom(current); err != nil { + return mutationResult{}, nil, err + } + if cmd.TargetUserID <= 0 { + return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "target_user_id is required") + } + if err := requireOwnerOrHostPresent(current, cmd.ActorUserID()); err != nil { + return mutationResult{}, nil, err + } + if !current.BanUsers[cmd.TargetUserID] { + // 重复 unban 是 no-op,仍写 command log 确保 command_id 幂等可判定。 + return mutationResult{snapshot: current.ToProto()}, nil, nil + } + + delete(current.BanUsers, cmd.TargetUserID) + current.Version++ + + unbanEvent, err := outbox.Build(current.RoomID, "RoomUserUnbanned", current.Version, now, &roomeventsv1.RoomUserUnbanned{ + ActorUserId: cmd.ActorUserID(), + TargetUserId: cmd.TargetUserID, + }) + if err != nil { + return mutationResult{}, nil, err + } + + return mutationResult{ + snapshot: current.ToProto(), + syncEvent: &tencentim.RoomEvent{ + EventID: unbanEvent.EventID, + RoomID: current.RoomID, + EventType: "room_user_unbanned", + ActorUserID: cmd.ActorUserID(), + TargetUserID: cmd.TargetUserID, + RoomVersion: current.Version, + }, + }, []outbox.Record{unbanEvent}, nil + }) + if err != nil { + return nil, err + } + + return &roomv1.UnbanUserResponse{ + Result: commandResult(result.applied, result.snapshot.GetVersion(), s.clock.Now()), + Room: result.snapshot, + }, nil +} diff --git a/services/room-service/internal/room/service/outbox_worker.go b/services/room-service/internal/room/service/outbox_worker.go index 0b2185ee..072de22f 100644 --- a/services/room-service/internal/room/service/outbox_worker.go +++ b/services/room-service/internal/room/service/outbox_worker.go @@ -28,6 +28,7 @@ type OutboxWorkerOptions struct { } func defaultOutboxWorkerOptions() OutboxWorkerOptions { + // 默认值对应文档里的 V1 固定间隔补偿策略,优先避免 busy loop 和无限外部调用。 return OutboxWorkerOptions{ PollInterval: time.Second, BatchSize: 100, @@ -37,18 +38,23 @@ func defaultOutboxWorkerOptions() OutboxWorkerOptions { } func normalizeOutboxWorkerOptions(options OutboxWorkerOptions) OutboxWorkerOptions { + // worker 层再次归一化,防止测试或手动构造绕过 config.Normalize。 defaults := defaultOutboxWorkerOptions() if options.PollInterval <= 0 { + // 非正间隔不能直接传给 ticker,否则会 panic 或形成忙循环。 options.PollInterval = defaults.PollInterval } if options.BatchSize <= 0 { + // 非正批量按安全默认值处理,避免 repository 扫描不受控。 options.BatchSize = defaults.BatchSize } if options.PublishTimeout <= 0 { + // 单条外部投递必须有 deadline,shutdown 才能等待有界。 options.PublishTimeout = defaults.PublishTimeout } options.RetryStrategy = strings.TrimSpace(options.RetryStrategy) if options.RetryStrategy == "" { + // 空策略按 fixed_interval 处理,保持和配置默认值一致。 options.RetryStrategy = defaults.RetryStrategy } @@ -134,10 +140,12 @@ func (s *Service) ProcessPendingOutbox(ctx context.Context, options OutboxWorker } func trimOutboxError(value string) string { + // last_error 会写入 MySQL 和日志,先去掉首尾空白避免不可见差异影响排障。 value = strings.TrimSpace(value) if len(value) <= 512 { return value } + // V1 只保留错误前缀,避免外部响应过大撑爆日志或 TEXT 字段查询成本。 return value[:512] } diff --git a/services/room-service/internal/room/service/pipeline.go b/services/room-service/internal/room/service/pipeline.go index 56292430..8276a5d3 100644 --- a/services/room-service/internal/room/service/pipeline.go +++ b/services/room-service/internal/room/service/pipeline.go @@ -1,6 +1,7 @@ package service import ( + "bytes" "context" "log" "time" @@ -18,9 +19,11 @@ func (s *Service) mutateRoom(ctx context.Context, cmd command.Command, replayabl return mutationResult{}, xerr.New(xerr.InvalidArgument, "command meta is incomplete") } - if seen, err := s.repository.HasCommand(ctx, cmd.RoomID(), cmd.ID()); err != nil { + payload, seen, err := s.commandPayloadForIdempotency(ctx, cmd) + if err != nil { return mutationResult{}, err - } else if seen { + } + if seen { // 幂等命令不重新执行 mutate,也不会再次调用 wallet 或写 outbox。 snapshot, err := s.currentSnapshot(ctx, cmd.RoomID()) if err != nil { @@ -55,17 +58,12 @@ func (s *Service) mutateRoom(ctx context.Context, cmd command.Command, replayabl if result.snapshot.GetVersion() != current.Version { // 版本变化代表本命令产生实际状态变更,需要持久化命令和事件。 - payload, err := command.Serialize(cmd) - if err != nil { - return nil, err - } - if err := s.repository.SaveCommand(ctx, CommandRecord{ RoomID: cmd.RoomID(), RoomVersion: nextState.Version, CommandID: cmd.ID(), CommandType: cmd.Type(), - Payload: payload, + Payload: commandPayloadForRecord(payload, result), Replayable: replayable, CreatedAt: now, }); err != nil { @@ -84,6 +82,20 @@ func (s *Service) mutateRoom(ctx context.Context, cmd command.Command, replayabl currentRank.ReplaceWith(nextRank) result.snapshot = current.ToProto() result.applied = true + } else { + // no-op 命令仍写入 command log,保证相同 command_id 重试可命中幂等, + // 不同 payload 复用 command_id 会被后续入口拒绝为 CONFLICT。 + if err := s.repository.SaveCommand(ctx, CommandRecord{ + RoomID: cmd.RoomID(), + RoomVersion: current.Version, + CommandID: cmd.ID(), + CommandType: cmd.Type(), + Payload: payload, + Replayable: false, + CreatedAt: now, + }); err != nil { + return nil, err + } } return result, nil @@ -116,3 +128,38 @@ func (s *Service) mutateRoom(ctx context.Context, cmd command.Command, replayabl return result, nil } + +func (s *Service) commandPayloadForIdempotency(ctx context.Context, cmd command.Command) ([]byte, bool, error) { + payload, err := command.Serialize(cmd) + if err != nil { + return nil, false, err + } + + record, exists, err := s.repository.GetCommand(ctx, cmd.RoomID(), cmd.ID()) + if err != nil { + return nil, false, err + } + if !exists { + return payload, false, nil + } + currentIDPayload, err := command.IdempotencyPayload(payload) + if err != nil { + return nil, true, err + } + storedIDPayload, err := command.IdempotencyPayload(record.Payload) + if err != nil { + return nil, true, err + } + if record.CommandType != cmd.Type() || !bytes.Equal(storedIDPayload, currentIDPayload) { + return nil, true, xerr.New(xerr.Conflict, "command_id payload conflict") + } + + return payload, true, nil +} + +func commandPayloadForRecord(defaultPayload []byte, result mutationResult) []byte { + if len(result.commandPayload) > 0 { + return result.commandPayload + } + return defaultPayload +} diff --git a/services/room-service/internal/room/service/presence.go b/services/room-service/internal/room/service/presence.go index 7f4cede5..d3160796 100644 --- a/services/room-service/internal/room/service/presence.go +++ b/services/room-service/internal/room/service/presence.go @@ -121,7 +121,7 @@ func (s *Service) leaveRoom(ctx context.Context, cmd command.LeaveRoom, reason s // 离房必须同步释放麦位,但对外只发 RoomUserLeft 主事件,避免客户端处理双事件竞态。 index := current.SeatIndex(seat.SeatNo) if index >= 0 { - current.MicSeats[index].UserID = 0 + current.ClearMicSession(index) } } current.Version++ diff --git a/services/room-service/internal/room/service/recovery.go b/services/room-service/internal/room/service/recovery.go index 5bbab260..22b0d1e2 100644 --- a/services/room-service/internal/room/service/recovery.go +++ b/services/room-service/internal/room/service/recovery.go @@ -13,6 +13,7 @@ import ( "hyapp/services/room-service/internal/room/state" ) +// ensureCell 确保当前节点拥有房间执行权,并在本地缺少 Cell 时从 MySQL 恢复。 func (s *Service) ensureCell(ctx context.Context, roomID string) (*cell.RoomCell, error) { // 每次写命令先刷新或接管 lease,确保同一房间同一时刻只有一个执行节点。 lease, err := s.directory.EnsureOwner(ctx, roomID, s.nodeID, s.clock.Now(), s.leaseTTL) @@ -54,6 +55,7 @@ func (s *Service) ensureCell(ctx context.Context, roomID string) (*cell.RoomCell return roomCell, nil } +// recoverRoom 按“最新快照 + 之后的可回放命令”重建 Room Cell 状态。 func (s *Service) recoverRoom(ctx context.Context, roomMeta RoomMeta) (*state.RoomState, error) { // 恢复优先从最新快照开始,再回放快照版本之后的 replayable command log。 snapshotRecord, hasSnapshot, err := s.repository.GetLatestSnapshot(ctx, roomMeta.RoomID) @@ -96,13 +98,8 @@ func (s *Service) recoverRoom(ctx context.Context, roomMeta RoomMeta) (*state.Ro return nil, err } - if cmd == nil { - // 未知命令类型跳过,保留兼容未来命令的恢复容错。 - continue - } - if err := replay(recovered, cmd); err != nil { - // replay 失败说明命令日志和当前状态不兼容,不能继续装载错误 Cell。 + // replay 失败说明命令日志无法被当前状态机解释,不能继续装载错误 Cell。 return nil, err } } @@ -110,6 +107,7 @@ func (s *Service) recoverRoom(ctx context.Context, roomMeta RoomMeta) (*state.Ro return recovered, nil } +// replay 只重放房间内确定性状态变更,不重复执行钱包扣费或腾讯云 IM 投递。 func replay(current *state.RoomState, cmd command.Command) error { // replay 只重建房间内状态,不重复写 outbox、不重复调用 wallet、不重复同步腾讯云 IM。 switch typed := cmd.(type) { @@ -136,34 +134,89 @@ func replay(current *state.RoomState, cmd command.Command) error { if seat, exists := current.SeatByUser(typed.ActorUserID()); exists { // 离房恢复也必须释放麦位,避免 snapshot 落后时重建出幽灵占位。 index := current.SeatIndex(seat.SeatNo) - current.MicSeats[index].UserID = 0 + current.ClearMicSession(index) } current.Version++ case *command.MicUp: index := current.SeatIndex(typed.SeatNo) - if index >= 0 { - // 回放对不存在麦位保持容错,避免旧命令阻塞新结构恢复。 - current.MicSeats[index].UserID = typed.ActorUserID() - current.Version++ + if index < 0 { + return fmt.Errorf("mic_up replay references missing seat: %d", typed.SeatNo) } + current.Version++ + current.MicSeats[index].UserID = typed.ActorUserID() + current.MicSeats[index].PublishState = state.MicPublishPending + current.MicSeats[index].MicSessionID = typed.MicSessionID + current.MicSeats[index].PublishDeadlineMS = typed.PublishDeadlineMS + current.MicSeats[index].MicSessionRoomVersion = current.Version + // pending_publish 还没有接受 RTC/客户端事件,恢复时保持 0,避免客户端时间慢于服务端命令时间时无法确认。 + current.MicSeats[index].LastPublishEventTimeMS = 0 case *command.MicDown: - if seat, exists := current.SeatByUser(typed.TargetUserID); exists { - // 只有目标仍在麦上时才清空,重复或乱序异常不会制造负状态。 - index := current.SeatIndex(seat.SeatNo) - current.MicSeats[index].UserID = 0 - current.Version++ + seat, exists := current.SeatByUser(typed.TargetUserID) + if !exists { + return fmt.Errorf("mic_down replay target is not on seat: %d", typed.TargetUserID) } + index := current.SeatIndex(seat.SeatNo) + current.ClearMicSession(index) + current.Version++ + case *command.ConfirmMicPublishing: + seat, exists := current.SeatByUser(typed.TargetUserID) + if !exists { + return fmt.Errorf("confirm_mic_publishing replay target is not on seat: %d", typed.TargetUserID) + } + index := current.SeatIndex(seat.SeatNo) + current.MicSeats[index].PublishState = state.MicPublishPublishing + current.MicSeats[index].LastPublishEventTimeMS = typed.EventTimeMS + current.Version++ case *command.ChangeMicSeat: - if from, exists := current.SeatByUser(typed.TargetUserID); exists { - // 换位回放必须同时找到源麦位和目标麦位。 - fromIndex := current.SeatIndex(from.SeatNo) - toIndex := current.SeatIndex(typed.SeatNo) - if fromIndex >= 0 && toIndex >= 0 { - current.MicSeats[fromIndex].UserID = 0 - current.MicSeats[toIndex].UserID = typed.TargetUserID - current.Version++ - } + from, exists := current.SeatByUser(typed.TargetUserID) + if !exists { + return fmt.Errorf("change_mic_seat replay target is not on seat: %d", typed.TargetUserID) } + fromIndex := current.SeatIndex(from.SeatNo) + toIndex := current.SeatIndex(typed.SeatNo) + if fromIndex < 0 || toIndex < 0 { + return fmt.Errorf("change_mic_seat replay references missing seat: from=%d to=%d", from.SeatNo, typed.SeatNo) + } + current.MicSeats[fromIndex].UserID = 0 + movedSeat := current.MicSeats[fromIndex] + current.MicSeats[fromIndex].PublishState = "" + current.MicSeats[fromIndex].MicSessionID = "" + current.MicSeats[fromIndex].PublishDeadlineMS = 0 + current.MicSeats[fromIndex].MicSessionRoomVersion = 0 + current.MicSeats[fromIndex].LastPublishEventTimeMS = 0 + current.MicSeats[toIndex].UserID = typed.TargetUserID + current.MicSeats[toIndex].PublishState = movedSeat.PublishState + current.MicSeats[toIndex].MicSessionID = movedSeat.MicSessionID + current.MicSeats[toIndex].PublishDeadlineMS = movedSeat.PublishDeadlineMS + current.MicSeats[toIndex].MicSessionRoomVersion = movedSeat.MicSessionRoomVersion + current.MicSeats[toIndex].LastPublishEventTimeMS = movedSeat.LastPublishEventTimeMS + current.Version++ + case *command.SetMicSeatLock: + index := current.SeatIndex(typed.SeatNo) + if index < 0 { + return fmt.Errorf("set_mic_seat_lock replay references missing seat: %d", typed.SeatNo) + } + // 锁麦恢复只还原锁状态,不隐式清空已占用麦位。 + current.MicSeats[index].Locked = typed.Locked + current.Version++ + case *command.SetChatEnabled: + // 公屏开关恢复后直接影响 CheckSpeakPermission 的 chat_disabled 判断。 + current.ChatEnabled = typed.Enabled + current.Version++ + case *command.SetRoomAdmin: + if typed.Enabled { + // 管理员集合是持久权限,恢复后用户重新进房即可继续具备 admin 身份。 + current.AdminUsers[typed.TargetUserID] = true + } else if typed.TargetUserID != current.OwnerUserID && typed.TargetUserID != current.HostUserID { + // owner/host 的管理身份不能被 admin 删除命令移除。 + delete(current.AdminUsers, typed.TargetUserID) + } + current.Version++ + case *command.TransferRoomHost: + // 新 host 自动进入管理员集合,和在线命令路径保持一致。 + current.HostUserID = typed.TargetUserID + current.AdminUsers[typed.TargetUserID] = true + current.Version++ case *command.MuteUser: if typed.Muted { // 回放禁言集合,供 CheckSpeakPermission 使用。 @@ -176,14 +229,19 @@ func replay(current *state.RoomState, cmd command.Command) error { if seat, exists := current.SeatByUser(typed.TargetUserID); exists { // 被踢用户恢复后不能继续占麦位。 index := current.SeatIndex(seat.SeatNo) - current.MicSeats[index].UserID = 0 + current.ClearMicSession(index) } delete(current.OnlineUsers, typed.TargetUserID) + delete(current.AdminUsers, typed.TargetUserID) current.BanUsers[typed.TargetUserID] = true current.Version++ + case *command.UnbanUser: + // Unban 只解除 ban,不恢复被 KickUser 删除的 presence、麦位或管理员身份。 + delete(current.BanUsers, typed.TargetUserID) + current.Version++ case *command.SendGift: - // 送礼回放不能再次调用 wallet,只使用命令中记录的礼物价值重建展示态。 - total := typed.GiftUnitValue * int64(typed.GiftCount) + // 送礼回放不能再次调用 wallet,只使用命令中记录的钱包结算热度重建展示态。 + total := typed.HeatValue current.Heat += total applied := false for index := range current.GiftRank { diff --git a/services/room-service/internal/room/service/repository.go b/services/room-service/internal/room/service/repository.go index df017c29..68adb643 100644 --- a/services/room-service/internal/room/service/repository.go +++ b/services/room-service/internal/room/service/repository.go @@ -60,13 +60,13 @@ type Repository interface { SaveRoomMeta(ctx context.Context, meta RoomMeta) error // GetRoomMeta 读取房间基础元数据,恢复无内存 Cell 的房间依赖它。 GetRoomMeta(ctx context.Context, roomID string) (RoomMeta, bool, error) - // HasCommand 判断命令是否已经成功提交,用于所有写命令幂等。 - HasCommand(ctx context.Context, roomID string, commandID string) (bool, error) + // GetCommand 读取已提交命令,用于幂等重试和 command_id 冲突判定。 + GetCommand(ctx context.Context, roomID string, commandID string) (CommandRecord, bool, error) // SaveCommand 追加成功命令日志,关键命令恢复必须依赖它。 SaveCommand(ctx context.Context, record CommandRecord) error // ListCommandsAfter 读取快照版本之后的命令,恢复时按版本顺序回放。 ListCommandsAfter(ctx context.Context, roomID string, roomVersion int64) ([]CommandRecord, error) - // SaveSnapshot 保存最新房间快照,允许覆盖旧版本但不能倒退。 + // SaveSnapshot 保存最新房间快照,允许覆盖较低版本号快照但不能倒退。 SaveSnapshot(ctx context.Context, snapshot SnapshotRecord) error // GetLatestSnapshot 读取当前最新快照,恢复时优先使用它减少回放成本。 GetLatestSnapshot(ctx context.Context, roomID string) (SnapshotRecord, bool, error) diff --git a/services/room-service/internal/room/service/service.go b/services/room-service/internal/room/service/service.go index f624f4e4..d9b88d83 100644 --- a/services/room-service/internal/room/service/service.go +++ b/services/room-service/internal/room/service/service.go @@ -26,6 +26,8 @@ type Config struct { SnapshotEveryN int64 // PresenceStaleAfter 控制业务 presence 多久未刷新后由后台 worker 清理。 PresenceStaleAfter time.Duration + // MicPublishTimeout 控制 MicUp 后等待 RTC 发流确认的最长时间。 + MicPublishTimeout time.Duration // Clock 允许测试注入稳定时间,生产默认使用系统时钟。 Clock clock.Clock } @@ -42,6 +44,8 @@ type Service struct { snapshotEveryN int64 // presenceStaleAfter 是断线或无心跳用户从 room-service presence 中移除的阈值。 presenceStaleAfter time.Duration + // micPublishTimeout 是 pending_publish 自动下麦的确认窗口。 + micPublishTimeout time.Duration // clock 是房间命令、事件和快照的统一时间来源。 clock clock.Clock // directory 负责 room_id -> node_id lease,生产路径是 Redis。 @@ -71,12 +75,18 @@ type mutationResult struct { user *roomv1.RoomUser // seatNo 是麦位命令返回的目标或原麦位编号。 seatNo int32 + // micSessionID 是 MicUp 创建的发流会话 ID,客户端确认发流时必须带回。 + micSessionID string + // publishDeadlineMS 是 pending_publish 自动释放的截止时间。 + publishDeadlineMS int64 // billingReceiptID 是 SendGift 从 wallet-service 得到的账务回执。 billingReceiptID string // roomHeat 是 SendGift 后的房间热度。 roomHeat int64 // giftRank 是 SendGift 后的本地礼物榜投影。 giftRank []*roomv1.RankItem + // commandPayload 允许少数命令把外部依赖的结算快照写入 command log,用于恢复。 + commandPayload []byte // syncEvent 是需要同步推给腾讯云 IM 的低时延房间系统事件。 syncEvent *tencentim.RoomEvent } @@ -107,12 +117,19 @@ func New(cfg Config, directory router.Directory, repository Repository, wallet i presenceStaleAfter = 2 * time.Minute } + micPublishTimeout := cfg.MicPublishTimeout + if micPublishTimeout <= 0 { + // MicUp 只是业务占麦,客户端必须在短窗口内确认 RTC 发流成功。 + micPublishTimeout = 15 * time.Second + } + return &Service{ nodeID: cfg.NodeID, leaseTTL: cfg.LeaseTTL, rankLimit: rankLimit, snapshotEveryN: snapshotEveryN, presenceStaleAfter: presenceStaleAfter, + micPublishTimeout: micPublishTimeout, clock: usedClock, directory: directory, repository: repository, diff --git a/services/room-service/internal/room/service/service_test.go b/services/room-service/internal/room/service/service_test.go index a9471462..6dd73dc0 100644 --- a/services/room-service/internal/room/service/service_test.go +++ b/services/room-service/internal/room/service/service_test.go @@ -22,12 +22,17 @@ import ( // fakeWallet 模拟 wallet-service 扣费成功路径。 type fakeWallet struct{} -// DebitGift 返回可预测账单回执和礼物总价值,测试只关注 room-service 状态变更。 +// DebitGift 返回可预测账单回执和钱包结算值,测试只关注 room-service 状态变更。 func (fakeWallet) DebitGift(_ context.Context, req *walletv1.DebitGiftRequest) (*walletv1.DebitGiftResponse, error) { + heatValue := int64(req.GetGiftCount()) * 10 return &walletv1.DebitGiftResponse{ BillingReceiptId: "receipt_" + req.GetCommandId(), - TotalGiftValue: int64(req.GetGiftCount()) * req.GetGiftUnitValue(), - BalanceAfter: 1000, + TransactionId: "wtx_" + req.GetCommandId(), + CoinSpent: heatValue, + GiftPointAdded: heatValue, + HeatValue: heatValue, + PriceVersion: "test-v1", + BalanceAfter: 1000 - heatValue, }, nil } @@ -143,23 +148,7 @@ func TestCreateRoomDefaultsOwnerAndHostFromActor(t *testing.T) { } } -func TestCreateRoomRejectsOwnerHostMismatchWithActor(t *testing.T) { - ctx := context.Background() - svc := newRoomService("node-a", 1, router.NewMemoryDirectory(), mysqltest.NewRepository(t), &fakeSyncPublisher{}, &fakeOutboxPublisher{}) - - _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{ - Meta: roomservice.NewRequestMeta("room-owner-mismatch", 42), - OwnerUserId: 99, - HostUserId: 99, - SeatCount: 4, - Mode: "social", - }) - if !xerr.IsCode(err, xerr.PermissionDenied) { - t.Fatalf("CreateRoom should reject owner/host mismatch, got %v", err) - } -} - -func TestCreateRoomRejectsIncompatibleRoomID(t *testing.T) { +func TestCreateRoomRejectsInvalidRoomID(t *testing.T) { ctx := context.Background() repository := mysqltest.NewRepository(t) svc := newRoomService("node-a", 1, router.NewMemoryDirectory(), repository, &fakeSyncPublisher{}, &fakeOutboxPublisher{}) @@ -170,7 +159,7 @@ func TestCreateRoomRejectsIncompatibleRoomID(t *testing.T) { Mode: "social", }) if !xerr.IsCode(err, xerr.InvalidArgument) { - t.Fatalf("CreateRoom should reject incompatible room_id, got %v", err) + t.Fatalf("CreateRoom should reject invalid room_id, got %v", err) } if _, exists, getErr := repository.GetRoomMeta(ctx, "room:1001"); getErr != nil || exists { t.Fatalf("invalid room_id must not create room meta: exists=%v err=%v", exists, getErr) @@ -212,11 +201,9 @@ func TestRoomLifecycleGiftAndGuards(t *testing.T) { // 创建房间会初始化 owner presence、麦位、snapshot、command log 和 RoomCreated outbox。 createResp, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{ - Meta: roomservice.NewRequestMeta(roomID, 1), - OwnerUserId: 1, - HostUserId: 1, - SeatCount: 4, - Mode: "social", + Meta: roomservice.NewRequestMeta(roomID, 1), + SeatCount: 4, + Mode: "social", }) if err != nil { t.Fatalf("CreateRoom failed: %v", err) @@ -249,11 +236,10 @@ func TestRoomLifecycleGiftAndGuards(t *testing.T) { // SendGift 先走 fakeWallet,再更新热度和房间内本地礼物榜。 giftResp, err := svc.SendGift(ctx, &roomv1.SendGiftRequest{ - Meta: roomservice.NewRequestMeta(roomID, 1), - TargetUserId: 2, - GiftId: "rose", - GiftCount: 2, - GiftUnitValue: 10, + Meta: roomservice.NewRequestMeta(roomID, 1), + TargetUserId: 2, + GiftId: "rose", + GiftCount: 2, }) if err != nil { t.Fatalf("SendGift failed: %v", err) @@ -313,6 +299,430 @@ func TestRoomLifecycleGiftAndGuards(t *testing.T) { } } +func TestRoomManagementPermissionsAndHostFlow(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + svc := newRoomService("node-a", 1, router.NewMemoryDirectory(), repository, &fakeSyncPublisher{}, &fakeOutboxPublisher{}) + roomID := "room-management-flow" + + if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 1), SeatCount: 4, Mode: "social"}); err != nil { + t.Fatalf("CreateRoom failed: %v", err) + } + if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 2)}); err != nil { + t.Fatalf("JoinRoom user2 failed: %v", err) + } + if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 3)}); err != nil { + t.Fatalf("JoinRoom user3 failed: %v", err) + } + if _, err := svc.MicUp(ctx, &roomv1.MicUpRequest{Meta: roomservice.NewRequestMeta(roomID, 2), SeatNo: 1}); err != nil { + t.Fatalf("MicUp user2 failed: %v", err) + } + + _, err := svc.MicDown(ctx, &roomv1.MicDownRequest{Meta: roomservice.NewRequestMeta(roomID, 3), TargetUserId: 2}) + if !xerr.IsCode(err, xerr.PermissionDenied) { + t.Fatalf("audience should not mic down others, got %v", err) + } + + adminResp, err := svc.SetRoomAdmin(ctx, &roomv1.SetRoomAdminRequest{ + Meta: roomservice.NewRequestMeta(roomID, 1), + TargetUserId: 3, + Enabled: true, + }) + if err != nil { + t.Fatalf("SetRoomAdmin failed: %v", err) + } + if !containsInt64(adminResp.GetRoom().GetAdminUserIds(), 3) { + t.Fatalf("admin ids should contain user3: %+v", adminResp.GetRoom().GetAdminUserIds()) + } + + if _, err := svc.MicDown(ctx, &roomv1.MicDownRequest{Meta: roomservice.NewRequestMeta(roomID, 3), TargetUserId: 2}); err != nil { + t.Fatalf("admin MicDown failed: %v", err) + } + hostResp, err := svc.TransferRoomHost(ctx, &roomv1.TransferRoomHostRequest{ + Meta: roomservice.NewRequestMeta(roomID, 1), + TargetUserId: 2, + }) + if err != nil { + t.Fatalf("TransferRoomHost failed: %v", err) + } + if hostResp.GetRoom().GetHostUserId() != 2 || !containsInt64(hostResp.GetRoom().GetAdminUserIds(), 2) { + t.Fatalf("host transfer should update host and admin set: %+v", hostResp.GetRoom()) + } + + _, err = svc.MuteUser(ctx, &roomv1.MuteUserRequest{ + Meta: roomservice.NewRequestMeta(roomID, 3), + TargetUserId: 2, + Muted: true, + }) + if !xerr.IsCode(err, xerr.PermissionDenied) { + t.Fatalf("admin should not mute current host, got %v", err) + } + if _, err := svc.SetChatEnabled(ctx, &roomv1.SetChatEnabledRequest{Meta: roomservice.NewRequestMeta(roomID, 2), Enabled: false}); err != nil { + t.Fatalf("host SetChatEnabled failed: %v", err) + } + speakResp, err := svc.CheckSpeakPermission(ctx, &roomv1.CheckSpeakPermissionRequest{RoomId: roomID, UserId: 1}) + if err != nil { + t.Fatalf("CheckSpeakPermission failed: %v", err) + } + if speakResp.GetAllowed() || speakResp.GetReason() != "chat_disabled" { + t.Fatalf("chat disabled should block all users: %+v", speakResp) + } +} + +func TestMicPublishingConfirmAndStaleEvents(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + usedClock := &mutableClock{now: time.UnixMilli(1_700_000_000_000)} + svc := newRoomServiceWithClock("node-a", 1, router.NewMemoryDirectory(), repository, &fakeSyncPublisher{}, &fakeOutboxPublisher{}, usedClock, time.Minute) + roomID := "room-mic-publish-confirm" + + if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 1), SeatCount: 4, Mode: "voice"}); err != nil { + t.Fatalf("CreateRoom failed: %v", err) + } + if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 2)}); err != nil { + t.Fatalf("JoinRoom failed: %v", err) + } + + micResp, err := svc.MicUp(ctx, &roomv1.MicUpRequest{Meta: roomservice.NewRequestMeta(roomID, 2), SeatNo: 1}) + if err != nil { + t.Fatalf("MicUp failed: %v", err) + } + seat := findSeat(micResp.GetRoom(), 1) + if seat == nil || seat.GetUserId() != 2 || seat.GetPublishState() != "pending_publish" || seat.GetMicSessionId() == "" { + t.Fatalf("MicUp should create pending_publish session: seat=%+v", seat) + } + if micResp.GetMicSessionId() != seat.GetMicSessionId() || micResp.GetPublishDeadlineMs() != seat.GetPublishDeadlineMs() { + t.Fatalf("MicUp response should expose current mic session: resp=%+v seat=%+v", micResp, seat) + } + if seat.GetLastPublishEventTimeMs() != 0 { + t.Fatalf("new pending session should not pre-fill publish event time: seat=%+v", seat) + } + + confirmEventTime := usedClock.now.Add(-time.Second).UnixMilli() + confirmResp, err := svc.ConfirmMicPublishing(ctx, &roomv1.ConfirmMicPublishingRequest{ + Meta: roomservice.NewRequestMeta(roomID, 2), + MicSessionId: micResp.GetMicSessionId(), + RoomVersion: micResp.GetResult().GetRoomVersion(), + EventTimeMs: confirmEventTime, + Source: "client", + }) + if err != nil { + t.Fatalf("ConfirmMicPublishing failed: %v", err) + } + seat = findSeat(confirmResp.GetRoom(), 1) + if seat == nil || seat.GetPublishState() != "publishing" || seat.GetLastPublishEventTimeMs() != confirmEventTime { + t.Fatalf("confirm should move seat to publishing: seat=%+v", seat) + } + + staleResp, err := svc.ConfirmMicPublishing(ctx, &roomv1.ConfirmMicPublishingRequest{ + Meta: roomservice.NewRequestMeta(roomID, 2), + MicSessionId: micResp.GetMicSessionId(), + RoomVersion: micResp.GetResult().GetRoomVersion(), + EventTimeMs: confirmEventTime, + Source: "rtc_webhook", + }) + if err != nil { + t.Fatalf("stale ConfirmMicPublishing should be ignored without error: %v", err) + } + if staleResp.GetResult().GetApplied() { + t.Fatalf("stale confirmation must not advance room version") + } +} + +func TestMicPublishTimeoutMicDownsPendingSession(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + usedClock := &mutableClock{now: time.UnixMilli(1_700_000_000_000)} + syncPublisher := &fakeSyncPublisher{} + svc := newRoomServiceWithClock("node-a", 1, router.NewMemoryDirectory(), repository, syncPublisher, &fakeOutboxPublisher{}, usedClock, time.Minute) + roomID := "room-mic-publish-timeout" + + if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 1), SeatCount: 4, Mode: "voice"}); err != nil { + t.Fatalf("CreateRoom failed: %v", err) + } + if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 2)}); err != nil { + t.Fatalf("JoinRoom failed: %v", err) + } + micResp, err := svc.MicUp(ctx, &roomv1.MicUpRequest{Meta: roomservice.NewRequestMeta(roomID, 2), SeatNo: 1}) + if err != nil { + t.Fatalf("MicUp failed: %v", err) + } + usedClock.now = time.UnixMilli(micResp.GetPublishDeadlineMs()) + if err := svc.SweepMicPublishTimeouts(ctx); err != nil { + t.Fatalf("SweepMicPublishTimeouts failed: %v", err) + } + + joinResp, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 1)}) + if err != nil { + t.Fatalf("JoinRoom refresh failed: %v", err) + } + if seat := findSeat(joinResp.GetRoom(), 1); seat == nil || seat.GetUserId() != 0 || seat.GetMicSessionId() != "" { + t.Fatalf("publish timeout should release seat and session: %+v", seat) + } + if len(syncPublisher.events) == 0 { + t.Fatalf("expected timeout mic_down event") + } + last := syncPublisher.events[len(syncPublisher.events)-1] + if last.EventType != "room_mic_down" || last.Attributes["reason"] != "publish_timeout" || last.Attributes["mic_session_id"] != micResp.GetMicSessionId() { + t.Fatalf("timeout should publish mic_down with session reason: %+v", last) + } +} + +func TestOldMicSessionEventsDoNotClearNewMicUp(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + usedClock := &mutableClock{now: time.UnixMilli(1_700_000_000_000)} + svc := newRoomServiceWithClock("node-a", 1, router.NewMemoryDirectory(), repository, &fakeSyncPublisher{}, &fakeOutboxPublisher{}, usedClock, time.Minute) + roomID := "room-mic-old-session" + + if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 1), SeatCount: 4, Mode: "voice"}); err != nil { + t.Fatalf("CreateRoom failed: %v", err) + } + if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 2)}); err != nil { + t.Fatalf("JoinRoom failed: %v", err) + } + oldMic, err := svc.MicUp(ctx, &roomv1.MicUpRequest{Meta: roomservice.NewRequestMeta(roomID, 2), SeatNo: 1}) + if err != nil { + t.Fatalf("first MicUp failed: %v", err) + } + if _, err := svc.MicDown(ctx, &roomv1.MicDownRequest{Meta: roomservice.NewRequestMeta(roomID, 2)}); err != nil { + t.Fatalf("MicDown failed: %v", err) + } + + usedClock.now = usedClock.now.Add(time.Second) + newMic, err := svc.MicUp(ctx, &roomv1.MicUpRequest{Meta: roomservice.NewRequestMeta(roomID, 2), SeatNo: 1}) + if err != nil { + t.Fatalf("second MicUp failed: %v", err) + } + if newMic.GetMicSessionId() == oldMic.GetMicSessionId() { + t.Fatalf("new MicUp must create a new session: old=%s new=%s", oldMic.GetMicSessionId(), newMic.GetMicSessionId()) + } + + usedClock.now = time.UnixMilli(oldMic.GetPublishDeadlineMs()) + if err := svc.SweepMicPublishTimeouts(ctx); err != nil { + t.Fatalf("old timeout sweep failed: %v", err) + } + staleConfirm, err := svc.ConfirmMicPublishing(ctx, &roomv1.ConfirmMicPublishingRequest{ + Meta: roomservice.NewRequestMeta(roomID, 2), + MicSessionId: oldMic.GetMicSessionId(), + RoomVersion: oldMic.GetResult().GetRoomVersion(), + EventTimeMs: usedClock.now.Add(time.Second).UnixMilli(), + Source: "rtc_webhook", + }) + if err != nil { + t.Fatalf("old session confirm should be ignored without error: %v", err) + } + seat := findSeat(staleConfirm.GetRoom(), 1) + if seat == nil || seat.GetUserId() != 2 || seat.GetMicSessionId() != newMic.GetMicSessionId() || seat.GetPublishState() != "pending_publish" { + t.Fatalf("old session event must not clear new MicUp: seat=%+v new=%+v old=%+v", seat, newMic, oldMic) + } +} + +func TestMicSeatLockAndNoopIdempotency(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + syncPublisher := &fakeSyncPublisher{fail: true} + svc := newRoomService("node-a", 1, router.NewMemoryDirectory(), repository, syncPublisher, &fakeOutboxPublisher{}) + roomID := "room-mic-lock" + + if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 1), SeatCount: 3, Mode: "social"}); err != nil { + t.Fatalf("CreateRoom failed: %v", err) + } + if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 2)}); err != nil { + t.Fatalf("JoinRoom failed: %v", err) + } + + lockResp, err := svc.SetMicSeatLock(ctx, &roomv1.SetMicSeatLockRequest{ + Meta: roomservice.NewRequestMeta(roomID, 1), + SeatNo: 2, + Locked: true, + }) + if err != nil { + t.Fatalf("SetMicSeatLock failed: %v", err) + } + if seat := findSeat(lockResp.GetRoom(), 2); seat == nil || !seat.GetLocked() { + t.Fatalf("seat should be locked: %+v", seat) + } + if got := countPendingOutboxType(t, repository, "RoomMicSeatLocked"); got != 1 { + t.Fatalf("lock should write one outbox event, got %d", got) + } + + repeatResp, err := svc.SetMicSeatLock(ctx, &roomv1.SetMicSeatLockRequest{ + Meta: roomservice.NewRequestMeta(roomID, 1), + SeatNo: 2, + Locked: true, + }) + if err != nil { + t.Fatalf("repeat SetMicSeatLock failed: %v", err) + } + if repeatResp.GetResult().GetApplied() { + t.Fatalf("repeat lock should be a no-op") + } + if got := countPendingOutboxType(t, repository, "RoomMicSeatLocked"); got != 1 { + t.Fatalf("repeat lock should not write duplicate outbox, got %d", got) + } + + _, err = svc.MicUp(ctx, &roomv1.MicUpRequest{Meta: roomservice.NewRequestMeta(roomID, 2), SeatNo: 2}) + if !xerr.IsCode(err, xerr.PermissionDenied) { + t.Fatalf("locked seat MicUp should be denied, got %v", err) + } + if _, err := svc.MicUp(ctx, &roomv1.MicUpRequest{Meta: roomservice.NewRequestMeta(roomID, 2), SeatNo: 1}); err != nil { + t.Fatalf("MicUp unlocked seat failed: %v", err) + } + _, err = svc.SetMicSeatLock(ctx, &roomv1.SetMicSeatLockRequest{Meta: roomservice.NewRequestMeta(roomID, 1), SeatNo: 1, Locked: true}) + if !xerr.IsCode(err, xerr.Conflict) { + t.Fatalf("locking occupied seat should conflict, got %v", err) + } +} + +func TestKickUnbanAndAdminRemoval(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + svc := newRoomService("node-a", 1, router.NewMemoryDirectory(), repository, &fakeSyncPublisher{}, &fakeOutboxPublisher{}) + roomID := "room-ban-unban" + + if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 1), SeatCount: 4, Mode: "social"}); err != nil { + t.Fatalf("CreateRoom failed: %v", err) + } + if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 2)}); err != nil { + t.Fatalf("JoinRoom user2 failed: %v", err) + } + if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 3)}); err != nil { + t.Fatalf("JoinRoom user3 failed: %v", err) + } + if _, err := svc.SetRoomAdmin(ctx, &roomv1.SetRoomAdminRequest{Meta: roomservice.NewRequestMeta(roomID, 1), TargetUserId: 2, Enabled: true}); err != nil { + t.Fatalf("SetRoomAdmin user2 failed: %v", err) + } + if _, err := svc.SetRoomAdmin(ctx, &roomv1.SetRoomAdminRequest{Meta: roomservice.NewRequestMeta(roomID, 1), TargetUserId: 3, Enabled: true}); err != nil { + t.Fatalf("SetRoomAdmin user3 failed: %v", err) + } + + kickResp, err := svc.KickUser(ctx, &roomv1.KickUserRequest{Meta: roomservice.NewRequestMeta(roomID, 1), TargetUserId: 2}) + if err != nil { + t.Fatalf("KickUser failed: %v", err) + } + if !containsInt64(kickResp.GetRoom().GetBanUserIds(), 2) || containsInt64(kickResp.GetRoom().GetAdminUserIds(), 2) { + t.Fatalf("kick should ban user2 and remove admin: %+v", kickResp.GetRoom()) + } + if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 2)}); !xerr.IsCode(err, xerr.PermissionDenied) { + t.Fatalf("banned user should not join, got %v", err) + } + presenceResp, err := svc.VerifyRoomPresence(ctx, &roomv1.VerifyRoomPresenceRequest{RoomId: roomID, UserId: 2}) + if err != nil { + t.Fatalf("VerifyRoomPresence failed: %v", err) + } + if presenceResp.GetPresent() || presenceResp.GetReason() != "user_banned" { + t.Fatalf("banned user should fail presence guard: %+v", presenceResp) + } + + _, err = svc.UnbanUser(ctx, &roomv1.UnbanUserRequest{Meta: roomservice.NewRequestMeta(roomID, 3), TargetUserId: 2}) + if !xerr.IsCode(err, xerr.PermissionDenied) { + t.Fatalf("admin should not unban, got %v", err) + } + unbanResp, err := svc.UnbanUser(ctx, &roomv1.UnbanUserRequest{Meta: roomservice.NewRequestMeta(roomID, 1), TargetUserId: 2}) + if err != nil { + t.Fatalf("owner UnbanUser failed: %v", err) + } + if containsInt64(unbanResp.GetRoom().GetBanUserIds(), 2) || containsInt64(unbanResp.GetRoom().GetAdminUserIds(), 2) { + t.Fatalf("unban should not restore ban or admin state: %+v", unbanResp.GetRoom()) + } + if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 2)}); err != nil { + t.Fatalf("unbanned user should join again: %v", err) + } +} + +func TestCommandIDPayloadConflictIgnoresTraceFields(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + svc := newRoomService("node-a", 1, router.NewMemoryDirectory(), repository, &fakeSyncPublisher{}, &fakeOutboxPublisher{}) + roomID := "room-command-conflict" + + if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 1), SeatCount: 4, Mode: "social"}); err != nil { + t.Fatalf("CreateRoom failed: %v", err) + } + + firstMeta := roomservice.NewRequestMeta(roomID, 1) + firstMeta.CommandId = "cmd-chat-disable-fixed" + firstMeta.RequestId = "req-first" + if _, err := svc.SetChatEnabled(ctx, &roomv1.SetChatEnabledRequest{Meta: firstMeta, Enabled: false}); err != nil { + t.Fatalf("first SetChatEnabled failed: %v", err) + } + + retryMeta := roomservice.NewRequestMeta(roomID, 1) + retryMeta.CommandId = firstMeta.GetCommandId() + retryMeta.RequestId = "req-retry" + retryMeta.SessionId = "different-session" + retryMeta.SentAtMs = firstMeta.GetSentAtMs() + 1000 + retryResp, err := svc.SetChatEnabled(ctx, &roomv1.SetChatEnabledRequest{Meta: retryMeta, Enabled: false}) + if err != nil { + t.Fatalf("same command with different trace fields should be idempotent: %v", err) + } + if retryResp.GetResult().GetApplied() { + t.Fatalf("idempotent retry should not apply again") + } + + _, err = svc.SetChatEnabled(ctx, &roomv1.SetChatEnabledRequest{Meta: retryMeta, Enabled: true}) + if !xerr.IsCode(err, xerr.Conflict) { + t.Fatalf("same command_id with different business payload should conflict, got %v", err) + } +} + +func TestRoomManagementRecoveryReplay(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + directory := router.NewMemoryDirectory() + serviceA := newRoomService("node-a", 100, directory, repository, &fakeSyncPublisher{}, &fakeOutboxPublisher{}) + roomID := "room-management-replay" + + if _, err := serviceA.CreateRoom(ctx, &roomv1.CreateRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 1), SeatCount: 4, Mode: "social"}); err != nil { + t.Fatalf("CreateRoom failed: %v", err) + } + if _, err := serviceA.JoinRoom(ctx, &roomv1.JoinRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 2)}); err != nil { + t.Fatalf("JoinRoom user2 failed: %v", err) + } + if _, err := serviceA.JoinRoom(ctx, &roomv1.JoinRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 3)}); err != nil { + t.Fatalf("JoinRoom user3 failed: %v", err) + } + if _, err := serviceA.SetMicSeatLock(ctx, &roomv1.SetMicSeatLockRequest{Meta: roomservice.NewRequestMeta(roomID, 1), SeatNo: 4, Locked: true}); err != nil { + t.Fatalf("SetMicSeatLock failed: %v", err) + } + if _, err := serviceA.SetChatEnabled(ctx, &roomv1.SetChatEnabledRequest{Meta: roomservice.NewRequestMeta(roomID, 1), Enabled: false}); err != nil { + t.Fatalf("SetChatEnabled failed: %v", err) + } + if _, err := serviceA.SetRoomAdmin(ctx, &roomv1.SetRoomAdminRequest{Meta: roomservice.NewRequestMeta(roomID, 1), TargetUserId: 2, Enabled: true}); err != nil { + t.Fatalf("SetRoomAdmin failed: %v", err) + } + if _, err := serviceA.TransferRoomHost(ctx, &roomv1.TransferRoomHostRequest{Meta: roomservice.NewRequestMeta(roomID, 1), TargetUserId: 2}); err != nil { + t.Fatalf("TransferRoomHost failed: %v", err) + } + if _, err := serviceA.KickUser(ctx, &roomv1.KickUserRequest{Meta: roomservice.NewRequestMeta(roomID, 1), TargetUserId: 3}); err != nil { + t.Fatalf("KickUser failed: %v", err) + } + if _, err := serviceA.UnbanUser(ctx, &roomv1.UnbanUserRequest{Meta: roomservice.NewRequestMeta(roomID, 1), TargetUserId: 3}); err != nil { + t.Fatalf("UnbanUser failed: %v", err) + } + + directory.ForceExpire(roomID) + serviceB := newRoomService("node-b", 100, directory, repository, &fakeSyncPublisher{}, &fakeOutboxPublisher{}) + joinResp, err := serviceB.JoinRoom(ctx, &roomv1.JoinRoomRequest{Meta: roomservice.NewRequestMeta(roomID, 4)}) + if err != nil { + t.Fatalf("JoinRoom on recovered service failed: %v", err) + } + snapshot := joinResp.GetRoom() + if snapshot.GetHostUserId() != 2 || !containsInt64(snapshot.GetAdminUserIds(), 2) { + t.Fatalf("host/admin state did not replay: %+v", snapshot) + } + if snapshot.GetChatEnabled() { + t.Fatalf("chat_enabled should replay as false") + } + if seat := findSeat(snapshot, 4); seat == nil || !seat.GetLocked() { + t.Fatalf("locked seat did not replay: %+v", seat) + } + if containsInt64(snapshot.GetBanUserIds(), 3) { + t.Fatalf("unban should replay after kick: %+v", snapshot.GetBanUserIds()) + } +} + // TestRepeatedJoinRefreshesPresenceWithoutDuplicateJoinEvent 验证重连只刷新 presence,不重复广播进房。 func TestRepeatedJoinRefreshesPresenceWithoutDuplicateJoinEvent(t *testing.T) { ctx := context.Background() @@ -323,11 +733,9 @@ func TestRepeatedJoinRefreshesPresenceWithoutDuplicateJoinEvent(t *testing.T) { roomID := "room-repeat-join" if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{ - Meta: roomservice.NewRequestMeta(roomID, 1), - OwnerUserId: 1, - HostUserId: 1, - SeatCount: 4, - Mode: "social", + Meta: roomservice.NewRequestMeta(roomID, 1), + SeatCount: 4, + Mode: "social", }); err != nil { t.Fatalf("CreateRoom failed: %v", err) } @@ -367,11 +775,9 @@ func TestLeaveRoomReleasesSeatAndBlocksGuards(t *testing.T) { roomID := "room-leave-seat" if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{ - Meta: roomservice.NewRequestMeta(roomID, 1), - OwnerUserId: 1, - HostUserId: 1, - SeatCount: 4, - Mode: "social", + Meta: roomservice.NewRequestMeta(roomID, 1), + SeatCount: 4, + Mode: "social", }); err != nil { t.Fatalf("CreateRoom failed: %v", err) } @@ -415,11 +821,9 @@ func TestRoomRecoveryAfterLeaseTakeover(t *testing.T) { // node-a 先创建并推进房间状态,snapshotEveryN=2 会让恢复需要结合快照和命令日志。 if _, err := serviceA.CreateRoom(ctx, &roomv1.CreateRoomRequest{ - Meta: roomservice.NewRequestMeta(roomID, 1), - OwnerUserId: 1, - HostUserId: 1, - SeatCount: 4, - Mode: "social", + Meta: roomservice.NewRequestMeta(roomID, 1), + SeatCount: 4, + Mode: "social", }); err != nil { t.Fatalf("CreateRoom failed: %v", err) } @@ -432,11 +836,10 @@ func TestRoomRecoveryAfterLeaseTakeover(t *testing.T) { } if _, err := serviceA.SendGift(ctx, &roomv1.SendGiftRequest{ - Meta: roomservice.NewRequestMeta(roomID, 1), - TargetUserId: 2, - GiftId: "rose", - GiftCount: 1, - GiftUnitValue: 10, + Meta: roomservice.NewRequestMeta(roomID, 1), + TargetUserId: 2, + GiftId: "rose", + GiftCount: 1, }); err != nil { t.Fatalf("SendGift failed: %v", err) } @@ -482,11 +885,9 @@ func TestGuardRecoveryReplaysCommandsAfterSnapshot(t *testing.T) { roomID := "room-guard-recover" if _, err := serviceA.CreateRoom(ctx, &roomv1.CreateRoomRequest{ - Meta: roomservice.NewRequestMeta(roomID, 1), - OwnerUserId: 1, - HostUserId: 1, - SeatCount: 4, - Mode: "social", + Meta: roomservice.NewRequestMeta(roomID, 1), + SeatCount: 4, + Mode: "social", }); err != nil { t.Fatalf("CreateRoom failed: %v", err) } @@ -523,11 +924,9 @@ func TestSweepStalePresenceRemovesUserAndReleasesSeat(t *testing.T) { roomID := "room-stale" if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{ - Meta: roomservice.NewRequestMeta(roomID, 1), - OwnerUserId: 1, - HostUserId: 1, - SeatCount: 4, - Mode: "social", + Meta: roomservice.NewRequestMeta(roomID, 1), + SeatCount: 4, + Mode: "social", }); err != nil { t.Fatalf("CreateRoom failed: %v", err) } @@ -581,11 +980,9 @@ func TestRoomOutboxCompensationWhenSyncPublishFails(t *testing.T) { // 创建和进房先建立可送礼的基本房间态。 if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{ - Meta: roomservice.NewRequestMeta(roomID, 1), - OwnerUserId: 1, - HostUserId: 1, - SeatCount: 4, - Mode: "social", + Meta: roomservice.NewRequestMeta(roomID, 1), + SeatCount: 4, + Mode: "social", }); err != nil { t.Fatalf("CreateRoom failed: %v", err) } @@ -598,11 +995,10 @@ func TestRoomOutboxCompensationWhenSyncPublishFails(t *testing.T) { } if _, err := svc.SendGift(ctx, &roomv1.SendGiftRequest{ - Meta: roomservice.NewRequestMeta(roomID, 1), - TargetUserId: 2, - GiftId: "car", - GiftCount: 1, - GiftUnitValue: 100, + Meta: roomservice.NewRequestMeta(roomID, 1), + TargetUserId: 2, + GiftId: "car", + GiftCount: 1, }); err != nil { t.Fatalf("SendGift failed: %v", err) } @@ -770,6 +1166,15 @@ func findSeat(snapshot *roomv1.RoomSnapshot, seatNo int32) *roomv1.SeatState { return nil } +func containsInt64(values []int64, target int64) bool { + for _, value := range values { + if value == target { + return true + } + } + return false +} + func hasSyncEventReason(events []tencentim.RoomEvent, reason string) bool { for _, event := range events { if event.EventType == "room_user_left" && event.Attributes["reason"] == reason { diff --git a/services/room-service/internal/room/service/snapshot.go b/services/room-service/internal/room/service/snapshot.go index 33055983..033d0571 100644 --- a/services/room-service/internal/room/service/snapshot.go +++ b/services/room-service/internal/room/service/snapshot.go @@ -9,6 +9,7 @@ import ( "hyapp/services/room-service/internal/room/rank" ) +// currentSnapshot 返回当前房间的最新快照;没有内存 Cell 时会从持久化恢复后再返回。 func (s *Service) currentSnapshot(ctx context.Context, roomID string) (*roomv1.RoomSnapshot, error) { if roomCell := s.loadCell(roomID); roomCell != nil { // 内存 Cell 是最新状态来源,guard 查询优先读它。 @@ -38,6 +39,7 @@ func (s *Service) currentSnapshot(ctx context.Context, roomID string) (*roomv1.R return restoredState.ToProto(), nil } +// persistSnapshot 持久化 protobuf 快照,作为后续恢复和 guard 查询的快速入口。 func (s *Service) persistSnapshot(ctx context.Context, snapshot *roomv1.RoomSnapshot) error { // 快照统一使用 protobuf,保持和跨服务契约一致。 payload, err := proto.Marshal(snapshot) @@ -53,6 +55,7 @@ func (s *Service) persistSnapshot(ctx context.Context, snapshot *roomv1.RoomSnap }) } +// shouldSnapshot 判定当前命令结果是否需要落快照。 func shouldSnapshot(snapshot *roomv1.RoomSnapshot, every int64) bool { if snapshot == nil || every <= 1 { // every<=1 表示每次有快照结果都保存,nil 快照永远不保存。 diff --git a/services/room-service/internal/room/state/state.go b/services/room-service/internal/room/state/state.go index 38d1e72d..21c20017 100644 --- a/services/room-service/internal/room/state/state.go +++ b/services/room-service/internal/room/state/state.go @@ -7,6 +7,15 @@ import ( roomv1 "hyapp/api/proto/room/v1" ) +const ( + // MicPublishIdle 表示麦位没有正在确认或已确认的 RTC 发流会话。 + MicPublishIdle = "idle" + // MicPublishPending 表示业务上麦成功,但客户端/RTC 尚未确认音频发布成功。 + MicPublishPending = "pending_publish" + // MicPublishPublishing 表示当前 mic_session 已确认在 RTC 侧发布音频。 + MicPublishPublishing = "publishing" +) + // MicSeat 表达房间内单个麦位的占用状态。 type MicSeat struct { // SeatNo 是房间内稳定麦位编号,不使用切片下标作为对外标识。 @@ -15,6 +24,16 @@ type MicSeat struct { UserID int64 // Locked 表示麦位被锁定,锁定麦位不能上麦或换入。 Locked bool + // PublishState 区分业务占麦和 RTC 发流确认状态,避免把 MicUp 当作已发流。 + PublishState string + // MicSessionID 标识单次上麦发流会话,确认和超时清理都必须匹配它。 + MicSessionID string + // PublishDeadlineMS 是 pending_publish 自动释放麦位的截止时间。 + PublishDeadlineMS int64 + // MicSessionRoomVersion 是创建该会话的房间版本,用于丢弃旧版本 RTC 事件。 + MicSessionRoomVersion int64 + // LastPublishEventTimeMS 是已接受的最新 RTC/客户端发布事件时间。 + LastPublishEventTimeMS int64 } // RoomUserState 只保存 room-service 真正需要的轻量用户态。 @@ -175,6 +194,20 @@ func (s *RoomState) SeatByUser(userID int64) (MicSeat, bool) { return MicSeat{}, false } +// ClearMicSession 清空麦位占用和 RTC 发流会话字段。 +func (s *RoomState) ClearMicSession(index int) { + if index < 0 || index >= len(s.MicSeats) { + return + } + + seatNo := s.MicSeats[index].SeatNo + locked := s.MicSeats[index].Locked + s.MicSeats[index] = MicSeat{ + SeatNo: seatNo, + Locked: locked, + } +} + // SeatIndex 返回指定麦位在切片中的下标。 func (s *RoomState) SeatIndex(seatNo int32) int { // 返回 -1 表示外部传入的麦位编号不存在。 @@ -222,9 +255,14 @@ func (s *RoomState) ToProto() *roomv1.RoomSnapshot { for _, seat := range s.MicSeats { // 麦位顺序沿用内部切片顺序,即初始化的 SeatNo 升序和后续原位修改。 seats = append(seats, &roomv1.SeatState{ - SeatNo: seat.SeatNo, - UserId: seat.UserID, - Locked: seat.Locked, + SeatNo: seat.SeatNo, + UserId: seat.UserID, + Locked: seat.Locked, + PublishState: seat.PublishState, + MicSessionId: seat.MicSessionID, + PublishDeadlineMs: seat.PublishDeadlineMS, + MicSessionRoomVersion: seat.MicSessionRoomVersion, + LastPublishEventTimeMs: seat.LastPublishEventTimeMS, }) } @@ -288,9 +326,14 @@ func FromProto(snapshot *roomv1.RoomSnapshot) *RoomState { for _, seat := range snapshot.GetMicSeats() { // protobuf 空字段会映射成零值,UserID=0 仍表示空麦位。 restored.MicSeats = append(restored.MicSeats, MicSeat{ - SeatNo: seat.GetSeatNo(), - UserID: seat.GetUserId(), - Locked: seat.GetLocked(), + SeatNo: seat.GetSeatNo(), + UserID: seat.GetUserId(), + Locked: seat.GetLocked(), + PublishState: seat.GetPublishState(), + MicSessionID: seat.GetMicSessionId(), + PublishDeadlineMS: seat.GetPublishDeadlineMs(), + MicSessionRoomVersion: seat.GetMicSessionRoomVersion(), + LastPublishEventTimeMS: seat.GetLastPublishEventTimeMs(), }) } @@ -308,6 +351,9 @@ func FromProto(snapshot *roomv1.RoomSnapshot) *RoomState { // set 在 protobuf 中用数组表达,恢复时重新转为 map。 restored.AdminUsers[userID] = true } + // owner 和 host 是派生管理员身份,快照恢复后必须重新压实该不变量。 + restored.AdminUsers[restored.OwnerUserID] = true + restored.AdminUsers[restored.HostUserID] = true for _, userID := range snapshot.GetBanUserIds() { restored.BanUsers[userID] = true diff --git a/services/room-service/internal/storage/mysql/repository.go b/services/room-service/internal/storage/mysql/repository.go index 94f468bb..0d6c5830 100644 --- a/services/room-service/internal/storage/mysql/repository.go +++ b/services/room-service/internal/storage/mysql/repository.go @@ -160,11 +160,11 @@ func (r *Repository) GetRoomMeta(ctx context.Context, roomID string) (roomservic return meta, true, nil } -// HasCommand 判断命令是否已经成功落盘。 -func (r *Repository) HasCommand(ctx context.Context, roomID string, commandID string) (bool, error) { +// GetCommand 读取已提交命令,用于幂等重试和 command_id 冲突判定。 +func (r *Repository) GetCommand(ctx context.Context, roomID string, commandID string) (roomservice.CommandRecord, bool, error) { // 幂等检查只看 command log,只有成功提交过的命令才算 seen。 row := r.db.QueryRowContext(ctx, - `SELECT 1 + `SELECT room_id, room_version, command_id, command_type, payload, replayable, created_at FROM room_command_log WHERE room_id = ? AND command_id = ? LIMIT 1`, @@ -172,17 +172,17 @@ func (r *Repository) HasCommand(ctx context.Context, roomID string, commandID st commandID, ) - var marker int - if err := row.Scan(&marker); err != nil { + var record roomservice.CommandRecord + if err := row.Scan(&record.RoomID, &record.RoomVersion, &record.CommandID, &record.CommandType, &record.Payload, &record.Replayable, &record.CreatedAt); err != nil { if errors.Is(err, sql.ErrNoRows) { // 没有命令日志表示本命令尚未成功提交。 - return false, nil + return roomservice.CommandRecord{}, false, nil } - return false, err + return roomservice.CommandRecord{}, false, err } - return true, nil + return record, true, nil } // SaveCommand 追加一条成功命令日志。 diff --git a/services/room-service/internal/transport/grpc/server.go b/services/room-service/internal/transport/grpc/server.go index 3b12df12..4e16cd09 100644 --- a/services/room-service/internal/transport/grpc/server.go +++ b/services/room-service/internal/transport/grpc/server.go @@ -11,9 +11,9 @@ import ( // Server 把 room-service 领域实现挂到 gRPC 接口上。 type Server struct { - // UnimplementedRoomCommandServiceServer 保持新增 RPC 时的向前兼容默认行为。 + // UnimplementedRoomCommandServiceServer 提供未实现 RPC 的标准 gRPC 返回。 roomv1.UnimplementedRoomCommandServiceServer - // UnimplementedRoomGuardServiceServer 保持守卫服务新增 RPC 时的向前兼容默认行为。 + // UnimplementedRoomGuardServiceServer 提供守卫服务未实现 RPC 的标准 gRPC 返回。 roomv1.UnimplementedRoomGuardServiceServer // svc 是领域服务入口,gRPC 层不保存任何房间状态。 @@ -73,6 +73,36 @@ func (s *Server) ChangeMicSeat(ctx context.Context, req *roomv1.ChangeMicSeatReq return mapServiceResult(s.svc.ChangeMicSeat(ctx, req)) } +// ConfirmMicPublishing 代理到领域服务。 +func (s *Server) ConfirmMicPublishing(ctx context.Context, req *roomv1.ConfirmMicPublishingRequest) (*roomv1.ConfirmMicPublishingResponse, error) { + // 发流确认必须进入 Room Cell 串行状态机,避免旧 RTC 事件覆盖新 mic_session。 + return mapServiceResult(s.svc.ConfirmMicPublishing(ctx, req)) +} + +// SetMicSeatLock 代理到领域服务。 +func (s *Server) SetMicSeatLock(ctx context.Context, req *roomv1.SetMicSeatLockRequest) (*roomv1.SetMicSeatLockResponse, error) { + // 锁麦权限和目标麦位状态由 Room Cell 判定。 + return mapServiceResult(s.svc.SetMicSeatLock(ctx, req)) +} + +// SetChatEnabled 代理到领域服务。 +func (s *Server) SetChatEnabled(ctx context.Context, req *roomv1.SetChatEnabledRequest) (*roomv1.SetChatEnabledResponse, error) { + // 公屏开关是 room-service 发言守卫的权威状态。 + return mapServiceResult(s.svc.SetChatEnabled(ctx, req)) +} + +// SetRoomAdmin 代理到领域服务。 +func (s *Server) SetRoomAdmin(ctx context.Context, req *roomv1.SetRoomAdminRequest) (*roomv1.SetRoomAdminResponse, error) { + // 管理员集合只能由 room-service 权限矩阵修改。 + return mapServiceResult(s.svc.SetRoomAdmin(ctx, req)) +} + +// TransferRoomHost 代理到领域服务。 +func (s *Server) TransferRoomHost(ctx context.Context, req *roomv1.TransferRoomHostRequest) (*roomv1.TransferRoomHostResponse, error) { + // 主持人转移由 Room Cell 串行提交,并进入 command log/outbox。 + return mapServiceResult(s.svc.TransferRoomHost(ctx, req)) +} + // MuteUser 代理到领域服务。 func (s *Server) MuteUser(ctx context.Context, req *roomv1.MuteUserRequest) (*roomv1.MuteUserResponse, error) { // 禁言状态是腾讯云 IM 发言回调的权威来源。 @@ -85,6 +115,12 @@ func (s *Server) KickUser(ctx context.Context, req *roomv1.KickUserRequest) (*ro return mapServiceResult(s.svc.KickUser(ctx, req)) } +// UnbanUser 代理到领域服务。 +func (s *Server) UnbanUser(ctx context.Context, req *roomv1.UnbanUserRequest) (*roomv1.UnbanUserResponse, error) { + // 解封只解除 ban,不恢复 presence、管理员身份或麦位。 + return mapServiceResult(s.svc.UnbanUser(ctx, req)) +} + // SendGift 代理到领域服务。 func (s *Server) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*roomv1.SendGiftResponse, error) { // SendGift 先同步扣费,成功后才更新房间热度和本地榜。 diff --git a/services/room-service/internal/transport/grpc/server_test.go b/services/room-service/internal/transport/grpc/server_test.go index dc9c454f..c7fa2755 100644 --- a/services/room-service/internal/transport/grpc/server_test.go +++ b/services/room-service/internal/transport/grpc/server_test.go @@ -44,10 +44,8 @@ func TestCreateRoomMapsDomainErrorToGRPCReason(t *testing.T) { _, err := server.CreateRoom(context.Background(), &roomv1.CreateRoomRequest{ // room_id 缺失属于客户端参数错误,不能在 gRPC 边界丢失 reason 后变成 INTERNAL_ERROR。 - OwnerUserId: 1, - HostUserId: 1, - SeatCount: 8, - Mode: "voice", + SeatCount: 8, + Mode: "voice", }) if err == nil { t.Fatalf("CreateRoom without room_id should fail") diff --git a/services/user-service/configs/config.yaml b/services/user-service/configs/config.yaml index f8ae4d7a..330d9740 100644 --- a/services/user-service/configs/config.yaml +++ b/services/user-service/configs/config.yaml @@ -1,7 +1,7 @@ service_name: user-service node_id: user-local grpc_addr: ":13005" -mysql_dsn: "hyapp:hyapp@tcp(127.0.0.1:13306)/hy_user?parseTime=true&charset=utf8mb4&loc=Local" +mysql_dsn: "hyapp:hyapp@tcp(127.0.0.1:23306)/hy_user?parseTime=true&charset=utf8mb4&loc=Local" mysql_auto_migrate: false id_generator: node_id: 1 diff --git a/services/user-service/deploy/mysql/initdb/001_user_service.sql b/services/user-service/deploy/mysql/initdb/001_user_service.sql index 9dae4ecb..1405d7e9 100644 --- a/services/user-service/deploy/mysql/initdb/001_user_service.sql +++ b/services/user-service/deploy/mysql/initdb/001_user_service.sql @@ -54,14 +54,6 @@ CREATE TABLE IF NOT EXISTS countries ( KEY idx_countries_status_sort (status, sort_order) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -CREATE TABLE IF NOT EXISTS country_aliases ( - alias_code VARCHAR(3) NOT NULL PRIMARY KEY, - country_code VARCHAR(3) NOT NULL, - created_at_ms BIGINT NOT NULL, - updated_at_ms BIGINT NOT NULL, - KEY idx_country_aliases_country (country_code) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - CREATE TABLE IF NOT EXISTS regions ( region_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, region_code VARCHAR(64) NOT NULL, @@ -140,9 +132,6 @@ INSERT IGNORE INTO countries (country_name, country_code, country_display_name, ('Macao', 'MO', '中国澳门', 'active', 110, 0, 0), ('Taiwan', 'TW', '中国台湾', 'active', 120, 0, 0); -INSERT IGNORE INTO country_aliases (alias_code, country_code, created_at_ms, updated_at_ms) VALUES - ('USA', 'US', 0, 0); - CREATE TABLE IF NOT EXISTS user_display_user_ids ( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, display_user_id VARCHAR(32) NOT NULL, diff --git a/services/user-service/internal/app/app.go b/services/user-service/internal/app/app.go index a7db939a..c85274af 100644 --- a/services/user-service/internal/app/app.go +++ b/services/user-service/internal/app/app.go @@ -56,9 +56,6 @@ func New(cfg config.Config) (*App, error) { if err != nil { return nil, err } - var userRepository userservice.Repository = mysqlRepo - var authRepository authservice.Repository = mysqlRepo - // listener 在 New 阶段创建,端口占用会作为启动失败返回。 listener, err := net.Listen("tcp", cfg.GRPCAddr) if err != nil { @@ -77,13 +74,21 @@ func New(cfg config.Config) (*App, error) { SigningAlg: cfg.JWT.SigningAlg, SigningSecret: cfg.JWT.SigningSecret, }, - authservice.WithRepository(authRepository), + authservice.WithAuthRepository(mysqlRepo), + authservice.WithUserRepository(mysqlRepo), + authservice.WithIdentityRepository(mysqlRepo), + authservice.WithCountryRegionRepository(mysqlRepo), authservice.WithIDGenerator(idgen.NewInt64Generator(cfg.IDGenerator.NodeID)), authservice.WithDisplayUserIDAllocateMaxAttempts(cfg.DisplayUserID.AllocateMaxAttempts), authservice.WithThirdPartyVerifier(authservice.NewStaticThirdPartyVerifier(cfg.ThirdParty.AllowedProviders)), ) // user service 负责用户主状态和 display_user_id/靓号用例,不进入房间高频流程。 - userSvc := userservice.New(userRepository, + userSvc := userservice.New(mysqlRepo, + userservice.WithIdentityRepository(mysqlRepo), + userservice.WithCountryRegionRepository(mysqlRepo), + userservice.WithCountryAdminRepository(mysqlRepo), + userservice.WithRegionAdminRepository(mysqlRepo), + userservice.WithRegionRebuildRepository(mysqlRepo), userservice.WithIDGenerator(idgen.NewInt64Generator(cfg.IDGenerator.NodeID)), userservice.WithDisplayUserIDPolicy(cfg.DisplayUserID.AllocateMaxAttempts, time.Duration(cfg.DisplayUserID.ChangeCooldownSec)*time.Second), ) diff --git a/services/user-service/internal/config/config.go b/services/user-service/internal/config/config.go index 8da80409..f4c6ce8b 100644 --- a/services/user-service/internal/config/config.go +++ b/services/user-service/internal/config/config.go @@ -80,7 +80,7 @@ func Default() Config { ServiceName: "user-service", NodeID: "user-local", GRPCAddr: ":13005", - MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:13306)/hy_user?parseTime=true&charset=utf8mb4&loc=Local", + MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hy_user?parseTime=true&charset=utf8mb4&loc=Local", MySQLAutoMigrate: false, IDGenerator: IDGeneratorConfig{ NodeID: 1, diff --git a/services/user-service/internal/domain/user/region.go b/services/user-service/internal/domain/user/region.go index b6492790..1688d2e7 100644 --- a/services/user-service/internal/domain/user/region.go +++ b/services/user-service/internal/domain/user/region.go @@ -33,11 +33,6 @@ var ( regionCodePattern = regexp.MustCompile(`^[A-Z0-9_]{2,32}$`) ) -var defaultCountryAliases = map[string]string{ - // USA 是当前 seed 中唯一需要兼容的 alpha-3 输入;canonical 仍统一写 US,避免美国被配置到两个区域。 - "USA": "US", -} - // Country 是国家主数据;外部只提交 CountryCode,CountryID 只用于管理和审计。 type Country struct { CountryID int64 @@ -203,16 +198,6 @@ func ValidCountryCode(code string) bool { return countryCodePattern.MatchString(code) } -// DefaultCountryAliases 返回内置 alias -> canonical 映射;生产 MySQL 通过 country_aliases 表持久化同一规则。 -func DefaultCountryAliases() map[string]string { - aliases := make(map[string]string, len(defaultCountryAliases)) - for alias, canonical := range defaultCountryAliases { - aliases[alias] = canonical - } - - return aliases -} - // NormalizeRegionCode 统一区域业务编码形态。 func NormalizeRegionCode(code string) string { return strings.ToUpper(strings.TrimSpace(code)) diff --git a/services/user-service/internal/domain/user/user.go b/services/user-service/internal/domain/user/user.go index be6e05c7..8e3950c9 100644 --- a/services/user-service/internal/domain/user/user.go +++ b/services/user-service/internal/domain/user/user.go @@ -294,28 +294,6 @@ func (u User) EffectiveIdentity() Identity { } } -// NormalizeDisplayUserIDState 补齐老测试数据或迁移窗口中的默认短号字段。 -func (u User) NormalizeDisplayUserIDState() User { - if u.DefaultDisplayUserID == "" { - // 兼容旧测试或迁移窗口中只有 current_display_user_id 的用户。 - u.DefaultDisplayUserID = u.CurrentDisplayUserID - } - if u.CurrentDisplayUserID == "" { - // 当前展示号缺失时回退到默认短号,避免返回空短号。 - u.CurrentDisplayUserID = u.DefaultDisplayUserID - } - if u.CurrentDisplayUserIDKind == "" { - // 未写 kind 的老数据按默认短号处理。 - u.CurrentDisplayUserIDKind = DisplayUserIDKindDefault - } - if u.CurrentDisplayUserIDKind == DisplayUserIDKindDefault { - // 默认短号没有过期时间,清理脏数据中的 expires_at。 - u.CurrentDisplayUserIDExpiresAtMs = 0 - } - - return u -} - // ValidDisplayUserID 校验短号格式。 func ValidDisplayUserID(value string) bool { // 格式校验只判断形态,唯一性和可用性由 repository 事务决定。 diff --git a/services/user-service/internal/service/auth/service.go b/services/user-service/internal/service/auth/service.go index 343c75fe..ce2a1f4b 100644 --- a/services/user-service/internal/service/auth/service.go +++ b/services/user-service/internal/service/auth/service.go @@ -67,14 +67,6 @@ type IdentityRepository interface { ExpirePrettyDisplayUserID(ctx context.Context, command userdomain.ExpirePrettyDisplayUserIDCommand) (userdomain.Identity, error) } -// Repository 是兼容旧 WithRepository 调用的组合接口。 -// 新增实现可以分别注入 AuthRepository、UserRepository 和 IdentityRepository,避免继续扩大单一接口。 -type Repository interface { - AuthRepository - UserRepository - IdentityRepository -} - // ThirdPartyVerifier 把 provider credential 校验成稳定 provider_subject。 // gateway 不接触 provider secret,三方校验必须停留在 user-service。 type ThirdPartyVerifier interface { @@ -143,20 +135,6 @@ func New(cfg Config, options ...Option) *Service { return svc } -// WithRepository 注入认证持久化实现。 -func WithRepository(repository Repository) Option { - return func(s *Service) { - // 兼容旧构造方式:同一个 repository 同时承载 auth/user/identity 能力。 - s.authRepository = repository - s.userRepository = repository - s.identityRepository = repository - if countryRegionRepository, ok := repository.(userservice.CountryRegionRepository); ok { - // 国家主数据校验是注册写 users.country 前的强约束。 - s.countryRegionRepository = countryRegionRepository - } - } -} - // WithAuthRepository 注入纯认证持久化实现。 func WithAuthRepository(repository AuthRepository) Option { return func(s *Service) { diff --git a/services/user-service/internal/service/auth/service_test.go b/services/user-service/internal/service/auth/service_test.go index 04c135b3..0090f192 100644 --- a/services/user-service/internal/service/auth/service_test.go +++ b/services/user-service/internal/service/auth/service_test.go @@ -46,6 +46,17 @@ func (a sequenceDisplayUserIDAllocator) NextDisplayUserID(_ int64, attempt int) return a.values[attempt] } +func newUserService(repository *mysqltest.Repository, options ...userservice.Option) *userservice.Service { + base := []userservice.Option{ + userservice.WithIdentityRepository(repository), + userservice.WithCountryRegionRepository(repository), + userservice.WithCountryAdminRepository(repository), + userservice.WithRegionAdminRepository(repository), + userservice.WithRegionRebuildRepository(repository), + } + return userservice.New(repository, append(base, options...)...) +} + func newAuthService(repository *mysqltest.Repository, now *time.Time, ids []int64, displayIDs []string) *authservice.Service { // 测试服务固定 JWT、发号器、短号候选和时钟,保证 token/session 语义可断言。 return authservice.New(authservice.Config{ @@ -55,7 +66,10 @@ func newAuthService(repository *mysqltest.Repository, now *time.Time, ids []int6 SigningAlg: "HS256", SigningSecret: "test-secret", }, - authservice.WithRepository(repository), + authservice.WithAuthRepository(repository), + authservice.WithUserRepository(repository), + authservice.WithIdentityRepository(repository), + authservice.WithCountryRegionRepository(repository), authservice.WithIDGenerator(&sequenceIDGenerator{values: ids}), authservice.WithDisplayUserIDAllocator(sequenceDisplayUserIDAllocator{values: displayIDs}), authservice.WithClock(func() time.Time { return *now }), @@ -75,7 +89,7 @@ func seedCountry(t *testing.T, repository *mysqltest.Repository, code string) us } else if ok { return country } - svc := userservice.New(repository) + svc := newUserService(repository) country, err := svc.CreateCountry(context.Background(), code+" Name", code, code+" Display", 0, 1, "seed-country-"+code) if err != nil { t.Fatalf("seed country %s failed: %v", code, err) @@ -85,7 +99,7 @@ func seedCountry(t *testing.T, repository *mysqltest.Repository, code string) us func seedRegion(t *testing.T, repository *mysqltest.Repository, code string, countries []string) userdomain.Region { t.Helper() - svc := userservice.New(repository) + svc := newUserService(repository) region, err := svc.CreateRegion(context.Background(), code, code+" Region", countries, 0, 1, "seed-region-"+code) if err != nil { t.Fatalf("seed region %s failed: %v", code, err) @@ -315,40 +329,12 @@ func TestThirdPartyRegisterAssignsRegionAndExistingLoginKeepsOriginalCountry(t * } } -func TestThirdPartyRegisterCanonicalizesCountryAlias(t *testing.T) { - // 注册允许客户端传 USA alias,但 users.country 和区域快照必须落 canonical US。 - ctx := context.Background() - repository := mysqltest.NewRepository(t) - seedCountry(t, repository, "US") - region := seedRegion(t, repository, "NA", []string{"USA"}) - now := time.UnixMilli(1000) - svc := newAuthService(repository, &now, []int64{900009}, []string{"100009"}) - - token, isNew, err := svc.LoginThirdParty(ctx, "wechat", "openid-us-alias", authdomain.ThirdPartyRegistration{ - DeviceID: "ios", - Country: "usa", - }, authservice.Meta{RequestID: "req-us-alias"}) - if err != nil { - t.Fatalf("LoginThirdParty failed: %v", err) - } - if !isNew { - t.Fatalf("first third-party login must create user") - } - user, err := repository.GetUser(ctx, token.UserID) - if err != nil { - t.Fatalf("GetUser failed: %v", err) - } - if user.Country != "US" || user.RegionID != region.RegionID || user.RegionCode != "NA" { - t.Fatalf("country alias should canonicalize to US: %+v", user) - } -} - func TestThirdPartyRegisterRejectsUnknownOrDisabledCountry(t *testing.T) { // 国家格式合法但未命中 active countries 时,也必须返回稳定 INVALID_ARGUMENT。 ctx := context.Background() repository := mysqltest.NewRepository(t) country := seedCountry(t, repository, "MY") - userSvc := userservice.New(repository) + userSvc := newUserService(repository) if _, err := userSvc.DisableCountry(ctx, country.CountryID, 1, "disable-my"); err != nil { t.Fatalf("DisableCountry failed: %v", err) } @@ -447,7 +433,7 @@ func TestThirdPartyRegisterRetriesDisplayUserIDConflict(t *testing.T) { t.Fatalf("LoginThirdParty should retry display_user_id conflict: %v", err) } - userSvc := userservice.New(repository) + userSvc := newUserService(repository) identity, err := userSvc.GetUserIdentity(ctx, token.UserID) if err != nil { t.Fatalf("GetUserIdentity failed: %v", err) @@ -463,7 +449,7 @@ func TestPasswordLoginUsesCurrentDisplayUserIDWithPrettyLease(t *testing.T) { repository := mysqltest.NewRepository(t) now := time.UnixMilli(1000) authSvc := newAuthService(repository, &now, []int64{900001}, []string{"100001"}) - userSvc := userservice.New(repository, userservice.WithClock(func() time.Time { return now })) + userSvc := newUserService(repository, userservice.WithClock(func() time.Time { return now })) token, _, err := authSvc.LoginThirdParty(ctx, "wechat", "openid-pretty", thirdPartyRegistration("ios"), authservice.Meta{}) if err != nil { diff --git a/services/user-service/internal/service/auth/token.go b/services/user-service/internal/service/auth/token.go index 53ed3268..303b0ad0 100644 --- a/services/user-service/internal/service/auth/token.go +++ b/services/user-service/internal/service/auth/token.go @@ -120,11 +120,9 @@ func (s *Service) newSession(userID int64, deviceID string) (authdomain.Session, } func (s *Service) issueToken(user userdomain.User, sessionID string, refreshToken string) (authdomain.Token, error) { - // 签发前补齐 display_user_id 兼容字段,避免 token claim 出现空短号。 - user = user.NormalizeDisplayUserIDState() now := s.now() expiresAt := now.Add(time.Duration(s.cfg.AccessTokenTTLSec) * time.Second) - // claim 同时包含 sub 字符串和 user_id 数值,兼容不同消费方。 + // claim 同时包含 JWT subject 和网关鉴权需要的数值 user_id。 claims := jwt.MapClaims{ "iss": s.cfg.Issuer, "sub": strconv.FormatInt(user.UserID, 10), @@ -170,7 +168,6 @@ func (s *Service) freshUser(ctx context.Context, userID int64, nowMs int64, requ if err != nil { return userdomain.User{}, err } - user = user.NormalizeDisplayUserIDState() if !user.DisplayUserIDExpired(nowMs) { // 当前展示号仍有效,直接返回规范化用户。 return user, nil diff --git a/services/user-service/internal/service/user/pretty_id.go b/services/user-service/internal/service/user/pretty_id.go index 153aa17f..f491a9de 100644 --- a/services/user-service/internal/service/user/pretty_id.go +++ b/services/user-service/internal/service/user/pretty_id.go @@ -66,8 +66,6 @@ func (s *Service) ExpirePrettyDisplayUserID(ctx context.Context, userID int64, l } func (s *Service) refreshExpiredUser(ctx context.Context, user userdomain.User, requestID string) (userdomain.User, error) { - // 先补齐迁移窗口字段,避免老数据因为 kind/expires 缺失误判。 - user = user.NormalizeDisplayUserIDState() nowMs := s.now().UnixMilli() if !user.DisplayUserIDExpired(nowMs) { // 当前展示号仍有效时直接返回用户快照。 diff --git a/services/user-service/internal/service/user/service.go b/services/user-service/internal/service/user/service.go index 44517af3..e1196691 100644 --- a/services/user-service/internal/service/user/service.go +++ b/services/user-service/internal/service/user/service.go @@ -74,13 +74,6 @@ type IdentityRepository interface { ExpirePrettyDisplayUserID(ctx context.Context, command userdomain.ExpirePrettyDisplayUserIDCommand) (userdomain.Identity, error) } -// Repository 是兼容旧构造方式的组合接口。 -// 新代码应优先让 Service 字段依赖 UserRepository 和 IdentityRepository,而不是把所有方法塞回一个贫血大接口。 -type Repository interface { - UserRepository - IdentityRepository -} - // IDGenerator 生成系统内部不可变 user_id。 type IDGenerator interface { // NewInt64 返回全局唯一 user_id。 @@ -138,27 +131,6 @@ func New(userRepository UserRepository, options ...Option) *Service { displayUserIDCooldown: 30 * 24 * time.Hour, countryChangeCooldown: 30 * 24 * time.Hour, } - if identityRepository, ok := userRepository.(IdentityRepository); ok { - // MySQL repository 同时实现短号接口时自动启用短号用例。 - svc.identityRepository = identityRepository - } - if countryRegionRepository, ok := userRepository.(CountryRegionRepository); ok { - // 国家和区域主数据通常与 users 同库,自动接入注册和改国家校验。 - svc.countryRegionRepository = countryRegionRepository - } - if countryAdminRepository, ok := userRepository.(CountryAdminRepository); ok { - // 管理 RPC 只在 repository 明确实现完整事务能力时启用。 - svc.countryAdminRepository = countryAdminRepository - } - if regionAdminRepository, ok := userRepository.(RegionAdminRepository); ok { - // 区域管理包含国家映射和 rebuild task,必须由同一 repository 保证事务。 - svc.regionAdminRepository = regionAdminRepository - } - if regionRebuildRepository, ok := userRepository.(RegionRebuildRepository); ok { - // rebuild worker 只消费持久化 task,不参与注册或改国家主链路。 - svc.regionRebuildRepository = regionRebuildRepository - } - for _, option := range options { // option 只改依赖或策略,不做 IO。 option(svc) diff --git a/services/user-service/internal/service/user/service_test.go b/services/user-service/internal/service/user/service_test.go index 5b29d220..23d716ab 100644 --- a/services/user-service/internal/service/user/service_test.go +++ b/services/user-service/internal/service/user/service_test.go @@ -48,6 +48,17 @@ func strptr(value string) *string { return &value } +func newUserService(repository *mysqltest.Repository, options ...userservice.Option) *userservice.Service { + base := []userservice.Option{ + userservice.WithIdentityRepository(repository), + userservice.WithCountryRegionRepository(repository), + userservice.WithCountryAdminRepository(repository), + userservice.WithRegionAdminRepository(repository), + userservice.WithRegionRebuildRepository(repository), + } + return userservice.New(repository, append(base, options...)...) +} + func seedCountry(t *testing.T, repository *mysqltest.Repository, code string) userdomain.Country { t.Helper() if country, ok, err := repository.ResolveActiveCountryByCode(context.Background(), code); err != nil { @@ -55,7 +66,7 @@ func seedCountry(t *testing.T, repository *mysqltest.Repository, code string) us } else if ok { return country } - svc := userservice.New(repository) + svc := newUserService(repository) country, err := svc.CreateCountry(context.Background(), code+" Name", code, code+" Display", 0, 1, "seed-country-"+code) if err != nil { t.Fatalf("seed country %s failed: %v", code, err) @@ -65,7 +76,7 @@ func seedCountry(t *testing.T, repository *mysqltest.Repository, code string) us func seedRegion(t *testing.T, repository *mysqltest.Repository, code string, countries []string) userdomain.Region { t.Helper() - svc := userservice.New(repository) + svc := newUserService(repository) region, err := svc.CreateRegion(context.Background(), code, code+" Region", countries, 0, 1, "seed-region-"+code) if err != nil { t.Fatalf("seed region %s failed: %v", code, err) @@ -79,7 +90,7 @@ func TestGetUserUsesRepository(t *testing.T) { // PutUser 是测试辅助入口,直接准备用户主记录。 repository.PutUser(userdomain.User{UserID: 10001, CurrentDisplayUserID: "100001", Status: userdomain.StatusActive}) - svc := userservice.New(repository) + svc := newUserService(repository) user, err := svc.GetUser(context.Background(), 10001) if err != nil { t.Fatalf("GetUser failed: %v", err) @@ -101,7 +112,7 @@ func TestUpdateUserProfile(t *testing.T) { Status: userdomain.StatusActive, }) now := time.UnixMilli(2000) - svc := userservice.New(repository, userservice.WithClock(func() time.Time { return now })) + svc := newUserService(repository, userservice.WithClock(func() time.Time { return now })) user, err := svc.UpdateUserProfile(context.Background(), 10001, strptr(" new-name "), strptr(" https://cdn.example/a.png "), strptr("2000-01-02")) if err != nil { @@ -164,7 +175,7 @@ func TestChangeUserCountryWritesCooldown(t *testing.T) { seedCountry(t, repository, "JP") repository.PutUser(userdomain.User{UserID: 10001, CurrentDisplayUserID: "100001", Country: "CN", Status: userdomain.StatusActive}) current := time.UnixMilli(1000) - svc := userservice.New(repository, + svc := newUserService(repository, userservice.WithClock(func() time.Time { return current }), userservice.WithCountryChangeCooldown(time.Hour), ) @@ -197,10 +208,10 @@ func TestChangeUserCountryWritesCooldown(t *testing.T) { } func TestCountryAndRegionAdminValidation(t *testing.T) { - // 国家管理要接受小写归一,同时拒绝非法国家码、alias 国家码和重复区域归属。 + // 国家管理要接受小写归一,同时拒绝非法国家码、重复国家码和重复区域归属。 ctx := context.Background() repository := mysqltest.NewRepository(t) - svc := userservice.New(repository) + svc := newUserService(repository) aa, err := svc.CreateCountry(ctx, "Alpha Area", " aa ", "测试国家", 10, 1, "req-aa") if err != nil { @@ -216,8 +227,8 @@ func TestCountryAndRegionAdminValidation(t *testing.T) { if tla.CountryCode != "TLA" { t.Fatalf("three-letter canonical country code mismatch: %+v", tla) } - if _, err := svc.CreateCountry(ctx, "United States", "usa", "美国", 21, 1, "req-usa"); !xerr.IsCode(err, xerr.Conflict) { - t.Fatalf("expected alias country conflict, got %v", err) + if _, err := svc.CreateCountry(ctx, "Alpha Area Duplicate", "AA", "重复国家", 11, 1, "req-aa-dup"); !xerr.IsCode(err, xerr.Conflict) { + t.Fatalf("expected duplicate country conflict, got %v", err) } if _, err := svc.CreateCountry(ctx, "Bad", "C1", "Bad", 0, 1, "req-bad"); !xerr.IsCode(err, xerr.InvalidArgument) { t.Fatalf("expected invalid country code, got %v", err) @@ -235,37 +246,6 @@ func TestCountryAndRegionAdminValidation(t *testing.T) { } } -func TestCountryAliasCanonicalizesUserAndRegionWrites(t *testing.T) { - // alpha-3 alias 只用于输入兼容,users.country 和 region_countries 都必须写 canonical alpha-2。 - ctx := context.Background() - repository := mysqltest.NewRepository(t) - seedCountry(t, repository, "US") - repository.PutUser(userdomain.User{UserID: 10001, CurrentDisplayUserID: "100001", Status: userdomain.StatusActive}) - current := time.UnixMilli(1000) - svc := userservice.New(repository, - userservice.WithClock(func() time.Time { return current }), - userservice.WithCountryChangeCooldown(0), - ) - - region, err := svc.CreateRegion(ctx, "NA", "North America", []string{"usa"}, 0, 1, "req-na") - if err != nil { - t.Fatalf("CreateRegion with USA alias failed: %v", err) - } - if len(region.Countries) != 1 || region.Countries[0] != "US" { - t.Fatalf("region countries should store canonical code: %+v", region) - } - user, _, err := svc.ChangeUserCountry(ctx, 10001, "USA", "req-country-usa") - if err != nil { - t.Fatalf("ChangeUserCountry with USA alias failed: %v", err) - } - if user.Country != "US" || user.RegionID != region.RegionID || user.RegionCode != "NA" { - t.Fatalf("user country alias was not canonicalized: %+v", user) - } - if _, err := svc.ReplaceRegionCountries(ctx, region.RegionID, []string{"US", "USA"}, 1, "req-dup-alias"); !xerr.IsCode(err, xerr.InvalidArgument) { - t.Fatalf("expected duplicate canonical countries to be rejected, got %v", err) - } -} - func TestChangeUserCountryUpdatesRegionAndSameCountryRepairsStaleRegion(t *testing.T) { // 改国家同步重算 region_id;相同国家不写冷却日志,但允许修正历史 stale region_id。 ctx := context.Background() @@ -275,7 +255,7 @@ func TestChangeUserCountryUpdatesRegionAndSameCountryRepairsStaleRegion(t *testi region := seedRegion(t, repository, "SEA", []string{"SG"}) repository.PutUser(userdomain.User{UserID: 10001, CurrentDisplayUserID: "100001", Country: "US", Status: userdomain.StatusActive}) current := time.UnixMilli(1000) - svc := userservice.New(repository, + svc := newUserService(repository, userservice.WithClock(func() time.Time { return current }), userservice.WithCountryChangeCooldown(time.Hour), ) @@ -312,7 +292,7 @@ func TestChangeUserCountryRejectsUnknownOrDisabledCountry(t *testing.T) { repository := mysqltest.NewRepository(t) country := seedCountry(t, repository, "MY") repository.PutUser(userdomain.User{UserID: 10001, CurrentDisplayUserID: "100001", Country: "", Status: userdomain.StatusActive}) - svc := userservice.New(repository, userservice.WithCountryChangeCooldown(0)) + svc := newUserService(repository, userservice.WithCountryChangeCooldown(0)) if _, err := svc.DisableCountry(ctx, country.CountryID, 1, "disable-my"); err != nil { t.Fatalf("DisableCountry failed: %v", err) } @@ -332,7 +312,7 @@ func TestRegionRebuildWorkerUpdatesHistoricalUsers(t *testing.T) { repository.PutUser(userdomain.User{UserID: 10001, CurrentDisplayUserID: "100001", Country: "SG", Status: userdomain.StatusActive}) repository.PutUser(userdomain.User{UserID: 10002, CurrentDisplayUserID: "100002", Country: "SG", Status: userdomain.StatusActive}) current := time.UnixMilli(1000) - svc := userservice.New(repository, userservice.WithClock(func() time.Time { return current })) + svc := newUserService(repository, userservice.WithClock(func() time.Time { return current })) region, err := svc.CreateRegion(ctx, "SEA", "Southeast Asia", []string{"SG"}, 0, 1, "req-sea") if err != nil { t.Fatalf("CreateRegion failed: %v", err) @@ -363,7 +343,7 @@ func TestRegionRebuildWorkerSkipsStaleRevision(t *testing.T) { repository := mysqltest.NewRepository(t) seedCountry(t, repository, "SG") current := time.UnixMilli(1000) - svc := userservice.New(repository, userservice.WithClock(func() time.Time { return current })) + svc := newUserService(repository, userservice.WithClock(func() time.Time { return current })) region, err := svc.CreateRegion(ctx, "SEA", "Southeast Asia", []string{"SG"}, 0, 1, "req-sea") if err != nil { t.Fatalf("CreateRegion failed: %v", err) @@ -402,7 +382,7 @@ func TestRegionRebuildWorkerSkipsStaleRevision(t *testing.T) { // TestGetUserValidatesID 锁定 service 层的领域错误,不让 transport 层承担参数校验。 func TestGetUserValidatesID(t *testing.T) { - svc := userservice.New(mysqltest.NewRepository(t)) + svc := newUserService(mysqltest.NewRepository(t)) _, err := svc.GetUser(context.Background(), 0) if !xerr.IsCode(err, xerr.InvalidArgument) { @@ -413,7 +393,7 @@ func TestGetUserValidatesID(t *testing.T) { // TestCreateUserAllocatesDisplayUserID 验证新用户创建时同时生成 user_id 和 active display_user_id。 func TestCreateUserAllocatesDisplayUserID(t *testing.T) { repository := mysqltest.NewRepository(t) - svc := userservice.New(repository, + svc := newUserService(repository, userservice.WithIDGenerator(&sequenceIDGenerator{values: []int64{900001}}), userservice.WithDisplayUserIDAllocator(sequenceDisplayUserIDAllocator{values: []string{"100001"}}), userservice.WithClock(func() time.Time { return time.UnixMilli(1000) }), @@ -443,7 +423,7 @@ func TestCreateUserRetriesDisplayUserIDConflict(t *testing.T) { repository := mysqltest.NewRepository(t) // 先占用 100001,强制 CreateUser 走第二个候选。 repository.PutUser(userdomain.User{UserID: 1, CurrentDisplayUserID: "100001", Status: userdomain.StatusActive}) - svc := userservice.New(repository, + svc := newUserService(repository, userservice.WithIDGenerator(&sequenceIDGenerator{values: []int64{900001, 900002}}), userservice.WithDisplayUserIDAllocator(sequenceDisplayUserIDAllocator{values: []string{"100001", "100002"}}), ) @@ -460,7 +440,7 @@ func TestCreateUserRetriesDisplayUserIDConflict(t *testing.T) { // TestResolveDisplayUserIDNotFound 锁定短号不存在的专用 reason。 func TestResolveDisplayUserIDNotFound(t *testing.T) { - svc := userservice.New(mysqltest.NewRepository(t)) + svc := newUserService(mysqltest.NewRepository(t)) _, err := svc.ResolveDisplayUserID(context.Background(), "100001") if !xerr.IsCode(err, xerr.DisplayUserIDNotFound) { @@ -474,7 +454,7 @@ func TestChangeDisplayUserID(t *testing.T) { // 当前默认短号为 100001,修改后旧短号应释放。 repository.PutUser(userdomain.User{UserID: 10001, CurrentDisplayUserID: "100001", Status: userdomain.StatusActive}) now := time.UnixMilli(2000) - svc := userservice.New(repository, + svc := newUserService(repository, userservice.WithClock(func() time.Time { return now }), userservice.WithDisplayUserIDPolicy(8, 30*24*time.Hour), ) @@ -501,7 +481,7 @@ func TestChangeDisplayUserIDConflictAndCooldown(t *testing.T) { repository.PutUser(userdomain.User{UserID: 10001, CurrentDisplayUserID: "100001", Status: userdomain.StatusActive}) repository.PutUser(userdomain.User{UserID: 10002, CurrentDisplayUserID: "100002", Status: userdomain.StatusActive}) current := time.UnixMilli(3000) - svc := userservice.New(repository, + svc := newUserService(repository, userservice.WithClock(func() time.Time { return current }), userservice.WithDisplayUserIDPolicy(8, time.Hour), ) @@ -528,7 +508,7 @@ func TestApplyPrettyDisplayUserIDOverridesAndExpires(t *testing.T) { // 覆盖靓号申请、默认号 held、靓号 active 解析、过期后默认号恢复。 repository := mysqltest.NewRepository(t) current := time.UnixMilli(1000) - svc := userservice.New(repository, + svc := newUserService(repository, userservice.WithIDGenerator(&sequenceIDGenerator{values: []int64{900001}}), userservice.WithDisplayUserIDAllocator(sequenceDisplayUserIDAllocator{values: []string{"100001"}}), userservice.WithClock(func() time.Time { return current }), diff --git a/services/user-service/internal/storage/mysql/country_alias_integration_test.go b/services/user-service/internal/storage/mysql/country_alias_integration_test.go deleted file mode 100644 index 08125040..00000000 --- a/services/user-service/internal/storage/mysql/country_alias_integration_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package mysql - -import ( - "context" - "testing" - - "hyapp/pkg/xerr" - userdomain "hyapp/services/user-service/internal/domain/user" - "hyapp/services/user-service/internal/testutil/mysqlschema" -) - -const ( - countryAliasIntegrationCountry = "ZQ" - countryAliasIntegrationAlias = "ZQQ" - countryAliasIntegrationRegionCode = "TEST_ALIAS_CANONICAL" - countryAliasIntegrationDisplayName = "Alias Country" -) - -func TestCountryAliasCanonicalizesRegionWrites(t *testing.T) { - ctx := context.Background() - schema := mysqlschema.New(t) - repository, err := Open(ctx, schema.DSN) - if err != nil { - t.Fatalf("Open failed: %v", err) - } - t.Cleanup(func() { _ = repository.Close() }) - - if _, err := repository.db.ExecContext(ctx, ` - INSERT INTO countries (country_name, country_code, country_display_name, status, sort_order, created_at_ms, updated_at_ms) - VALUES (?, ?, ?, ?, 998, 1000, 1000) - `, "Alias Country", countryAliasIntegrationCountry, countryAliasIntegrationDisplayName, userdomain.CountryStatusActive); err != nil { - t.Fatalf("insert country failed: %v", err) - } - if _, err := repository.db.ExecContext(ctx, ` - INSERT INTO country_aliases (alias_code, country_code, created_at_ms, updated_at_ms) - VALUES (?, ?, 1000, 1000) - `, countryAliasIntegrationAlias, countryAliasIntegrationCountry); err != nil { - t.Fatalf("insert country alias failed: %v", err) - } - - country, ok, err := repository.ResolveActiveCountryByCode(ctx, countryAliasIntegrationAlias) - if err != nil { - t.Fatalf("ResolveActiveCountryByCode failed: %v", err) - } - if !ok || country.CountryCode != countryAliasIntegrationCountry { - t.Fatalf("alias should resolve to canonical country: ok=%v country=%+v", ok, country) - } - - region, err := repository.CreateRegion(ctx, userdomain.CreateRegionCommand{ - RegionCode: countryAliasIntegrationRegionCode, - Name: "Alias Region", - Countries: []string{countryAliasIntegrationAlias}, - OperatorUserID: 1, - RequestID: "req-alias-region", - NowMs: 2000, - }) - if err != nil { - t.Fatalf("CreateRegion failed: %v", err) - } - if len(region.Countries) != 1 || region.Countries[0] != countryAliasIntegrationCountry { - t.Fatalf("region should store canonical country code: %+v", region) - } - - var storedCountry string - if err := repository.db.QueryRowContext(ctx, ` - SELECT country_code - FROM region_countries - WHERE region_id = ? AND status = ? - `, region.RegionID, userdomain.RegionStatusActive).Scan(&storedCountry); err != nil { - t.Fatalf("query region country failed: %v", err) - } - if storedCountry != countryAliasIntegrationCountry { - t.Fatalf("region_countries should store canonical country code: %s", storedCountry) - } - - _, err = repository.ReplaceRegionCountries(ctx, userdomain.ReplaceRegionCountriesCommand{ - RegionID: region.RegionID, - Countries: []string{countryAliasIntegrationCountry, countryAliasIntegrationAlias}, - OperatorUserID: 1, - RequestID: "req-alias-dup", - NowMs: 3000, - }) - if !xerr.IsCode(err, xerr.InvalidArgument) { - t.Fatalf("expected duplicate canonical countries to be rejected, got %v", err) - } -} diff --git a/services/user-service/internal/storage/mysql/identity_repository.go b/services/user-service/internal/storage/mysql/identity_repository.go index 5e070637..35a4b7f1 100644 --- a/services/user-service/internal/storage/mysql/identity_repository.go +++ b/services/user-service/internal/storage/mysql/identity_repository.go @@ -316,7 +316,6 @@ func (r *Repository) expirePrettyByDisplayUserID(ctx context.Context, displayUse func (r *Repository) expirePrettyInTx(ctx context.Context, tx *sql.Tx, user userdomain.User, leaseID string, nowMs int64, requestID string) (userdomain.Identity, error) { // 调用方必须已经锁住 users 行;本函数继续锁 active lease 行。 - user = user.NormalizeDisplayUserIDState() if user.CurrentDisplayUserIDKind != userdomain.DisplayUserIDKindPretty { // 非 pretty 状态重复过期按幂等成功返回当前身份。 return user.EffectiveIdentity(), nil diff --git a/services/user-service/internal/storage/mysql/region_repository.go b/services/user-service/internal/storage/mysql/region_repository.go index 94dace53..cc89fc77 100644 --- a/services/user-service/internal/storage/mysql/region_repository.go +++ b/services/user-service/internal/storage/mysql/region_repository.go @@ -63,12 +63,6 @@ func (r *Repository) ResolveActiveRegionByCountry(ctx context.Context, countryCo // CreateCountry 创建 active 国家主数据。 func (r *Repository) CreateCountry(ctx context.Context, command userdomain.CreateCountryCommand) (userdomain.Country, error) { - if canonicalCode, exists, err := countryAliasTargetCode(ctx, r.db, command.CountryCode); err != nil { - return userdomain.Country{}, err - } else if exists { - // alias code 不能再作为 countries 主键,避免同一国家出现两套区域归属。 - return userdomain.Country{}, xerr.New(xerr.Conflict, "country_code is an alias of "+canonicalCode) - } result, err := r.db.ExecContext(ctx, ` INSERT INTO countries (country_name, country_code, country_display_name, status, sort_order, created_by_user_id, updated_by_user_id, created_at_ms, updated_at_ms) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) @@ -628,7 +622,7 @@ func canonicalActiveCountryCodes(ctx context.Context, q queryer, countries []str return nil, xerr.New(xerr.InvalidArgument, "country is not supported") } if _, exists := seen[country.CountryCode]; exists { - // alias 解析后仍要查重,防止 US 和 USA 同时配置成同一区域的两个国家。 + // 区域国家列表不能重复,避免重复写入 active 国家归属。 return nil, xerr.New(xerr.InvalidArgument, "countries must not contain duplicate canonical codes") } seen[country.CountryCode] = struct{}{} @@ -640,20 +634,6 @@ func canonicalActiveCountryCodes(ctx context.Context, q queryer, countries []str func resolveActiveCountryByCode(ctx context.Context, q queryer, countryCode string) (userdomain.Country, bool, error) { countryCode = userdomain.NormalizeCountryCode(countryCode) - if canonicalCode, exists, err := countryAliasTargetCode(ctx, q, countryCode); err != nil { - return userdomain.Country{}, false, err - } else if exists { - country, err := queryCountry(ctx, q, "WHERE country_code = ? AND status = ?", canonicalCode, userdomain.CountryStatusActive) - if err == sql.ErrNoRows { - return userdomain.Country{}, false, nil - } - if err != nil { - return userdomain.Country{}, false, err - } - - return country, true, nil - } - country, err := queryCountry(ctx, q, "WHERE country_code = ? AND status = ?", countryCode, userdomain.CountryStatusActive) if err == sql.ErrNoRows { return userdomain.Country{}, false, nil @@ -665,24 +645,6 @@ func resolveActiveCountryByCode(ctx context.Context, q queryer, countryCode stri return country, true, nil } -func countryAliasTargetCode(ctx context.Context, q queryer, aliasCode string) (string, bool, error) { - var canonicalCode string - err := q.QueryRowContext(ctx, ` - SELECT country_code - FROM country_aliases - WHERE alias_code = ? - LIMIT 1 - `, userdomain.NormalizeCountryCode(aliasCode)).Scan(&canonicalCode) - if err == sql.ErrNoRows { - return "", false, nil - } - if err != nil { - return "", false, err - } - - return canonicalCode, true, nil -} - func ensureCountriesAssignableToRegion(ctx context.Context, q queryer, countries []string, regionID int64) error { for _, country := range countries { mappedRegionID, mapped, err := activeRegionIDByCountry(ctx, q, country) diff --git a/services/user-service/internal/storage/mysql/user_repository.go b/services/user-service/internal/storage/mysql/user_repository.go index 4530c93c..499ff275 100644 --- a/services/user-service/internal/storage/mysql/user_repository.go +++ b/services/user-service/internal/storage/mysql/user_repository.go @@ -21,9 +21,9 @@ const userSelectColumns = ` COALESCE(gender, ''), COALESCE(country, ''), COALESCE(region_id, 0), - COALESCE((SELECT c.country_id FROM countries c LEFT JOIN country_aliases ca ON ca.country_code = c.country_code AND ca.alias_code = users.country WHERE c.country_code = users.country OR ca.alias_code = users.country ORDER BY CASE WHEN ca.alias_code = users.country THEN 0 ELSE 1 END LIMIT 1), 0), - COALESCE((SELECT c.country_name FROM countries c LEFT JOIN country_aliases ca ON ca.country_code = c.country_code AND ca.alias_code = users.country WHERE c.country_code = users.country OR ca.alias_code = users.country ORDER BY CASE WHEN ca.alias_code = users.country THEN 0 ELSE 1 END LIMIT 1), ''), - COALESCE((SELECT c.country_display_name FROM countries c LEFT JOIN country_aliases ca ON ca.country_code = c.country_code AND ca.alias_code = users.country WHERE c.country_code = users.country OR ca.alias_code = users.country ORDER BY CASE WHEN ca.alias_code = users.country THEN 0 ELSE 1 END LIMIT 1), ''), + COALESCE((SELECT c.country_id FROM countries c WHERE c.country_code = users.country LIMIT 1), 0), + COALESCE((SELECT c.country_name FROM countries c WHERE c.country_code = users.country LIMIT 1), ''), + COALESCE((SELECT c.country_display_name FROM countries c WHERE c.country_code = users.country LIMIT 1), ''), COALESCE((SELECT rg.region_code FROM regions rg WHERE rg.region_id = users.region_id LIMIT 1), ''), COALESCE((SELECT rg.name FROM regions rg WHERE rg.region_id = users.region_id LIMIT 1), ''), COALESCE(invite_code, ''), @@ -152,7 +152,7 @@ func (r *Repository) UpdateUserProfile(ctx context.Context, command userdomain.P return userdomain.User{}, err } - return user.NormalizeDisplayUserIDState(), nil + return user, nil } // ChangeUserCountry 修改国家并写日志;冷却期检查和日志写入必须在同一事务内完成。 @@ -239,7 +239,7 @@ func (r *Repository) queryUser(ctx context.Context, q queryer, clause string, ar return scanUser(row) } -// scanUser 固定 users 投影到领域模型的转换,并补齐迁移窗口中的默认值。 +// scanUser 固定 users 投影到领域模型的转换。 func scanUser(scanner interface { Scan(dest ...any) error }) (userdomain.User, error) { @@ -289,17 +289,15 @@ func scanUser(scanner interface { user.Status = userdomain.Status(status) user.CurrentDisplayUserIDKind = userdomain.DisplayUserIDKind(kind) - // Normalize 兼容迁移窗口中默认短号或 kind 为空的历史数据。 - return user.NormalizeDisplayUserIDState(), nil + return user, nil } // insertUserIdentity 是跨 auth/user 创建用户用例共享的事务片段。 // 它只在调用方已经开启事务后执行,避免 service 层手动拼跨表事务。 func insertUserIdentity(ctx context.Context, tx *sql.Tx, user userdomain.User, identity userdomain.Identity) error { // 调用方已开启事务,本函数只负责插入 users 和默认 active display_user_id。 - user = user.NormalizeDisplayUserIDState() if user.DefaultDisplayUserID == "" { - // 创建路径允许 user 中未预填默认短号,以 identity 为准。 + // 创建路径允许 user 中未预填默认短号,以同事务创建的 identity 为准。 user.DefaultDisplayUserID = identity.DisplayUserID } if user.CurrentDisplayUserID == "" { diff --git a/services/user-service/internal/testutil/mysqltest/mysqltest.go b/services/user-service/internal/testutil/mysqltest/mysqltest.go index 1ca60c22..684cc6f5 100644 --- a/services/user-service/internal/testutil/mysqltest/mysqltest.go +++ b/services/user-service/internal/testutil/mysqltest/mysqltest.go @@ -52,7 +52,15 @@ func (r *Repository) Close() { func (r *Repository) PutUser(user userdomain.User) { r.t.Helper() - user = user.NormalizeDisplayUserIDState() + if user.DefaultDisplayUserID == "" { + user.DefaultDisplayUserID = user.CurrentDisplayUserID + } + if user.CurrentDisplayUserID == "" { + user.CurrentDisplayUserID = user.DefaultDisplayUserID + } + if user.CurrentDisplayUserIDKind == "" { + user.CurrentDisplayUserIDKind = userdomain.DisplayUserIDKindDefault + } if user.Status == "" { user.Status = userdomain.StatusActive } diff --git a/services/user-service/internal/transport/grpc/server.go b/services/user-service/internal/transport/grpc/server.go index 13c9b357..fa67f5b7 100644 --- a/services/user-service/internal/transport/grpc/server.go +++ b/services/user-service/internal/transport/grpc/server.go @@ -3,7 +3,6 @@ package grpc import ( "context" - "strings" userv1 "hyapp/api/proto/user/v1" "hyapp/pkg/xerr" @@ -14,15 +13,15 @@ import ( // Server 把 user-service 用例层适配为 protobuf gRPC 接口。 type Server struct { - // UnimplementedAuthServiceServer 保持新增 RPC 时的向前兼容默认行为。 + // UnimplementedAuthServiceServer 提供未实现 RPC 的默认返回。 userv1.UnimplementedAuthServiceServer - // UnimplementedUserServiceServer 保持用户服务新增 RPC 时的向前兼容默认行为。 + // UnimplementedUserServiceServer 提供未实现 RPC 的默认返回。 userv1.UnimplementedUserServiceServer - // UnimplementedUserIdentityServiceServer 保持短号服务新增 RPC 时的向前兼容默认行为。 + // UnimplementedUserIdentityServiceServer 提供未实现 RPC 的默认返回。 userv1.UnimplementedUserIdentityServiceServer - // UnimplementedCountryAdminServiceServer 保持国家管理 RPC 新增时的默认兼容行为。 + // UnimplementedCountryAdminServiceServer 提供未实现 RPC 的默认返回。 userv1.UnimplementedCountryAdminServiceServer - // UnimplementedRegionAdminServiceServer 保持区域管理 RPC 新增时的默认兼容行为。 + // UnimplementedRegionAdminServiceServer 提供未实现 RPC 的默认返回。 userv1.UnimplementedRegionAdminServiceServer // authSvc 承载登录、设置密码、refresh 和 logout。 @@ -59,7 +58,7 @@ func (s *Server) LoginThirdParty(ctx context.Context, req *userv1.LoginThirdPart Country: req.GetCountry(), InviteCode: req.GetInviteCode(), IP: req.GetMeta().GetClientIp(), - DeviceID: firstNonEmpty(req.GetDeviceId(), req.GetMeta().GetDeviceId()), + DeviceID: req.GetDeviceId(), Device: req.GetDevice(), OSVersion: req.GetOsVersion(), Avatar: req.GetAvatar(), @@ -82,17 +81,6 @@ func (s *Server) LoginThirdParty(ctx context.Context, req *userv1.LoginThirdPart return &userv1.AuthResponse{Token: toProtoToken(token), IsNewUser: isNewUser}, nil } -func firstNonEmpty(values ...string) string { - for _, value := range values { - if trimmed := strings.TrimSpace(value); trimmed != "" { - // gRPC request fields win over meta; meta is only a gateway fallback. - return trimmed - } - } - - return "" -} - // SetPassword 为已登录用户首次写入密码身份。 func (s *Server) SetPassword(ctx context.Context, req *userv1.SetPasswordRequest) (*userv1.SetPasswordResponse, error) { // user_id 必须由 gateway 鉴权后传入,service 层会拒绝非法值。 diff --git a/services/user-service/internal/transport/grpc/server_test.go b/services/user-service/internal/transport/grpc/server_test.go index 408e0196..c4ccbd5c 100644 --- a/services/user-service/internal/transport/grpc/server_test.go +++ b/services/user-service/internal/transport/grpc/server_test.go @@ -15,6 +15,17 @@ import ( grpcserver "hyapp/services/user-service/internal/transport/grpc" ) +func newUserService(repository *mysqltest.Repository, options ...userservice.Option) *userservice.Service { + base := []userservice.Option{ + userservice.WithIdentityRepository(repository), + userservice.WithCountryRegionRepository(repository), + userservice.WithCountryAdminRepository(repository), + userservice.WithRegionAdminRepository(repository), + userservice.WithRegionRebuildRepository(repository), + } + return userservice.New(repository, append(base, options...)...) +} + func seedCountry(t *testing.T, repository *mysqltest.Repository, code string) { t.Helper() if _, ok, err := repository.ResolveActiveCountryByCode(context.Background(), code); err != nil { @@ -22,7 +33,7 @@ func seedCountry(t *testing.T, repository *mysqltest.Repository, code string) { } else if ok { return } - svc := userservice.New(repository) + svc := newUserService(repository) if _, err := svc.CreateCountry(context.Background(), code+" Name", code, code+" Display", 0, 1, "seed-country-"+code); err != nil { t.Fatalf("seed country %s failed: %v", code, err) } @@ -31,7 +42,7 @@ func seedCountry(t *testing.T, repository *mysqltest.Repository, code string) { // TestLoginPasswordErrorCarriesReason 验证 transport 层把领域错误转换为 ErrorInfo.reason。 func TestLoginPasswordErrorCarriesReason(t *testing.T) { // Auth service 没有完整输入时返回 INVALID_ARGUMENT,transport 必须转成 gRPC ErrorInfo.reason。 - server := grpcserver.NewServer(authservice.New(authservice.Config{}), userservice.New(mysqltest.NewRepository(t))) + server := grpcserver.NewServer(authservice.New(authservice.Config{}), newUserService(mysqltest.NewRepository(t))) _, err := server.LoginPassword(context.Background(), &userv1.LoginPasswordRequest{ Meta: &userv1.RequestMeta{DeviceId: "ios"}, @@ -42,7 +53,7 @@ func TestLoginPasswordErrorCarriesReason(t *testing.T) { } func TestLoginThirdPartyMapsRegistrationProfile(t *testing.T) { - // gRPC 层负责把 request 字段和 meta fallback 收敛成 auth service 的注册快照。 + // gRPC 层负责把 request 字段和服务端 meta 收敛成 auth service 的注册快照。 ctx := context.Background() repository := mysqltest.NewRepository(t) seedCountry(t, repository, "CN") @@ -53,8 +64,11 @@ func TestLoginThirdPartyMapsRegistrationProfile(t *testing.T) { RefreshTokenTTLSec: 2592000, SigningAlg: "HS256", SigningSecret: "test-secret", - }, authservice.WithRepository(repository), authservice.WithThirdPartyVerifier(authservice.NewStaticThirdPartyVerifier([]string{"wechat"}))) - server := grpcserver.NewServer(authSvc, userservice.New(repository)) + }, authservice.WithAuthRepository(repository), + authservice.WithUserRepository(repository), + authservice.WithIdentityRepository(repository), + authservice.WithCountryRegionRepository(repository), authservice.WithThirdPartyVerifier(authservice.NewStaticThirdPartyVerifier([]string{"wechat"}))) + server := grpcserver.NewServer(authSvc, newUserService(repository)) resp, err := server.LoginThirdParty(ctx, &userv1.LoginThirdPartyRequest{ Meta: &userv1.RequestMeta{RequestId: "req-third", DeviceId: "meta-device", ClientIp: "203.0.113.10", UserAgent: "hyapp-test/1.0", CountryByIp: "US"}, @@ -64,7 +78,6 @@ func TestLoginThirdPartyMapsRegistrationProfile(t *testing.T) { Gender: "male", Country: "CN", InviteCode: "INV-1", - Ip: "198.51.100.9", DeviceId: "req-device", Device: "iPhone 15", OsVersion: "iOS 18.1", @@ -102,7 +115,7 @@ func TestUpdateUserProfileAndCountry(t *testing.T) { seedCountry(t, repository, "CN") seedCountry(t, repository, "US") repository.PutUser(userdomain.User{UserID: 10001, CurrentDisplayUserID: "100001", Country: "CN", Status: userdomain.StatusActive}) - server := grpcserver.NewServer(authservice.New(authservice.Config{}), userservice.New(repository, userservice.WithCountryChangeCooldown(0))) + server := grpcserver.NewServer(authservice.New(authservice.Config{}), newUserService(repository, userservice.WithCountryChangeCooldown(0))) username := "hy" avatar := "https://cdn.example/a.png" birth := "2000-01-02" @@ -138,7 +151,7 @@ func TestCountryAndRegionAdminRPC(t *testing.T) { ctx := context.Background() repository := mysqltest.NewRepository(t) now := time.UnixMilli(1000) - userSvc := userservice.New(repository, userservice.WithClock(func() time.Time { return now })) + userSvc := newUserService(repository, userservice.WithClock(func() time.Time { return now })) server := grpcserver.NewServer(authservice.New(authservice.Config{}), userSvc) countryResp, err := server.CreateCountry(ctx, &userv1.CreateCountryRequest{ @@ -174,7 +187,7 @@ func TestCountryAndRegionAdminRPC(t *testing.T) { // TestResolveDisplayUserIDErrorCarriesReason 验证短号接口也使用 gRPC ErrorInfo.reason。 func TestResolveDisplayUserIDErrorCarriesReason(t *testing.T) { // 短号格式错误也必须通过同一 gRPC 错误映射路径暴露稳定 reason。 - server := grpcserver.NewServer(authservice.New(authservice.Config{}), userservice.New(mysqltest.NewRepository(t))) + server := grpcserver.NewServer(authservice.New(authservice.Config{}), newUserService(mysqltest.NewRepository(t))) _, err := server.ResolveDisplayUserID(context.Background(), &userv1.ResolveDisplayUserIDRequest{ DisplayUserId: "bad", diff --git a/services/wallet-service/configs/config.docker.yaml b/services/wallet-service/configs/config.docker.yaml index 2b728c97..f7839a41 100644 --- a/services/wallet-service/configs/config.docker.yaml +++ b/services/wallet-service/configs/config.docker.yaml @@ -3,6 +3,3 @@ node_id: wallet-docker grpc_addr: ":13004" mysql_dsn: "hyapp:hyapp@tcp(mysql:3306)/hy_wallet?parseTime=true&charset=utf8mb4&loc=Local" mysql_auto_migrate: false -ledger: - currency: "coin" - idempotency_ttl_sec: 86400 diff --git a/services/wallet-service/configs/config.tencent.example.yaml b/services/wallet-service/configs/config.tencent.example.yaml index d3943ff4..e20d88d2 100644 --- a/services/wallet-service/configs/config.tencent.example.yaml +++ b/services/wallet-service/configs/config.tencent.example.yaml @@ -3,6 +3,3 @@ node_id: wallet-tencent grpc_addr: ":13004" mysql_dsn: "${TENCENT_MYSQL_WALLET_DSN}" mysql_auto_migrate: false -ledger: - currency: "coin" - idempotency_ttl_sec: 86400 diff --git a/services/wallet-service/configs/config.yaml b/services/wallet-service/configs/config.yaml index 5e267e09..986fdb5c 100644 --- a/services/wallet-service/configs/config.yaml +++ b/services/wallet-service/configs/config.yaml @@ -1,8 +1,5 @@ service_name: wallet-service node_id: wallet-local grpc_addr: ":13004" -mysql_dsn: "hyapp:hyapp@tcp(127.0.0.1:13306)/hy_wallet?parseTime=true&charset=utf8mb4&loc=Local" +mysql_dsn: "hyapp:hyapp@tcp(127.0.0.1:23306)/hy_wallet?parseTime=true&charset=utf8mb4&loc=Local" mysql_auto_migrate: false -ledger: - currency: "coin" - idempotency_ttl_sec: 86400 diff --git a/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql b/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql index 960be586..ae1795e5 100644 --- a/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql +++ b/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql @@ -4,37 +4,83 @@ USE hy_wallet; CREATE TABLE IF NOT EXISTS wallet_accounts ( user_id BIGINT NOT NULL, - currency VARCHAR(32) NOT NULL, - balance BIGINT NOT NULL, + asset_type VARCHAR(32) NOT NULL, + available_amount BIGINT NOT NULL, + frozen_amount BIGINT NOT NULL, version BIGINT NOT NULL, created_at_ms BIGINT NOT NULL, updated_at_ms BIGINT NOT NULL, - PRIMARY KEY (user_id, currency) + PRIMARY KEY (user_id, asset_type) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -CREATE TABLE IF NOT EXISTS wallet_ledger ( - ledger_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, - billing_receipt_id VARCHAR(96) NOT NULL, - command_id VARCHAR(96) NOT NULL, - user_id BIGINT NOT NULL, - room_id VARCHAR(96) NOT NULL, - counterparty_user_id BIGINT NOT NULL, - currency VARCHAR(32) NOT NULL, - amount BIGINT NOT NULL, - direction VARCHAR(16) NOT NULL, - balance_after BIGINT NOT NULL, +CREATE TABLE IF NOT EXISTS wallet_transactions ( + transaction_id VARCHAR(96) NOT NULL PRIMARY KEY, + command_id VARCHAR(128) NOT NULL, biz_type VARCHAR(64) NOT NULL, + status VARCHAR(32) NOT NULL, + request_hash VARCHAR(128) NOT NULL, + external_ref VARCHAR(128) NOT NULL DEFAULT '', + metadata_json JSON NULL, created_at_ms BIGINT NOT NULL, - UNIQUE KEY uk_wallet_ledger_receipt (billing_receipt_id), - UNIQUE KEY uk_wallet_ledger_command_user (command_id, user_id), - KEY idx_wallet_ledger_user_time (user_id, created_at_ms) + updated_at_ms BIGINT NOT NULL, + UNIQUE KEY uk_wallet_tx_command (command_id), + KEY idx_wallet_tx_biz_time (biz_type, created_at_ms), + KEY idx_wallet_tx_external_ref (external_ref) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -CREATE TABLE IF NOT EXISTS wallet_idempotency ( - command_id VARCHAR(96) NOT NULL PRIMARY KEY, - billing_receipt_id VARCHAR(96) NOT NULL, - request_hash VARCHAR(128) NOT NULL, - expires_at_ms BIGINT NOT NULL, +CREATE TABLE IF NOT EXISTS wallet_entries ( + entry_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + transaction_id VARCHAR(96) NOT NULL, + user_id BIGINT NOT NULL, + asset_type VARCHAR(32) NOT NULL, + available_delta BIGINT NOT NULL, + frozen_delta BIGINT NOT NULL, + available_after BIGINT NOT NULL, + frozen_after BIGINT NOT NULL, + counterparty_user_id BIGINT NOT NULL DEFAULT 0, + room_id VARCHAR(96) NOT NULL DEFAULT '', created_at_ms BIGINT NOT NULL, - KEY idx_wallet_idempotency_expires_at (expires_at_ms) + KEY idx_wallet_entries_user_time (user_id, created_at_ms), + KEY idx_wallet_entries_tx (transaction_id), + KEY idx_wallet_entries_asset_time (asset_type, created_at_ms) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS wallet_outbox ( + event_id VARCHAR(128) NOT NULL PRIMARY KEY, + event_type VARCHAR(64) NOT NULL, + transaction_id VARCHAR(96) NOT NULL, + command_id VARCHAR(128) NOT NULL, + user_id BIGINT NOT NULL, + asset_type VARCHAR(32) NOT NULL, + available_delta BIGINT NOT NULL, + frozen_delta BIGINT NOT NULL, + payload JSON NOT NULL, + status VARCHAR(32) NOT NULL, + retry_count INT NOT NULL DEFAULT 0, + last_error TEXT NULL, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + KEY idx_wallet_outbox_pending (status, created_at_ms), + KEY idx_wallet_outbox_tx (transaction_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS wallet_gift_prices ( + gift_id VARCHAR(96) NOT NULL, + price_version VARCHAR(64) NOT NULL, + status VARCHAR(32) NOT NULL, + coin_price BIGINT NOT NULL, + gift_point_amount BIGINT NOT NULL, + heat_value BIGINT NOT NULL, + effective_at_ms BIGINT NOT NULL, + created_at_ms BIGINT NOT NULL, + updated_at_ms BIGINT NOT NULL, + PRIMARY KEY (gift_id, price_version), + KEY idx_wallet_gift_prices_effective (gift_id, status, effective_at_ms) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +INSERT IGNORE INTO wallet_gift_prices ( + gift_id, price_version, status, coin_price, gift_point_amount, heat_value, + effective_at_ms, created_at_ms, updated_at_ms +) VALUES + ('rose', 'v1', 'active', 10, 10, 10, 0, 0, 0), + ('rocket', 'v1', 'active', 100, 100, 100, 0, 0, 0); diff --git a/services/wallet-service/internal/app/app.go b/services/wallet-service/internal/app/app.go index db46064b..0c0e43b9 100644 --- a/services/wallet-service/internal/app/app.go +++ b/services/wallet-service/internal/app/app.go @@ -31,8 +31,8 @@ func New(cfg config.Config) (*App, error) { startupCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - // wallet-service 的余额、流水和幂等记录必须共享同一个 MySQL 事务边界。 - repository, err := mysqlstorage.Open(startupCtx, cfg.MySQLDSN, cfg.Ledger.Currency, cfg.Ledger.IdempotencyTTLSec) + // wallet-service 的账户、交易、分录和 outbox 必须共享同一个 MySQL 事务边界。 + repository, err := mysqlstorage.Open(startupCtx, cfg.MySQLDSN) if err != nil { return nil, err } @@ -44,10 +44,7 @@ func New(cfg config.Config) (*App, error) { } server := grpc.NewServer() - svc := walletservice.New(walletservice.Config{ - Currency: cfg.Ledger.Currency, - IdempotencyTTLSec: cfg.Ledger.IdempotencyTTLSec, - }, repository) + svc := walletservice.New(repository) walletv1.RegisterWalletServiceServer(server, grpcserver.NewServer(svc)) health := grpchealth.NewServingChecker("wallet-service") healthgrpc.RegisterHealthServer(server, grpchealth.NewServer(health)) diff --git a/services/wallet-service/internal/config/config.go b/services/wallet-service/internal/config/config.go index 74cde2da..300ae48f 100644 --- a/services/wallet-service/internal/config/config.go +++ b/services/wallet-service/internal/config/config.go @@ -7,17 +7,10 @@ type Config struct { ServiceName string `yaml:"service_name"` NodeID string `yaml:"node_id"` GRPCAddr string `yaml:"grpc_addr"` - // MySQLDSN 是余额、流水和账务幂等记录的必需存储连接串。 + // MySQLDSN 是账户、交易、分录和 outbox 的必需存储连接串。 MySQLDSN string `yaml:"mysql_dsn"` // MySQLAutoMigrate 保留给显式本地迁移;线上 schema 由发布流程或 initdb 管理。 - MySQLAutoMigrate bool `yaml:"mysql_auto_migrate"` - Ledger LedgerConfig `yaml:"ledger"` -} - -// LedgerConfig 保存账务底座配置。 -type LedgerConfig struct { - Currency string `yaml:"currency"` - IdempotencyTTLSec int64 `yaml:"idempotency_ttl_sec"` + MySQLAutoMigrate bool `yaml:"mysql_auto_migrate"` } // Default 返回本地开发默认配置。 @@ -26,12 +19,8 @@ func Default() Config { ServiceName: "wallet-service", NodeID: "wallet-local", GRPCAddr: ":13004", - MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:13306)/hy_wallet?parseTime=true&charset=utf8mb4&loc=Local", + MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hy_wallet?parseTime=true&charset=utf8mb4&loc=Local", MySQLAutoMigrate: false, - Ledger: LedgerConfig{ - Currency: "coin", - IdempotencyTTLSec: 86400, - }, } } diff --git a/services/wallet-service/internal/domain/ledger/ledger.go b/services/wallet-service/internal/domain/ledger/ledger.go index deaad23d..0d5f99f2 100644 --- a/services/wallet-service/internal/domain/ledger/ledger.go +++ b/services/wallet-service/internal/domain/ledger/ledger.go @@ -1,24 +1,65 @@ package ledger +const ( + // AssetCoin 是用户送礼消费资产。 + AssetCoin = "COIN" + // AssetGiftPoint 是主播礼物积分,不能直接消费或提现。 + AssetGiftPoint = "GIFT_POINT" + // AssetDiamond 是后续兑换用资产,当前只在余额模型中保留。 + AssetDiamond = "DIAMOND" + // AssetUSDBalance 是主播可提现美元余额,单位由后续提现策略固定。 + AssetUSDBalance = "USD_BALANCE" +) + // DebitGiftCommand 是 room-service 送礼扣费的账务命令。 type DebitGiftCommand struct { - CommandID string - RoomID string - SenderUserID int64 - TargetUserID int64 - GiftID string - GiftCount int32 - GiftUnitValue int64 + CommandID string + RoomID string + SenderUserID int64 + TargetUserID int64 + GiftID string + GiftCount int32 + PriceVersion string } // Receipt 是账务命令落账后的稳定回执。 type Receipt struct { BillingReceiptID string - TotalGiftValue int64 + TransactionID string + CoinSpent int64 + GiftPointAdded int64 + HeatValue int64 + PriceVersion string BalanceAfter int64 } -// TotalGiftValue 计算本次礼物扣费总额。 -func (c DebitGiftCommand) TotalGiftValue() int64 { - return int64(c.GiftCount) * c.GiftUnitValue +// AssetBalance 是账户当前可用/冻结余额投影。 +type AssetBalance struct { + UserID int64 + AssetType string + AvailableAmount int64 + FrozenAmount int64 + Version int64 + UpdatedAtMs int64 +} + +// AdminCreditAssetCommand 是后台手动入账的最小审计命令。 +type AdminCreditAssetCommand struct { + CommandID string + TargetUserID int64 + AssetType string + Amount int64 + OperatorUserID int64 + Reason string + EvidenceRef string +} + +// ValidAssetType 限定账本允许落账的资产类型,避免写入任意字符串资产。 +func ValidAssetType(assetType string) bool { + switch assetType { + case AssetCoin, AssetGiftPoint, AssetDiamond, AssetUSDBalance: + return true + default: + return false + } } diff --git a/services/wallet-service/internal/domain/wallet/wallet.go b/services/wallet-service/internal/domain/wallet/wallet.go deleted file mode 100644 index 697f61d7..00000000 --- a/services/wallet-service/internal/domain/wallet/wallet.go +++ /dev/null @@ -1,14 +0,0 @@ -package wallet - -// Account 表达用户钱包账户的当前余额投影。 -type Account struct { - UserID int64 - Currency string - Balance int64 - UpdatedAtMs int64 -} - -// CanDebit 判断当前余额是否可以完成扣费。 -func (a Account) CanDebit(amount int64) bool { - return amount > 0 && a.Balance >= amount -} diff --git a/services/wallet-service/internal/service/wallet/service.go b/services/wallet-service/internal/service/wallet/service.go index ae5fd175..f2436330 100644 --- a/services/wallet-service/internal/service/wallet/service.go +++ b/services/wallet-service/internal/service/wallet/service.go @@ -2,6 +2,7 @@ package wallet import ( "context" + "strings" "hyapp/pkg/xerr" "hyapp/services/wallet-service/internal/domain/ledger" @@ -10,23 +11,18 @@ import ( // Repository 隔离账务存储,扣费必须由 repository 保证余额和幂等。 type Repository interface { DebitGift(ctx context.Context, command ledger.DebitGiftCommand) (ledger.Receipt, error) -} - -// Config 保存钱包底座配置。 -type Config struct { - Currency string - IdempotencyTTLSec int64 + GetBalances(ctx context.Context, userID int64, assetTypes []string) ([]ledger.AssetBalance, error) + AdminCreditAsset(ctx context.Context, command ledger.AdminCreditAssetCommand) (ledger.AssetBalance, string, error) } // Service 承载钱包账务用例。 type Service struct { - cfg Config repository Repository } // New 创建钱包服务。 -func New(cfg Config, repository Repository) *Service { - return &Service{cfg: cfg, repository: repository} +func New(repository Repository) *Service { + return &Service{repository: repository} } // DebitGift 校验送礼扣费命令,并交给 repository 做原子落账和幂等。 @@ -34,8 +30,11 @@ func (s *Service) DebitGift(ctx context.Context, command ledger.DebitGiftCommand if command.CommandID == "" || command.RoomID == "" || command.SenderUserID <= 0 || command.TargetUserID <= 0 || command.GiftID == "" { return ledger.Receipt{}, xerr.New(xerr.InvalidArgument, "billing command is incomplete") } - if command.GiftCount <= 0 || command.GiftUnitValue <= 0 { - return ledger.Receipt{}, xerr.New(xerr.InvalidArgument, "gift_count and gift_unit_value must be positive") + if command.SenderUserID == command.TargetUserID { + return ledger.Receipt{}, xerr.New(xerr.InvalidArgument, "sender and target must be different") + } + if command.GiftCount <= 0 { + return ledger.Receipt{}, xerr.New(xerr.InvalidArgument, "gift_count must be positive") } if s.repository == nil { return ledger.Receipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") @@ -43,3 +42,45 @@ func (s *Service) DebitGift(ctx context.Context, command ledger.DebitGiftCommand return s.repository.DebitGift(ctx, command) } + +// GetBalances 返回用户多资产余额;鉴权范围由 gateway 或后台入口控制。 +func (s *Service) GetBalances(ctx context.Context, userID int64, assetTypes []string) ([]ledger.AssetBalance, error) { + if userID <= 0 { + return nil, xerr.New(xerr.InvalidArgument, "user_id is required") + } + if s.repository == nil { + return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + normalized := make([]string, 0, len(assetTypes)) + for _, assetType := range assetTypes { + assetType = strings.ToUpper(strings.TrimSpace(assetType)) + if assetType == "" { + continue + } + if !ledger.ValidAssetType(assetType) { + return nil, xerr.New(xerr.InvalidArgument, "asset_type is invalid") + } + normalized = append(normalized, assetType) + } + + return s.repository.GetBalances(ctx, userID, normalized) +} + +// AdminCreditAsset 执行后台手动入账;所有审计字段必须随交易一起落库。 +func (s *Service) AdminCreditAsset(ctx context.Context, command ledger.AdminCreditAssetCommand) (ledger.AssetBalance, string, error) { + if command.CommandID == "" || command.TargetUserID <= 0 || command.OperatorUserID <= 0 || command.Amount <= 0 { + return ledger.AssetBalance{}, "", xerr.New(xerr.InvalidArgument, "admin credit command is incomplete") + } + command.AssetType = strings.ToUpper(strings.TrimSpace(command.AssetType)) + if !ledger.ValidAssetType(command.AssetType) { + return ledger.AssetBalance{}, "", xerr.New(xerr.InvalidArgument, "asset_type is invalid") + } + if command.Reason == "" || command.EvidenceRef == "" { + return ledger.AssetBalance{}, "", xerr.New(xerr.InvalidArgument, "reason and evidence_ref are required") + } + if s.repository == nil { + return ledger.AssetBalance{}, "", xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + + return s.repository.AdminCreditAsset(ctx, command) +} diff --git a/services/wallet-service/internal/service/wallet/service_test.go b/services/wallet-service/internal/service/wallet/service_test.go index 945e46c0..f54ac96b 100644 --- a/services/wallet-service/internal/service/wallet/service_test.go +++ b/services/wallet-service/internal/service/wallet/service_test.go @@ -10,52 +10,170 @@ import ( "hyapp/services/wallet-service/internal/testutil/mysqltest" ) -// TestDebitGiftIsIdempotent 验证扣费命令重复提交时返回原始回执。 +// TestDebitGiftUsesServerPriceAndCreditsGiftPoint 验证送礼只信服务端价格并产生双边分录。 +func TestDebitGiftUsesServerPriceAndCreditsGiftPoint(t *testing.T) { + repository := mysqltest.NewRepository(t) + repository.SetBalance(10001, 100) + repository.SetGiftPrice("rose", "v9", 7, 3, 11) + svc := walletservice.New(repository) + + receipt, err := svc.DebitGift(context.Background(), ledger.DebitGiftCommand{ + CommandID: "cmd-gift-v2", + RoomID: "room-1", + SenderUserID: 10001, + TargetUserID: 10002, + GiftID: "rose", + GiftCount: 2, + PriceVersion: "v9", + }) + if err != nil { + t.Fatalf("DebitGift failed: %v", err) + } + + if receipt.CoinSpent != 14 || receipt.GiftPointAdded != 6 || receipt.HeatValue != 22 || receipt.BalanceAfter != 86 || receipt.PriceVersion != "v9" { + t.Fatalf("server price settlement mismatch: %+v", receipt) + } + if receipt.TransactionID == "" || receipt.BillingReceiptID == "" { + t.Fatalf("receipt identifiers missing: %+v", receipt) + } + + balances, err := svc.GetBalances(context.Background(), 10001, []string{ledger.AssetCoin}) + if err != nil { + t.Fatalf("GetBalances sender failed: %v", err) + } + if balanceAmount(balances, ledger.AssetCoin) != 86 { + t.Fatalf("sender COIN balance mismatch: %+v", balances) + } + balances, err = svc.GetBalances(context.Background(), 10002, []string{ledger.AssetGiftPoint}) + if err != nil { + t.Fatalf("GetBalances target failed: %v", err) + } + if balanceAmount(balances, ledger.AssetGiftPoint) != 6 { + t.Fatalf("target GIFT_POINT balance mismatch: %+v", balances) + } + if got := repository.CountRows("wallet_entries", "transaction_id = ?", receipt.TransactionID); got != 2 { + t.Fatalf("gift debit should write two entries, got %d", got) + } + if got := repository.CountRows("wallet_outbox", "transaction_id = ?", receipt.TransactionID); got != 3 { + t.Fatalf("gift debit should write balance and gift outbox events, got %d", got) + } +} + +// TestDebitGiftIsIdempotent 验证扣费命令重复提交时返回原始回执且不重复落账。 func TestDebitGiftIsIdempotent(t *testing.T) { repository := mysqltest.NewRepository(t) repository.SetBalance(10001, 100) - svc := walletservice.New(walletservice.Config{Currency: "coin"}, repository) + svc := walletservice.New(repository) command := ledger.DebitGiftCommand{ - CommandID: "cmd-1", - RoomID: "room-1", - SenderUserID: 10001, - TargetUserID: 10002, - GiftID: "rose", - GiftCount: 2, - GiftUnitValue: 10, + CommandID: "cmd-1", + RoomID: "room-1", + SenderUserID: 10001, + TargetUserID: 10002, + GiftID: "rose", + GiftCount: 2, } first, err := svc.DebitGift(context.Background(), command) if err != nil { t.Fatalf("first debit failed: %v", err) } - second, err := svc.DebitGift(context.Background(), command) if err != nil { t.Fatalf("second debit failed: %v", err) } - if first.BillingReceiptID != second.BillingReceiptID || second.BalanceAfter != 80 { + if first.BillingReceiptID != second.BillingReceiptID || second.BalanceAfter != 80 || second.CoinSpent != 20 { t.Fatalf("idempotency mismatch: first=%+v second=%+v", first, second) } + if got := repository.CountRows("wallet_transactions", "command_id = ?", command.CommandID); got != 1 { + t.Fatalf("idempotent command should write one transaction, got %d", got) + } } -// TestDebitGiftInsufficientBalance 锁定余额不足的稳定 reason。 +// TestDebitGiftRejectsCommandPayloadConflict 锁定同 command_id 不同业务 payload 的冲突语义。 +func TestDebitGiftRejectsCommandPayloadConflict(t *testing.T) { + repository := mysqltest.NewRepository(t) + repository.SetBalance(10001, 100) + svc := walletservice.New(repository) + command := ledger.DebitGiftCommand{CommandID: "cmd-conflict", RoomID: "room-1", SenderUserID: 10001, TargetUserID: 10002, GiftID: "rose", GiftCount: 1} + if _, err := svc.DebitGift(context.Background(), command); err != nil { + t.Fatalf("first debit failed: %v", err) + } + command.GiftCount = 2 + _, err := svc.DebitGift(context.Background(), command) + if !xerr.IsCode(err, xerr.LedgerConflict) { + t.Fatalf("expected LEDGER_CONFLICT, got %v", err) + } +} + +// TestDebitGiftInsufficientBalance 锁定余额不足时不写成功交易、分录或 outbox。 func TestDebitGiftInsufficientBalance(t *testing.T) { repository := mysqltest.NewRepository(t) repository.SetBalance(10001, 1) - svc := walletservice.New(walletservice.Config{Currency: "coin"}, repository) + svc := walletservice.New(repository) _, err := svc.DebitGift(context.Background(), ledger.DebitGiftCommand{ - CommandID: "cmd-2", - RoomID: "room-1", - SenderUserID: 10001, - TargetUserID: 10002, - GiftID: "rose", - GiftCount: 1, - GiftUnitValue: 10, + CommandID: "cmd-2", + RoomID: "room-1", + SenderUserID: 10001, + TargetUserID: 10002, + GiftID: "rose", + GiftCount: 1, }) if !xerr.IsCode(err, xerr.InsufficientBalance) { t.Fatalf("expected INSUFFICIENT_BALANCE, got %v", err) } + if got := repository.CountRows("wallet_transactions", "command_id = ?", "cmd-2"); got != 0 { + t.Fatalf("insufficient balance must not write transaction, got %d", got) + } + if got := repository.CountRows("wallet_entries", "transaction_id LIKE ?", "wtx_%"); got != 0 { + t.Fatalf("insufficient balance must not write entries, got %d", got) + } + if got := repository.CountRows("wallet_outbox", "command_id = ?", "cmd-2"); got != 0 { + t.Fatalf("insufficient balance must not write outbox, got %d", got) + } +} + +// TestAdminCreditAssetIsIdempotent 验证后台入账有审计字段、幂等和余额查询闭环。 +func TestAdminCreditAssetIsIdempotent(t *testing.T) { + repository := mysqltest.NewRepository(t) + svc := walletservice.New(repository) + command := ledger.AdminCreditAssetCommand{ + CommandID: "cmd-admin-credit", + TargetUserID: 20001, + AssetType: "coin", + Amount: 500, + OperatorUserID: 90001, + Reason: "manual recharge", + EvidenceRef: "ticket-1", + } + + first, txID, err := svc.AdminCreditAsset(context.Background(), command) + if err != nil { + t.Fatalf("AdminCreditAsset failed: %v", err) + } + second, secondTxID, err := svc.AdminCreditAsset(context.Background(), command) + if err != nil { + t.Fatalf("AdminCreditAsset retry failed: %v", err) + } + if txID == "" || txID != secondTxID || first.AvailableAmount != 500 || second.AvailableAmount != 500 || first.AssetType != ledger.AssetCoin { + t.Fatalf("admin credit idempotency mismatch: first=%+v second=%+v tx=%s/%s", first, second, txID, secondTxID) + } + + balances, err := svc.GetBalances(context.Background(), 20001, []string{ledger.AssetCoin, ledger.AssetGiftPoint}) + if err != nil { + t.Fatalf("GetBalances failed: %v", err) + } + if balanceAmount(balances, ledger.AssetCoin) != 500 || balanceAmount(balances, ledger.AssetGiftPoint) != 0 { + t.Fatalf("balances mismatch: %+v", balances) + } +} + +func balanceAmount(balances []ledger.AssetBalance, assetType string) int64 { + for _, balance := range balances { + if balance.AssetType == assetType { + return balance.AvailableAmount + } + } + return -1 } diff --git a/services/wallet-service/internal/storage/mysql/repository.go b/services/wallet-service/internal/storage/mysql/repository.go index 8521092e..8a9c2612 100644 --- a/services/wallet-service/internal/storage/mysql/repository.go +++ b/services/wallet-service/internal/storage/mysql/repository.go @@ -5,8 +5,11 @@ import ( "crypto/sha256" "database/sql" "encoding/hex" + "encoding/json" "errors" "fmt" + "math" + "sort" "strings" "time" @@ -15,28 +18,24 @@ import ( "hyapp/services/wallet-service/internal/domain/ledger" ) -// Repository 是 wallet-service 的 MySQL 账本入口。 +const ( + transactionStatusSucceeded = "succeeded" + bizTypeGiftDebit = "gift_debit" + bizTypeManualCredit = "manual_credit" + outboxStatusPending = "pending" +) + +// Repository 是 wallet-service 的 MySQL v2 账本入口。 type Repository struct { - // db 是余额、流水和幂等记录的同一事务边界。 + // db 是账户、交易、分录和 outbox 的同一事务边界。 db *sql.DB - // currency 约束当前服务实例处理的账本币种,避免请求侧伪造币种。 - currency string - // idempotencyTTL 控制幂等记录保留窗口,过期清理可以后续由后台任务执行。 - idempotencyTTL time.Duration } // Open 创建 MySQL 连接池并做启动 ping。 -func Open(ctx context.Context, dsn string, currency string, idempotencyTTLSec int64) (*Repository, error) { +func Open(ctx context.Context, dsn string) (*Repository, error) { if strings.TrimSpace(dsn) == "" { return nil, xerr.New(xerr.InvalidArgument, "mysql_dsn is required") } - if strings.TrimSpace(currency) == "" { - return nil, xerr.New(xerr.InvalidArgument, "wallet currency is required") - } - if idempotencyTTLSec <= 0 { - // 非正 TTL 会让幂等记录立即过期,本地和线上都拒绝这种配置。 - return nil, xerr.New(xerr.InvalidArgument, "wallet idempotency ttl must be positive") - } db, err := sql.Open("mysql", dsn) if err != nil { @@ -48,11 +47,7 @@ func Open(ctx context.Context, dsn string, currency string, idempotencyTTLSec in return nil, err } - return &Repository{ - db: db, - currency: currency, - idempotencyTTL: time.Duration(idempotencyTTLSec) * time.Second, - }, nil + return &Repository{db: db}, nil } // Close 释放 MySQL 连接池。 @@ -73,7 +68,7 @@ func (r *Repository) Ping(ctx context.Context) error { return r.db.PingContext(ctx) } -// DebitGift 在一个 MySQL 事务内完成幂等检查、余额锁定、扣减和流水落账。 +// DebitGift 在一个 MySQL 事务内完成 sender COIN 扣减、target GIFT_POINT 入账、分录和 outbox。 func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftCommand) (ledger.Receipt, error) { if r == nil || r.db == nil { return ledger.Receipt{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") @@ -89,177 +84,676 @@ func (r *Repository) DebitGift(ctx context.Context, command ledger.DebitGiftComm }() requestHash := debitRequestHash(command) - if receipt, exists, err := r.lookupIdempotentReceipt(ctx, tx, command, requestHash); err != nil || exists { - return receipt, err - } - - total := command.TotalGiftValue() - account, err := r.lockAccount(ctx, tx, command.SenderUserID) - if err != nil { - return ledger.Receipt{}, err - } - if account.balance < total { - return ledger.Receipt{}, xerr.New(xerr.InsufficientBalance, "insufficient balance") + if txRow, exists, err := r.lookupTransaction(ctx, tx, command.CommandID, requestHash, bizTypeGiftDebit); err != nil || exists { + if err != nil || !exists { + return ledger.Receipt{}, err + } + receipt, receiptErr := r.receiptForGiftTransaction(ctx, tx, txRow.TransactionID) + return receipt, receiptErr } nowMs := time.Now().UnixMilli() - receipt := ledger.Receipt{ - BillingReceiptID: billingReceiptID(command.CommandID), - TotalGiftValue: total, - BalanceAfter: account.balance - total, + price, err := r.resolveGiftPrice(ctx, tx, command.GiftID, command.PriceVersion, nowMs) + if err != nil { + return ledger.Receipt{}, err } - if err := r.saveDebit(ctx, tx, command, receipt, requestHash, nowMs); err != nil { + coinSpent, err := checkedMul(price.CoinPrice, int64(command.GiftCount)) + if err != nil { + return ledger.Receipt{}, err + } + giftPointAdded, err := checkedMul(price.GiftPointAmount, int64(command.GiftCount)) + if err != nil { + return ledger.Receipt{}, err + } + heatValue, err := checkedMul(price.HeatValue, int64(command.GiftCount)) + if err != nil { + return ledger.Receipt{}, err + } + + sender, err := r.lockAccount(ctx, tx, command.SenderUserID, ledger.AssetCoin, false, nowMs) + if err != nil { + return ledger.Receipt{}, err + } + if sender.AvailableAmount < coinSpent { + return ledger.Receipt{}, xerr.New(xerr.InsufficientBalance, "insufficient balance") + } + target, err := r.lockAccount(ctx, tx, command.TargetUserID, ledger.AssetGiftPoint, true, nowMs) + if err != nil { + return ledger.Receipt{}, err + } + + transactionID := transactionID(command.CommandID) + metadata := giftMetadata{ + GiftID: command.GiftID, + GiftCount: command.GiftCount, + PriceVersion: price.PriceVersion, + CoinSpent: coinSpent, + GiftPointAdded: giftPointAdded, + HeatValue: heatValue, + BalanceAfter: sender.AvailableAmount - coinSpent, + BillingReceipt: billingReceiptID(command.CommandID), + SenderUserID: command.SenderUserID, + TargetUserID: command.TargetUserID, + RoomID: command.RoomID, + CoinPrice: price.CoinPrice, + GiftPointAmount: price.GiftPointAmount, + HeatUnitValue: price.HeatValue, + } + if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeGiftDebit, requestHash, fmt.Sprintf("%s:%s", command.GiftID, price.PriceVersion), metadata, nowMs); err != nil { + return ledger.Receipt{}, err + } + if err := r.applyAccountDelta(ctx, tx, sender, -coinSpent, 0, nowMs); err != nil { + return ledger.Receipt{}, err + } + senderAfter := sender.AvailableAmount - coinSpent + if err := r.insertEntry(ctx, tx, walletEntry{ + TransactionID: transactionID, + UserID: command.SenderUserID, + AssetType: ledger.AssetCoin, + AvailableDelta: -coinSpent, + FrozenDelta: 0, + AvailableAfter: senderAfter, + FrozenAfter: sender.FrozenAmount, + CounterpartyUserID: command.TargetUserID, + RoomID: command.RoomID, + CreatedAtMS: nowMs, + }); err != nil { + return ledger.Receipt{}, err + } + if err := r.applyAccountDelta(ctx, tx, target, giftPointAdded, 0, nowMs); err != nil { + return ledger.Receipt{}, err + } + targetAfter := target.AvailableAmount + giftPointAdded + if err := r.insertEntry(ctx, tx, walletEntry{ + TransactionID: transactionID, + UserID: command.TargetUserID, + AssetType: ledger.AssetGiftPoint, + AvailableDelta: giftPointAdded, + FrozenDelta: 0, + AvailableAfter: targetAfter, + FrozenAfter: target.FrozenAmount, + CounterpartyUserID: command.SenderUserID, + RoomID: command.RoomID, + CreatedAtMS: nowMs, + }); err != nil { + return ledger.Receipt{}, err + } + if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{ + balanceChangedEvent(transactionID, command.CommandID, command.SenderUserID, ledger.AssetCoin, -coinSpent, 0, senderAfter, sender.FrozenAmount, metadata, nowMs), + balanceChangedEvent(transactionID, command.CommandID, command.TargetUserID, ledger.AssetGiftPoint, giftPointAdded, 0, targetAfter, target.FrozenAmount, metadata, nowMs), + giftDebitedEvent(transactionID, command.CommandID, command.SenderUserID, ledger.AssetCoin, -coinSpent, 0, metadata, nowMs), + }); err != nil { return ledger.Receipt{}, err } if err := tx.Commit(); err != nil { return ledger.Receipt{}, err } - return receipt, nil + return receiptFromGiftMetadata(transactionID, metadata), nil +} + +// GetBalances 读取用户多资产余额;指定资产不存在时返回 0 投影,便于客户端稳定渲染。 +func (r *Repository) GetBalances(ctx context.Context, userID int64, assetTypes []string) ([]ledger.AssetBalance, error) { + if r == nil || r.db == nil { + return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + assetTypes = normalizeAssetTypes(assetTypes) + + query := `SELECT user_id, asset_type, available_amount, frozen_amount, version, updated_at_ms + FROM wallet_accounts WHERE user_id = ?` + args := []any{userID} + if len(assetTypes) > 0 { + placeholders := strings.TrimRight(strings.Repeat("?,", len(assetTypes)), ",") + query += " AND asset_type IN (" + placeholders + ")" + for _, assetType := range assetTypes { + args = append(args, assetType) + } + } + query += " ORDER BY asset_type" + + rows, err := r.db.QueryContext(ctx, query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + byAsset := make(map[string]ledger.AssetBalance) + for rows.Next() { + var balance ledger.AssetBalance + if err := rows.Scan(&balance.UserID, &balance.AssetType, &balance.AvailableAmount, &balance.FrozenAmount, &balance.Version, &balance.UpdatedAtMs); err != nil { + return nil, err + } + byAsset[balance.AssetType] = balance + } + if err := rows.Err(); err != nil { + return nil, err + } + + if len(assetTypes) == 0 { + balances := make([]ledger.AssetBalance, 0, len(byAsset)) + for _, balance := range byAsset { + balances = append(balances, balance) + } + sort.Slice(balances, func(i, j int) bool { return balances[i].AssetType < balances[j].AssetType }) + return balances, nil + } + + balances := make([]ledger.AssetBalance, 0, len(assetTypes)) + for _, assetType := range assetTypes { + if balance, ok := byAsset[assetType]; ok { + balances = append(balances, balance) + continue + } + balances = append(balances, ledger.AssetBalance{UserID: userID, AssetType: assetType}) + } + return balances, nil +} + +// AdminCreditAsset 在一个事务内写入后台入账交易、分录和 outbox。 +func (r *Repository) AdminCreditAsset(ctx context.Context, command ledger.AdminCreditAssetCommand) (ledger.AssetBalance, string, error) { + if r == nil || r.db == nil { + return ledger.AssetBalance{}, "", xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return ledger.AssetBalance{}, "", err + } + defer func() { _ = tx.Rollback() }() + + requestHash := adminCreditRequestHash(command) + if txRow, exists, err := r.lookupTransaction(ctx, tx, command.CommandID, requestHash, bizTypeManualCredit); err != nil || exists { + if err != nil || !exists { + return ledger.AssetBalance{}, "", err + } + balance, balanceErr := r.balanceAfterTransaction(ctx, tx, txRow.TransactionID, command.TargetUserID, command.AssetType) + return balance, txRow.TransactionID, balanceErr + } + + nowMs := time.Now().UnixMilli() + account, err := r.lockAccount(ctx, tx, command.TargetUserID, command.AssetType, true, nowMs) + if err != nil { + return ledger.AssetBalance{}, "", err + } + transactionID := transactionID(command.CommandID) + metadata := adminCreditMetadata{ + TargetUserID: command.TargetUserID, + AssetType: command.AssetType, + Amount: command.Amount, + OperatorUserID: command.OperatorUserID, + Reason: command.Reason, + EvidenceRef: command.EvidenceRef, + } + if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeManualCredit, requestHash, command.EvidenceRef, metadata, nowMs); err != nil { + return ledger.AssetBalance{}, "", err + } + if err := r.applyAccountDelta(ctx, tx, account, command.Amount, 0, nowMs); err != nil { + return ledger.AssetBalance{}, "", err + } + balance := ledger.AssetBalance{ + UserID: command.TargetUserID, + AssetType: command.AssetType, + AvailableAmount: account.AvailableAmount + command.Amount, + FrozenAmount: account.FrozenAmount, + Version: account.Version + 1, + UpdatedAtMs: nowMs, + } + if err := r.insertEntry(ctx, tx, walletEntry{ + TransactionID: transactionID, + UserID: command.TargetUserID, + AssetType: command.AssetType, + AvailableDelta: command.Amount, + FrozenDelta: 0, + AvailableAfter: balance.AvailableAmount, + FrozenAfter: balance.FrozenAmount, + CreatedAtMS: nowMs, + }); err != nil { + return ledger.AssetBalance{}, "", err + } + if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{ + balanceChangedEvent(transactionID, command.CommandID, command.TargetUserID, command.AssetType, command.Amount, 0, balance.AvailableAmount, balance.FrozenAmount, metadata, nowMs), + }); err != nil { + return ledger.AssetBalance{}, "", err + } + if err := tx.Commit(); err != nil { + return ledger.AssetBalance{}, "", err + } + + return balance, transactionID, nil +} + +type transactionRow struct { + TransactionID string + MetadataJSON string +} + +func (r *Repository) lookupTransaction(ctx context.Context, tx *sql.Tx, commandID string, requestHash string, bizType string) (transactionRow, bool, error) { + row := tx.QueryRowContext(ctx, + `SELECT transaction_id, request_hash, biz_type, COALESCE(CAST(metadata_json AS CHAR), '{}') + FROM wallet_transactions + WHERE command_id = ? + FOR UPDATE`, + commandID, + ) + + var txRow transactionRow + var storedHash string + var storedBizType string + if err := row.Scan(&txRow.TransactionID, &storedHash, &storedBizType, &txRow.MetadataJSON); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return transactionRow{}, false, nil + } + return transactionRow{}, false, err + } + if storedHash != requestHash || storedBizType != bizType { + // command_id 是钱包幂等主键,业务 payload 或命令类型变化必须 fail-closed。 + return transactionRow{}, true, xerr.New(xerr.LedgerConflict, "wallet command idempotency conflict") + } + return txRow, true, nil } type walletAccount struct { - balance int64 - version int64 + UserID int64 + AssetType string + AvailableAmount int64 + FrozenAmount int64 + Version int64 + UpdatedAtMS int64 } -func (r *Repository) lookupIdempotentReceipt(ctx context.Context, tx *sql.Tx, command ledger.DebitGiftCommand, requestHash string) (ledger.Receipt, bool, error) { - var billingReceiptID string - var storedHash string - row := tx.QueryRowContext(ctx, - `SELECT billing_receipt_id, request_hash - FROM wallet_idempotency - WHERE command_id = ? - FOR UPDATE`, - command.CommandID, - ) - if err := row.Scan(&billingReceiptID, &storedHash); err != nil { - if errors.Is(err, sql.ErrNoRows) { - // 没有幂等记录说明该账务命令尚未成功提交。 - return ledger.Receipt{}, false, nil +func (r *Repository) lockAccount(ctx context.Context, tx *sql.Tx, userID int64, assetType string, createIfMissing bool, nowMs int64) (walletAccount, error) { + account, exists, err := r.queryAccountForUpdate(ctx, tx, userID, assetType) + if err != nil || exists || !createIfMissing { + if err != nil { + return walletAccount{}, err } - - return ledger.Receipt{}, false, err - } - if storedHash != requestHash { - // 同一个 command_id 携带不同扣费内容不能回放旧回执,否则会掩盖上游幂等键复用错误。 - return ledger.Receipt{}, true, xerr.New(xerr.LedgerConflict, "billing command idempotency conflict") - } - - receipt, err := r.getReceipt(ctx, tx, command.CommandID, billingReceiptID) - return receipt, true, err -} - -func (r *Repository) getReceipt(ctx context.Context, tx *sql.Tx, commandID string, billingReceiptID string) (ledger.Receipt, error) { - row := tx.QueryRowContext(ctx, - `SELECT billing_receipt_id, amount, balance_after - FROM wallet_ledger - WHERE command_id = ? AND billing_receipt_id = ? AND currency = ? - LIMIT 1`, - commandID, - billingReceiptID, - r.currency, - ) - - var receipt ledger.Receipt - if err := row.Scan(&receipt.BillingReceiptID, &receipt.TotalGiftValue, &receipt.BalanceAfter); err != nil { - if errors.Is(err, sql.ErrNoRows) { - // 幂等记录存在但流水缺失代表事务外数据被破坏,不能当作可重试业务错误。 - return ledger.Receipt{}, xerr.New(xerr.Internal, "wallet ledger record is missing") - } - - return ledger.Receipt{}, err - } - - return receipt, nil -} - -func (r *Repository) lockAccount(ctx context.Context, tx *sql.Tx, userID int64) (walletAccount, error) { - row := tx.QueryRowContext(ctx, - `SELECT balance, version - FROM wallet_accounts - WHERE user_id = ? AND currency = ? - FOR UPDATE`, - userID, - r.currency, - ) - - var account walletAccount - if err := row.Scan(&account.balance, &account.version); err != nil { - if errors.Is(err, sql.ErrNoRows) { - // 未开户在扣费语义上等价于没有可用余额,避免向房间链路暴露账务内部结构。 + if !exists { return walletAccount{}, xerr.New(xerr.InsufficientBalance, "insufficient balance") } - - return walletAccount{}, err + return account, nil } + if _, err := tx.ExecContext(ctx, + `INSERT INTO wallet_accounts (user_id, asset_type, available_amount, frozen_amount, version, created_at_ms, updated_at_ms) + VALUES (?, ?, 0, 0, 1, ?, ?)`, + userID, assetType, nowMs, nowMs, + ); err != nil { + return walletAccount{}, err + } + return r.queryRequiredAccountForUpdate(ctx, tx, userID, assetType) +} + +func (r *Repository) queryAccountForUpdate(ctx context.Context, tx *sql.Tx, userID int64, assetType string) (walletAccount, bool, error) { + row := tx.QueryRowContext(ctx, + `SELECT user_id, asset_type, available_amount, frozen_amount, version, updated_at_ms + FROM wallet_accounts + WHERE user_id = ? AND asset_type = ? + FOR UPDATE`, + userID, assetType, + ) + var account walletAccount + if err := row.Scan(&account.UserID, &account.AssetType, &account.AvailableAmount, &account.FrozenAmount, &account.Version, &account.UpdatedAtMS); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return walletAccount{}, false, nil + } + return walletAccount{}, false, err + } + return account, true, nil +} + +func (r *Repository) queryRequiredAccountForUpdate(ctx context.Context, tx *sql.Tx, userID int64, assetType string) (walletAccount, error) { + account, exists, err := r.queryAccountForUpdate(ctx, tx, userID, assetType) + if err != nil { + return walletAccount{}, err + } + if !exists { + return walletAccount{}, xerr.New(xerr.Internal, "wallet account creation failed") + } return account, nil } -func (r *Repository) saveDebit(ctx context.Context, tx *sql.Tx, command ledger.DebitGiftCommand, receipt ledger.Receipt, requestHash string, nowMs int64) error { - if _, err := tx.ExecContext(ctx, +func (r *Repository) applyAccountDelta(ctx context.Context, tx *sql.Tx, account walletAccount, availableDelta int64, frozenDelta int64, nowMs int64) error { + availableAfter := account.AvailableAmount + availableDelta + frozenAfter := account.FrozenAmount + frozenDelta + if availableAfter < 0 || frozenAfter < 0 || availableAfter > math.MaxInt64-frozenAfter { + // 钱包负债不能出现负数,也不能因溢出绕过余额约束。 + return xerr.New(xerr.LedgerConflict, "wallet balance delta is invalid") + } + + result, err := tx.ExecContext(ctx, `UPDATE wallet_accounts - SET balance = ?, version = version + 1, updated_at_ms = ? - WHERE user_id = ? AND currency = ?`, - receipt.BalanceAfter, + SET available_amount = ?, frozen_amount = ?, version = version + 1, updated_at_ms = ? + WHERE user_id = ? AND asset_type = ? AND version = ?`, + availableAfter, + frozenAfter, nowMs, - command.SenderUserID, - r.currency, - ); err != nil { + account.UserID, + account.AssetType, + account.Version, + ) + if err != nil { return err } - - if _, err := tx.ExecContext(ctx, - `INSERT INTO wallet_ledger ( - billing_receipt_id, command_id, user_id, room_id, counterparty_user_id, - currency, amount, direction, balance_after, biz_type, created_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - receipt.BillingReceiptID, - command.CommandID, - command.SenderUserID, - command.RoomID, - command.TargetUserID, - r.currency, - receipt.TotalGiftValue, - "debit", - receipt.BalanceAfter, - "gift", - nowMs, - ); err != nil { + rows, err := result.RowsAffected() + if err != nil { return err } + if rows != 1 { + return xerr.New(xerr.LedgerConflict, "wallet account version conflict") + } + return nil +} - _, err := tx.ExecContext(ctx, - `INSERT INTO wallet_idempotency ( - command_id, billing_receipt_id, request_hash, expires_at_ms, created_at_ms - ) VALUES (?, ?, ?, ?, ?)`, - command.CommandID, - receipt.BillingReceiptID, +type giftPrice struct { + GiftID string + PriceVersion string + CoinPrice int64 + GiftPointAmount int64 + HeatValue int64 +} + +func (r *Repository) resolveGiftPrice(ctx context.Context, tx *sql.Tx, giftID string, priceVersion string, nowMs int64) (giftPrice, error) { + var row *sql.Row + if strings.TrimSpace(priceVersion) != "" { + row = tx.QueryRowContext(ctx, + `SELECT gift_id, price_version, coin_price, gift_point_amount, heat_value + FROM wallet_gift_prices + WHERE gift_id = ? AND price_version = ? AND status = 'active' AND effective_at_ms <= ?`, + giftID, priceVersion, nowMs, + ) + } else { + row = tx.QueryRowContext(ctx, + `SELECT gift_id, price_version, coin_price, gift_point_amount, heat_value + FROM wallet_gift_prices + WHERE gift_id = ? AND status = 'active' AND effective_at_ms <= ? + ORDER BY effective_at_ms DESC, price_version DESC + LIMIT 1`, + giftID, nowMs, + ) + } + + var price giftPrice + if err := row.Scan(&price.GiftID, &price.PriceVersion, &price.CoinPrice, &price.GiftPointAmount, &price.HeatValue); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return giftPrice{}, xerr.New(xerr.NotFound, "gift price is not active") + } + return giftPrice{}, err + } + if price.CoinPrice <= 0 || price.GiftPointAmount < 0 || price.HeatValue < 0 { + return giftPrice{}, xerr.New(xerr.Internal, "gift price is invalid") + } + return price, nil +} + +func (r *Repository) insertTransaction(ctx context.Context, tx *sql.Tx, transactionID string, commandID string, bizType string, requestHash string, externalRef string, metadata any, nowMs int64) error { + metadataJSON, err := json.Marshal(metadata) + if err != nil { + return err + } + _, err = tx.ExecContext(ctx, + `INSERT INTO wallet_transactions ( + transaction_id, command_id, biz_type, status, request_hash, external_ref, metadata_json, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + transactionID, + commandID, + bizType, + transactionStatusSucceeded, requestHash, - nowMs+r.idempotencyTTL.Milliseconds(), + externalRef, + string(metadataJSON), + nowMs, nowMs, ) - return err } +type walletEntry struct { + TransactionID string + UserID int64 + AssetType string + AvailableDelta int64 + FrozenDelta int64 + AvailableAfter int64 + FrozenAfter int64 + CounterpartyUserID int64 + RoomID string + CreatedAtMS int64 +} + +func (r *Repository) insertEntry(ctx context.Context, tx *sql.Tx, entry walletEntry) error { + _, err := tx.ExecContext(ctx, + `INSERT INTO wallet_entries ( + transaction_id, user_id, asset_type, available_delta, frozen_delta, + available_after, frozen_after, counterparty_user_id, room_id, created_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + entry.TransactionID, + entry.UserID, + entry.AssetType, + entry.AvailableDelta, + entry.FrozenDelta, + entry.AvailableAfter, + entry.FrozenAfter, + entry.CounterpartyUserID, + entry.RoomID, + entry.CreatedAtMS, + ) + return err +} + +type walletOutboxEvent struct { + EventID string + EventType string + TransactionID string + CommandID string + UserID int64 + AssetType string + AvailableDelta int64 + FrozenDelta int64 + Payload any + CreatedAtMS int64 +} + +func (r *Repository) insertWalletOutbox(ctx context.Context, tx *sql.Tx, events []walletOutboxEvent) error { + for _, event := range events { + payloadJSON, err := json.Marshal(event.Payload) + if err != nil { + return err + } + if _, err := tx.ExecContext(ctx, + `INSERT INTO wallet_outbox ( + event_id, event_type, transaction_id, command_id, user_id, asset_type, + available_delta, frozen_delta, payload, status, retry_count, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?)`, + event.EventID, + event.EventType, + event.TransactionID, + event.CommandID, + event.UserID, + event.AssetType, + event.AvailableDelta, + event.FrozenDelta, + string(payloadJSON), + outboxStatusPending, + event.CreatedAtMS, + event.CreatedAtMS, + ); err != nil { + return err + } + } + return nil +} + +func (r *Repository) receiptForGiftTransaction(ctx context.Context, tx *sql.Tx, transactionID string) (ledger.Receipt, error) { + var metadataJSON string + if err := tx.QueryRowContext(ctx, + `SELECT COALESCE(CAST(metadata_json AS CHAR), '{}') + FROM wallet_transactions + WHERE transaction_id = ?`, + transactionID, + ).Scan(&metadataJSON); err != nil { + return ledger.Receipt{}, err + } + var metadata giftMetadata + if err := json.Unmarshal([]byte(metadataJSON), &metadata); err != nil { + return ledger.Receipt{}, err + } + return receiptFromGiftMetadata(transactionID, metadata), nil +} + +func (r *Repository) balanceAfterTransaction(ctx context.Context, tx *sql.Tx, transactionID string, userID int64, assetType string) (ledger.AssetBalance, error) { + row := tx.QueryRowContext(ctx, + `SELECT user_id, asset_type, available_after, frozen_after + FROM wallet_entries + WHERE transaction_id = ? AND user_id = ? AND asset_type = ? + ORDER BY entry_id DESC LIMIT 1`, + transactionID, userID, assetType, + ) + var balance ledger.AssetBalance + if err := row.Scan(&balance.UserID, &balance.AssetType, &balance.AvailableAmount, &balance.FrozenAmount); err != nil { + return ledger.AssetBalance{}, err + } + return balance, nil +} + +type giftMetadata struct { + GiftID string `json:"gift_id"` + GiftCount int32 `json:"gift_count"` + PriceVersion string `json:"price_version"` + CoinPrice int64 `json:"coin_price"` + GiftPointAmount int64 `json:"gift_point_amount"` + HeatUnitValue int64 `json:"heat_unit_value"` + CoinSpent int64 `json:"coin_spent"` + GiftPointAdded int64 `json:"gift_point_added"` + HeatValue int64 `json:"heat_value"` + BalanceAfter int64 `json:"balance_after"` + BillingReceipt string `json:"billing_receipt_id"` + SenderUserID int64 `json:"sender_user_id"` + TargetUserID int64 `json:"target_user_id"` + RoomID string `json:"room_id"` +} + +type adminCreditMetadata struct { + TargetUserID int64 `json:"target_user_id"` + AssetType string `json:"asset_type"` + Amount int64 `json:"amount"` + OperatorUserID int64 `json:"operator_user_id"` + Reason string `json:"reason"` + EvidenceRef string `json:"evidence_ref"` +} + +func receiptFromGiftMetadata(transactionID string, metadata giftMetadata) ledger.Receipt { + return ledger.Receipt{ + BillingReceiptID: metadata.BillingReceipt, + TransactionID: transactionID, + CoinSpent: metadata.CoinSpent, + GiftPointAdded: metadata.GiftPointAdded, + HeatValue: metadata.HeatValue, + PriceVersion: metadata.PriceVersion, + BalanceAfter: metadata.BalanceAfter, + } +} + +func balanceChangedEvent(transactionID string, commandID string, userID int64, assetType string, availableDelta int64, frozenDelta int64, availableAfter int64, frozenAfter int64, payload any, nowMs int64) walletOutboxEvent { + return walletOutboxEvent{ + EventID: eventID(transactionID, "WalletBalanceChanged", userID, assetType), + EventType: "WalletBalanceChanged", + TransactionID: transactionID, + CommandID: commandID, + UserID: userID, + AssetType: assetType, + AvailableDelta: availableDelta, + FrozenDelta: frozenDelta, + Payload: map[string]any{ + "transaction_id": transactionID, + "command_id": commandID, + "user_id": userID, + "asset_type": assetType, + "available_delta": availableDelta, + "frozen_delta": frozenDelta, + "available_after": availableAfter, + "frozen_after": frozenAfter, + "metadata": payload, + "created_at_ms": nowMs, + }, + CreatedAtMS: nowMs, + } +} + +func giftDebitedEvent(transactionID string, commandID string, userID int64, assetType string, availableDelta int64, frozenDelta int64, payload any, nowMs int64) walletOutboxEvent { + return walletOutboxEvent{ + EventID: eventID(transactionID, "WalletGiftDebited", userID, assetType), + EventType: "WalletGiftDebited", + TransactionID: transactionID, + CommandID: commandID, + UserID: userID, + AssetType: assetType, + AvailableDelta: availableDelta, + FrozenDelta: frozenDelta, + Payload: payload, + CreatedAtMS: nowMs, + } +} + +func normalizeAssetTypes(assetTypes []string) []string { + seen := make(map[string]bool, len(assetTypes)) + result := make([]string, 0, len(assetTypes)) + for _, assetType := range assetTypes { + assetType = strings.ToUpper(strings.TrimSpace(assetType)) + if assetType == "" || seen[assetType] { + continue + } + seen[assetType] = true + result = append(result, assetType) + } + return result +} + +func checkedMul(unit int64, count int64) (int64, error) { + if unit < 0 || count <= 0 { + return 0, xerr.New(xerr.InvalidArgument, "amount is invalid") + } + if unit != 0 && count > math.MaxInt64/unit { + return 0, xerr.New(xerr.InvalidArgument, "amount overflow") + } + return unit * count, nil +} + func debitRequestHash(command ledger.DebitGiftCommand) string { - // 哈希覆盖会影响扣费结果的全部字段,用于识别同一 command_id 的错误复用。 - sum := sha256.Sum256([]byte(fmt.Sprintf("%s|%d|%d|%s|%d|%d", + // 哈希覆盖所有账务语义字段;相同 command_id 只能重放完全相同的业务命令。 + return stableHash(fmt.Sprintf("gift|%s|%d|%d|%s|%d|%s", command.RoomID, command.SenderUserID, command.TargetUserID, command.GiftID, command.GiftCount, - command.GiftUnitValue, - ))) + strings.TrimSpace(command.PriceVersion), + )) +} +func adminCreditRequestHash(command ledger.AdminCreditAssetCommand) string { + return stableHash(fmt.Sprintf("admin_credit|%d|%s|%d|%d|%s|%s", + command.TargetUserID, + command.AssetType, + command.Amount, + command.OperatorUserID, + command.Reason, + command.EvidenceRef, + )) +} + +func stableHash(value string) string { + sum := sha256.Sum256([]byte(value)) return hex.EncodeToString(sum[:]) } -func billingReceiptID(commandID string) string { - sum := sha256.Sum256([]byte(commandID)) - // receipt_id 有 96 字符上限,使用哈希避免上游 command_id 过长导致落账失败。 - return "bill_" + hex.EncodeToString(sum[:]) +func transactionID(commandID string) string { + return "wtx_" + stableHash(commandID) +} + +func billingReceiptID(commandID string) string { + return "bill_" + stableHash(commandID) +} + +func eventID(transactionID string, eventType string, userID int64, assetType string) string { + return "wev_" + stableHash(fmt.Sprintf("%s|%s|%d|%s", transactionID, eventType, userID, assetType)) } diff --git a/services/wallet-service/internal/testutil/mysqltest/mysqltest.go b/services/wallet-service/internal/testutil/mysqltest/mysqltest.go index 60e3b9dd..b7c796ed 100644 --- a/services/wallet-service/internal/testutil/mysqltest/mysqltest.go +++ b/services/wallet-service/internal/testutil/mysqltest/mysqltest.go @@ -3,6 +3,7 @@ package mysqltest import ( "context" + "fmt" "testing" "time" @@ -10,8 +11,6 @@ import ( mysqlstorage "hyapp/services/wallet-service/internal/storage/mysql" ) -const testCurrency = "coin" - // Repository wraps the production MySQL wallet repository with test seed helpers. type Repository struct { *mysqlstorage.Repository @@ -20,6 +19,37 @@ type Repository struct { schema *mysqlschema.Schema } +// CountRows returns a simple count from a known wallet table for persistence assertions. +func (r *Repository) CountRows(table string, where string, args ...any) int { + r.t.Helper() + + query := "SELECT COUNT(*) FROM " + validateTableName(table) + if where != "" { + query += " WHERE " + where + } + var count int + if err := r.schema.DB.QueryRowContext(context.Background(), query, args...).Scan(&count); err != nil { + r.t.Fatalf("count %s rows failed: %v\nquery=%s", table, err, query) + } + return count +} + +// InsertRawGiftPrice allows tests to seed inactive or future prices without service helpers. +func (r *Repository) InsertRawGiftPrice(giftID string, priceVersion string, status string, effectiveAtMs int64) { + r.t.Helper() + + nowMs := time.Now().UnixMilli() + _, err := r.schema.DB.ExecContext(context.Background(), ` + INSERT INTO wallet_gift_prices ( + gift_id, price_version, status, coin_price, gift_point_amount, heat_value, + effective_at_ms, created_at_ms, updated_at_ms + ) VALUES (?, ?, ?, 1, 1, 1, ?, ?, ?) + `, giftID, priceVersion, status, effectiveAtMs, nowMs, nowMs) + if err != nil { + r.t.Fatalf("insert raw gift price failed: %v", err) + } +} + // NewRepository creates an isolated wallet schema and opens the production repository. func NewRepository(t testing.TB) *Repository { t.Helper() @@ -29,7 +59,7 @@ func NewRepository(t testing.TB) *Repository { InitDBPath: initDBPath(t), DatabasePrefix: "hy_wallet_test", }) - repository, err := mysqlstorage.Open(context.Background(), schema.DSN, testCurrency, 3600) + repository, err := mysqlstorage.Open(context.Background(), schema.DSN) if err != nil { t.Fatalf("open wallet mysql repository failed: %v", err) } @@ -48,24 +78,47 @@ func (r *Repository) Close() { } } -// SetBalance prepares an account row using the same currency as the test repository. +// SetBalance prepares a sender COIN account for debit tests. func (r *Repository) SetBalance(userID int64, balance int64) { r.t.Helper() nowMs := time.Now().UnixMilli() _, err := r.schema.DB.ExecContext(context.Background(), ` - INSERT INTO wallet_accounts (user_id, currency, balance, version, created_at_ms, updated_at_ms) - VALUES (?, ?, ?, 1, ?, ?) + INSERT INTO wallet_accounts (user_id, asset_type, available_amount, frozen_amount, version, created_at_ms, updated_at_ms) + VALUES (?, 'COIN', ?, 0, 1, ?, ?) ON DUPLICATE KEY UPDATE - balance = VALUES(balance), + available_amount = VALUES(available_amount), + frozen_amount = VALUES(frozen_amount), version = version + 1, updated_at_ms = VALUES(updated_at_ms) - `, userID, testCurrency, balance, nowMs, nowMs) + `, userID, balance, nowMs, nowMs) if err != nil { r.t.Fatalf("seed wallet balance failed: %v", err) } } +// SetGiftPrice overrides or inserts a gift settlement price for server-side billing tests. +func (r *Repository) SetGiftPrice(giftID string, priceVersion string, coinPrice int64, giftPointAmount int64, heatValue int64) { + r.t.Helper() + + nowMs := time.Now().UnixMilli() + _, err := r.schema.DB.ExecContext(context.Background(), ` + INSERT INTO wallet_gift_prices ( + gift_id, price_version, status, coin_price, gift_point_amount, heat_value, + effective_at_ms, created_at_ms, updated_at_ms + ) VALUES (?, ?, 'active', ?, ?, ?, 0, ?, ?) + ON DUPLICATE KEY UPDATE + status = VALUES(status), + coin_price = VALUES(coin_price), + gift_point_amount = VALUES(gift_point_amount), + heat_value = VALUES(heat_value), + updated_at_ms = VALUES(updated_at_ms) + `, giftID, priceVersion, coinPrice, giftPointAmount, heatValue, nowMs, nowMs) + if err != nil { + r.t.Fatalf("seed wallet gift price failed: %v", err) + } +} + func initDBPath(t testing.TB) string { t.Helper() @@ -74,3 +127,12 @@ func initDBPath(t testing.TB) string { "..", "..", "..", "deploy", "mysql", "initdb", "001_wallet_service.sql", ) } + +func validateTableName(table string) string { + switch table { + case "wallet_transactions", "wallet_entries", "wallet_outbox", "wallet_accounts": + return table + default: + panic(fmt.Sprintf("unsupported wallet test table %q", table)) + } +} diff --git a/services/wallet-service/internal/transport/grpc/server.go b/services/wallet-service/internal/transport/grpc/server.go index 7d23e1f9..8b8dd2fb 100644 --- a/services/wallet-service/internal/transport/grpc/server.go +++ b/services/wallet-service/internal/transport/grpc/server.go @@ -24,13 +24,13 @@ func NewServer(svc *walletservice.Service) *Server { // DebitGift 只负责 protobuf 转换和错误映射,扣费规则在 service/repository。 func (s *Server) DebitGift(ctx context.Context, req *walletv1.DebitGiftRequest) (*walletv1.DebitGiftResponse, error) { receipt, err := s.svc.DebitGift(ctx, ledger.DebitGiftCommand{ - CommandID: req.GetCommandId(), - RoomID: req.GetRoomId(), - SenderUserID: req.GetSenderUserId(), - TargetUserID: req.GetTargetUserId(), - GiftID: req.GetGiftId(), - GiftCount: req.GetGiftCount(), - GiftUnitValue: req.GetGiftUnitValue(), + CommandID: req.GetCommandId(), + RoomID: req.GetRoomId(), + SenderUserID: req.GetSenderUserId(), + TargetUserID: req.GetTargetUserId(), + GiftID: req.GetGiftId(), + GiftCount: req.GetGiftCount(), + PriceVersion: req.GetPriceVersion(), }) if err != nil { return nil, xerr.ToGRPCError(err) @@ -38,7 +38,57 @@ func (s *Server) DebitGift(ctx context.Context, req *walletv1.DebitGiftRequest) return &walletv1.DebitGiftResponse{ BillingReceiptId: receipt.BillingReceiptID, - TotalGiftValue: receipt.TotalGiftValue, BalanceAfter: receipt.BalanceAfter, + TransactionId: receipt.TransactionID, + CoinSpent: receipt.CoinSpent, + GiftPointAdded: receipt.GiftPointAdded, + HeatValue: receipt.HeatValue, + PriceVersion: receipt.PriceVersion, + }, nil +} + +// GetBalances 返回用户多资产余额投影。 +func (s *Server) GetBalances(ctx context.Context, req *walletv1.GetBalancesRequest) (*walletv1.GetBalancesResponse, error) { + balances, err := s.svc.GetBalances(ctx, req.GetUserId(), req.GetAssetTypes()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + + response := &walletv1.GetBalancesResponse{Balances: make([]*walletv1.AssetBalance, 0, len(balances))} + for _, balance := range balances { + response.Balances = append(response.Balances, &walletv1.AssetBalance{ + AssetType: balance.AssetType, + AvailableAmount: balance.AvailableAmount, + FrozenAmount: balance.FrozenAmount, + Version: balance.Version, + }) + } + + return response, nil +} + +// AdminCreditAsset 处理后台手动入账,公网权限和审计入口由 gateway/admin 层控制。 +func (s *Server) AdminCreditAsset(ctx context.Context, req *walletv1.AdminCreditAssetRequest) (*walletv1.AdminCreditAssetResponse, error) { + balance, transactionID, err := s.svc.AdminCreditAsset(ctx, ledger.AdminCreditAssetCommand{ + CommandID: req.GetCommandId(), + TargetUserID: req.GetTargetUserId(), + AssetType: req.GetAssetType(), + Amount: req.GetAmount(), + OperatorUserID: req.GetOperatorUserId(), + Reason: req.GetReason(), + EvidenceRef: req.GetEvidenceRef(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + + return &walletv1.AdminCreditAssetResponse{ + TransactionId: transactionID, + Balance: &walletv1.AssetBalance{ + AssetType: balance.AssetType, + AvailableAmount: balance.AvailableAmount, + FrozenAmount: balance.FrozenAmount, + Version: balance.Version, + }, }, nil }