topfans/backend/gateway/controller/activity_controller.go
2026-04-07 22:29:48 +08:00

561 lines
17 KiB
Go
Raw Permalink 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"
pbActivity "github.com/topfans/backend/pkg/proto/activity"
pbCommon "github.com/topfans/backend/pkg/proto/common"
"github.com/topfans/backend/pkg/logger"
"go.uber.org/zap"
)
// ActivityController 活动控制器
type ActivityController struct {
activityService pbActivity.ActivityService
}
// NewActivityController 创建活动控制器
func NewActivityController(dubboClient *client.Client) (*ActivityController, error) {
// 创建 ActivityService 客户端
activityService, err := pbActivity.NewActivityService(dubboClient)
if err != nil {
return nil, err
}
return &ActivityController{
activityService: activityService,
}, nil
}
// GetActivityList 获取活动列表
// @Summary 获取活动列表
// @Description 获取活动列表支持按star_id筛选
// @Tags activities
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param star_id query int64 true "粉丝身份ID"
// @Param status query string false "活动状态: pending/active/completed/expired"
// @Param page query int false "页码默认1"
// @Param page_size query int false "每页数量默认10"
// @Success 200 {object} response.Response
// @Router /api/v1/activities [get]
func (ctrl *ActivityController) GetActivityList(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
}
status := c.Query("status")
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
logger.Logger.Info("GetActivityList request",
zap.Int64("star_id", starID),
zap.String("status", status),
zap.Int("page", page),
zap.Int("page_size", pageSize),
)
// 设置上下文
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 调用 RPC
resp, err := ctrl.activityService.GetActivityList(ctx, &pbActivity.GetActivityListRequest{
StarId: starID,
Status: status,
Page: int32(page),
PageSize: int32(pageSize),
})
if err != nil {
logger.Logger.Error("GetActivityList 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 := convertActivityListResponse(resp)
response.Success(c, data)
}
// GetActivity 获取活动详情
// @Summary 获取活动详情
// @Description 获取活动详情
// @Tags activities
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param activity_id path int64 true "活动ID"
// @Success 200 {object} response.Response
// @Router /api/v1/activities/{activity_id} [get]
func (ctrl *ActivityController) GetActivity(c *gin.Context) {
// 解析路径参数
activityIDStr := c.Param("id")
activityID, err := strconv.ParseInt(activityIDStr, 10, 64)
if err != nil {
response.Error(c, http.StatusBadRequest, "活动ID参数错误")
return
}
logger.Logger.Info("GetActivity request", zap.Int64("activity_id", activityID))
// 设置上下文
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 调用 RPC
activity, err := ctrl.activityService.GetActivity(ctx, &pbActivity.GetProgressRequest{
ActivityId: activityID,
})
if err != nil {
logger.Logger.Error("GetActivity RPC failed", zap.Error(err))
response.Error(c, http.StatusInternalServerError, "获取活动详情失败")
return
}
// 转换响应
data := convertActivityResponse(activity)
response.Success(c, data)
}
// GetActivityItems 获取活动道具列表
// @Summary 获取活动道具列表
// @Description 获取活动道具列表
// @Tags activities
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param activity_id path int64 true "活动ID"
// @Success 200 {object} response.Response
// @Router /api/v1/activities/{activity_id}/items [get]
func (ctrl *ActivityController) GetActivityItems(c *gin.Context) {
// 解析路径参数
activityIDStr := c.Param("id")
activityID, err := strconv.ParseInt(activityIDStr, 10, 64)
if err != nil {
response.Error(c, http.StatusBadRequest, "活动ID参数错误")
return
}
logger.Logger.Info("GetActivityItems request", zap.Int64("activity_id", activityID))
// 设置上下文
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 调用 RPC
resp, err := ctrl.activityService.GetActivityItems(ctx, &pbActivity.GetProgressRequest{
ActivityId: activityID,
})
if err != nil {
logger.Logger.Error("GetActivityItems RPC failed", zap.Error(err))
response.Error(c, http.StatusInternalServerError, "获取活动道具列表失败")
return
}
// 转换响应
data := convertActivityItemsResponse(resp)
response.Success(c, data)
}
// GetProgress 获取活动进度
// @Summary 获取活动进度
// @Description 获取活动进度
// @Tags activities
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param activity_id path int64 true "活动ID"
// @Success 200 {object} response.Response
// @Router /api/v1/activities/{activity_id}/progress [get]
func (ctrl *ActivityController) GetProgress(c *gin.Context) {
// 解析路径参数
activityIDStr := c.Param("id")
activityID, err := strconv.ParseInt(activityIDStr, 10, 64)
if err != nil {
response.Error(c, http.StatusBadRequest, "活动ID参数错误")
return
}
logger.Logger.Info("GetProgress request", zap.Int64("activity_id", activityID))
// 设置上下文
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 调用 RPC
resp, err := ctrl.activityService.GetProgress(ctx, &pbActivity.GetProgressRequest{
ActivityId: activityID,
})
if err != nil {
logger.Logger.Error("GetProgress 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 := convertProgressResponse(resp)
response.Success(c, data)
}
// PurchaseItem 购买道具
// @Summary 购买道具
// @Description 购买活动道具
// @Tags activities
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param activity_id path int64 true "活动ID"
// @Param request body pbActivity.PurchaseItemRequest true "购买请求"
// @Success 200 {object} response.Response
// @Router /api/v1/activities/{activity_id}/purchase [post]
func (ctrl *ActivityController) PurchaseItem(c *gin.Context) {
// 从上下文获取用户信息
userID, exists := c.Get("user_id")
if !exists {
response.Error(c, http.StatusUnauthorized, "未授权")
return
}
starID, exists := c.Get("star_id")
if !exists {
response.Error(c, http.StatusUnauthorized, "未授权")
return
}
// 解析路径参数
activityIDStr := c.Param("id")
activityID, err := strconv.ParseInt(activityIDStr, 10, 64)
if err != nil {
response.Error(c, http.StatusBadRequest, "活动ID参数错误")
return
}
// 解析请求体
var req struct {
ItemType string `json:"item_type"`
Quantity int `json:"quantity"`
}
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, http.StatusBadRequest, "请求参数错误")
return
}
if req.ItemType == "" {
response.Error(c, http.StatusBadRequest, "item_type 是必填参数")
return
}
if req.Quantity <= 0 {
req.Quantity = 1
}
logger.Logger.Info("PurchaseItem request",
zap.Int64("user_id", userID.(int64)),
zap.Int64("star_id", starID.(int64)),
zap.Int64("activity_id", activityID),
zap.String("item_type", req.ItemType),
zap.Int("quantity", req.Quantity),
)
// 设置上下文
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.(int64), 10),
})
// 调用 RPC
resp, err := ctrl.activityService.PurchaseItem(ctx, &pbActivity.PurchaseItemRequest{
ActivityId: activityID,
ItemType: req.ItemType,
Quantity: int32(req.Quantity),
StarId: starID.(int64),
UserId: userID.(int64),
})
if err != nil {
logger.Logger.Error("PurchaseItem 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 := convertPurchaseResponse(resp)
response.Success(c, data)
}
// GetContributionRanking 获取贡献点排名
// @Summary 获取贡献点排名
// @Description 获取活动贡献点排名
// @Tags activities
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param activity_id path int64 true "活动ID"
// @Param star_id query int64 false "粉丝身份ID"
// @Param page query int false "页码默认1"
// @Param page_size query int false "每页数量默认10"
// @Success 200 {object} response.Response
// @Router /api/v1/activities/{activity_id}/ranking [get]
func (ctrl *ActivityController) GetContributionRanking(c *gin.Context) {
// 从上下文获取用户信息
userID, exists := c.Get("user_id")
if !exists {
response.Error(c, http.StatusUnauthorized, "未授权")
return
}
starID, exists := c.Get("star_id")
if !exists {
response.Error(c, http.StatusUnauthorized, "未授权")
return
}
// 解析路径参数
activityIDStr := c.Param("id")
activityID, err := strconv.ParseInt(activityIDStr, 10, 64)
if err != nil {
response.Error(c, http.StatusBadRequest, "活动ID参数错误")
return
}
// 解析查询参数
starIDParam := c.Query("star_id")
var starIDParamInt int64
if starIDParam != "" {
starIDParamInt, err = strconv.ParseInt(starIDParam, 10, 64)
if err != nil {
response.Error(c, http.StatusBadRequest, "star_id 参数错误")
return
}
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
// 如果传了 star_id 参数,使用传入的值;否则使用当前用户的 star_id
reqStarID := starID.(int64)
if starIDParamInt > 0 {
reqStarID = starIDParamInt
}
logger.Logger.Info("GetContributionRanking request",
zap.Int64("user_id", userID.(int64)),
zap.Int64("activity_id", activityID),
zap.Int64("star_id", reqStarID),
zap.Int("page", page),
zap.Int("page_size", pageSize),
)
// 设置上下文
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(reqStarID, 10),
})
// 调用 RPC
resp, err := ctrl.activityService.GetContributionRanking(ctx, &pbActivity.ContributionRankingRequest{
ActivityId: activityID,
StarId: reqStarID,
Page: int32(page),
PageSize: int32(pageSize),
UserId: userID.(int64),
})
if err != nil {
logger.Logger.Error("GetContributionRanking 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 := convertContributionRankingResponse(resp)
response.Success(c, data)
}
// convertActivityListResponse 转换活动列表响应
func convertActivityListResponse(resp *pbActivity.GetActivityListResponse) map[string]interface{} {
activities := make([]map[string]interface{}, 0, len(resp.Activities))
for _, activity := range resp.Activities {
activities = append(activities, map[string]interface{}{
"id": activity.Id,
"activity_type": activity.ActivityType,
"title": activity.Title,
"theme": activity.Theme,
"description": activity.Description,
"star_id": activity.StarId,
"start_time": activity.StartTime,
"end_time": activity.EndTime,
"target_progress": activity.TargetProgress,
"current_progress": activity.CurrentProgress,
"status": activity.Status,
"current_stage": activity.CurrentStage,
"cover_image": activity.CoverImage,
"banner_image": activity.BannerImage,
"current_stage_background": activity.CurrentStageBackground,
"current_stage_title": activity.CurrentStageTitle,
})
}
return map[string]interface{}{
"activities": activities,
"page": resp.Page,
"page_size": resp.PageSize,
"total": resp.Total,
}
}
// convertActivityResponse 转换活动响应
func convertActivityResponse(activity *pbActivity.Activity) map[string]interface{} {
items := make([]map[string]interface{}, 0, len(activity.Items))
for _, item := range activity.Items {
items = append(items, map[string]interface{}{
"id": item.Id,
"item_type": item.ItemType,
"item_name": item.ItemName,
"icon_url": item.IconUrl,
"crystal_cost": item.CrystalCost,
"contribution_points": item.ContributionPoints,
})
}
return map[string]interface{}{
"id": activity.Id,
"activity_type": activity.ActivityType,
"title": activity.Title,
"theme": activity.Theme,
"description": activity.Description,
"star_id": activity.StarId,
"start_time": activity.StartTime,
"end_time": activity.EndTime,
"target_progress": activity.TargetProgress,
"current_progress": activity.CurrentProgress,
"status": activity.Status,
"current_stage": activity.CurrentStage,
"cover_image": activity.CoverImage,
"banner_image": activity.BannerImage,
"current_stage_background": activity.CurrentStageBackground,
"current_stage_title": activity.CurrentStageTitle,
"items": items,
}
}
// convertActivityItemsResponse 转换活动道具列表响应
func convertActivityItemsResponse(resp *pbActivity.ActivityItemsResponse) map[string]interface{} {
items := resp.Items
result := make([]map[string]interface{}, 0, len(items))
for _, item := range items {
result = append(result, map[string]interface{}{
"id": item.Id,
"item_type": item.ItemType,
"item_name": item.ItemName,
"icon_url": item.IconUrl,
"crystal_cost": item.CrystalCost,
"contribution_points": item.ContributionPoints,
})
}
return map[string]interface{}{
"items": result,
}
}
// convertProgressResponse 转换进度响应
func convertProgressResponse(resp *pbActivity.GetProgressResponse) map[string]interface{} {
return map[string]interface{}{
"activity_id": resp.ActivityId,
"current_progress": resp.CurrentProgress,
"target_progress": resp.TargetProgress,
"current_stage": resp.CurrentStage,
"end_time": resp.EndTime,
"status": resp.Status,
}
}
// convertPurchaseResponse 转换购买响应
func convertPurchaseResponse(resp *pbActivity.PurchaseItemResponse) map[string]interface{} {
return map[string]interface{}{
"total_crystal_spent": resp.TotalCrystalSpent,
"total_contribution": resp.TotalContribution,
"current_progress": resp.CurrentProgress,
"remaining_balance": resp.RemainingBalance,
}
}
// convertContributionRankingResponse 转换排名响应
func convertContributionRankingResponse(resp *pbActivity.ContributionRankingResponse) map[string]interface{} {
items := make([]map[string]interface{}, 0, len(resp.Items))
for _, item := range resp.Items {
items = append(items, map[string]interface{}{
"rank": item.Rank,
"user_id": item.UserId,
"nickname": item.Nickname,
"avatar_url": item.AvatarUrl,
"total_contribution": item.TotalContribution,
"total_crystal_spent": item.TotalCrystalSpent,
})
}
var myContribution map[string]interface{}
if resp.MyContribution != nil {
myContribution = map[string]interface{}{
"rank": resp.MyContribution.Rank,
"total_contribution": resp.MyContribution.TotalContribution,
"total_crystal_spent": resp.MyContribution.TotalCrystalSpent,
"status": resp.MyContribution.Status,
"nickname": resp.MyContribution.Nickname,
"avatar_url": resp.MyContribution.AvatarUrl,
}
}
return map[string]interface{}{
"items": items,
"my_contribution": myContribution,
"page": resp.Page,
"page_size": resp.PageSize,
"total": resp.Total,
}
}