feat: switch gateway to common grpc proto

This commit is contained in:
ZuoZuo 2026-04-04 15:26:26 +08:00
parent 5be5435bb0
commit 92edaabff7
13 changed files with 263 additions and 1178 deletions

View File

@ -1,645 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.34.2
// protoc v5.29.2
// source: gateway.proto
package gatewaypb
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type LoginRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
LoginType string `protobuf:"bytes,1,opt,name=login_type,json=loginType,proto3" json:"login_type,omitempty"`
Account string `protobuf:"bytes,2,opt,name=account,proto3" json:"account,omitempty"`
Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"`
CountryCode string `protobuf:"bytes,4,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"`
VerifyCode string `protobuf:"bytes,5,opt,name=verify_code,json=verifyCode,proto3" json:"verify_code,omitempty"`
Provider string `protobuf:"bytes,6,opt,name=provider,proto3" json:"provider,omitempty"`
ProviderToken string `protobuf:"bytes,7,opt,name=provider_token,json=providerToken,proto3" json:"provider_token,omitempty"`
DeviceId string `protobuf:"bytes,8,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"`
Platform string `protobuf:"bytes,9,opt,name=platform,proto3" json:"platform,omitempty"`
AppVersion string `protobuf:"bytes,10,opt,name=app_version,json=appVersion,proto3" json:"app_version,omitempty"`
}
func (x *LoginRequest) Reset() {
*x = LoginRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_gateway_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *LoginRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LoginRequest) ProtoMessage() {}
func (x *LoginRequest) ProtoReflect() protoreflect.Message {
mi := &file_gateway_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead.
func (*LoginRequest) Descriptor() ([]byte, []int) {
return file_gateway_proto_rawDescGZIP(), []int{0}
}
func (x *LoginRequest) GetLoginType() string {
if x != nil {
return x.LoginType
}
return ""
}
func (x *LoginRequest) GetAccount() string {
if x != nil {
return x.Account
}
return ""
}
func (x *LoginRequest) GetPassword() string {
if x != nil {
return x.Password
}
return ""
}
func (x *LoginRequest) GetCountryCode() string {
if x != nil {
return x.CountryCode
}
return ""
}
func (x *LoginRequest) GetVerifyCode() string {
if x != nil {
return x.VerifyCode
}
return ""
}
func (x *LoginRequest) GetProvider() string {
if x != nil {
return x.Provider
}
return ""
}
func (x *LoginRequest) GetProviderToken() string {
if x != nil {
return x.ProviderToken
}
return ""
}
func (x *LoginRequest) GetDeviceId() string {
if x != nil {
return x.DeviceId
}
return ""
}
func (x *LoginRequest) GetPlatform() string {
if x != nil {
return x.Platform
}
return ""
}
func (x *LoginRequest) GetAppVersion() string {
if x != nil {
return x.AppVersion
}
return ""
}
type UserProfile struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
Nickname string `protobuf:"bytes,2,opt,name=nickname,proto3" json:"nickname,omitempty"`
AvatarUrl string `protobuf:"bytes,3,opt,name=avatar_url,json=avatarUrl,proto3" json:"avatar_url,omitempty"`
}
func (x *UserProfile) Reset() {
*x = UserProfile{}
if protoimpl.UnsafeEnabled {
mi := &file_gateway_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *UserProfile) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UserProfile) ProtoMessage() {}
func (x *UserProfile) ProtoReflect() protoreflect.Message {
mi := &file_gateway_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UserProfile.ProtoReflect.Descriptor instead.
func (*UserProfile) Descriptor() ([]byte, []int) {
return file_gateway_proto_rawDescGZIP(), []int{1}
}
func (x *UserProfile) GetUserId() string {
if x != nil {
return x.UserId
}
return ""
}
func (x *UserProfile) GetNickname() string {
if x != nil {
return x.Nickname
}
return ""
}
func (x *UserProfile) GetAvatarUrl() string {
if x != nil {
return x.AvatarUrl
}
return ""
}
type LoginResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
AccessToken string `protobuf:"bytes,2,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"`
RefreshToken string `protobuf:"bytes,3,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"`
ExpiresIn int64 `protobuf:"varint,4,opt,name=expires_in,json=expiresIn,proto3" json:"expires_in,omitempty"`
IsNewUser bool `protobuf:"varint,5,opt,name=is_new_user,json=isNewUser,proto3" json:"is_new_user,omitempty"`
Profile *UserProfile `protobuf:"bytes,6,opt,name=profile,proto3" json:"profile,omitempty"`
}
func (x *LoginResponse) Reset() {
*x = LoginResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_gateway_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *LoginResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*LoginResponse) ProtoMessage() {}
func (x *LoginResponse) ProtoReflect() protoreflect.Message {
mi := &file_gateway_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use LoginResponse.ProtoReflect.Descriptor instead.
func (*LoginResponse) Descriptor() ([]byte, []int) {
return file_gateway_proto_rawDescGZIP(), []int{2}
}
func (x *LoginResponse) GetUserId() string {
if x != nil {
return x.UserId
}
return ""
}
func (x *LoginResponse) GetAccessToken() string {
if x != nil {
return x.AccessToken
}
return ""
}
func (x *LoginResponse) GetRefreshToken() string {
if x != nil {
return x.RefreshToken
}
return ""
}
func (x *LoginResponse) GetExpiresIn() int64 {
if x != nil {
return x.ExpiresIn
}
return 0
}
func (x *LoginResponse) GetIsNewUser() bool {
if x != nil {
return x.IsNewUser
}
return false
}
func (x *LoginResponse) GetProfile() *UserProfile {
if x != nil {
return x.Profile
}
return nil
}
type QueryOrderRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
OrderNo string `protobuf:"bytes,1,opt,name=order_no,json=orderNo,proto3" json:"order_no,omitempty"`
}
func (x *QueryOrderRequest) Reset() {
*x = QueryOrderRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_gateway_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *QueryOrderRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*QueryOrderRequest) ProtoMessage() {}
func (x *QueryOrderRequest) ProtoReflect() protoreflect.Message {
mi := &file_gateway_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use QueryOrderRequest.ProtoReflect.Descriptor instead.
func (*QueryOrderRequest) Descriptor() ([]byte, []int) {
return file_gateway_proto_rawDescGZIP(), []int{3}
}
func (x *QueryOrderRequest) GetOrderNo() string {
if x != nil {
return x.OrderNo
}
return ""
}
type QueryOrderResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
OrderNo string `protobuf:"bytes,1,opt,name=order_no,json=orderNo,proto3" json:"order_no,omitempty"`
UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"`
Amount string `protobuf:"bytes,4,opt,name=amount,proto3" json:"amount,omitempty"`
Currency string `protobuf:"bytes,5,opt,name=currency,proto3" json:"currency,omitempty"`
Subject string `protobuf:"bytes,6,opt,name=subject,proto3" json:"subject,omitempty"`
PayMethod string `protobuf:"bytes,7,opt,name=pay_method,json=payMethod,proto3" json:"pay_method,omitempty"`
PaidAt string `protobuf:"bytes,8,opt,name=paid_at,json=paidAt,proto3" json:"paid_at,omitempty"`
}
func (x *QueryOrderResponse) Reset() {
*x = QueryOrderResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_gateway_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *QueryOrderResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*QueryOrderResponse) ProtoMessage() {}
func (x *QueryOrderResponse) ProtoReflect() protoreflect.Message {
mi := &file_gateway_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use QueryOrderResponse.ProtoReflect.Descriptor instead.
func (*QueryOrderResponse) Descriptor() ([]byte, []int) {
return file_gateway_proto_rawDescGZIP(), []int{4}
}
func (x *QueryOrderResponse) GetOrderNo() string {
if x != nil {
return x.OrderNo
}
return ""
}
func (x *QueryOrderResponse) GetUserId() string {
if x != nil {
return x.UserId
}
return ""
}
func (x *QueryOrderResponse) GetStatus() string {
if x != nil {
return x.Status
}
return ""
}
func (x *QueryOrderResponse) GetAmount() string {
if x != nil {
return x.Amount
}
return ""
}
func (x *QueryOrderResponse) GetCurrency() string {
if x != nil {
return x.Currency
}
return ""
}
func (x *QueryOrderResponse) GetSubject() string {
if x != nil {
return x.Subject
}
return ""
}
func (x *QueryOrderResponse) GetPayMethod() string {
if x != nil {
return x.PayMethod
}
return ""
}
func (x *QueryOrderResponse) GetPaidAt() string {
if x != nil {
return x.PaidAt
}
return ""
}
var File_gateway_proto protoreflect.FileDescriptor
var file_gateway_proto_rawDesc = []byte{
0x0a, 0x0d, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
0x19, 0x63, 0x68, 0x61, 0x74, 0x61, 0x70, 0x70, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e,
0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x76, 0x31, 0x22, 0xc4, 0x02, 0x0a, 0x0c, 0x4c,
0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6c,
0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x09, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x63,
0x6f, 0x75, 0x6e, 0x74, 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,
0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65,
0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43,
0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x63, 0x6f,
0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79,
0x43, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72,
0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72,
0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x74, 0x6f, 0x6b,
0x65, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64,
0x65, 0x72, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x76, 0x69, 0x63,
0x65, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x65, 0x76, 0x69,
0x63, 0x65, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d,
0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d,
0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x70, 0x70, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18,
0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x70, 0x70, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
0x6e, 0x22, 0x61, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65,
0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x69, 0x63,
0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x69, 0x63,
0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x76, 0x61, 0x74, 0x61, 0x72, 0x5f,
0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x76, 0x61, 0x74, 0x61,
0x72, 0x55, 0x72, 0x6c, 0x22, 0xf1, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12,
0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18,
0x02, 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, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65,
0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72,
0x65, 0x73, 0x5f, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x65, 0x78, 0x70,
0x69, 0x72, 0x65, 0x73, 0x49, 0x6e, 0x12, 0x1e, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x6e, 0x65, 0x77,
0x5f, 0x75, 0x73, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x4e,
0x65, 0x77, 0x55, 0x73, 0x65, 0x72, 0x12, 0x40, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c,
0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x63, 0x68, 0x61, 0x74, 0x61, 0x70,
0x70, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79,
0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52,
0x07, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x2e, 0x0a, 0x11, 0x51, 0x75, 0x65, 0x72,
0x79, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a,
0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x4e, 0x6f, 0x22, 0xe6, 0x01, 0x0a, 0x12, 0x51, 0x75, 0x65,
0x72, 0x79, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x19, 0x0a, 0x08, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x5f, 0x6e, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x4e, 0x6f, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73,
0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 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, 0x16, 0x0a, 0x06, 0x61,
0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f,
0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18,
0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12,
0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x79,
0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70,
0x61, 0x79, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x61, 0x69, 0x64,
0x5f, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x61, 0x69, 0x64, 0x41,
0x74, 0x32, 0x69, 0x0a, 0x0b, 0x43, 0x68, 0x61, 0x74, 0x41, 0x70, 0x70, 0x55, 0x73, 0x65, 0x72,
0x12, 0x5a, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x27, 0x2e, 0x63, 0x68, 0x61, 0x74,
0x61, 0x70, 0x70, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77,
0x61, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x28, 0x2e, 0x63, 0x68, 0x61, 0x74, 0x61, 0x70, 0x70, 0x67, 0x61, 0x74, 0x65,
0x77, 0x61, 0x79, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x76, 0x31, 0x2e, 0x4c,
0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x77, 0x0a, 0x0a,
0x43, 0x68, 0x61, 0x74, 0x41, 0x70, 0x70, 0x50, 0x61, 0x79, 0x12, 0x69, 0x0a, 0x0a, 0x51, 0x75,
0x65, 0x72, 0x79, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x2c, 0x2e, 0x63, 0x68, 0x61, 0x74, 0x61,
0x70, 0x70, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61,
0x79, 0x2e, 0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x63, 0x68, 0x61, 0x74, 0x61, 0x70, 0x70,
0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e,
0x76, 0x31, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x24, 0x5a, 0x22, 0x63, 0x68, 0x61, 0x74, 0x61, 0x70, 0x70,
0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x3b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
}
var (
file_gateway_proto_rawDescOnce sync.Once
file_gateway_proto_rawDescData = file_gateway_proto_rawDesc
)
func file_gateway_proto_rawDescGZIP() []byte {
file_gateway_proto_rawDescOnce.Do(func() {
file_gateway_proto_rawDescData = protoimpl.X.CompressGZIP(file_gateway_proto_rawDescData)
})
return file_gateway_proto_rawDescData
}
var file_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_gateway_proto_goTypes = []any{
(*LoginRequest)(nil), // 0: chatappgateway.gateway.v1.LoginRequest
(*UserProfile)(nil), // 1: chatappgateway.gateway.v1.UserProfile
(*LoginResponse)(nil), // 2: chatappgateway.gateway.v1.LoginResponse
(*QueryOrderRequest)(nil), // 3: chatappgateway.gateway.v1.QueryOrderRequest
(*QueryOrderResponse)(nil), // 4: chatappgateway.gateway.v1.QueryOrderResponse
}
var file_gateway_proto_depIdxs = []int32{
1, // 0: chatappgateway.gateway.v1.LoginResponse.profile:type_name -> chatappgateway.gateway.v1.UserProfile
0, // 1: chatappgateway.gateway.v1.ChatAppUser.Login:input_type -> chatappgateway.gateway.v1.LoginRequest
3, // 2: chatappgateway.gateway.v1.ChatAppPay.QueryOrder:input_type -> chatappgateway.gateway.v1.QueryOrderRequest
2, // 3: chatappgateway.gateway.v1.ChatAppUser.Login:output_type -> chatappgateway.gateway.v1.LoginResponse
4, // 4: chatappgateway.gateway.v1.ChatAppPay.QueryOrder:output_type -> chatappgateway.gateway.v1.QueryOrderResponse
3, // [3:5] is the sub-list for method output_type
1, // [1:3] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_gateway_proto_init() }
func file_gateway_proto_init() {
if File_gateway_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_gateway_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*LoginRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_gateway_proto_msgTypes[1].Exporter = func(v any, i int) any {
switch v := v.(*UserProfile); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_gateway_proto_msgTypes[2].Exporter = func(v any, i int) any {
switch v := v.(*LoginResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_gateway_proto_msgTypes[3].Exporter = func(v any, i int) any {
switch v := v.(*QueryOrderRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_gateway_proto_msgTypes[4].Exporter = func(v any, i int) any {
switch v := v.(*QueryOrderResponse); 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_gateway_proto_rawDesc,
NumEnums: 0,
NumMessages: 5,
NumExtensions: 0,
NumServices: 2,
},
GoTypes: file_gateway_proto_goTypes,
DependencyIndexes: file_gateway_proto_depIdxs,
MessageInfos: file_gateway_proto_msgTypes,
}.Build()
File_gateway_proto = out.File
file_gateway_proto_rawDesc = nil
file_gateway_proto_goTypes = nil
file_gateway_proto_depIdxs = nil
}

View File

@ -1,56 +0,0 @@
syntax = "proto3";
package chatappgateway.gateway.v1;
option go_package = "chatappgateway/api/proto;gatewaypb";
service ChatAppUser {
rpc Login(LoginRequest) returns (LoginResponse);
}
service ChatAppPay {
rpc QueryOrder(QueryOrderRequest) returns (QueryOrderResponse);
}
message LoginRequest {
string login_type = 1;
string account = 2;
string password = 3;
string country_code = 4;
string verify_code = 5;
string provider = 6;
string provider_token = 7;
string device_id = 8;
string platform = 9;
string app_version = 10;
}
message UserProfile {
string user_id = 1;
string nickname = 2;
string avatar_url = 3;
}
message LoginResponse {
string user_id = 1;
string access_token = 2;
string refresh_token = 3;
int64 expires_in = 4;
bool is_new_user = 5;
UserProfile profile = 6;
}
message QueryOrderRequest {
string order_no = 1;
}
message QueryOrderResponse {
string order_no = 1;
string user_id = 2;
string status = 3;
string amount = 4;
string currency = 5;
string subject = 6;
string pay_method = 7;
string paid_at = 8;
}

View File

@ -1,223 +0,0 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.2
// source: gateway.proto
package gatewaypb
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
ChatAppUser_Login_FullMethodName = "/chatappgateway.gateway.v1.ChatAppUser/Login"
)
// ChatAppUserClient is the client API for ChatAppUser 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.
type ChatAppUserClient interface {
Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error)
}
type chatAppUserClient struct {
cc grpc.ClientConnInterface
}
func NewChatAppUserClient(cc grpc.ClientConnInterface) ChatAppUserClient {
return &chatAppUserClient{cc}
}
func (c *chatAppUserClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(LoginResponse)
err := c.cc.Invoke(ctx, ChatAppUser_Login_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// ChatAppUserServer is the server API for ChatAppUser service.
// All implementations must embed UnimplementedChatAppUserServer
// for forward compatibility.
type ChatAppUserServer interface {
Login(context.Context, *LoginRequest) (*LoginResponse, error)
mustEmbedUnimplementedChatAppUserServer()
}
// UnimplementedChatAppUserServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedChatAppUserServer struct{}
func (UnimplementedChatAppUserServer) Login(context.Context, *LoginRequest) (*LoginResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Login not implemented")
}
func (UnimplementedChatAppUserServer) mustEmbedUnimplementedChatAppUserServer() {}
func (UnimplementedChatAppUserServer) testEmbeddedByValue() {}
// UnsafeChatAppUserServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ChatAppUserServer will
// result in compilation errors.
type UnsafeChatAppUserServer interface {
mustEmbedUnimplementedChatAppUserServer()
}
func RegisterChatAppUserServer(s grpc.ServiceRegistrar, srv ChatAppUserServer) {
// If the following call pancis, it indicates UnimplementedChatAppUserServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&ChatAppUser_ServiceDesc, srv)
}
func _ChatAppUser_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LoginRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ChatAppUserServer).Login(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ChatAppUser_Login_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ChatAppUserServer).Login(ctx, req.(*LoginRequest))
}
return interceptor(ctx, in, info, handler)
}
// ChatAppUser_ServiceDesc is the grpc.ServiceDesc for ChatAppUser service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var ChatAppUser_ServiceDesc = grpc.ServiceDesc{
ServiceName: "chatappgateway.gateway.v1.ChatAppUser",
HandlerType: (*ChatAppUserServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Login",
Handler: _ChatAppUser_Login_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "gateway.proto",
}
const (
ChatAppPay_QueryOrder_FullMethodName = "/chatappgateway.gateway.v1.ChatAppPay/QueryOrder"
)
// ChatAppPayClient is the client API for ChatAppPay 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.
type ChatAppPayClient interface {
QueryOrder(ctx context.Context, in *QueryOrderRequest, opts ...grpc.CallOption) (*QueryOrderResponse, error)
}
type chatAppPayClient struct {
cc grpc.ClientConnInterface
}
func NewChatAppPayClient(cc grpc.ClientConnInterface) ChatAppPayClient {
return &chatAppPayClient{cc}
}
func (c *chatAppPayClient) QueryOrder(ctx context.Context, in *QueryOrderRequest, opts ...grpc.CallOption) (*QueryOrderResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(QueryOrderResponse)
err := c.cc.Invoke(ctx, ChatAppPay_QueryOrder_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// ChatAppPayServer is the server API for ChatAppPay service.
// All implementations must embed UnimplementedChatAppPayServer
// for forward compatibility.
type ChatAppPayServer interface {
QueryOrder(context.Context, *QueryOrderRequest) (*QueryOrderResponse, error)
mustEmbedUnimplementedChatAppPayServer()
}
// UnimplementedChatAppPayServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedChatAppPayServer struct{}
func (UnimplementedChatAppPayServer) QueryOrder(context.Context, *QueryOrderRequest) (*QueryOrderResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method QueryOrder not implemented")
}
func (UnimplementedChatAppPayServer) mustEmbedUnimplementedChatAppPayServer() {}
func (UnimplementedChatAppPayServer) testEmbeddedByValue() {}
// UnsafeChatAppPayServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ChatAppPayServer will
// result in compilation errors.
type UnsafeChatAppPayServer interface {
mustEmbedUnimplementedChatAppPayServer()
}
func RegisterChatAppPayServer(s grpc.ServiceRegistrar, srv ChatAppPayServer) {
// If the following call pancis, it indicates UnimplementedChatAppPayServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&ChatAppPay_ServiceDesc, srv)
}
func _ChatAppPay_QueryOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(QueryOrderRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ChatAppPayServer).QueryOrder(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ChatAppPay_QueryOrder_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ChatAppPayServer).QueryOrder(ctx, req.(*QueryOrderRequest))
}
return interceptor(ctx, in, info, handler)
}
// ChatAppPay_ServiceDesc is the grpc.ServiceDesc for ChatAppPay service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var ChatAppPay_ServiceDesc = grpc.ServiceDesc{
ServiceName: "chatappgateway.gateway.v1.ChatAppPay",
HandlerType: (*ChatAppPayServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "QueryOrder",
Handler: _ChatAppPay_QueryOrder_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "gateway.proto",
}

View File

@ -6,9 +6,9 @@
- `ChatAppGateway` 不是纯反向代理网关,而是 BFF / 聚合编排服务。 - `ChatAppGateway` 不是纯反向代理网关,而是 BFF / 聚合编排服务。
- 对客户端暴露 HTTP JSON API。 - 对客户端暴露 HTTP JSON API。
- 对内通过 gRPC 调用: - 对内通过 `Common` 里的 proto 调用:
- `ChatAppUser.Login` - `ChatAppUserService.Register`
- `ChatAppPay.QueryOrder` - `ChatAppPayService.Pay`
- 自身不实现用户域认证规则,也不实现支付域核心规则,只做参数校验、字段归一化、协议适配、统一错误响应、日志与 trace。 - 自身不实现用户域认证规则,也不实现支付域核心规则,只做参数校验、字段归一化、协议适配、统一错误响应、日志与 trace。
## 目标能力 ## 目标能力
@ -17,8 +17,8 @@
1. `GET /health` 1. `GET /health`
2. `GET /ready` 2. `GET /ready`
3. `POST /api/v1/auth/login` 3. `POST /api/v1/users/register`
4. `GET /api/v1/pay/orders/{order_no}` 4. `POST /api/v1/pay`
## 配置要求 ## 配置要求
@ -51,7 +51,7 @@ grpc:
## 登录接口要求 ## 登录接口要求
`POST /api/v1/auth/login` `POST /api/v1/users/register`
请求 DTO 至少包含: 请求 DTO 至少包含:
@ -77,16 +77,14 @@ grpc:
网关职责: 网关职责:
- 只做模式校验和字段归一化 - 做基础参数校验和字段归一化
- 将请求映射到 `ChatAppUser.Login` - 将请求映射到 `ChatAppUserService.Register`
- 接收用户服务返回后再统一包装给客户端 - 接收用户服务返回后再统一包装给客户端
`ChatAppUser.Login` 返回至少包含: `ChatAppUserService.Register` 返回至少包含:
- `user_id` - `user_id`
- `access_token` - `access_token`
- `refresh_token`
- `expires_in`
- `is_new_user` - `is_new_user`
- `profile` - `profile`
@ -98,24 +96,25 @@ grpc:
## 支付查询接口要求 ## 支付查询接口要求
`GET /api/v1/pay/orders/{order_no}` `POST /api/v1/pay`
网关职责: 网关职责:
- 校验 `order_no` 非空 - 校验 `order_no` 非空
- 调用 `ChatAppPay.QueryOrder` - 调用 `ChatAppPayService.Pay`
- 把支付服务返回的订单结果转成统一 HTTP JSON 响应 - 把支付服务返回的订单结果转成统一 HTTP JSON 响应
返回字段至少包含: 返回字段至少包含:
- `payment_id`
- `order_no` - `order_no`
- `user_id` - `user_id`
- `status` - `status`
- `amount` - `amount`
- `currency` - `currency`
- `subject`
- `pay_method` - `pay_method`
- `paid_at` - `subject`
- `created_at`
## 健康检查要求 ## 健康检查要求
@ -148,7 +147,7 @@ grpc:
## 错误码和状态码要求 ## 错误码和状态码要求
- 参数错误:`400` - 参数错误:`400`
- 登录失败或凭证错误:`401` - 注册失败或凭证错误:`401`
- 订单不存在:`404` - 订单不存在:`404`
- 下游 gRPC 超时:`504` - 下游 gRPC 超时:`504`
- 下游 gRPC 不可用或内部异常:`502` - 下游 gRPC 不可用或内部异常:`502`

7
go.mod
View File

@ -3,14 +3,17 @@ module chatappgateway
go 1.23.1 go 1.23.1
require ( require (
gitea.haiyihy.com/hy/chatappcommon v0.0.0
google.golang.org/grpc v1.67.3 google.golang.org/grpc v1.67.3
google.golang.org/protobuf v1.36.11
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
golang.org/x/net v0.35.0 // indirect golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.30.0 // indirect golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect golang.org/x/text v0.22.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
google.golang.org/protobuf v1.36.11 // indirect
) )
replace gitea.haiyihy.com/hy/chatappcommon => ../Common

4
go.sum
View File

@ -1,7 +1,7 @@
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=

View File

@ -6,13 +6,13 @@ import (
"io" "io"
"log/slog" "log/slog"
gatewaypb "chatappgateway/api/proto"
"chatappgateway/internal/config" "chatappgateway/internal/config"
"chatappgateway/internal/integration/paygrpc" "chatappgateway/internal/integration/paygrpc"
"chatappgateway/internal/integration/usergrpc" "chatappgateway/internal/integration/usergrpc"
"chatappgateway/internal/service/auth" "chatappgateway/internal/service/auth"
"chatappgateway/internal/service/pay" "chatappgateway/internal/service/pay"
httpserver "chatappgateway/internal/transport/http" httpserver "chatappgateway/internal/transport/http"
commonpb "gitea.haiyihy.com/hy/chatappcommon/proto"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
@ -37,8 +37,8 @@ func New(ctx context.Context, cfg config.Config, logger *slog.Logger) (*Applicat
return nil, fmt.Errorf("dial ChatAppPay: %w", err) return nil, fmt.Errorf("dial ChatAppPay: %w", err)
} }
userClient := usergrpc.New(gatewaypb.NewChatAppUserClient(userConn), cfg.GRPC.User.Timeout) userClient := usergrpc.New(commonpb.NewChatAppUserServiceClient(userConn), cfg.GRPC.User.Timeout)
payClient := paygrpc.New(gatewaypb.NewChatAppPayClient(payConn), cfg.GRPC.Pay.Timeout) payClient := paygrpc.New(commonpb.NewChatAppPayServiceClient(payConn), cfg.GRPC.Pay.Timeout)
authService := auth.New(userClient) authService := auth.New(userClient)
payService := pay.New(payClient) payService := pay.New(payClient)

View File

@ -4,27 +4,27 @@ import (
"context" "context"
"time" "time"
gatewaypb "chatappgateway/api/proto" commonpb "gitea.haiyihy.com/hy/chatappcommon/proto"
) )
// Client 封装支付服务 gRPC client并统一超时控制。 // Client 封装支付服务 gRPC client并统一超时控制。
type Client struct { type Client struct {
timeout time.Duration timeout time.Duration
client gatewaypb.ChatAppPayClient client commonpb.ChatAppPayServiceClient
} }
// New 根据底层 gRPC client 构造支付服务调用器。 // New 根据底层 gRPC client 构造支付服务调用器。
func New(client gatewaypb.ChatAppPayClient, timeout time.Duration) *Client { func New(client commonpb.ChatAppPayServiceClient, timeout time.Duration) *Client {
return &Client{ return &Client{
timeout: timeout, timeout: timeout,
client: client, client: client,
} }
} }
// QueryOrder 调用支付服务最小订单查询接口。 // Pay 调用支付服务最小支付接口。
func (c *Client) QueryOrder(ctx context.Context, request *gatewaypb.QueryOrderRequest) (*gatewaypb.QueryOrderResponse, error) { func (c *Client) Pay(ctx context.Context, request *commonpb.PayRequest) (*commonpb.PayResponse, error) {
callCtx, cancel := context.WithTimeout(ctx, c.timeout) callCtx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel() defer cancel()
return c.client.QueryOrder(callCtx, request) return c.client.Pay(callCtx, request)
} }

View File

@ -4,27 +4,27 @@ import (
"context" "context"
"time" "time"
gatewaypb "chatappgateway/api/proto" commonpb "gitea.haiyihy.com/hy/chatappcommon/proto"
) )
// Client 封装用户服务 gRPC client并统一超时控制。 // Client 封装用户服务 gRPC client并统一超时控制。
type Client struct { type Client struct {
timeout time.Duration timeout time.Duration
client gatewaypb.ChatAppUserClient client commonpb.ChatAppUserServiceClient
} }
// New 根据底层 gRPC client 构造用户服务调用器。 // New 根据底层 gRPC client 构造用户服务调用器。
func New(client gatewaypb.ChatAppUserClient, timeout time.Duration) *Client { func New(client commonpb.ChatAppUserServiceClient, timeout time.Duration) *Client {
return &Client{ return &Client{
timeout: timeout, timeout: timeout,
client: client, client: client,
} }
} }
// Login 调用用户服务登录接口。 // Register 调用用户服务注册接口。
func (c *Client) Login(ctx context.Context, request *gatewaypb.LoginRequest) (*gatewaypb.LoginResponse, error) { func (c *Client) Register(ctx context.Context, request *commonpb.RegisterRequest) (*commonpb.RegisterResponse, error) {
callCtx, cancel := context.WithTimeout(ctx, c.timeout) callCtx, cancel := context.WithTimeout(ctx, c.timeout)
defer cancel() defer cancel()
return c.client.Login(callCtx, request) return c.client.Register(callCtx, request)
} }

View File

@ -4,103 +4,76 @@ import (
"context" "context"
"strings" "strings"
gatewaypb "chatappgateway/api/proto"
"chatappgateway/internal/apperr" "chatappgateway/internal/apperr"
commonpb "gitea.haiyihy.com/hy/chatappcommon/proto"
) )
// Client 定义用户服务 gRPC 客户端能力。 // Client 定义用户服务 gRPC 客户端能力。
type Client interface { type Client interface {
Login(ctx context.Context, request *gatewaypb.LoginRequest) (*gatewaypb.LoginResponse, error) Register(ctx context.Context, request *commonpb.RegisterRequest) (*commonpb.RegisterResponse, error)
} }
// Service 负责登录参数校验、字段归一化和下游调用。 // Service 负责注册参数校验、字段归一化和下游调用。
type Service struct { type Service struct {
client Client client Client
} }
// LoginRequest 描述 HTTP 层的登录入参。 // RegisterRequest 描述 HTTP 层的注册入参。
type LoginRequest struct { type RegisterRequest struct {
LoginType string `json:"login_type"` Account string `json:"account"`
Account string `json:"account"` Password string `json:"password"`
Password string `json:"password"` CountryCode string `json:"country_code"`
CountryCode string `json:"country_code"` VerifyCode string `json:"verify_code"`
VerifyCode string `json:"verify_code"` Nickname string `json:"nickname"`
Provider string `json:"provider"` DeviceID string `json:"device_id"`
ProviderToken string `json:"provider_token"` Platform string `json:"platform"`
DeviceID string `json:"device_id"` AppVersion string `json:"app_version"`
Platform string `json:"platform"`
AppVersion string `json:"app_version"`
} }
// New 创建登录服务。 // New 创建注册服务。
func New(client Client) *Service { func New(client Client) *Service {
return &Service{client: client} return &Service{client: client}
} }
// Login 校验客户端请求并转成 gRPC 请求。 // Register 校验客户端请求并转成 gRPC 请求。
func (s *Service) Login(ctx context.Context, request LoginRequest) (*gatewaypb.LoginResponse, error) { func (s *Service) Register(ctx context.Context, request RegisterRequest) (*commonpb.RegisterResponse, error) {
normalized, err := normalize(request) normalized, err := normalize(request)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return s.client.Login(ctx, &gatewaypb.LoginRequest{ return s.client.Register(ctx, &commonpb.RegisterRequest{
LoginType: normalized.LoginType, Account: normalized.Account,
Account: normalized.Account, Password: normalized.Password,
Password: normalized.Password, CountryCode: normalized.CountryCode,
CountryCode: normalized.CountryCode, VerifyCode: normalized.VerifyCode,
VerifyCode: normalized.VerifyCode, Nickname: normalized.Nickname,
Provider: normalized.Provider, DeviceId: normalized.DeviceID,
ProviderToken: normalized.ProviderToken, Platform: normalized.Platform,
DeviceId: normalized.DeviceID, AppVersion: normalized.AppVersion,
Platform: normalized.Platform,
AppVersion: normalized.AppVersion,
}) })
} }
func normalize(request LoginRequest) (LoginRequest, error) { func normalize(request RegisterRequest) (RegisterRequest, error) {
normalized := LoginRequest{ normalized := RegisterRequest{
LoginType: strings.ToLower(strings.TrimSpace(request.LoginType)), Account: strings.TrimSpace(request.Account),
Account: strings.TrimSpace(request.Account), Password: strings.TrimSpace(request.Password),
Password: request.Password, CountryCode: strings.TrimSpace(request.CountryCode),
CountryCode: strings.TrimSpace(request.CountryCode), VerifyCode: strings.TrimSpace(request.VerifyCode),
VerifyCode: strings.TrimSpace(request.VerifyCode), Nickname: strings.TrimSpace(request.Nickname),
Provider: strings.ToLower(strings.TrimSpace(request.Provider)), DeviceID: strings.TrimSpace(request.DeviceID),
ProviderToken: strings.TrimSpace(request.ProviderToken), Platform: strings.TrimSpace(request.Platform),
DeviceID: strings.TrimSpace(request.DeviceID), AppVersion: strings.TrimSpace(request.AppVersion),
Platform: strings.TrimSpace(request.Platform),
AppVersion: strings.TrimSpace(request.AppVersion),
} }
switch normalized.LoginType { if normalized.Account == "" {
case "": return RegisterRequest{}, apperr.New(400, "bad_request", "account is required")
return LoginRequest{}, apperr.New(400, "bad_request", "login_type is required") }
case "password": if normalized.Password == "" {
if normalized.Account == "" { return RegisterRequest{}, apperr.New(400, "bad_request", "password is required")
return LoginRequest{}, apperr.New(400, "bad_request", "account is required") }
} if normalized.Nickname == "" {
if strings.TrimSpace(normalized.Password) == "" { normalized.Nickname = normalized.Account
return LoginRequest{}, apperr.New(400, "bad_request", "password is required")
}
case "sms_code":
if normalized.CountryCode == "" {
return LoginRequest{}, apperr.New(400, "bad_request", "country_code is required")
}
if normalized.Account == "" {
return LoginRequest{}, apperr.New(400, "bad_request", "account is required")
}
if normalized.VerifyCode == "" {
return LoginRequest{}, apperr.New(400, "bad_request", "verify_code is required")
}
case "oauth":
if normalized.Provider == "" {
return LoginRequest{}, apperr.New(400, "bad_request", "provider is required")
}
if normalized.ProviderToken == "" {
return LoginRequest{}, apperr.New(400, "bad_request", "provider_token is required")
}
default:
return LoginRequest{}, apperr.New(400, "bad_request", "login_type must be one of password, sms_code, oauth")
} }
return normalized, nil return normalized, nil

View File

@ -4,16 +4,26 @@ import (
"context" "context"
"strings" "strings"
gatewaypb "chatappgateway/api/proto"
"chatappgateway/internal/apperr" "chatappgateway/internal/apperr"
commonpb "gitea.haiyihy.com/hy/chatappcommon/proto"
) )
// Client 定义支付服务 gRPC 客户端能力。 // Client 定义支付服务 gRPC 客户端能力。
type Client interface { type Client interface {
QueryOrder(ctx context.Context, request *gatewaypb.QueryOrderRequest) (*gatewaypb.QueryOrderResponse, error) Pay(ctx context.Context, request *commonpb.PayRequest) (*commonpb.PayResponse, error)
} }
// Service 负责订单号校验和下游支付查询。 // Request 描述客户端发起支付的请求。
type Request struct {
OrderNo string `json:"order_no"`
UserID string `json:"user_id"`
Amount string `json:"amount"`
Currency string `json:"currency"`
PayMethod string `json:"pay_method"`
Subject string `json:"subject"`
}
// Service 负责支付请求校验和下游支付调用。
type Service struct { type Service struct {
client Client client Client
} }
@ -23,14 +33,33 @@ func New(client Client) *Service {
return &Service{client: client} return &Service{client: client}
} }
// QueryOrder 校验订单号并调用支付服务。 // Pay 校验支付请求并调用支付服务。
func (s *Service) QueryOrder(ctx context.Context, orderNo string) (*gatewaypb.QueryOrderResponse, error) { func (s *Service) Pay(ctx context.Context, request Request) (*commonpb.PayResponse, error) {
normalized := strings.TrimSpace(orderNo) normalized := Request{
if normalized == "" { OrderNo: strings.TrimSpace(request.OrderNo),
return nil, apperr.New(400, "bad_request", "order_no is required") UserID: strings.TrimSpace(request.UserID),
Amount: strings.TrimSpace(request.Amount),
Currency: strings.TrimSpace(request.Currency),
PayMethod: strings.TrimSpace(request.PayMethod),
Subject: strings.TrimSpace(request.Subject),
} }
return s.client.QueryOrder(ctx, &gatewaypb.QueryOrderRequest{ if normalized.OrderNo == "" {
OrderNo: normalized, return nil, apperr.New(400, "bad_request", "order_no is required")
}
if normalized.UserID == "" {
return nil, apperr.New(400, "bad_request", "user_id is required")
}
if normalized.Amount == "" {
return nil, apperr.New(400, "bad_request", "amount is required")
}
return s.client.Pay(ctx, &commonpb.PayRequest{
OrderNo: normalized.OrderNo,
UserId: normalized.UserID,
Amount: normalized.Amount,
Currency: normalized.Currency,
PayMethod: normalized.PayMethod,
Subject: normalized.Subject,
}) })
} }

