bindbox-game/internal/api/activity/draw_logs_app.go

345 lines
10 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 app
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"time"
"bindbox-game/internal/code"
"bindbox-game/internal/pkg/core"
"bindbox-game/internal/pkg/validation"
"bindbox-game/internal/repository/mysql/model"
)
type listDrawLogsRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
Level *int32 `form:"level"`
}
type drawLogItem struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
UserName string `json:"user_name"`
Avatar string `json:"avatar"`
IssueID int64 `json:"issue_id"`
OrderID int64 `json:"order_id"`
RewardID int64 `json:"reward_id"`
RewardName string `json:"reward_name"`
RewardImage string `json:"reward_image"`
IsWinner int32 `json:"is_winner"`
Level int32 `json:"level"`
CurrentLevel int32 `json:"current_level"`
CreatedAt time.Time `json:"created_at"`
}
type listDrawLogsResponse struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Total int64 `json:"total"`
List []drawLogItem `json:"list"`
}
// ListDrawLogs 抽奖记录列表
// @Summary 抽奖记录列表
// @Description 查看指定活动期数的抽奖记录支持等级筛选默认返回最新的100条不支持自定义翻页
// @Tags APP端.活动
// @Accept json
// @Produce json
// @Param activity_id path integer true "活动ID"
// @Param issue_id path integer true "期ID"
// @Param level query int false "奖品等级过滤"
// @Success 200 {object} listDrawLogsResponse
// @Failure 400 {object} code.Failure
// @Router /api/app/activities/{activity_id}/issues/{issue_id}/draw_logs [get]
func (h *handler) ListDrawLogs() core.HandlerFunc {
return func(ctx core.Context) {
req := new(listDrawLogsRequest)
res := new(listDrawLogsResponse)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
return
}
issueID, err := strconv.ParseInt(ctx.Param("issue_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递期ID"))
return
}
page := req.Page
if page <= 0 {
page = 1
}
pageSize := req.PageSize
if pageSize <= 0 {
pageSize = 100
}
// 计算5分钟前的时间点
fiveMinutesAgo := time.Now().Add(-5 * time.Minute)
// 为了保证过滤后依然有足够数据,我们多取一些
fetchPageSize := pageSize
if pageSize < 100 {
fetchPageSize = 100 // 至少取100条来过滤
}
items, total, err := h.activity.ListDrawLogs(ctx.RequestContext(), issueID, page, fetchPageSize, req.Level)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ListDrawLogsError, err.Error()))
return
}
// 收集ID用于批量查询
var userIDs []int64
var rewardIDs []int64
userSet := make(map[int64]struct{})
rewardSet := make(map[int64]struct{})
var filteredItems []*model.ActivityDrawLogs
for _, v := range items {
// 恢复 5 分钟过滤逻辑
if v.CreatedAt.After(fiveMinutesAgo) {
continue
}
if len(filteredItems) >= pageSize {
break
}
filteredItems = append(filteredItems, v)
if v.UserID > 0 {
if _, ok := userSet[v.UserID]; !ok {
userSet[v.UserID] = struct{}{}
userIDs = append(userIDs, v.UserID)
}
}
if v.RewardID > 0 {
if _, ok := rewardSet[v.RewardID]; !ok {
rewardSet[v.RewardID] = struct{}{}
rewardIDs = append(rewardIDs, v.RewardID)
}
}
}
items = filteredItems
total = int64(len(items))
// 批量查询用户信息
userNameMap := make(map[int64]string)
userAvatarMap := make(map[int64]string)
if len(userIDs) > 0 {
users, err := h.readDB.Users.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Users.ID.In(userIDs...)).Find()
if err == nil {
for _, u := range users {
userNameMap[u.ID] = u.Nickname
userAvatarMap[u.ID] = u.Avatar
}
}
}
// 批量查询奖品与商品信息
rewardNameMap := make(map[int64]string)
rewardImageMap := make(map[int64]string)
if len(rewardIDs) > 0 {
rewards, err := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ActivityRewardSettings.ID.In(rewardIDs...)).Find()
if err == nil {
var productIDs []int64
productSet := make(map[int64]struct{})
rewardProductMap := make(map[int64]int64)
for _, r := range rewards {
// 不再使用 r.Name只通过 ProductID 关联查询商品名称
if r.ProductID > 0 {
if _, ok := productSet[r.ProductID]; !ok {
productSet[r.ProductID] = struct{}{}
productIDs = append(productIDs, r.ProductID)
}
rewardProductMap[r.ID] = r.ProductID
}
}
if len(productIDs) > 0 {
products, err := h.readDB.Products.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Products.ID.In(productIDs...)).Find()
if err == nil {
productImageMap := make(map[int64]string)
productNameMap := make(map[int64]string)
for _, p := range products {
first := ""
if p.ImagesJSON != "" {
var arr []string
_ = json.Unmarshal([]byte(p.ImagesJSON), &arr)
if len(arr) > 0 {
first = arr[0]
}
}
productImageMap[p.ID] = first
productNameMap[p.ID] = p.Name
}
// 填充奖品图片,优先使用商品名称
for rid, pid := range rewardProductMap {
rewardImageMap[rid] = productImageMap[pid]
// 优先使用商品名称
if pn, ok := productNameMap[pid]; ok && pn != "" {
rewardNameMap[rid] = pn
}
}
}
}
}
}
res.Page = page
res.PageSize = pageSize
res.Total = total
res.List = make([]drawLogItem, len(items))
for i, v := range items {
res.List[i] = drawLogItem{
ID: v.ID,
UserID: v.UserID,
UserName: userNameMap[v.UserID],
Avatar: userAvatarMap[v.UserID],
IssueID: v.IssueID,
OrderID: v.OrderID,
RewardID: v.RewardID,
RewardName: rewardNameMap[v.RewardID],
RewardImage: rewardImageMap[v.RewardID],
IsWinner: v.IsWinner,
Level: v.Level,
CurrentLevel: v.CurrentLevel,
CreatedAt: v.CreatedAt,
}
}
ctx.Payload(res)
}
}
// ListDrawLogsByLevel 按奖品等级分类的抽奖记录
// @Summary 按奖品等级分类的抽奖记录
// @Description 查看指定活动期数的抽奖记录,按奖品等级分组返回
// @Tags APP端.活动
// @Accept json
// @Produce json
// @Param activity_id path integer true "活动ID"
// @Param issue_id path integer true "期ID"
// @Success 200 {object} listDrawLogsByLevelResponse
// @Failure 400 {object} code.Failure
// @Router /api/app/activities/{activity_id}/issues/{issue_id}/draw_logs_grouped [get]
func (h *handler) ListDrawLogsByLevel() core.HandlerFunc {
return func(ctx core.Context) {
issueID, err := strconv.ParseInt(ctx.Param("issue_id"), 10, 64)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递期ID"))
return
}
// 1. 获取所有中奖记录
// 我们假设这里不需要分页,或者分页逻辑比较复杂(每个等级分页?)。
// 根据需求描述“按奖品等级进行归类”,通常 implied 展示所有或者前N个。
// 这里暂且获取所有(或者一个较大的限制),然后在内存中分组。
// 如果数据量巨大,需要由 Service 层提供 Group By 查询。
// 考虑到单期中奖人数通常有限(除非是大规模活动),先尝试获取列表后分组。
logs, _, err := h.activity.ListDrawLogs(ctx.RequestContext(), issueID, 1, 1000, nil) // 假设最多显示1000条中奖记录
if err != nil {
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ListDrawLogsError, err.Error()))
return
}
// 2. 获取奖品配置以获取等级名称
rewards, err := h.activity.ListIssueRewards(ctx.RequestContext(), issueID)
if err != nil {
ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.GetActivityError, err.Error()))
return
}
levelNameMap := make(map[int32]string)
// 收集所有 ProductID 用于批量查询商品名称
productIDs := make([]int64, 0, len(rewards))
rewardProductMap := make(map[int64]int64) // rewardID -> productID
for _, r := range rewards {
if r.ProductID > 0 {
productIDs = append(productIDs, r.ProductID)
rewardProductMap[r.ID] = r.ProductID
}
}
// 批量查询商品名称
productNameMap := make(map[int64]string)
if len(productIDs) > 0 {
products, _ := h.readDB.Products.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Products.ID.In(productIDs...)).Find()
for _, p := range products {
productNameMap[p.ID] = p.Name
}
}
// 构建等级名称映射
for _, r := range rewards {
if _, ok := levelNameMap[r.Level]; !ok {
// 使用商品名称作为等级名称
if r.ProductID > 0 {
if pn, ok := productNameMap[r.ProductID]; ok && pn != "" {
levelNameMap[r.Level] = pn
}
}
// 如果没有商品名称,使用等级编号
if levelNameMap[r.Level] == "" {
levelNameMap[r.Level] = fmt.Sprintf("等级%d", r.Level)
}
}
}
// 3. 分组 (恢复 5 分钟过滤)
groupsMap := make(map[int32][]drawLogItem)
fiveMinutesAgo := time.Now().Add(-5 * time.Minute)
for _, v := range logs {
if v.CreatedAt.After(fiveMinutesAgo) {
continue
}
if v.IsWinner == 1 {
item := drawLogItem{
ID: v.ID,
UserID: v.UserID,
IssueID: v.IssueID,
OrderID: v.OrderID,
RewardID: v.RewardID,
IsWinner: v.IsWinner,
Level: v.Level,
CurrentLevel: v.CurrentLevel,
}
groupsMap[v.Level] = append(groupsMap[v.Level], item)
}
}
// 4. 构造响应
var resp listDrawLogsByLevelResponse
for level, items := range groupsMap {
group := drawLogGroup{
Level: level,
LevelName: levelNameMap[level],
List: items,
}
resp.Groups = append(resp.Groups, group)
}
// 排序 Groups (Level 升序? 也就是 1等奖在前)
// 简单的冒泡排序或 slice sort
for i := 0; i < len(resp.Groups)-1; i++ {
for j := 0; j < len(resp.Groups)-1-i; j++ {
if resp.Groups[j].Level > resp.Groups[j+1].Level {
resp.Groups[j], resp.Groups[j+1] = resp.Groups[j+1], resp.Groups[j]
}
}
}
ctx.Payload(resp)
}
}
type drawLogGroup struct {
Level int32 `json:"level"`
LevelName string `json:"level_name"`
List []drawLogItem `json:"list"`
}
type listDrawLogsByLevelResponse struct {
Groups []drawLogGroup `json:"groups"`
}