package handler import ( "fmt" "strconv" "time" "github.com/gin-gonic/gin" "gorm.io/gorm" "nl-game-api-gin/internal/database" "nl-game-api-gin/internal/middleware" "nl-game-api-gin/internal/model" "nl-game-api-gin/internal/service" "nl-game-api-gin/pkg/resp" ) // vipGameBrief 周免游戏的精简信息(VIP页展示用) type vipGameBrief struct { ID int `json:"id"` // 游戏ID Code string `json:"code"` // 游戏编码 Name string `json:"name"` // 游戏名称 Icon string `json:"icon"` // emoji 图标 Price int `json:"price"` // 原价(积分) } // vipLevelItem VIP等级 + 本周周免批次 type vipLevelItem struct { model.VipLevel WeeklyGames []vipGameBrief `json:"weekly_games"` // 该等级本周的周免游戏 } // VipInfo VIP中心数据:我的VIP状态、5个等级的权益与本周周免批次、下次刷新时间 func VipInfo(c *gin.Context) { uid := middleware.UID(c) now := time.Now() var user model.User if err := database.DB.First(&user, uid).Error; err != nil { resp.Fail(c, "用户不存在") return } var levels []model.VipLevel database.DB.Order("level").Find(&levels) pool := service.VipWeeklyPool(now) myLevel := service.UserVipLevel(&user, now) list := make([]vipLevelItem, 0, len(levels)) myName := "" for _, l := range levels { batch := service.VipWeeklyBatch(pool, levels, l.Level) games := make([]vipGameBrief, 0, len(batch)) for _, g := range batch { games = append(games, vipGameBrief{ID: g.ID, Code: g.Code, Name: g.Name, Icon: g.Icon, Price: g.Price}) } list = append(list, vipLevelItem{VipLevel: l, WeeklyGames: games}) if l.Level == myLevel { myName = l.Name } } resp.OK(c, gin.H{ "my": gin.H{ "level": myLevel, // 当前生效等级(过期后为 0) "level_name": myName, // 等级名称 "expire": user.VipExpire, // 到期时间戳(秒) "active": myLevel > 0, // 是否生效中 }, "refresh_at": service.VipNextRefresh(now).Unix(), // 下次周免刷新时间(周五12:00) "levels": list, }) } // vipBuyReq 购买VIP请求体 type vipBuyReq struct { Level int `json:"level" binding:"required,min=1,max=5"` // 目标等级(1~5) } // VipBuy 开通/续费/升级VIP:扣积分并更新等级与到期时间(30天/次) // 规则:同等级续费在原到期时间上顺延;升级立即生效、按30天重新计时;不允许降级购买 func VipBuy(c *gin.Context) { var req vipBuyReq if err := c.ShouldBindJSON(&req); err != nil { resp.Fail(c, "参数有误") return } uid := middleware.UID(c) var target model.VipLevel if err := database.DB.Where("level = ?", req.Level).First(&target).Error; err != nil { resp.Fail(c, "VIP等级不存在") return } now := time.Now() var newExpire int64 var balance int err := database.DB.Transaction(func(tx *gorm.DB) error { var user model.User if err := tx.Set("gorm:query_option", "FOR UPDATE").First(&user, uid).Error; err != nil { return fmt.Errorf("用户不存在") } curLevel := service.UserVipLevel(&user, now) if curLevel > req.Level { return fmt.Errorf("当前已是更高等级(VIP%d),无需降级购买", curLevel) } // 计算新到期时间:同级续费顺延,升级/新开从现在起算30天 if curLevel == req.Level { newExpire = user.VipExpire + 30*86400 } else { newExpire = now.Unix() + 30*86400 } // 扣积分并记流水(复用事务,余额不足会报错回滚) b, err := service.ChangePoints(tx, uid, -target.PricePoints, model.PointTypeVIP, target.ID, fmt.Sprintf("开通VIP%d·%s(30天)", target.Level, target.Name)) if err != nil { return err } balance = b return tx.Model(&model.User{}).Where("id = ?", uid). Updates(map[string]any{"vip_level": req.Level, "vip_expire": newExpire}).Error }) if err != nil { resp.Fail(c, err.Error()) return } resp.OK(c, gin.H{"level": req.Level, "expire": newExpire, "balance": balance}) } // AdminVipLevels 后台:VIP等级配置列表 func AdminVipLevels(c *gin.Context) { var levels []model.VipLevel database.DB.Order("level").Find(&levels) resp.OK(c, levels) } // adminUpdateVipReq 后台修改VIP等级配置请求体(nil=不修改) type adminUpdateVipReq struct { Name *string `json:"name"` // 等级名称 PricePoints *int `json:"price_points"` // 30天价格(积分) WeeklyFreeQuota *int `json:"weekly_free_quota"` // 周免游戏数量 SigninBonus *int `json:"signin_bonus"` // 签到加成积分 Description *string `json:"description"` // 权益描述 } // AdminUpdateVipLevel 后台:修改某一VIP等级的价格与权益 func AdminUpdateVipLevel(c *gin.Context) { id, _ := strconv.Atoi(c.Param("id")) var req adminUpdateVipReq if err := c.ShouldBindJSON(&req); err != nil { resp.Fail(c, "参数有误") return } updates := map[string]any{} if req.Name != nil && *req.Name != "" { updates["name"] = *req.Name } if req.PricePoints != nil && *req.PricePoints >= 0 { updates["price_points"] = *req.PricePoints } if req.WeeklyFreeQuota != nil && *req.WeeklyFreeQuota >= 0 { updates["weekly_free_quota"] = *req.WeeklyFreeQuota } if req.SigninBonus != nil && *req.SigninBonus >= 0 { updates["signin_bonus"] = *req.SigninBonus } if req.Description != nil { updates["description"] = *req.Description } if len(updates) == 0 { resp.Fail(c, "没有需要修改的内容") return } if err := database.DB.Model(&model.VipLevel{}).Where("id = ?", id).Updates(updates).Error; err != nil { resp.Fail(c, "保存失败") return } resp.OK(c, nil) }