Files
2026-08-14 13:17:03 +08:00

175 lines
5.5 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 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·%s30天", 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)
}