package activityapi import ( "hyapp/services/gateway-service/internal/transport/http/httpkit" "net/http" "strings" activityv1 "hyapp.local/api/proto/activity/v1" "hyapp/services/gateway-service/internal/auth" ) type taskSectionData struct { Section string `json:"section"` Items []taskItemData `json:"items"` ServerTimeMS int64 `json:"server_time_ms"` NextRefreshAtMS int64 `json:"next_refresh_at_ms"` } type taskItemData struct { TaskID string `json:"task_id"` TaskType string `json:"task_type"` Category string `json:"category"` MetricType string `json:"metric_type"` Title string `json:"title"` Description string `json:"description"` TargetValue int64 `json:"target_value"` TargetUnit string `json:"target_unit"` ProgressValue int64 `json:"progress_value"` RewardCoinAmount int64 `json:"reward_coin_amount"` Status string `json:"status"` Claimable bool `json:"claimable"` TaskDay string `json:"task_day"` ServerTimeMS int64 `json:"server_time_ms"` NextRefreshAtMS int64 `json:"next_refresh_at_ms"` SortOrder int32 `json:"sort_order"` Version int64 `json:"version"` } type taskClaimData struct { ClaimID string `json:"claim_id"` TaskID string `json:"task_id"` TaskType string `json:"task_type"` TaskDay string `json:"task_day"` RewardCoinAmount int64 `json:"reward_coin_amount"` Status string `json:"status"` WalletTransactionID string `json:"wallet_transaction_id"` GrantedAtMS int64 `json:"granted_at_ms"` Claimed bool `json:"claimed"` } type taskClaimRequestBody struct { TaskID string `json:"task_id"` TaskIDCamel string `json:"taskId"` TaskType string `json:"task_type"` TaskTypeCamel string `json:"taskType"` TaskDay string `json:"task_day"` TaskDayCamel string `json:"taskDay"` CommandID string `json:"command_id"` CommandIDCamel string `json:"commandId"` } func (b taskClaimRequestBody) normalizedTaskID() string { if value := strings.TrimSpace(b.TaskID); value != "" { return value } return strings.TrimSpace(b.TaskIDCamel) } func (b taskClaimRequestBody) normalizedTaskType() string { if value := strings.TrimSpace(b.TaskType); value != "" { return value } return strings.TrimSpace(b.TaskTypeCamel) } func (b taskClaimRequestBody) normalizedTaskDay() string { if value := strings.TrimSpace(b.TaskDay); value != "" { return value } return strings.TrimSpace(b.TaskDayCamel) } func (b taskClaimRequestBody) normalizedCommandID() string { if value := strings.TrimSpace(b.CommandID); value != "" { return value } return strings.TrimSpace(b.CommandIDCamel) } // listTaskTabs 返回当前用户任务页;进度由 activity-service 事实表决定。 func (h *Handler) listTaskTabs(writer http.ResponseWriter, request *http.Request) { if h.taskClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } resp, err := h.taskClient.ListUserTasks(request.Context(), &activityv1.ListUserTasksRequest{ Meta: httpkit.ActivityMeta(request), UserId: auth.UserIDFromContext(request.Context()), }) if err != nil { httpkit.WriteRPCError(writer, request, err) return } sections := make([]taskSectionData, 0, len(resp.GetSections())) for _, section := range resp.GetSections() { items := make([]taskItemData, 0, len(section.GetItems())) for _, item := range section.GetItems() { items = append(items, taskItemFromProto(item)) } sections = append(sections, taskSectionData{ Section: section.GetSection(), Items: items, ServerTimeMS: section.GetServerTimeMs(), NextRefreshAtMS: section.GetNextRefreshAtMs(), }) } httpkit.WriteOK(writer, request, map[string]any{ "sections": sections, "server_time_ms": resp.GetServerTimeMs(), "next_refresh_at_ms": resp.GetNextRefreshAtMs(), }) } // claimTaskReward 只透传当前用户身份和幂等命令,完成校验及发奖在 activity-service 内完成。 func (h *Handler) claimTaskReward(writer http.ResponseWriter, request *http.Request) { if h.taskClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } var body taskClaimRequestBody if !httpkit.Decode(writer, request, &body) { return } taskID := body.normalizedTaskID() taskType := body.normalizedTaskType() taskDay := body.normalizedTaskDay() // command_id 是客户端动作的幂等锚点,gateway 只裁剪空白,不替客户端补值。 commandID := strings.TrimSpace(body.normalizedCommandID()) if commandID == "" || taskID == "" || taskType == "" || taskDay == "" { httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") return } resp, err := h.taskClient.ClaimTaskReward(request.Context(), &activityv1.ClaimTaskRewardRequest{ Meta: httpkit.ActivityMeta(request), UserId: auth.UserIDFromContext(request.Context()), TaskId: taskID, TaskType: taskType, TaskDay: taskDay, CommandId: commandID, }) if err != nil { httpkit.WriteRPCError(writer, request, err) return } httpkit.WriteOK(writer, request, taskClaimData{ ClaimID: resp.GetClaimId(), TaskID: resp.GetTaskId(), TaskType: resp.GetTaskType(), TaskDay: resp.GetTaskDay(), RewardCoinAmount: resp.GetRewardCoinAmount(), Status: resp.GetStatus(), WalletTransactionID: resp.GetWalletTransactionId(), GrantedAtMS: resp.GetGrantedAtMs(), Claimed: resp.GetClaimed(), }) } func taskItemFromProto(item *activityv1.TaskItem) taskItemData { return taskItemData{ TaskID: item.GetTaskId(), TaskType: item.GetTaskType(), Category: item.GetCategory(), MetricType: item.GetMetricType(), Title: item.GetTitle(), Description: item.GetDescription(), TargetValue: item.GetTargetValue(), TargetUnit: item.GetTargetUnit(), ProgressValue: item.GetProgressValue(), RewardCoinAmount: item.GetRewardCoinAmount(), Status: item.GetStatus(), Claimable: item.GetClaimable(), TaskDay: item.GetTaskDay(), ServerTimeMS: item.GetServerTimeMs(), NextRefreshAtMS: item.GetNextRefreshAtMs(), SortOrder: item.GetSortOrder(), Version: item.GetVersion(), } }