2026-05-25 23:41:50 +08:00

432 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package grpc 将 user-service 认证、用户和短号用例适配成 protobuf gRPC 服务。
package grpc
import (
userv1 "hyapp.local/api/proto/user/v1"
authdomain "hyapp/services/user-service/internal/domain/auth"
hostdomain "hyapp/services/user-service/internal/domain/host"
invitedomain "hyapp/services/user-service/internal/domain/invite"
mictimedomain "hyapp/services/user-service/internal/domain/mictime"
userdomain "hyapp/services/user-service/internal/domain/user"
authservice "hyapp/services/user-service/internal/service/auth"
)
// toProtoApp 把 App 注册表事实转换为 protobuf 投影。
func toProtoApp(app userdomain.App) *userv1.App {
return &userv1.App{
AppId: app.AppID,
AppCode: app.AppCode,
AppName: app.AppName,
PackageName: app.PackageName,
Platform: app.Platform,
Status: app.Status,
CreatedAtMs: app.CreatedAtMs,
UpdatedAtMs: app.UpdatedAtMs,
}
}
// toProtoToken 把领域令牌转换为 protobuf 响应。
func toProtoToken(token authdomain.Token) *userv1.AuthToken {
// Token 中包含 refresh token 原文,只能出现在登录/刷新响应路径。
return &userv1.AuthToken{
AppCode: token.AppCode,
UserId: token.UserID,
DisplayUserId: token.DisplayUserID,
DefaultDisplayUserId: token.DefaultDisplayUserID,
DisplayUserIdKind: token.DisplayUserIDKind,
DisplayUserIdExpiresAtMs: token.DisplayUserIDExpiresAtMs,
ProfileCompleted: token.ProfileCompleted,
OnboardingStatus: token.OnboardingStatus,
SessionId: token.SessionID,
AccessToken: token.AccessToken,
RefreshToken: token.RefreshToken,
ExpiresInSec: token.ExpiresInSec,
TokenType: token.TokenType,
}
}
// toProtoUser 把领域用户转换为 protobuf 用户投影。
func toProtoUser(user userdomain.User) *userv1.User {
// User proto 只暴露用户主状态和当前展示短号,不包含密码、三方身份或房间状态。
return &userv1.User{
AppCode: user.AppCode,
UserId: user.UserID,
Status: toProtoStatus(user.Status),
CreatedAtMs: user.CreatedAtMs,
UpdatedAtMs: user.UpdatedAtMs,
DisplayUserId: user.CurrentDisplayUserID,
DefaultDisplayUserId: user.DefaultDisplayUserID,
DisplayUserIdKind: string(user.CurrentDisplayUserIDKind),
DisplayUserIdExpiresAtMs: user.CurrentDisplayUserIDExpiresAtMs,
Username: user.Username,
Gender: user.Gender,
Country: user.Country,
Avatar: user.Avatar,
Birth: user.BirthDate,
Language: user.Language,
CountryId: user.CountryID,
CountryName: user.CountryName,
CountryDisplayName: user.CountryDisplayName,
RegionId: user.RegionID,
RegionCode: user.RegionCode,
RegionName: user.RegionName,
ProfileCompleted: user.ProfileCompleted,
ProfileCompletedAtMs: user.ProfileCompletedAtMs,
OnboardingStatus: string(user.OnboardingStatus),
IsoNumeric: user.ISONumeric,
PhoneCountryCode: user.PhoneCountryCode,
CountryEnabled: user.CountryEnabled,
Invite: toProtoInviteOverview(user.InviteOverview),
}
}
func toProtoBusinessUserLookupItem(item userdomain.BusinessUserLookupItem) *userv1.BusinessUserLookupItem {
return &userv1.BusinessUserLookupItem{
UserId: item.UserID,
DisplayUserId: item.DisplayUserID,
Username: item.Username,
Avatar: item.Avatar,
Status: toProtoStatus(item.Status),
RegionId: item.RegionID,
RegionCode: item.RegionCode,
RegionName: item.RegionName,
}
}
func toProtoInviteOverview(overview invitedomain.Overview) *userv1.InviteOverview {
if overview.MyInviteCode == "" && !overview.InviteEnabled && overview.InviteCount == 0 && overview.ValidInviteCount == 0 && overview.ValidInviteThresholdCoin == 0 {
return nil
}
return &userv1.InviteOverview{
MyInviteCode: overview.MyInviteCode,
InviteEnabled: overview.InviteEnabled,
InviteCount: overview.InviteCount,
ValidInviteCount: overview.ValidInviteCount,
ValidInviteThresholdCoin: overview.ValidInviteThresholdCoin,
}
}
func toProtoInviteBinding(binding invitedomain.Binding) *userv1.InviteBinding {
if !binding.Bound && binding.InviteCode == "" && binding.InviterUserID == 0 {
return nil
}
return &userv1.InviteBinding{
Bound: binding.Bound,
InviteCode: binding.InviteCode,
InviterUserId: binding.InviterUserID,
}
}
func toProtoMicLifetimeStats(stats mictimedomain.LifetimeStats) *userv1.UserMicLifetimeStats {
return &userv1.UserMicLifetimeStats{
AppCode: stats.AppCode,
UserId: stats.UserID,
SeatOccupiedMs: stats.SeatOccupiedMs,
MicOnlineMs: stats.MicOnlineMs,
SessionCount: stats.SessionCount,
FirstMicAtMs: stats.FirstMicAtMs,
LastMicAtMs: stats.LastMicAtMs,
UpdatedAtMs: stats.UpdatedAtMs,
}
}
func toProtoProfileStats(stats userdomain.ProfileStats) *userv1.UserProfileStats {
return &userv1.UserProfileStats{
AppCode: stats.AppCode,
UserId: stats.UserID,
VisitorsCount: stats.VisitorsCount,
FollowingCount: stats.FollowingCount,
FriendsCount: stats.FriendsCount,
UpdatedAtMs: stats.UpdatedAtMs,
}
}
func toProtoProfileVisitRecord(record userdomain.ProfileVisitRecord) *userv1.ProfileVisitRecord {
return &userv1.ProfileVisitRecord{
VisitorUserId: record.VisitorUserID,
TargetUserId: record.TargetUserID,
VisitCount: record.VisitCount,
LastVisitedAtMs: record.LastVisitedAtMs,
}
}
func toProtoFollowRecord(record userdomain.FollowRecord) *userv1.FollowRecord {
return &userv1.FollowRecord{
FollowerUserId: record.FollowerUserID,
FolloweeUserId: record.FolloweeUserID,
FollowedAtMs: record.FollowedAtMs,
}
}
func toProtoFriendRecord(record userdomain.FriendRecord) *userv1.FriendRecord {
return &userv1.FriendRecord{
UserId: record.UserID,
FriendUserId: record.FriendUserID,
FriendedAtMs: record.FriendedAtMs,
}
}
func toProtoFriendApplication(application userdomain.FriendApplication) *userv1.FriendApplication {
return &userv1.FriendApplication{
RequesterUserId: application.RequesterUserID,
TargetUserId: application.TargetUserID,
Status: application.Status,
CreatedAtMs: application.CreatedAtMs,
UpdatedAtMs: application.UpdatedAtMs,
}
}
// toProtoCountry 把国家领域对象转换为管理 RPC 响应。
func toProtoCountry(country userdomain.Country) *userv1.Country {
return &userv1.Country{
AppCode: country.AppCode,
CountryId: country.CountryID,
CountryName: country.CountryName,
CountryCode: country.CountryCode,
CountryDisplayName: country.CountryDisplayName,
SortOrder: country.SortOrder,
CreatedAtMs: country.CreatedAtMs,
UpdatedAtMs: country.UpdatedAtMs,
IsoAlpha3: country.ISOAlpha3,
IsoNumeric: country.ISONumeric,
PhoneCountryCode: country.PhoneCountryCode,
Enabled: country.Enabled,
Flag: country.Flag,
}
}
// toProtoRegion 把区域领域对象转换为管理 RPC 响应。
func toProtoRegion(region userdomain.Region) *userv1.Region {
return &userv1.Region{
AppCode: region.AppCode,
RegionId: region.RegionID,
RegionCode: region.RegionCode,
Name: region.Name,
Status: region.Status,
Countries: append([]string{}, region.Countries...),
SortOrder: region.SortOrder,
CreatedAtMs: region.CreatedAtMs,
UpdatedAtMs: region.UpdatedAtMs,
}
}
// toProtoIdentity 把短号领域投影转换为 protobuf 响应。
func toProtoIdentity(identity userdomain.Identity) *userv1.UserIdentity {
// Identity proto 用于短号解析和修改结果,字段语义与领域 Identity 保持一致。
return &userv1.UserIdentity{
AppCode: identity.AppCode,
UserId: identity.UserID,
DisplayUserId: identity.DisplayUserID,
Status: string(identity.Status),
DefaultDisplayUserId: identity.DefaultDisplayUserID,
DisplayUserIdKind: string(identity.DisplayUserIDKind),
DisplayUserIdExpiresAtMs: identity.DisplayUserIDExpiresAtMs,
}
}
// toProtoStatus 固定未知状态的安全默认值。
func toProtoStatus(status userdomain.Status) userv1.UserStatus {
// 状态枚举转换集中在这里,未知状态使用 UNSPECIFIED 安全兜底。
switch status {
case userdomain.StatusActive:
return userv1.UserStatus_USER_STATUS_ACTIVE
case userdomain.StatusDisabled:
return userv1.UserStatus_USER_STATUS_DISABLED
case userdomain.StatusBanned:
return userv1.UserStatus_USER_STATUS_BANNED
default:
return userv1.UserStatus_USER_STATUS_UNSPECIFIED
}
}
func fromProtoStatus(status userv1.UserStatus) userdomain.Status {
switch status {
case userv1.UserStatus_USER_STATUS_ACTIVE:
return userdomain.StatusActive
case userv1.UserStatus_USER_STATUS_DISABLED:
return userdomain.StatusDisabled
case userv1.UserStatus_USER_STATUS_BANNED:
return userdomain.StatusBanned
default:
return ""
}
}
// authMeta 提取认证链路审计所需的 gateway 透传字段。
func authMeta(meta *userv1.RequestMeta) authservice.Meta {
if meta == nil {
// meta 可选,缺失时审计字段为空但不能让 RPC panic。
return authservice.Meta{}
}
// 只提取认证审计需要的字段device_id 在各 RPC 中单独传入。
return authservice.Meta{
AppCode: meta.GetAppCode(),
RequestID: meta.GetRequestId(),
ClientIP: meta.GetClientIp(),
UserAgent: meta.GetUserAgent(),
CountryByIP: meta.GetCountryByIp(),
Platform: meta.GetPlatform(),
Language: meta.GetLanguage(),
Timezone: meta.GetTimezone(),
}
}
// toProtoHostProfile 把 host profile 事实转换为内部 gRPC 投影。
func toProtoHostProfile(profile hostdomain.HostProfile) *userv1.HostProfile {
if profile.UserID == 0 {
return nil
}
return &userv1.HostProfile{
UserId: profile.UserID,
Status: profile.Status,
RegionId: profile.RegionID,
CurrentAgencyId: profile.CurrentAgencyID,
CurrentMembershipId: profile.CurrentMembershipID,
Source: profile.Source,
FirstBecameHostAtMs: profile.FirstBecameHostAtMs,
CreatedAtMs: profile.CreatedAtMs,
UpdatedAtMs: profile.UpdatedAtMs,
}
}
// toProtoAgency 把 Agency 事实转换为内部 gRPC 投影。
func toProtoAgency(agency hostdomain.Agency) *userv1.Agency {
if agency.AgencyID == 0 {
return nil
}
return &userv1.Agency{
AgencyId: agency.AgencyID,
OwnerUserId: agency.OwnerUserID,
RegionId: agency.RegionID,
ParentBdUserId: agency.ParentBDUserID,
Name: agency.Name,
Status: agency.Status,
JoinEnabled: agency.JoinEnabled,
MaxHosts: agency.MaxHosts,
CreatedByUserId: agency.CreatedByUserID,
CreatedAtMs: agency.CreatedAtMs,
UpdatedAtMs: agency.UpdatedAtMs,
}
}
// toProtoBDProfile 把 BD profile 事实转换为内部 gRPC 投影。
func toProtoBDProfile(profile hostdomain.BDProfile) *userv1.BDProfile {
if profile.UserID == 0 {
return nil
}
return &userv1.BDProfile{
UserId: profile.UserID,
Role: profile.Role,
RegionId: profile.RegionID,
ParentLeaderUserId: profile.ParentLeaderUserID,
Status: profile.Status,
CreatedByUserId: profile.CreatedByUserID,
CreatedAtMs: profile.CreatedAtMs,
UpdatedAtMs: profile.UpdatedAtMs,
}
}
// toProtoCoinSellerProfile 把币商身份事实转换为内部 gRPC 投影。
func toProtoCoinSellerProfile(profile hostdomain.CoinSellerProfile) *userv1.CoinSellerProfile {
if profile.UserID == 0 {
return nil
}
return &userv1.CoinSellerProfile{
UserId: profile.UserID,
Status: profile.Status,
MerchantAssetType: profile.MerchantAssetType,
CreatedByUserId: profile.CreatedByUserID,
CreatedAtMs: profile.CreatedAtMs,
UpdatedAtMs: profile.UpdatedAtMs,
}
}
// toProtoUserRoleSummary 把 host domain 入口显隐摘要转换为 gRPC 投影。
func toProtoUserRoleSummary(summary hostdomain.UserRoleSummary) *userv1.UserRoleSummary {
if summary.UserID == 0 {
return nil
}
return &userv1.UserRoleSummary{
UserId: summary.UserID,
IsHost: summary.IsHost,
IsAgency: summary.IsAgency,
IsManager: summary.IsManager,
IsBd: summary.IsBD,
IsBdLeader: summary.IsBDLeader,
IsCoinSeller: summary.IsCoinSeller,
HostStatus: summary.HostStatus,
AgencyId: summary.AgencyID,
BdId: summary.BDID,
BdStatus: summary.BDStatus,
CoinSellerStatus: summary.CoinSellerStatus,
PendingRoleInvitations: summary.PendingRoleInvitations,
}
}
// toProtoAgencyMembership 把 Agency membership 事实转换为内部 gRPC 投影。
func toProtoAgencyMembership(membership hostdomain.AgencyMembership) *userv1.AgencyMembership {
if membership.MembershipID == 0 {
return nil
}
return &userv1.AgencyMembership{
MembershipId: membership.MembershipID,
AgencyId: membership.AgencyID,
HostUserId: membership.HostUserID,
RegionId: membership.RegionID,
MembershipType: membership.MembershipType,
Status: membership.Status,
JoinedAtMs: membership.JoinedAtMs,
EndedAtMs: membership.EndedAtMs,
EndedByUserId: membership.EndedByUserID,
EndedReason: membership.EndedReason,
CreatedAtMs: membership.CreatedAtMs,
UpdatedAtMs: membership.UpdatedAtMs,
}
}
// toProtoAgencyApplication 把 Agency application 事实转换为内部 gRPC 投影。
func toProtoAgencyApplication(application hostdomain.AgencyApplication) *userv1.AgencyApplication {
if application.ApplicationID == 0 {
return nil
}
return &userv1.AgencyApplication{
ApplicationId: application.ApplicationID,
CommandId: application.CommandID,
ApplicantUserId: application.ApplicantUserID,
AgencyId: application.AgencyID,
RegionId: application.RegionID,
Status: application.Status,
ReviewedByUserId: application.ReviewedByUserID,
ReviewReason: application.ReviewReason,
ReviewedAtMs: application.ReviewedAtMs,
CreatedAtMs: application.CreatedAtMs,
UpdatedAtMs: application.UpdatedAtMs,
}
}
// toProtoRoleInvitation 把角色邀请事实转换为内部 gRPC 投影。
func toProtoRoleInvitation(invitation hostdomain.RoleInvitation) *userv1.RoleInvitation {
if invitation.InvitationID == 0 {
return nil
}
return &userv1.RoleInvitation{
InvitationId: invitation.InvitationID,
CommandId: invitation.CommandID,
InvitationType: invitation.InvitationType,
Status: invitation.Status,
InviterUserId: invitation.InviterUserID,
InviterBdUserId: invitation.InviterBDUserID,
TargetUserId: invitation.TargetUserID,
RegionId: invitation.RegionID,
AgencyName: invitation.AgencyName,
ParentBdUserId: invitation.ParentBDUserID,
ParentLeaderUserId: invitation.ParentLeaderUserID,
ProcessedByUserId: invitation.ProcessedByUserID,
ProcessReason: invitation.ProcessReason,
ProcessedAtMs: invitation.ProcessedAtMs,
CreatedAtMs: invitation.CreatedAtMs,
UpdatedAtMs: invitation.UpdatedAtMs,
}
}