323 lines
14 KiB
Go
323 lines
14 KiB
Go
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(),
|
||
}
|
||
}
|