175 lines
5.5 KiB
Go
175 lines
5.5 KiB
Go
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)
|
||
}
|