531 lines
20 KiB
Go
531 lines
20 KiB
Go
package walletapi
|
||
|
||
import (
|
||
"crypto/sha256"
|
||
"encoding/json"
|
||
"fmt"
|
||
"net/http"
|
||
"strings"
|
||
|
||
activityv1 "hyapp.local/api/proto/activity/v1"
|
||
userv1 "hyapp.local/api/proto/user/v1"
|
||
walletv1 "hyapp.local/api/proto/wallet/v1"
|
||
"hyapp/pkg/appcode"
|
||
"hyapp/services/gateway-service/internal/auth"
|
||
"hyapp/services/gateway-service/internal/transport/http/httpkit"
|
||
)
|
||
|
||
const (
|
||
vipOnlineNoticeBenefitCode = "online_global_notice"
|
||
vipOnlineNoticeBroadcastType = "vip_online_notice"
|
||
vipOnlineNoticeAvatarFrame = "avatar_frame"
|
||
vipOnlineNoticeCommandMaxLen = 128
|
||
vipOnlineNoticeMessageMaxLen = 256
|
||
)
|
||
|
||
type vipOnlineNoticeRequestBody struct {
|
||
// CommandID 是单次 App 进程启动的业务幂等键;request_id 只用于链路追踪,不能替代它。
|
||
CommandID string `json:"command_id"`
|
||
CommandIDAlt string `json:"commandId"`
|
||
}
|
||
|
||
type vipOnlineNoticeMetadata struct {
|
||
Message string `json:"message"`
|
||
}
|
||
|
||
type vipOnlineNoticeAvatarFrameData struct {
|
||
ResourceID int64
|
||
Code string
|
||
AssetURL string
|
||
PreviewURL string
|
||
AnimationURL string
|
||
}
|
||
|
||
// getMyVIP 同时返回旧版扁平 VIP 字段和新的 paid/trial/effective 状态。
|
||
// 扁平字段继续取 GetMyVipResponse.vip,保证旧客户端升级期间无需同时切换解析路径。
|
||
func (h *Handler) getMyVIP(writer http.ResponseWriter, request *http.Request) {
|
||
if h.walletClient == nil {
|
||
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||
return
|
||
}
|
||
resp, err := h.walletClient.GetMyVip(request.Context(), &walletv1.GetMyVipRequest{
|
||
RequestId: httpkit.RequestIDFromContext(request.Context()),
|
||
AppCode: appcode.FromContext(request.Context()),
|
||
UserId: auth.UserIDFromContext(request.Context()),
|
||
})
|
||
if err != nil {
|
||
httpkit.WriteRPCError(writer, request, err)
|
||
return
|
||
}
|
||
state := vipStateFromProto(resp.GetState())
|
||
httpkit.WriteOK(writer, request, vipMeData{
|
||
OverviewVIPData: vipFromProto(resp.GetVip()),
|
||
ProgramConfig: state.ProgramConfig,
|
||
State: state,
|
||
})
|
||
}
|
||
|
||
// listVIPPackages 返回可购买 VIP 包和当前会员状态。
|
||
func (h *Handler) listVIPPackages(writer http.ResponseWriter, request *http.Request) {
|
||
if h.walletClient == nil {
|
||
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||
return
|
||
}
|
||
resp, err := h.walletClient.ListVipPackages(request.Context(), &walletv1.ListVipPackagesRequest{
|
||
RequestId: httpkit.RequestIDFromContext(request.Context()),
|
||
AppCode: appcode.FromContext(request.Context()),
|
||
UserId: auth.UserIDFromContext(request.Context()),
|
||
})
|
||
if err != nil {
|
||
httpkit.WriteRPCError(writer, request, err)
|
||
return
|
||
}
|
||
packages := make([]vipPackageData, 0, len(resp.GetPackages()))
|
||
for _, item := range resp.GetPackages() {
|
||
packages = append(packages, vipPackageFromProto(item))
|
||
}
|
||
state := vipStateFromProto(resp.GetState())
|
||
programConfig := vipProgramConfigFromProto(resp.GetProgramConfig())
|
||
if resp.GetProgramConfig() == nil {
|
||
programConfig = state.ProgramConfig
|
||
}
|
||
httpkit.WriteOK(writer, request, map[string]any{
|
||
"current_vip": vipFromProto(resp.GetCurrentVip()),
|
||
"packages": packages,
|
||
"program_config": programConfig,
|
||
"state": state,
|
||
})
|
||
}
|
||
|
||
// purchaseVIP 购买、续期或升级 VIP。
|
||
func (h *Handler) purchaseVIP(writer http.ResponseWriter, request *http.Request) {
|
||
if h.walletClient == nil {
|
||
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||
return
|
||
}
|
||
var body vipPurchaseRequestBody
|
||
if !httpkit.Decode(writer, request, &body) {
|
||
return
|
||
}
|
||
commandID := strings.TrimSpace(body.CommandID)
|
||
if commandID == "" {
|
||
commandID = strings.TrimSpace(body.CommandIDAlt)
|
||
}
|
||
if commandID == "" || body.Level <= 0 {
|
||
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
|
||
return
|
||
}
|
||
resp, err := h.walletClient.PurchaseVip(request.Context(), &walletv1.PurchaseVipRequest{
|
||
CommandId: commandID,
|
||
AppCode: appcode.FromContext(request.Context()),
|
||
UserId: auth.UserIDFromContext(request.Context()),
|
||
Level: body.Level,
|
||
})
|
||
if err != nil {
|
||
httpkit.WriteRPCError(writer, request, err)
|
||
return
|
||
}
|
||
httpkit.WriteOK(writer, request, map[string]any{
|
||
"order_id": resp.GetOrderId(),
|
||
"transaction_id": resp.GetTransactionId(),
|
||
"vip": vipFromProto(resp.GetVip()),
|
||
"coin_spent": resp.GetCoinSpent(),
|
||
"coin_balance_after": resp.GetCoinBalanceAfter(),
|
||
"reward_items": vipRewardItemsFromProto(resp.GetRewardItems()),
|
||
"program_config": vipProgramConfigFromProto(resp.GetState().GetProgramConfig()),
|
||
"state": vipStateFromProto(resp.GetState()),
|
||
})
|
||
}
|
||
|
||
// equipVIPTrialCard 只接受背包实例 entitlement_id;同资源的多张卡不能用 resource_id
|
||
// 代替,否则切换时会误改另一张卡的独立到期时间。
|
||
func (h *Handler) equipVIPTrialCard(writer http.ResponseWriter, request *http.Request) {
|
||
if h.walletClient == nil {
|
||
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||
return
|
||
}
|
||
entitlementID := strings.TrimSpace(request.PathValue("entitlement_id"))
|
||
if entitlementID == "" {
|
||
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
|
||
return
|
||
}
|
||
resp, err := h.walletClient.EquipVipTrialCard(request.Context(), &walletv1.EquipVipTrialCardRequest{
|
||
RequestId: httpkit.RequestIDFromContext(request.Context()),
|
||
AppCode: appcode.FromContext(request.Context()),
|
||
UserId: auth.UserIDFromContext(request.Context()),
|
||
EntitlementId: entitlementID,
|
||
})
|
||
if err != nil {
|
||
httpkit.WriteRPCError(writer, request, err)
|
||
return
|
||
}
|
||
state := vipStateFromProto(resp.GetState())
|
||
httpkit.WriteOK(writer, request, map[string]any{
|
||
"trial_card": vipTrialCardFromProto(resp.GetTrialCard()),
|
||
"program_config": state.ProgramConfig,
|
||
"state": state,
|
||
"server_time_ms": resp.GetServerTimeMs(),
|
||
})
|
||
}
|
||
|
||
// unequipVIPTrialCard 只解除当前装备关系;体验卡实例和其绝对过期时间仍保留在背包,
|
||
// 因而客户端可以随后重新佩戴尚未过期的任意等级体验卡。
|
||
func (h *Handler) unequipVIPTrialCard(writer http.ResponseWriter, request *http.Request) {
|
||
if h.walletClient == nil {
|
||
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||
return
|
||
}
|
||
resp, err := h.walletClient.UnequipVipTrialCard(request.Context(), &walletv1.UnequipVipTrialCardRequest{
|
||
RequestId: httpkit.RequestIDFromContext(request.Context()),
|
||
AppCode: appcode.FromContext(request.Context()),
|
||
UserId: auth.UserIDFromContext(request.Context()),
|
||
})
|
||
if err != nil {
|
||
httpkit.WriteRPCError(writer, request, err)
|
||
return
|
||
}
|
||
state := vipStateFromProto(resp.GetState())
|
||
httpkit.WriteOK(writer, request, map[string]any{
|
||
"unequipped": resp.GetUnequipped(),
|
||
"program_config": state.ProgramConfig,
|
||
"state": state,
|
||
"server_time_ms": resp.GetServerTimeMs(),
|
||
})
|
||
}
|
||
|
||
// triggerVIPOnlineNotice 是 VIP 上线全服飘屏的唯一 App HTTP 入口。
|
||
// Flutter 负责进程内“UTC 当日只请求一次”的展示策略;服务端不做按日持久化去重,因而杀进程
|
||
// 后的新进程可以再次请求。单次请求重试仍通过 command_id 映射为稳定 event_id,避免网络重试重复播报。
|
||
func (h *Handler) triggerVIPOnlineNotice(writer http.ResponseWriter, request *http.Request) {
|
||
if h.walletClient == nil || h.userProfileClient == nil || h.broadcastClient == nil {
|
||
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||
return
|
||
}
|
||
var body vipOnlineNoticeRequestBody
|
||
if !httpkit.Decode(writer, request, &body) {
|
||
return
|
||
}
|
||
commandID := strings.TrimSpace(body.CommandID)
|
||
if commandID == "" {
|
||
commandID = strings.TrimSpace(body.CommandIDAlt)
|
||
}
|
||
if commandID == "" || len(commandID) > vipOnlineNoticeCommandMaxLen {
|
||
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
|
||
return
|
||
}
|
||
|
||
ctx := request.Context()
|
||
userID := auth.UserIDFromContext(ctx)
|
||
appCode := appcode.FromContext(ctx)
|
||
benefitResp, err := h.walletClient.CheckVipBenefit(ctx, &walletv1.CheckVipBenefitRequest{
|
||
RequestId: httpkit.RequestIDFromContext(ctx),
|
||
AppCode: appCode,
|
||
UserId: userID,
|
||
BenefitCode: vipOnlineNoticeBenefitCode,
|
||
})
|
||
if err != nil {
|
||
httpkit.WriteRPCError(writer, request, err)
|
||
return
|
||
}
|
||
if !benefitResp.GetAllowed() {
|
||
// 客户端展示态可能滞后,执行前必须重新查 effective benefit;体验卡排除、过期和后台停用
|
||
// 都以 wallet-service 此刻的判定为准,不能仅比较 VIP9 等级。
|
||
httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodePermissionDenied, "permission denied")
|
||
return
|
||
}
|
||
equippedResp, err := h.walletClient.BatchGetUserEquippedResources(ctx, &walletv1.BatchGetUserEquippedResourcesRequest{
|
||
RequestId: httpkit.RequestIDFromContext(ctx),
|
||
AppCode: appCode,
|
||
UserIds: []int64{userID},
|
||
ResourceTypes: []string{vipOnlineNoticeAvatarFrame},
|
||
})
|
||
if err != nil {
|
||
// 头像框是全服消息的一部分,不能在 wallet 查询失败时把“无头像框”当成真实佩戴态广播。
|
||
// 失败请求由客户端复用同一 command_id 重试,成功后仍只会写入同一条 activity outbox。
|
||
httpkit.WriteRPCError(writer, request, err)
|
||
return
|
||
}
|
||
avatarFrame := vipOnlineNoticeAvatarFrameFromResponse(equippedResp, userID)
|
||
profileResp, err := h.userProfileClient.GetUser(ctx, &userv1.GetUserRequest{
|
||
Meta: httpkit.UserMeta(request, ""),
|
||
UserId: userID,
|
||
})
|
||
if err != nil {
|
||
httpkit.WriteRPCError(writer, request, err)
|
||
return
|
||
}
|
||
profile := profileResp.GetUser()
|
||
if profile == nil || profile.GetUserId() != userID {
|
||
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||
return
|
||
}
|
||
|
||
effectiveVIP := benefitResp.GetState().GetEffectiveVip()
|
||
displayID := strings.TrimSpace(profile.GetPrettyDisplayUserId())
|
||
if displayID == "" {
|
||
displayID = strings.TrimSpace(profile.GetDisplayUserId())
|
||
}
|
||
payload, err := json.Marshal(map[string]any{
|
||
"user_id": userID,
|
||
"sender_user_id": userID,
|
||
"nickname": profile.GetUsername(),
|
||
"avatar": profile.GetAvatar(),
|
||
"display_user_id": displayID,
|
||
"raw_display_user_id": profile.GetDisplayUserId(),
|
||
"pretty_display_user_id": profile.GetPrettyDisplayUserId(),
|
||
// avatar_frame_url 是客户端高频兼容字段,固定等同资源 asset_url;其余字段保留完整素材信息,
|
||
// 让静态、预览和动画渲染都只消费 wallet 返回的当前佩戴事实。
|
||
"avatar_frame_url": avatarFrame.AssetURL,
|
||
"avatar_frame_resource_id": avatarFrame.ResourceID,
|
||
"avatar_frame_code": avatarFrame.Code,
|
||
"avatar_frame_asset_url": avatarFrame.AssetURL,
|
||
"avatar_frame_preview_url": avatarFrame.PreviewURL,
|
||
"avatar_frame_animation_url": avatarFrame.AnimationURL,
|
||
"vip_program_type": effectiveVIP.GetProgramType(),
|
||
"vip_level": effectiveVIP.GetLevel(),
|
||
"vip_name": effectiveVIP.GetName(),
|
||
"message": vipOnlineNoticeMessage(appCode, benefitResp.GetBenefit().GetMetadataJson()),
|
||
"action": map[string]string{"type": "none"},
|
||
})
|
||
if err != nil {
|
||
httpkit.WriteError(writer, request, http.StatusInternalServerError, httpkit.CodeInternalError, "internal error")
|
||
return
|
||
}
|
||
// event_id 使用 user_id + command_id 摘要,既隔离不同用户,又避免客户端长命令超过
|
||
// activity outbox 的 VARCHAR(128) 主键;相同 command_id 的 HTTP 重试会命中同一条 outbox。
|
||
commandDigest := sha256.Sum256([]byte(commandID))
|
||
eventID := fmt.Sprintf("vip_online_notice:%d:%x", userID, commandDigest)
|
||
broadcastResp, err := h.broadcastClient.PublishGlobalBroadcast(ctx, &activityv1.PublishGlobalBroadcastRequest{
|
||
Meta: httpkit.ActivityMeta(request),
|
||
EventId: eventID,
|
||
BroadcastType: vipOnlineNoticeBroadcastType,
|
||
PayloadJson: string(payload),
|
||
})
|
||
if err != nil {
|
||
httpkit.WriteRPCError(writer, request, err)
|
||
return
|
||
}
|
||
httpkit.WriteOK(writer, request, map[string]any{
|
||
"event_id": broadcastResp.GetEventId(),
|
||
"group_id": broadcastResp.GetGroupId(),
|
||
"status": broadcastResp.GetStatus(),
|
||
"created": broadcastResp.GetCreated(),
|
||
"evaluated_at_ms": benefitResp.GetEvaluatedAtMs(),
|
||
})
|
||
}
|
||
|
||
// vipOnlineNoticeAvatarFrameFromResponse 只接受目标用户下的 avatar_frame。
|
||
// wallet 的 batch RPC 已负责过滤过期佩戴,这里仍防御错误分组、错误资源类型和空资源,避免串用户素材。
|
||
func vipOnlineNoticeAvatarFrameFromResponse(resp *walletv1.BatchGetUserEquippedResourcesResponse, userID int64) vipOnlineNoticeAvatarFrameData {
|
||
for _, user := range resp.GetUsers() {
|
||
if user.GetUserId() != userID {
|
||
continue
|
||
}
|
||
for _, item := range user.GetResources() {
|
||
resource := item.GetResource()
|
||
if resource.GetResourceType() != vipOnlineNoticeAvatarFrame || resource.GetResourceId() <= 0 {
|
||
continue
|
||
}
|
||
return vipOnlineNoticeAvatarFrameData{
|
||
ResourceID: resource.GetResourceId(),
|
||
Code: resource.GetResourceCode(),
|
||
AssetURL: resource.GetAssetUrl(),
|
||
PreviewURL: resource.GetPreviewUrl(),
|
||
AnimationURL: resource.GetAnimationUrl(),
|
||
}
|
||
}
|
||
}
|
||
return vipOnlineNoticeAvatarFrameData{}
|
||
}
|
||
|
||
// vipOnlineNoticeMessage 从每级权益 metadata_json 读取 App 独立文案。
|
||
// Fami 旧配置没有 metadata 时保留产品确认的默认文案;其他 App 使用中性兜底,避免把 Fami 名称串到 Lalu。
|
||
func vipOnlineNoticeMessage(appCode string, metadataJSON string) string {
|
||
var metadata vipOnlineNoticeMetadata
|
||
if json.Unmarshal([]byte(strings.TrimSpace(metadataJSON)), &metadata) == nil {
|
||
if message := strings.TrimSpace(metadata.Message); message != "" && len(message) <= vipOnlineNoticeMessageMaxLen {
|
||
return message
|
||
}
|
||
}
|
||
if strings.EqualFold(strings.TrimSpace(appCode), "fami") {
|
||
return "欢迎进入Fami,祝你有美好的一天"
|
||
}
|
||
return "欢迎进入,祝你有美好的一天"
|
||
}
|
||
|
||
func vipFromProto(vip *walletv1.UserVip) overviewVIPData {
|
||
if vip == nil {
|
||
return overviewVIPData{}
|
||
}
|
||
return overviewVIPData{
|
||
Level: vip.GetLevel(),
|
||
Name: vip.GetName(),
|
||
Active: vip.GetActive(),
|
||
StartedAtMS: vip.GetStartedAtMs(),
|
||
ExpiresAtMS: vip.GetExpiresAtMs(),
|
||
ProgramType: vip.GetProgramType(),
|
||
ConfigVersion: vip.GetConfigVersion(),
|
||
}
|
||
}
|
||
|
||
func vipPackageFromProto(item *walletv1.VipLevel) vipPackageData {
|
||
if item == nil {
|
||
return vipPackageData{}
|
||
}
|
||
return vipPackageData{
|
||
Level: item.GetLevel(),
|
||
Name: item.GetName(),
|
||
Status: item.GetStatus(),
|
||
PriceCoin: item.GetPriceCoin(),
|
||
DurationMS: item.GetDurationMs(),
|
||
RewardResourceGroupID: item.GetRewardResourceGroupId(),
|
||
RewardItems: vipRewardItemsFromProto(item.GetRewardItems()),
|
||
CanPurchase: item.GetCanPurchase(),
|
||
SortOrder: item.GetSortOrder(),
|
||
RechargeGateRequired: item.GetRechargeGateRequired(),
|
||
RequiredRechargeCoinAmount: item.GetRequiredRechargeCoinAmount(),
|
||
UserRechargeCoinAmount: item.GetUserRechargeCoinAmount(),
|
||
PurchaseLockedReason: item.GetPurchaseLockedReason(),
|
||
Benefits: vipBenefitsFromProto(item.GetBenefits()),
|
||
ConfigVersion: item.GetConfigVersion(),
|
||
}
|
||
}
|
||
|
||
func vipProgramConfigFromProto(config *walletv1.VipProgramConfig) vipProgramConfigData {
|
||
if config == nil {
|
||
return vipProgramConfigData{}
|
||
}
|
||
return vipProgramConfigData{
|
||
AppCode: config.GetAppCode(),
|
||
ProgramType: config.GetProgramType(),
|
||
LevelCount: config.GetLevelCount(),
|
||
SameLevelExpiryPolicy: config.GetSameLevelExpiryPolicy(),
|
||
UpgradeExpiryPolicy: config.GetUpgradeExpiryPolicy(),
|
||
DowngradePurchasePolicy: config.GetDowngradePurchasePolicy(),
|
||
BenefitInheritancePolicy: config.GetBenefitInheritancePolicy(),
|
||
GrantMode: config.GetGrantMode(),
|
||
TrialCardEnabled: config.GetTrialCardEnabled(),
|
||
Status: config.GetStatus(),
|
||
ConfigVersion: config.GetConfigVersion(),
|
||
UpdatedByAdminID: config.GetUpdatedByAdminId(),
|
||
CreatedAtMS: config.GetCreatedAtMs(),
|
||
UpdatedAtMS: config.GetUpdatedAtMs(),
|
||
}
|
||
}
|
||
|
||
func vipBenefitsFromProto(items []*walletv1.VipBenefit) []vipBenefitData {
|
||
result := make([]vipBenefitData, 0, len(items))
|
||
for _, item := range items {
|
||
if item == nil {
|
||
continue
|
||
}
|
||
result = append(result, vipBenefitData{
|
||
BenefitCode: item.GetBenefitCode(),
|
||
Name: item.GetName(),
|
||
BenefitType: item.GetBenefitType(),
|
||
UnlockLevel: item.GetUnlockLevel(),
|
||
Status: item.GetStatus(),
|
||
TrialEnabled: item.GetTrialEnabled(),
|
||
ResourceID: item.GetResourceId(),
|
||
ResourceType: item.GetResourceType(),
|
||
ExecutionScope: item.GetExecutionScope(),
|
||
AutoEquip: item.GetAutoEquip(),
|
||
SortOrder: item.GetSortOrder(),
|
||
MetadataJSON: item.GetMetadataJson(),
|
||
CreatedAtMS: item.GetCreatedAtMs(),
|
||
UpdatedAtMS: item.GetUpdatedAtMs(),
|
||
})
|
||
}
|
||
return result
|
||
}
|
||
|
||
func vipTrialCardFromProto(card *walletv1.VipTrialCard) *vipTrialCardData {
|
||
if card == nil {
|
||
return nil
|
||
}
|
||
return &vipTrialCardData{
|
||
TrialCardID: card.GetTrialCardId(),
|
||
EntitlementID: card.GetEntitlementId(),
|
||
ResourceID: card.GetResourceId(),
|
||
UserID: card.GetUserId(),
|
||
Level: card.GetLevel(),
|
||
Name: card.GetName(),
|
||
Status: card.GetStatus(),
|
||
Equipped: card.GetEquipped(),
|
||
DurationMS: card.GetDurationMs(),
|
||
EffectiveAtMS: card.GetEffectiveAtMs(),
|
||
ExpiresAtMS: card.GetExpiresAtMs(),
|
||
RemainingDurationMS: card.GetRemainingDurationMs(),
|
||
GrantSource: card.GetGrantSource(),
|
||
SourceGrantID: card.GetSourceGrantId(),
|
||
CreatedAtMS: card.GetCreatedAtMs(),
|
||
UpdatedAtMS: card.GetUpdatedAtMs(),
|
||
}
|
||
}
|
||
|
||
func vipStateFromProto(state *walletv1.VipState) vipStateData {
|
||
if state == nil {
|
||
return vipStateData{
|
||
EffectiveBenefits: []vipBenefitData{},
|
||
UserSettings: vipUserSettingsData{
|
||
RoomEntryNoticeEnabled: true, OnlineGlobalNoticeEnabled: true,
|
||
},
|
||
}
|
||
}
|
||
return vipStateData{
|
||
PaidVIP: vipFromProto(state.GetPaidVip()),
|
||
EquippedTrialCard: vipTrialCardFromProto(state.GetEquippedTrialCard()),
|
||
EffectiveVIP: vipFromProto(state.GetEffectiveVip()),
|
||
EffectiveSource: state.GetEffectiveSource(),
|
||
EffectiveBenefits: vipBenefitsFromProto(state.GetEffectiveBenefits()),
|
||
EvaluatedAtMS: state.GetEvaluatedAtMs(),
|
||
ProgramConfig: vipProgramConfigFromProto(state.GetProgramConfig()),
|
||
UserSettings: vipUserSettingsFromProto(state.GetUserSettings()),
|
||
}
|
||
}
|
||
|
||
func vipUserSettingsFromProto(settings *walletv1.VipUserSettings) vipUserSettingsData {
|
||
if settings == nil {
|
||
// wallet 滚动升级期间旧实例没有 tag 8;与 wallet owner 的缺行语义一致,按默认开启兼容。
|
||
return vipUserSettingsData{RoomEntryNoticeEnabled: true, OnlineGlobalNoticeEnabled: true}
|
||
}
|
||
return vipUserSettingsData{
|
||
AppCode: settings.GetAppCode(), UserID: settings.GetUserId(),
|
||
RoomEntryNoticeEnabled: settings.GetRoomEntryNoticeEnabled(),
|
||
OnlineGlobalNoticeEnabled: settings.GetOnlineGlobalNoticeEnabled(),
|
||
UpdatedAtMS: settings.GetUpdatedAtMs(),
|
||
}
|
||
}
|
||
|
||
func vipRewardItemsFromProto(items []*walletv1.VipRewardItem) []vipRewardItemData {
|
||
resp := make([]vipRewardItemData, 0, len(items))
|
||
for _, item := range items {
|
||
if item == nil {
|
||
continue
|
||
}
|
||
resp = append(resp, vipRewardItemData{
|
||
ResourceID: item.GetResourceId(),
|
||
ResourceCode: item.GetResourceCode(),
|
||
ResourceType: item.GetResourceType(),
|
||
Name: item.GetName(),
|
||
Quantity: item.GetQuantity(),
|
||
ExpiresAtMS: item.GetExpiresAtMs(),
|
||
CoverURL: vipRewardCoverURL(item),
|
||
AssetURL: item.GetAssetUrl(),
|
||
PreviewURL: item.GetPreviewUrl(),
|
||
AnimationURL: item.GetAnimationUrl(),
|
||
})
|
||
}
|
||
return resp
|
||
}
|
||
|
||
func vipRewardCoverURL(item *walletv1.VipRewardItem) string {
|
||
if item == nil {
|
||
return ""
|
||
}
|
||
if strings.TrimSpace(item.GetPreviewUrl()) != "" {
|
||
return item.GetPreviewUrl()
|
||
}
|
||
return item.GetAssetUrl()
|
||
}
|