2026-07-14 14:08:08 +08:00

323 lines
14 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 activityapi
import (
"net/http"
"strings"
activityv1 "hyapp.local/api/proto/activity/v1"
"hyapp/services/gateway-service/internal/auth"
"hyapp/services/gateway-service/internal/transport/http/httpkit"
)
type levelOverviewData struct {
Tracks []levelTrackOverviewData `json:"tracks"`
ServerTimeMS int64 `json:"server_time_ms"`
}
type levelTrackOverviewData struct {
Track string `json:"track"`
Name string `json:"name"`
Level int32 `json:"level"`
TierID int64 `json:"tier_id"`
TotalValue int64 `json:"total_value"`
CurrentLevelRequiredValue int64 `json:"current_level_required_value"`
NextLevel int32 `json:"next_level"`
NextLevelRequiredValue int64 `json:"next_level_required_value"`
DisplayAvatarFrameResourceID int64 `json:"display_avatar_frame_resource_id"`
DisplayAvatarFrameResource *achievementResourceData `json:"display_avatar_frame_resource,omitempty"`
DisplayBadgeResourceID int64 `json:"display_badge_resource_id"`
DisplayBadgeResource *achievementResourceData `json:"display_badge_resource,omitempty"`
DisplayBadgeSourceLevel int32 `json:"display_badge_source_level"`
RewardPendingCount int64 `json:"reward_pending_count"`
SortOrder int32 `json:"sort_order"`
}
type levelTrackDetailData struct {
Overview levelTrackOverviewData `json:"overview"`
Rules []levelRuleData `json:"rules"`
Tiers []levelTierData `json:"tiers"`
ServerTimeMS int64 `json:"server_time_ms"`
}
type levelRuleData struct {
Track string `json:"track"`
Level int32 `json:"level"`
RequiredValue int64 `json:"required_value"`
Name string `json:"name"`
Status string `json:"status"`
RewardResourceGroupID int64 `json:"reward_resource_group_id"`
RewardResourceGroup *achievementResourceGroupData `json:"reward_resource_group,omitempty"`
SortOrder int32 `json:"sort_order"`
DisplayConfigJSON string `json:"display_config_json"`
CreatedByAdminID int64 `json:"created_by_admin_id"`
UpdatedByAdminID int64 `json:"updated_by_admin_id"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
}
type levelTierData struct {
TierID int64 `json:"tier_id"`
Track string `json:"track"`
MinLevel int32 `json:"min_level"`
MaxLevel int32 `json:"max_level"`
Name string `json:"name"`
DisplayAvatarFrameResourceID int64 `json:"display_avatar_frame_resource_id"`
DisplayAvatarFrameResource *achievementResourceData `json:"display_avatar_frame_resource,omitempty"`
DisplayBadgeResourceID int64 `json:"display_badge_resource_id"`
DisplayBadgeResource *achievementResourceData `json:"display_badge_resource,omitempty"`
RewardResourceGroupID int64 `json:"reward_resource_group_id"`
RewardResourceGroup *achievementResourceGroupData `json:"reward_resource_group,omitempty"`
Status string `json:"status"`
DisplayConfigJSON string `json:"display_config_json"`
CreatedByAdminID int64 `json:"created_by_admin_id"`
UpdatedByAdminID int64 `json:"updated_by_admin_id"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
}
type levelRewardData struct {
RewardJobID string `json:"reward_job_id"`
UserID int64 `json:"user_id"`
Track string `json:"track"`
RewardSourceType string `json:"reward_source_type"`
RewardSourceID string `json:"reward_source_id"`
ResourceGroupID int64 `json:"resource_group_id"`
WalletCommandID string `json:"wallet_command_id"`
WalletGrantID string `json:"wallet_grant_id"`
Status string `json:"status"`
AttemptCount int32 `json:"attempt_count"`
FailureReason string `json:"failure_reason"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
}
func (h *Handler) getMyLevelOverview(writer http.ResponseWriter, request *http.Request) {
if h.growthLevelClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
resp, err := h.growthLevelClient.GetMyLevelOverview(request.Context(), &activityv1.GetMyLevelOverviewRequest{
Meta: httpkit.ActivityMeta(request),
UserId: auth.UserIDFromContext(request.Context()),
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
// activity-service 只拥有等级关系和资源 ID素材 URL 属于 wallet-service。Gateway 在同一响应补齐当前等级素材,
// 避免 H5 再依赖后台接口或把线上资源 ID 映射硬编码进页面。
resources, ok := h.resolveAchievementResources(writer, request, levelOverviewResourceIDs(resp.GetTracks()))
if !ok {
return
}
tracks := make([]levelTrackOverviewData, 0, len(resp.GetTracks()))
for _, item := range resp.GetTracks() {
tracks = append(tracks, levelTrackOverviewFromProto(item, resources))
}
httpkit.WriteOK(writer, request, levelOverviewData{Tracks: tracks, ServerTimeMS: resp.GetServerTimeMs()})
}
func (h *Handler) getLevelTrack(writer http.ResponseWriter, request *http.Request) {
if h.growthLevelClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
track := strings.TrimSpace(request.PathValue("track"))
if track == "" {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
resp, err := h.growthLevelClient.GetLevelTrack(request.Context(), &activityv1.GetLevelTrackRequest{
Meta: httpkit.ActivityMeta(request),
UserId: auth.UserIDFromContext(request.Context()),
Track: track,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
// 等级层页面必须一次拿到区间、徽章、头像框和升级礼物;这里按正 ID 去重后解析,
// 既保持 activity/wallet 的 owner 边界,也避免前端为每一行产生额外请求。
resources, ok := h.resolveAchievementResources(writer, request, levelTrackResourceIDs(resp.GetOverview(), resp.GetTiers()))
if !ok {
return
}
resourceGroups, ok := h.resolveAchievementResourceGroups(writer, request, levelTrackResourceGroupIDs(resp.GetRules(), resp.GetTiers()))
if !ok {
return
}
rules := make([]levelRuleData, 0, len(resp.GetRules()))
for _, rule := range resp.GetRules() {
rules = append(rules, levelRuleFromProto(rule, resourceGroups))
}
tiers := make([]levelTierData, 0, len(resp.GetTiers()))
for _, tier := range resp.GetTiers() {
tiers = append(tiers, levelTierFromProto(tier, resources, resourceGroups))
}
httpkit.WriteOK(writer, request, levelTrackDetailData{
Overview: levelTrackOverviewFromProto(resp.GetOverview(), resources),
Rules: rules,
Tiers: tiers,
ServerTimeMS: resp.GetServerTimeMs(),
})
}
func (h *Handler) listLevelRewards(writer http.ResponseWriter, request *http.Request) {
if h.growthLevelClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
page, ok := httpkit.PositiveInt32Query(request, "page", 1)
if !ok {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
pageSize, ok := httpkit.PositiveInt32Query(request, "page_size", 20)
if !ok {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
resp, err := h.growthLevelClient.ListLevelRewards(request.Context(), &activityv1.ListLevelRewardsRequest{
Meta: httpkit.ActivityMeta(request),
UserId: auth.UserIDFromContext(request.Context()),
Track: strings.TrimSpace(request.URL.Query().Get("track")),
Status: strings.TrimSpace(request.URL.Query().Get("status")),
Page: page,
PageSize: pageSize,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
rewards := make([]levelRewardData, 0, len(resp.GetRewards()))
for _, reward := range resp.GetRewards() {
rewards = append(rewards, levelRewardFromProto(reward))
}
httpkit.WriteOK(writer, request, map[string]any{
"rewards": rewards,
"total": resp.GetTotal(),
"page": page,
"page_size": pageSize,
})
}
func levelTrackOverviewFromProto(item *activityv1.LevelTrackOverview, resources map[int64]achievementResourceData) levelTrackOverviewData {
data := levelTrackOverviewData{
Track: item.GetTrack(),
Name: item.GetName(),
Level: item.GetLevel(),
TierID: item.GetTierId(),
TotalValue: item.GetTotalValue(),
CurrentLevelRequiredValue: item.GetCurrentLevelRequiredValue(),
NextLevel: item.GetNextLevel(),
NextLevelRequiredValue: item.GetNextLevelRequiredValue(),
DisplayAvatarFrameResourceID: item.GetDisplayAvatarFrameResourceId(),
DisplayBadgeResourceID: item.GetDisplayBadgeResourceId(),
DisplayBadgeSourceLevel: item.GetDisplayBadgeSourceLevel(),
RewardPendingCount: item.GetRewardPendingCount(),
SortOrder: item.GetSortOrder(),
}
if resource, ok := resources[item.GetDisplayAvatarFrameResourceId()]; ok {
data.DisplayAvatarFrameResource = &resource
}
if resource, ok := resources[item.GetDisplayBadgeResourceId()]; ok {
data.DisplayBadgeResource = &resource
}
return data
}
func levelRuleFromProto(item *activityv1.LevelRule, resourceGroups map[int64]achievementResourceGroupData) levelRuleData {
data := levelRuleData{
Track: item.GetTrack(),
Level: item.GetLevel(),
RequiredValue: item.GetRequiredValue(),
Name: item.GetName(),
Status: item.GetStatus(),
RewardResourceGroupID: item.GetRewardResourceGroupId(),
SortOrder: item.GetSortOrder(),
DisplayConfigJSON: item.GetDisplayConfigJson(),
CreatedByAdminID: item.GetCreatedByAdminId(),
UpdatedByAdminID: item.GetUpdatedByAdminId(),
CreatedAtMS: item.GetCreatedAtMs(),
UpdatedAtMS: item.GetUpdatedAtMs(),
}
if group, ok := resourceGroups[item.GetRewardResourceGroupId()]; ok {
data.RewardResourceGroup = &group
}
return data
}
func levelTierFromProto(item *activityv1.LevelTier, resources map[int64]achievementResourceData, resourceGroups map[int64]achievementResourceGroupData) levelTierData {
data := levelTierData{
TierID: item.GetTierId(),
Track: item.GetTrack(),
MinLevel: item.GetMinLevel(),
MaxLevel: item.GetMaxLevel(),
Name: item.GetName(),
DisplayAvatarFrameResourceID: item.GetDisplayAvatarFrameResourceId(),
DisplayBadgeResourceID: item.GetDisplayBadgeResourceId(),
RewardResourceGroupID: item.GetRewardResourceGroupId(),
Status: item.GetStatus(),
DisplayConfigJSON: item.GetDisplayConfigJson(),
CreatedByAdminID: item.GetCreatedByAdminId(),
UpdatedByAdminID: item.GetUpdatedByAdminId(),
CreatedAtMS: item.GetCreatedAtMs(),
UpdatedAtMS: item.GetUpdatedAtMs(),
}
if resource, ok := resources[item.GetDisplayAvatarFrameResourceId()]; ok {
data.DisplayAvatarFrameResource = &resource
}
if resource, ok := resources[item.GetDisplayBadgeResourceId()]; ok {
data.DisplayBadgeResource = &resource
}
if group, ok := resourceGroups[item.GetRewardResourceGroupId()]; ok {
data.RewardResourceGroup = &group
}
return data
}
func levelOverviewResourceIDs(items []*activityv1.LevelTrackOverview) []int64 {
ids := make([]int64, 0, len(items)*2)
for _, item := range items {
ids = append(ids, item.GetDisplayAvatarFrameResourceId(), item.GetDisplayBadgeResourceId())
}
return ids
}
func levelTrackResourceIDs(overview *activityv1.LevelTrackOverview, tiers []*activityv1.LevelTier) []int64 {
ids := levelOverviewResourceIDs([]*activityv1.LevelTrackOverview{overview})
for _, tier := range tiers {
ids = append(ids, tier.GetDisplayAvatarFrameResourceId(), tier.GetDisplayBadgeResourceId())
}
return ids
}
func levelTrackResourceGroupIDs(rules []*activityv1.LevelRule, tiers []*activityv1.LevelTier) []int64 {
ids := make([]int64, 0, len(rules)+len(tiers))
for _, rule := range rules {
ids = append(ids, rule.GetRewardResourceGroupId())
}
for _, tier := range tiers {
ids = append(ids, tier.GetRewardResourceGroupId())
}
return ids
}
func levelRewardFromProto(item *activityv1.LevelRewardJob) levelRewardData {
return levelRewardData{
RewardJobID: item.GetRewardJobId(),
UserID: item.GetUserId(),
Track: item.GetTrack(),
RewardSourceType: item.GetRewardSourceType(),
RewardSourceID: item.GetRewardSourceId(),
ResourceGroupID: item.GetResourceGroupId(),
WalletCommandID: item.GetWalletCommandId(),
WalletGrantID: item.GetWalletGrantId(),
Status: item.GetStatus(),
AttemptCount: item.GetAttemptCount(),
FailureReason: item.GetFailureReason(),
CreatedAtMS: item.GetCreatedAtMs(),
UpdatedAtMS: item.GetUpdatedAtMs(),
}
}