package controller import ( "context" "net/http" "strconv" "time" "dubbo.apache.org/dubbo-go/v3/client" "dubbo.apache.org/dubbo-go/v3/common/constant" "github.com/gin-gonic/gin" "github.com/topfans/backend/gateway/pkg/response" pbCommon "github.com/topfans/backend/pkg/proto/common" pbTask "github.com/topfans/backend/pkg/proto/task" "github.com/topfans/backend/pkg/logger" "go.uber.org/zap" ) // TaskController 任务控制器 type TaskController struct { taskMobileService pbTask.TaskMobileService } // NewTaskController 创建任务控制器 func NewTaskController(dubboClient *client.Client) (*TaskController, error) { taskMobileService, err := pbTask.NewTaskMobileService(dubboClient) if err != nil { return nil, err } return &TaskController{ taskMobileService: taskMobileService, }, nil } // GetDailyTasks 获取每日任务列表 // @Summary 获取每日任务列表 // @Description 获取每日任务列表 // @Tags tasks // @Accept json // @Produce json // @Security BearerAuth // @Param star_id query int64 true "粉丝身份ID" // @Success 200 {object} response.Response // @Router /api/tasks/daily [get] func (ctrl *TaskController) GetDailyTasks(c *gin.Context) { starIDStr := c.Query("star_id") if starIDStr == "" { response.Error(c, http.StatusBadRequest, "star_id 是必填参数") return } starID, err := strconv.ParseInt(starIDStr, 10, 64) if err != nil { response.Error(c, http.StatusBadRequest, "star_id 参数错误") return } userID, _ := c.Get("user_id") logger.Logger.Info("GetDailyTasks request", zap.Int64("user_id", userID.(int64)), zap.Int64("star_id", starID), ) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{ "user_id": strconv.FormatInt(userID.(int64), 10), "star_id": strconv.FormatInt(starID, 10), }) resp, err := ctrl.taskMobileService.GetDailyTasks(ctx, &pbTask.GetDailyTasksRequest{ StarId: starID, }) if err != nil { logger.Logger.Error("GetDailyTasks RPC failed", zap.Error(err)) response.Error(c, http.StatusInternalServerError, "获取每日任务列表失败") return } if resp.Base.Code != pbCommon.StatusCode_STATUS_OK { response.ErrorWithCode(c, int(resp.Base.Code), resp.Base.Message) return } data := convertDailyTasksResponse(resp) response.Success(c, data) } // ReportEvent 上报任务事件 // @Summary 上报任务事件 // @Description 上报任务事件,如 daily_login, daily_browse_asset // @Tags tasks // @Accept json // @Produce json // @Security BearerAuth // @Param request body pbTask.ReportEventRequest true "事件上报请求" // @Success 200 {object} response.Response // @Router /api/tasks/report-event [post] func (ctrl *TaskController) ReportEvent(c *gin.Context) { userID, _ := c.Get("user_id") var req struct { EventType string `json:"event_type"` StarId int64 `json:"star_id"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, http.StatusBadRequest, "请求参数错误") return } if req.EventType == "" { response.Error(c, http.StatusBadRequest, "event_type 是必填参数") return } logger.Logger.Info("ReportEvent request", zap.Int64("user_id", userID.(int64)), zap.String("event_type", req.EventType), zap.Int64("star_id", req.StarId), ) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{ "user_id": strconv.FormatInt(userID.(int64), 10), "star_id": strconv.FormatInt(req.StarId, 10), }) resp, err := ctrl.taskMobileService.ReportEvent(ctx, &pbTask.ReportEventRequest{ EventType: req.EventType, StarId: req.StarId, }) if err != nil { logger.Logger.Error("ReportEvent RPC failed", zap.Error(err)) response.Error(c, http.StatusInternalServerError, "上报事件失败") return } if resp.Base.Code != pbCommon.StatusCode_STATUS_OK { response.ErrorWithCode(c, int(resp.Base.Code), resp.Base.Message) return } response.Success(c, map[string]interface{}{ "success": resp.Success, "task_key": resp.TaskKey, "task_completed": resp.TaskCompleted, "message": resp.Message, }) } // ClaimDailyTask 领取单个每日任务奖励 // @Summary 领取每日任务奖励 // @Description 领取单个每日任务奖励 // @Tags tasks // @Accept json // @Produce json // @Security BearerAuth // @Param request body pbTask.ClaimDailyTaskRequest true "领取请求" // @Success 200 {object} response.Response // @Router /api/tasks/daily/claim [post] func (ctrl *TaskController) ClaimDailyTask(c *gin.Context) { userID, _ := c.Get("user_id") var req struct { TaskKey string `json:"task_key"` StarId int64 `json:"star_id"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, http.StatusBadRequest, "请求参数错误") return } if req.TaskKey == "" { response.Error(c, http.StatusBadRequest, "task_key 是必填参数") return } logger.Logger.Info("ClaimDailyTask request", zap.Int64("user_id", userID.(int64)), zap.String("task_key", req.TaskKey), zap.Int64("star_id", req.StarId), ) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{ "user_id": strconv.FormatInt(userID.(int64), 10), "star_id": strconv.FormatInt(req.StarId, 10), }) resp, err := ctrl.taskMobileService.ClaimDailyTask(ctx, &pbTask.ClaimDailyTaskRequest{ TaskKey: req.TaskKey, StarId: req.StarId, }) if err != nil { logger.Logger.Error("ClaimDailyTask RPC failed", zap.Error(err)) response.Error(c, http.StatusInternalServerError, "领取任务奖励失败") return } if resp.Base.Code != pbCommon.StatusCode_STATUS_OK { response.ErrorWithCode(c, int(resp.Base.Code), resp.Base.Message) return } response.Success(c, map[string]interface{}{ "success": resp.Success, "crystal_balance": resp.CrystalBalance, "experience": resp.Experience, }) } // ClaimAllDailyTasks 一键领取所有每日任务 // @Summary 一键领取所有每日任务 // @Description 一键领取所有可领取的每日任务奖励 // @Tags tasks // @Accept json // @Produce json // @Security BearerAuth // @Param star_id query int64 true "粉丝身份ID" // @Success 200 {object} response.Response // @Router /api/tasks/daily/claim-all [post] func (ctrl *TaskController) ClaimAllDailyTasks(c *gin.Context) { userID, _ := c.Get("user_id") starIDStr := c.Query("star_id") if starIDStr == "" { response.Error(c, http.StatusBadRequest, "star_id 是必填参数") return } starID, err := strconv.ParseInt(starIDStr, 10, 64) if err != nil { response.Error(c, http.StatusBadRequest, "star_id 参数错误") return } logger.Logger.Info("ClaimAllDailyTasks request", zap.Int64("user_id", userID.(int64)), zap.Int64("star_id", starID), ) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{ "user_id": strconv.FormatInt(userID.(int64), 10), "star_id": strconv.FormatInt(starID, 10), }) resp, err := ctrl.taskMobileService.ClaimAllDailyTasks(ctx, &pbTask.ClaimAllDailyTasksRequest{ StarId: starID, }) if err != nil { logger.Logger.Error("ClaimAllDailyTasks RPC failed", zap.Error(err)) response.Error(c, http.StatusInternalServerError, "一键领取失败") return } if resp.Base.Code != pbCommon.StatusCode_STATUS_OK { response.ErrorWithCode(c, int(resp.Base.Code), resp.Base.Message) return } response.Success(c, map[string]interface{}{ "claimed_count": resp.ClaimedCount, "crystal_balance": resp.CrystalBalance, "experience": resp.Experience, "claimed_task_keys": resp.ClaimedTaskKeys, }) } // CompleteGuide 完成引导任务 // @Summary 完成引导任务 // @Description 完成引导任务步骤 // @Tags tasks // @Accept json // @Produce json // @Security BearerAuth // @Param request body pbTask.CompleteGuideRequest true "引导完成请求" // @Success 200 {object} response.Response // @Router /api/tasks/guide/complete [post] func (ctrl *TaskController) CompleteGuide(c *gin.Context) { userID, _ := c.Get("user_id") var req struct { TaskKey string `json:"task_key"` Stages []*pbTask.OnboardingStage `json:"stages"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, http.StatusBadRequest, "请求参数错误") return } if req.TaskKey == "" { response.Error(c, http.StatusBadRequest, "task_key 是必填参数") return } logger.Logger.Info("CompleteGuide request", zap.Int64("user_id", userID.(int64)), zap.String("task_key", req.TaskKey), zap.Int("stages_count", len(req.Stages)), ) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{ "user_id": strconv.FormatInt(userID.(int64), 10), "star_id": "0", }) resp, err := ctrl.taskMobileService.CompleteGuide(ctx, &pbTask.CompleteGuideRequest{ TaskKey: req.TaskKey, Stages: req.Stages, }) if err != nil { logger.Logger.Error("CompleteGuide RPC failed", zap.Error(err)) response.Error(c, http.StatusInternalServerError, "完成引导失败") return } if resp.Base.Code != pbCommon.StatusCode_STATUS_OK { response.ErrorWithCode(c, int(resp.Base.Code), resp.Base.Message) return } data := convertCompleteGuideResponse(resp) response.Success(c, data) } // GetOnboardingStatus 获取引导状态 // @Summary 获取引导状态 // @Description 获取新手引导状态 // @Tags tasks // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} response.Response // @Router /api/tasks/onboarding/status [get] func (ctrl *TaskController) GetOnboardingStatus(c *gin.Context) { userID, _ := c.Get("user_id") logger.Logger.Info("GetOnboardingStatus request", zap.Int64("user_id", userID.(int64)), ) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{ "user_id": strconv.FormatInt(userID.(int64), 10), "star_id": "0", }) resp, err := ctrl.taskMobileService.GetOnboardingStatus(ctx, &pbTask.GetOnboardingStatusRequest{}) if err != nil { logger.Logger.Error("GetOnboardingStatus RPC failed", zap.Error(err)) response.Error(c, http.StatusInternalServerError, "获取引导状态失败") return } if resp.Base.Code != pbCommon.StatusCode_STATUS_OK { response.ErrorWithCode(c, int(resp.Base.Code), resp.Base.Message) return } data := convertOnboardingStatusResponse(resp) response.Success(c, data) } // AdvanceStage 进入下一引导阶段 // @Summary 进入下一引导阶段 // @Description 进入新手引导的下一阶段 // @Tags tasks // @Accept json // @Produce json // @Security BearerAuth // @Param request body pbTask.AdvanceStageRequest true "阶段推进请求" // @Success 200 {object} response.Response // @Router /api/tasks/onboarding/advance-stage [post] func (ctrl *TaskController) AdvanceStage(c *gin.Context) { userID, _ := c.Get("user_id") var req struct { TargetStage int32 `json:"target_stage"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, http.StatusBadRequest, "请求参数错误") return } logger.Logger.Info("AdvanceStage request", zap.Int64("user_id", userID.(int64)), zap.Int32("target_stage", req.TargetStage), ) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{ "user_id": strconv.FormatInt(userID.(int64), 10), "star_id": "0", }) resp, err := ctrl.taskMobileService.AdvanceStage(ctx, &pbTask.AdvanceStageRequest{ TargetStage: req.TargetStage, }) if err != nil { logger.Logger.Error("AdvanceStage RPC failed", zap.Error(err)) response.Error(c, http.StatusInternalServerError, "进入下一阶段失败") return } if resp.Base.Code != pbCommon.StatusCode_STATUS_OK { response.ErrorWithCode(c, int(resp.Base.Code), resp.Base.Message) return } data := convertAdvanceStageResponse(resp) response.Success(c, data) } // ClaimOnboardingReward 领取引导阶段奖励 // @Summary 领取引导阶段奖励 // @Description 领取新手引导阶段奖励 // @Tags tasks // @Accept json // @Produce json // @Security BearerAuth // @Param request body pbTask.ClaimOnboardingRewardRequest true "领取奖励请求" // @Success 200 {object} response.Response // @Router /api/tasks/onboarding/claim-reward [post] func (ctrl *TaskController) ClaimOnboardingReward(c *gin.Context) { userID, _ := c.Get("user_id") var req struct { Stage int32 `json:"stage"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, http.StatusBadRequest, "请求参数错误") return } logger.Logger.Info("ClaimOnboardingReward request", zap.Int64("user_id", userID.(int64)), zap.Int32("stage", req.Stage), ) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{ "user_id": strconv.FormatInt(userID.(int64), 10), "star_id": "0", }) resp, err := ctrl.taskMobileService.ClaimOnboardingReward(ctx, &pbTask.ClaimOnboardingRewardRequest{ Stage: req.Stage, }) if err != nil { logger.Logger.Error("ClaimOnboardingReward RPC failed", zap.Error(err)) response.Error(c, http.StatusInternalServerError, "领取引导奖励失败") return } if resp.Base.Code != pbCommon.StatusCode_STATUS_OK { response.ErrorWithCode(c, int(resp.Base.Code), resp.Base.Message) return } response.Success(c, map[string]interface{}{ "success": resp.Success, "crystal_balance": resp.CrystalBalance, "experience": resp.Experience, }) } // GetExhibitionRevenue 获取展示收益 // @Summary 获取展示收益 // @Description 获取展馆收益记录 // @Tags tasks // @Accept json // @Produce json // @Security BearerAuth // @Param star_id query int64 true "粉丝身份ID" // @Param status query string false "状态筛选:claimable/claimed" // @Param page query int false "页码,默认1" // @Param page_size query int false "每页数量,默认10" // @Success 200 {object} response.Response // @Router /api/tasks/exhibition-revenue [get] func (ctrl *TaskController) GetExhibitionRevenue(c *gin.Context) { userID, _ := c.Get("user_id") starIDStr := c.Query("star_id") if starIDStr == "" { response.Error(c, http.StatusBadRequest, "star_id 是必填参数") return } starID, err := strconv.ParseInt(starIDStr, 10, 64) if err != nil { response.Error(c, http.StatusBadRequest, "star_id 参数错误") return } status := c.Query("status") page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10")) logger.Logger.Info("GetExhibitionRevenue request", zap.Int64("user_id", userID.(int64)), zap.Int64("star_id", starID), zap.String("status", status), ) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{ "user_id": strconv.FormatInt(userID.(int64), 10), "star_id": strconv.FormatInt(starID, 10), }) resp, err := ctrl.taskMobileService.GetExhibitionRevenue(ctx, &pbTask.GetExhibitionRevenueRequest{ StarId: starID, Status: status, Page: int32(page), PageSize: int32(pageSize), }) if err != nil { logger.Logger.Error("GetExhibitionRevenue RPC failed", zap.Error(err)) response.Error(c, http.StatusInternalServerError, "获取展示收益失败") return } if resp.Base.Code != pbCommon.StatusCode_STATUS_OK { response.ErrorWithCode(c, int(resp.Base.Code), resp.Base.Message) return } data := convertExhibitionRevenueResponse(resp) response.Success(c, data) } // ClaimExhibitionRevenue 领取单条展示收益 // @Summary 领取单条展示收益 // @Description 领取单条展馆收益记录 // @Tags tasks // @Accept json // @Produce json // @Security BearerAuth // @Param request body pbTask.ClaimExhibitionRevenueRequest true "领取请求" // @Success 200 {object} response.Response // @Router /api/tasks/exhibition-revenue/claim [post] func (ctrl *TaskController) ClaimExhibitionRevenue(c *gin.Context) { userID, _ := c.Get("user_id") var req struct { RevenueId int64 `json:"revenue_id"` StarId int64 `json:"star_id"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, http.StatusBadRequest, "请求参数错误") return } logger.Logger.Info("ClaimExhibitionRevenue request", zap.Int64("user_id", userID.(int64)), zap.Int64("revenue_id", req.RevenueId), zap.Int64("star_id", req.StarId), ) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{ "user_id": strconv.FormatInt(userID.(int64), 10), "star_id": strconv.FormatInt(req.StarId, 10), }) resp, err := ctrl.taskMobileService.ClaimExhibitionRevenue(ctx, &pbTask.ClaimExhibitionRevenueRequest{ RevenueId: req.RevenueId, StarId: req.StarId, }) if err != nil { logger.Logger.Error("ClaimExhibitionRevenue RPC failed", zap.Error(err)) response.Error(c, http.StatusInternalServerError, "领取展示收益失败") return } if resp.Base.Code != pbCommon.StatusCode_STATUS_OK { response.ErrorWithCode(c, int(resp.Base.Code), resp.Base.Message) return } response.Success(c, map[string]interface{}{ "success": resp.Success, }) } // ClaimAllExhibitionRevenue 一键领取所有展示收益 // @Summary 一键领取所有展示收益 // @Description 一键领取所有可领取的展馆收益 // @Tags tasks // @Accept json // @Produce json // @Security BearerAuth // @Param star_id query int64 true "粉丝身份ID" // @Success 200 {object} response.Response // @Router /api/tasks/exhibition-revenue/claim-all [post] func (ctrl *TaskController) ClaimAllExhibitionRevenue(c *gin.Context) { userID, _ := c.Get("user_id") starIDStr := c.Query("star_id") if starIDStr == "" { response.Error(c, http.StatusBadRequest, "star_id 是必填参数") return } starID, err := strconv.ParseInt(starIDStr, 10, 64) if err != nil { response.Error(c, http.StatusBadRequest, "star_id 参数错误") return } logger.Logger.Info("ClaimAllExhibitionRevenue request", zap.Int64("user_id", userID.(int64)), zap.Int64("star_id", starID), ) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{ "user_id": strconv.FormatInt(userID.(int64), 10), "star_id": strconv.FormatInt(starID, 10), }) resp, err := ctrl.taskMobileService.ClaimAllExhibitionRevenue(ctx, &pbTask.ClaimAllExhibitionRevenueRequest{ StarId: starID, }) if err != nil { logger.Logger.Error("ClaimAllExhibitionRevenue RPC failed", zap.Error(err)) response.Error(c, http.StatusInternalServerError, "一键领取展示收益失败") return } if resp.Base.Code != pbCommon.StatusCode_STATUS_OK { response.ErrorWithCode(c, int(resp.Base.Code), resp.Base.Message) return } response.Success(c, map[string]interface{}{ "claimed_count": resp.ClaimedCount, }) } // ==================== 响应转换函数 ==================== func convertDailyTasksResponse(resp *pbTask.GetDailyTasksResponse) map[string]interface{} { tasks := make([]map[string]interface{}, 0, len(resp.Tasks)) for _, task := range resp.Tasks { tasks = append(tasks, map[string]interface{}{ "task_key": task.TaskKey, "star_id": task.StarId, "name": task.Name, "description": task.Description, "crystal_reward": task.CrystalReward, "exp_reward": task.ExpReward, "status": task.Status, "can_claim": task.CanClaim, }) } return map[string]interface{}{ "star_id": resp.StarId, "tasks": tasks, } } func convertOnboardingStatusResponse(resp *pbTask.GetOnboardingStatusResponse) map[string]interface{} { stages := make([]map[string]interface{}, 0, len(resp.Stages)) for _, stage := range resp.Stages { stages = append(stages, map[string]interface{}{ "stage": stage.Stage, "name": stage.Name, "required_task_keys": stage.RequiredTaskKeys, "crystal_reward": stage.CrystalReward, "exp_reward": stage.ExpReward, "status": stage.Status, "is_current": stage.IsCurrent, }) } return map[string]interface{}{ "user_id": resp.UserId, "current_stage": resp.CurrentStage, "status": resp.Status, "stages": stages, } } func convertAdvanceStageResponse(resp *pbTask.AdvanceStageResponse) map[string]interface{} { stages := make([]map[string]interface{}, 0, len(resp.Stages)) for _, stage := range resp.Stages { stages = append(stages, map[string]interface{}{ "stage": stage.Stage, "name": stage.Name, "required_task_keys": stage.RequiredTaskKeys, "crystal_reward": stage.CrystalReward, "exp_reward": stage.ExpReward, "status": stage.Status, "is_current": stage.IsCurrent, }) } return map[string]interface{}{ "current_stage": resp.CurrentStage, "status": resp.Status, "stages": stages, } } func convertCompleteGuideResponse(resp *pbTask.CompleteGuideResponse) map[string]interface{} { stages := make([]map[string]interface{}, 0, len(resp.Stages)) for _, stage := range resp.Stages { stages = append(stages, map[string]interface{}{ "stage": stage.Stage, "name": stage.Name, "required_task_keys": stage.RequiredTaskKeys, "crystal_reward": stage.CrystalReward, "exp_reward": stage.ExpReward, "status": stage.Status, "is_current": stage.IsCurrent, }) } return map[string]interface{}{ "user_id": resp.UserId, "current_stage": resp.CurrentStage, "status": resp.Status, "stages": stages, } } func convertExhibitionRevenueResponse(resp *pbTask.GetExhibitionRevenueResponse) map[string]interface{} { items := make([]map[string]interface{}, 0, len(resp.Items)) for _, item := range resp.Items { items = append(items, map[string]interface{}{ "id": item.Id, "star_id": item.StarId, "exhibition_id": item.ExhibitionId, "asset_id": item.AssetId, "slot_id": item.SlotId, "slot_type": item.SlotType, "crystal_amount": item.CrystalAmount, "cycle_start_time": item.CycleStartTime, "cycle_end_time": item.CycleEndTime, "status": item.Status, "can_claim": item.CanClaim, }) } return map[string]interface{}{ "items": items, "total": resp.Total, "page": resp.Page, "page_size": resp.PageSize, } }