561 lines
17 KiB
Go
561 lines
17 KiB
Go
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,
|
||
}
|
||
}
|