topfans/backend/gateway/controller/task_controller.go
2026-04-15 14:11:07 +08:00

772 lines
22 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 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"`
}
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),
)
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,
})
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,
})
}
// 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,
}
}