View File

@ -14,23 +14,22 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
gatewaypb "chatappgateway/api/proto"
"chatappgateway/internal/apperr" "chatappgateway/internal/apperr"
"chatappgateway/internal/service/auth" "chatappgateway/internal/service/auth"
payservice "chatappgateway/internal/service/pay"
commonpb "gitea.haiyihy.com/hy/chatappcommon/proto"
) )
const orderPrefix = "/api/v1/pay/orders/"
type requestIDKey struct{} type requestIDKey struct{}
// AuthService 定义 HTTP 层依赖的登录业务能力。 // AuthService 定义 HTTP 层依赖的用户注册能力。
type AuthService interface { type AuthService interface {
Login(ctx context.Context, request auth.LoginRequest) (*gatewaypb.LoginResponse, error) Register(ctx context.Context, request auth.RegisterRequest) (*commonpb.RegisterResponse, error)
} }
// PayService 定义 HTTP 层依赖的支付查询能力。 // PayService 定义 HTTP 层依赖的支付下单能力。
type PayService interface { type PayService interface {
QueryOrder(ctx context.Context, orderNo string) (*gatewaypb.QueryOrderResponse, error) Pay(ctx context.Context, request payservice.Request) (*commonpb.PayResponse, error)
} }
// ReadinessChecker 定义就绪检查能力,只有可接流量时才返回 nil。 // ReadinessChecker 定义就绪检查能力,只有可接流量时才返回 nil。
@ -104,9 +103,10 @@ func (s *Server) Run(ctx context.Context) error {
func (s *Server) routes() http.Handler { func (s *Server) routes() http.Handler {
mux := http.NewServeMux() mux := http.NewServeMux()
mux.HandleFunc("/health", s.handleHealth) mux.HandleFunc("/health", s.handleHealth)
mux.HandleFunc("/heath", s.handleHealth)
mux.HandleFunc("/ready", s.handleReady) mux.HandleFunc("/ready", s.handleReady)
mux.HandleFunc("/api/v1/auth/login", s.handleLogin) mux.HandleFunc("/api/v1/users/register", s.handleRegister)
mux.HandleFunc(orderPrefix, s.handleQueryOrder) mux.HandleFunc("/api/v1/pay", s.handlePay)
mux.HandleFunc("/", s.handleNotFound) mux.HandleFunc("/", s.handleNotFound)
return mux return mux
} }
@ -139,13 +139,13 @@ func (s *Server) handleReady(w http.ResponseWriter, r *http.Request) {
}) })
} }
func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) { func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
writeError(w, requestIDFromContext(r.Context()), apperr.New(http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed")) writeError(w, requestIDFromContext(r.Context()), apperr.New(http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed"))
return return
} }
var request auth.LoginRequest var request auth.RegisterRequest
decoder := json.NewDecoder(r.Body) decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields() decoder.DisallowUnknownFields()
if err := decoder.Decode(&request); err != nil { if err := decoder.Decode(&request); err != nil {
@ -153,7 +153,7 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
return return
} }
response, err := s.authService.Login(r.Context(), request) response, err := s.authService.Register(r.Context(), request)
if err != nil { if err != nil {
writeError(w, requestIDFromContext(r.Context()), err) writeError(w, requestIDFromContext(r.Context()), err)
return return
@ -162,19 +162,21 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
writeEnvelope(w, http.StatusOK, requestIDFromContext(r.Context()), response) writeEnvelope(w, http.StatusOK, requestIDFromContext(r.Context()), response)
} }
func (s *Server) handleQueryOrder(w http.ResponseWriter, r *http.Request) { func (s *Server) handlePay(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet { if r.Method != http.MethodPost {
writeError(w, requestIDFromContext(r.Context()), apperr.New(http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed")) writeError(w, requestIDFromContext(r.Context()), apperr.New(http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed"))
return return
} }
orderNo, ok := extractOrderNo(r.URL.Path) var request payservice.Request
if !ok { decoder := json.NewDecoder(r.Body)
writeError(w, requestIDFromContext(r.Context()), apperr.New(http.StatusNotFound, "not_found", "order not found")) decoder.DisallowUnknownFields()
if err := decoder.Decode(&request); err != nil {
writeError(w, requestIDFromContext(r.Context()), apperr.New(http.StatusBadRequest, "bad_request", "invalid json body"))
return return
} }
response, err := s.payService.QueryOrder(r.Context(), orderNo) response, err := s.payService.Pay(r.Context(), request)
if err != nil { if err != nil {
writeError(w, requestIDFromContext(r.Context()), err) writeError(w, requestIDFromContext(r.Context()), err)
return return
@ -234,17 +236,6 @@ func writeJSON(w http.ResponseWriter, statusCode int, payload any) {
_ = json.NewEncoder(w).Encode(payload) _ = json.NewEncoder(w).Encode(payload)
} }
func extractOrderNo(path string) (string, bool) {
if !strings.HasPrefix(path, orderPrefix) {
return "", false
}
orderNo := strings.TrimPrefix(path, orderPrefix)
if orderNo == "" || strings.Contains(orderNo, "/") {
return "", false
}
return orderNo, true
}
func requestIDFromContext(ctx context.Context) string { func requestIDFromContext(ctx context.Context) string {
requestID, _ := ctx.Value(requestIDKey{}).(string) requestID, _ := ctx.Value(requestIDKey{}).(string)
return requestID return requestID

View File

@ -15,12 +15,12 @@ import (
"testing" "testing"
"time" "time"
gatewaypb "chatappgateway/api/proto"
"chatappgateway/internal/integration/paygrpc" "chatappgateway/internal/integration/paygrpc"
"chatappgateway/internal/integration/usergrpc" "chatappgateway/internal/integration/usergrpc"
"chatappgateway/internal/service/auth" "chatappgateway/internal/service/auth"
"chatappgateway/internal/service/pay" payservice "chatappgateway/internal/service/pay"
httpserver "chatappgateway/internal/transport/http" httpserver "chatappgateway/internal/transport/http"
commonpb "gitea.haiyihy.com/hy/chatappcommon/proto"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
@ -78,16 +78,16 @@ func TestReady(t *testing.T) {
env := newTestEnv(t, 200*time.Millisecond, 200*time.Millisecond) env := newTestEnv(t, 200*time.Millisecond, 200*time.Millisecond)
defer env.Close() defer env.Close()
env.readinessChecker.err = errors.New("redis not ready") env.readinessChecker.err = errors.New("chatappuser not ready")
resp, responseBody := doRequest(t, env.server.Client(), http.MethodGet, env.server.URL+"/ready", nil) resp, responseBody := doRequest(t, env.server.Client(), http.MethodGet, env.server.URL+"/ready", nil)
defer resp.Body.Close() defer resp.Body.Close()
assertErrorResponse(t, resp.StatusCode, responseBody, http.StatusServiceUnavailable, "redis not ready") assertErrorResponse(t, resp.StatusCode, responseBody, http.StatusServiceUnavailable, "chatappuser not ready")
}) })
} }
func TestLoginValidation(t *testing.T) { func TestRegisterValidation(t *testing.T) {
t.Parallel() t.Parallel()
env := newTestEnv(t, 200*time.Millisecond, 200*time.Millisecond) env := newTestEnv(t, 200*time.Millisecond, 200*time.Millisecond)
@ -99,26 +99,21 @@ func TestLoginValidation(t *testing.T) {
message string message string
}{ }{
{ {
name: "password missing account", name: "missing account",
body: `{"login_type":"password","password":"secret"}`, body: `{"password":"secret"}`,
message: "account is required", message: "account is required",
}, },
{ {
name: "sms_code missing verify_code", name: "missing password",
body: `{"login_type":"sms_code","country_code":"+86","account":"13800138000"}`, body: `{"account":"demo@example.com"}`,
message: "verify_code is required", message: "password is required",
},
{
name: "oauth missing provider_token",
body: `{"login_type":"oauth","provider":"google"}`,
message: "provider_token is required",
}, },
} }
for _, testCase := range testCases { for _, testCase := range testCases {
testCase := testCase testCase := testCase
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
resp, body := doRequest(t, env.server.Client(), http.MethodPost, env.server.URL+"/api/v1/auth/login", strings.NewReader(testCase.body)) resp, body := doRequest(t, env.server.Client(), http.MethodPost, env.server.URL+"/api/v1/users/register", strings.NewReader(testCase.body))
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusBadRequest { if resp.StatusCode != http.StatusBadRequest {
@ -134,25 +129,19 @@ func TestLoginValidation(t *testing.T) {
} }
}) })
} }
if env.userServer.CallCount() != 0 {
t.Fatalf("expected user service not to be called, got %d", env.userServer.CallCount())
}
} }
func TestLoginDelegatesToUserGRPC(t *testing.T) { func TestRegisterDelegatesToUserGRPC(t *testing.T) {
t.Parallel() t.Parallel()
env := newTestEnv(t, 200*time.Millisecond, 200*time.Millisecond) env := newTestEnv(t, 200*time.Millisecond, 200*time.Millisecond)
defer env.Close() defer env.Close()
env.userServer.response = &gatewaypb.LoginResponse{ env.userServer.response = &commonpb.RegisterResponse{
UserId: "u-100", UserId: "u-100",
AccessToken: "access-token", AccessToken: "access-token",
RefreshToken: "refresh-token", IsNewUser: true,
ExpiresIn: 7200, Profile: &commonpb.UserProfile{
IsNewUser: true,
Profile: &gatewaypb.UserProfile{
UserId: "u-100", UserId: "u-100",
Nickname: "Neo", Nickname: "Neo",
AvatarUrl: "https://example.com/avatar.png", AvatarUrl: "https://example.com/avatar.png",
@ -160,22 +149,22 @@ func TestLoginDelegatesToUserGRPC(t *testing.T) {
} }
body := `{ body := `{
"login_type":" PASSWORD ",
"account":" demo@example.com ", "account":" demo@example.com ",
"password":"secret", "password":" secret ",
"nickname":" Neo ",
"device_id":" dev-1 ", "device_id":" dev-1 ",
"platform":" ios ", "platform":" ios ",
"app_version":" 1.0.0 " "app_version":" 1.0.0 "
}` }`
resp, responseBody := doRequest(t, env.server.Client(), http.MethodPost, env.server.URL+"/api/v1/auth/login", strings.NewReader(body)) resp, responseBody := doRequest(t, env.server.Client(), http.MethodPost, env.server.URL+"/api/v1/users/register", strings.NewReader(body))
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
t.Fatalf("unexpected status: %d body=%s", resp.StatusCode, string(responseBody)) t.Fatalf("unexpected status: %d body=%s", resp.StatusCode, string(responseBody))
} }
var payload successResponse[gatewaypb.LoginResponse] var payload successResponse[commonpb.RegisterResponse]
if err := json.Unmarshal(responseBody, &payload); err != nil { if err := json.Unmarshal(responseBody, &payload); err != nil {
t.Fatalf("Unmarshal returned error: %v", err) t.Fatalf("Unmarshal returned error: %v", err)
} }
@ -187,45 +176,90 @@ func TestLoginDelegatesToUserGRPC(t *testing.T) {
if request == nil { if request == nil {
t.Fatal("expected request to be captured") t.Fatal("expected request to be captured")
} }
if request.LoginType != "password" {
t.Fatalf("unexpected login type: %s", request.LoginType)
}
if request.Account != "demo@example.com" { if request.Account != "demo@example.com" {
t.Fatalf("unexpected account: %q", request.Account) t.Fatalf("unexpected account: %q", request.Account)
} }
if request.DeviceId != "dev-1" { if request.Nickname != "Neo" {
t.Fatalf("unexpected device id: %q", request.DeviceId) t.Fatalf("unexpected nickname: %q", request.Nickname)
}
if request.Platform != "ios" {
t.Fatalf("unexpected platform: %q", request.Platform)
} }
} }
func TestQueryOrderDelegatesToPayGRPC(t *testing.T) { func TestPayValidation(t *testing.T) {
t.Parallel() t.Parallel()
env := newTestEnv(t, 200*time.Millisecond, 200*time.Millisecond) env := newTestEnv(t, 200*time.Millisecond, 200*time.Millisecond)
defer env.Close() defer env.Close()
env.payServer.response = &gatewaypb.QueryOrderResponse{ testCases := []struct {
OrderNo: "order-001", name string
UserId: "u-100", body string
Status: "paid", message string
Amount: "9.99", }{
Currency: "USD", {
Subject: "VIP", name: "missing order_no",
PayMethod: "apple_pay", body: `{"user_id":"u-1","amount":"9.99"}`,
PaidAt: "2026-04-04T12:00:00Z", message: "order_no is required",
},
{
name: "missing user_id",
body: `{"order_no":"ord-1","amount":"9.99"}`,
message: "user_id is required",
},
{
name: "missing amount",
body: `{"order_no":"ord-1","user_id":"u-1"}`,
message: "amount is required",
},
} }
resp, responseBody := doRequest(t, env.server.Client(), http.MethodGet, env.server.URL+"/api/v1/pay/orders/order-001", nil) for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) {
resp, body := doRequest(t, env.server.Client(), http.MethodPost, env.server.URL+"/api/v1/pay", strings.NewReader(testCase.body))
defer resp.Body.Close()
if resp.StatusCode != http.StatusBadRequest {
t.Fatalf("unexpected status: %d", resp.StatusCode)
}
var payload errorResponse
if err := json.Unmarshal(body, &payload); err != nil {
t.Fatalf("Unmarshal returned error: %v", err)
}
if payload.Message != testCase.message {
t.Fatalf("unexpected message: %s", payload.Message)
}
})
}
}
func TestPayDelegatesToPayGRPC(t *testing.T) {
t.Parallel()
env := newTestEnv(t, 200*time.Millisecond, 200*time.Millisecond)
defer env.Close()
env.payServer.response = &commonpb.PayResponse{
PaymentId: "pay-001",
OrderNo: "order-001",
UserId: "u-100",
Status: "processing",
Amount: "9.99",
Currency: "USD",
PayMethod: "apple_pay",
Subject: "vip",
CreatedAt: "2026-04-04T12:00:00Z",
}
body := `{"order_no":"order-001","user_id":"u-100","amount":"9.99","currency":"USD","pay_method":"apple_pay","subject":"vip"}`
resp, responseBody := doRequest(t, env.server.Client(), http.MethodPost, env.server.URL+"/api/v1/pay", strings.NewReader(body))
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
t.Fatalf("unexpected status: %d body=%s", resp.StatusCode, string(responseBody)) t.Fatalf("unexpected status: %d body=%s", resp.StatusCode, string(responseBody))
} }
var payload successResponse[gatewaypb.QueryOrderResponse] var payload successResponse[commonpb.PayResponse]
if err := json.Unmarshal(responseBody, &payload); err != nil { if err := json.Unmarshal(responseBody, &payload); err != nil {
t.Fatalf("Unmarshal returned error: %v", err) t.Fatalf("Unmarshal returned error: %v", err)
} }
@ -242,37 +276,25 @@ func TestQueryOrderDelegatesToPayGRPC(t *testing.T) {
func TestErrorMapping(t *testing.T) { func TestErrorMapping(t *testing.T) {
t.Parallel() t.Parallel()
t.Run("login unauthorized", func(t *testing.T) { t.Run("register unauthorized", func(t *testing.T) {
env := newTestEnv(t, 200*time.Millisecond, 200*time.Millisecond) env := newTestEnv(t, 200*time.Millisecond, 200*time.Millisecond)
defer env.Close() defer env.Close()
env.userServer.err = status.Error(codes.Unauthenticated, "invalid credentials") env.userServer.err = status.Error(codes.Unauthenticated, "register denied")
resp, responseBody := doRequest(t, env.server.Client(), http.MethodPost, env.server.URL+"/api/v1/auth/login", strings.NewReader(`{"login_type":"password","account":"demo","password":"wrong"}`)) resp, responseBody := doRequest(t, env.server.Client(), http.MethodPost, env.server.URL+"/api/v1/users/register", strings.NewReader(`{"account":"demo","password":"secret"}`))
defer resp.Body.Close() defer resp.Body.Close()
assertErrorResponse(t, resp.StatusCode, responseBody, http.StatusUnauthorized, "invalid credentials") assertErrorResponse(t, resp.StatusCode, responseBody, http.StatusUnauthorized, "register denied")
}) })
t.Run("pay not found", func(t *testing.T) { t.Run("pay timeout", func(t *testing.T) {
env := newTestEnv(t, 200*time.Millisecond, 200*time.Millisecond) env := newTestEnv(t, 200*time.Millisecond, 20*time.Millisecond)
defer env.Close() defer env.Close()
env.payServer.err = status.Error(codes.NotFound, "order not found") env.payServer.delay = 150 * time.Millisecond
resp, responseBody := doRequest(t, env.server.Client(), http.MethodGet, env.server.URL+"/api/v1/pay/orders/missing-order", nil) resp, responseBody := doRequest(t, env.server.Client(), http.MethodPost, env.server.URL+"/api/v1/pay", strings.NewReader(`{"order_no":"ord-1","user_id":"u-1","amount":"9.99"}`))
defer resp.Body.Close()
assertErrorResponse(t, resp.StatusCode, responseBody, http.StatusNotFound, "order not found")
})
t.Run("login timeout", func(t *testing.T) {
env := newTestEnv(t, 20*time.Millisecond, 200*time.Millisecond)
defer env.Close()
env.userServer.delay = 150 * time.Millisecond
resp, responseBody := doRequest(t, env.server.Client(), http.MethodPost, env.server.URL+"/api/v1/auth/login", strings.NewReader(`{"login_type":"password","account":"demo","password":"secret"}`))
defer resp.Body.Close() defer resp.Body.Close()
assertErrorResponse(t, resp.StatusCode, responseBody, http.StatusGatewayTimeout, "upstream request timeout") assertErrorResponse(t, resp.StatusCode, responseBody, http.StatusGatewayTimeout, "upstream request timeout")
@ -284,7 +306,7 @@ func TestErrorMapping(t *testing.T) {
env.payServer.err = status.Error(codes.Unavailable, "pay service unavailable") env.payServer.err = status.Error(codes.Unavailable, "pay service unavailable")
resp, responseBody := doRequest(t, env.server.Client(), http.MethodGet, env.server.URL+"/api/v1/pay/orders/order-001", nil) resp, responseBody := doRequest(t, env.server.Client(), http.MethodPost, env.server.URL+"/api/v1/pay", strings.NewReader(`{"order_no":"ord-1","user_id":"u-1","amount":"9.99"}`))
defer resp.Body.Close() defer resp.Body.Close()
assertErrorResponse(t, resp.StatusCode, responseBody, http.StatusBadGateway, "pay service unavailable") assertErrorResponse(t, resp.StatusCode, responseBody, http.StatusBadGateway, "pay service unavailable")
@ -320,37 +342,37 @@ func newTestEnv(t *testing.T, userTimeout time.Duration, payTimeout time.Duratio
t.Helper() t.Helper()
userServer := &mockUserServer{ userServer := &mockUserServer{
response: &gatewaypb.LoginResponse{ response: &commonpb.RegisterResponse{
UserId: "default-user", UserId: "default-user",
AccessToken: "default-access", AccessToken: "default-access",
RefreshToken: "default-refresh", IsNewUser: true,
ExpiresIn: 3600, Profile: &commonpb.UserProfile{
Profile: &gatewaypb.UserProfile{
UserId: "default-user", UserId: "default-user",
Nickname: "default", Nickname: "default",
}, },
}, },
} }
payServer := &mockPayServer{ payServer := &mockPayServer{
response: &gatewaypb.QueryOrderResponse{ response: &commonpb.PayResponse{
OrderNo: "default-order", PaymentId: "default-pay",
UserId: "default-user", OrderNo: "default-order",
Status: "pending", UserId: "default-user",
Amount: "0", Status: "processing",
Currency: "USD", Amount: "9.99",
Currency: "USD",
}, },
} }
userConn, userClose := newBufConnClient(t, func(server *grpc.Server) { userConn, userClose := newBufConnClient(t, func(server *grpc.Server) {
gatewaypb.RegisterChatAppUserServer(server, userServer) commonpb.RegisterChatAppUserServiceServer(server, userServer)
}) })
payConn, payClose := newBufConnClient(t, func(server *grpc.Server) { payConn, payClose := newBufConnClient(t, func(server *grpc.Server) {
gatewaypb.RegisterChatAppPayServer(server, payServer) commonpb.RegisterChatAppPayServiceServer(server, payServer)
}) })
logger := slog.New(slog.NewTextHandler(io.Discard, nil)) logger := slog.New(slog.NewTextHandler(io.Discard, nil))
authService := auth.New(usergrpc.New(gatewaypb.NewChatAppUserClient(userConn), userTimeout)) authService := auth.New(usergrpc.New(commonpb.NewChatAppUserServiceClient(userConn), userTimeout))
payService := pay.New(paygrpc.New(gatewaypb.NewChatAppPayClient(payConn), payTimeout)) payService := payservice.New(paygrpc.New(commonpb.NewChatAppPayServiceClient(payConn), payTimeout))
readinessChecker := &mockReadinessChecker{} readinessChecker := &mockReadinessChecker{}
handler := httpserver.New("chatappgateway", ":0", 2*time.Second, logger, authService, payService, readinessChecker).Handler() handler := httpserver.New("chatappgateway", ":0", 2*time.Second, logger, authService, payService, readinessChecker).Handler()
@ -453,19 +475,17 @@ type errorResponse struct {
} }
type mockUserServer struct { type mockUserServer struct {
gatewaypb.UnimplementedChatAppUserServer commonpb.UnimplementedChatAppUserServiceServer
mu sync.Mutex mu sync.Mutex
callCount int lastReq *commonpb.RegisterRequest
lastReq *gatewaypb.LoginRequest response *commonpb.RegisterResponse
response *gatewaypb.LoginResponse err error
err error delay time.Duration
delay time.Duration
} }
func (s *mockUserServer) Login(ctx context.Context, request *gatewaypb.LoginRequest) (*gatewaypb.LoginResponse, error) { func (s *mockUserServer) Register(ctx context.Context, request *commonpb.RegisterRequest) (*commonpb.RegisterResponse, error) {
s.mu.Lock() s.mu.Lock()
s.callCount++
copied := *request copied := *request
s.lastReq = &copied s.lastReq = &copied
response := s.response response := s.response
@ -487,13 +507,7 @@ func (s *mockUserServer) Login(ctx context.Context, request *gatewaypb.LoginRequ
return response, nil return response, nil
} }
func (s *mockUserServer) CallCount() int { func (s *mockUserServer) LastRequest() *commonpb.RegisterRequest {
s.mu.Lock()
defer s.mu.Unlock()
return s.callCount
}
func (s *mockUserServer) LastRequest() *gatewaypb.LoginRequest {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if s.lastReq == nil { if s.lastReq == nil {
@ -504,16 +518,16 @@ func (s *mockUserServer) LastRequest() *gatewaypb.LoginRequest {
} }
type mockPayServer struct { type mockPayServer struct {
gatewaypb.UnimplementedChatAppPayServer commonpb.UnimplementedChatAppPayServiceServer
mu sync.Mutex mu sync.Mutex
lastReq *gatewaypb.QueryOrderRequest lastReq *commonpb.PayRequest
response *gatewaypb.QueryOrderResponse response *commonpb.PayResponse
err error err error
delay time.Duration delay time.Duration
} }
func (s *mockPayServer) QueryOrder(ctx context.Context, request *gatewaypb.QueryOrderRequest) (*gatewaypb.QueryOrderResponse, error) { func (s *mockPayServer) Pay(ctx context.Context, request *commonpb.PayRequest) (*commonpb.PayResponse, error) {
s.mu.Lock() s.mu.Lock()
copied := *request copied := *request
s.lastReq = &copied s.lastReq = &copied
@ -536,7 +550,7 @@ func (s *mockPayServer) QueryOrder(ctx context.Context, request *gatewaypb.Query
return response, nil return response, nil
} }
func (s *mockPayServer) LastRequest() *gatewaypb.QueryOrderRequest { func (s *mockPayServer) LastRequest() *commonpb.PayRequest {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if s.lastReq == nil { if s.lastReq == nil {