144 lines
5.1 KiB
Go
144 lines
5.1 KiB
Go
package handler
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"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"
|
||
)
|
||
|
||
// dayInt 把日期转成 yyyymmdd 整数(与 sign_ins.sign_day 对应)
|
||
func dayInt(t time.Time) int {
|
||
return t.Year()*10000 + int(t.Month())*100 + t.Day()
|
||
}
|
||
|
||
// calcSignPoints 计算签到基础积分:基础 + 连签加成(有上限),不含VIP加成
|
||
func calcSignPoints(streak int) int {
|
||
base := service.GetConfigInt(model.ConfKeySignBasePoints, 10)
|
||
step := service.GetConfigInt(model.ConfKeySignStreakStep, 5)
|
||
capDays := service.GetConfigInt(model.ConfKeySignStreakCap, 7)
|
||
if streak > capDays {
|
||
streak = capDays
|
||
}
|
||
return base + step*(streak-1)
|
||
}
|
||
|
||
// vipSigninBonus 查用户当前生效VIP等级与对应的签到加成积分(非VIP返回 0,0)
|
||
func vipSigninBonus(uid int, now time.Time) (int, int) {
|
||
var user model.User
|
||
if err := database.DB.First(&user, uid).Error; err != nil {
|
||
return 0, 0
|
||
}
|
||
level := service.UserVipLevel(&user, now)
|
||
if level <= 0 {
|
||
return 0, 0
|
||
}
|
||
var vl model.VipLevel
|
||
if err := database.DB.Where("level = ?", level).First(&vl).Error; err != nil {
|
||
return level, 0
|
||
}
|
||
return level, vl.SigninBonus
|
||
}
|
||
|
||
// SignInStatus 签到页数据:本月已签日期、今日是否已签、连签天数与奖励预告(含VIP加成)
|
||
func SignInStatus(c *gin.Context) {
|
||
uid := middleware.UID(c)
|
||
now := time.Now()
|
||
// 本月第一天的 yyyymmdd(用于筛选本月记录)
|
||
monthStart := now.Year()*10000 + int(now.Month())*100 + 1
|
||
var monthSigns []model.SignIn
|
||
database.DB.Where("user_id = ? AND sign_day >= ?", uid, monthStart).Order("sign_day").Find(&monthSigns)
|
||
signedDays := make([]int, 0, len(monthSigns))
|
||
for _, s := range monthSigns {
|
||
signedDays = append(signedDays, s.SignDay%100)
|
||
}
|
||
// 最近一次签到记录(用于计算今日/昨日连签)
|
||
var last model.SignIn
|
||
hasLast := database.DB.Where("user_id = ?", uid).Order("sign_day DESC").First(&last).Error == nil
|
||
today := dayInt(now)
|
||
yesterday := dayInt(now.AddDate(0, 0, -1))
|
||
todaySigned := hasLast && last.SignDay == today
|
||
// 计算"今天签到能连几天"(已签则取当前值)
|
||
nextStreak := 1
|
||
if hasLast {
|
||
if last.SignDay == today {
|
||
nextStreak = last.Streak
|
||
} else if last.SignDay == yesterday {
|
||
nextStreak = last.Streak + 1
|
||
}
|
||
}
|
||
// VIP签到加成(随等级配置,非VIP为 0)
|
||
vipLevel, vipBonus := vipSigninBonus(uid, now)
|
||
resp.OK(c, gin.H{
|
||
"year": now.Year(),
|
||
"month": int(now.Month()),
|
||
"today": now.Day(),
|
||
"signed_days": signedDays, // 本月已签的"日"列表
|
||
"today_signed": todaySigned, // 今日是否已签
|
||
"streak": nextStreak, // 连签天数(今日签到后的值)
|
||
"vip_level": vipLevel, // 当前生效VIP等级(0=非VIP)
|
||
"vip_bonus": vipBonus, // VIP签到加成积分
|
||
"today_points": calcSignPoints(nextStreak) + vipBonus, // 今日签到可得/已得积分(含加成)
|
||
"next_points": calcSignPoints(nextStreak+1) + vipBonus, // 明日签到可得积分(预告,含加成)
|
||
})
|
||
}
|
||
|
||
// DoSignIn 执行签到:防重复、算连签、发积分(基础 + 连签 + VIP加成)
|
||
func DoSignIn(c *gin.Context) {
|
||
uid := middleware.UID(c)
|
||
now := time.Now()
|
||
today := dayInt(now)
|
||
yesterday := dayInt(now.AddDate(0, 0, -1))
|
||
// 今日是否已签到
|
||
var cnt int64
|
||
database.DB.Model(&model.SignIn{}).Where("user_id = ? AND sign_day = ?", uid, today).Count(&cnt)
|
||
if cnt > 0 {
|
||
resp.Fail(c, "今天已经签过啦")
|
||
return
|
||
}
|
||
// 昨天有签则连签 +1,否则从 1 重新计
|
||
streak := 1
|
||
var last model.SignIn
|
||
if database.DB.Where("user_id = ?", uid).Order("sign_day DESC").First(&last).Error == nil {
|
||
if last.SignDay == yesterday {
|
||
streak = last.Streak + 1
|
||
}
|
||
}
|
||
// 总积分 = 基础 + 连签加成 + VIP加成
|
||
vipLevel, vipBonus := vipSigninBonus(uid, now)
|
||
points := calcSignPoints(streak) + vipBonus
|
||
remark := fmt.Sprintf("每日签到(连签 %d 天)", streak)
|
||
if vipBonus > 0 {
|
||
remark = fmt.Sprintf("每日签到(连签 %d 天,VIP%d 加成 +%d)", streak, vipLevel, vipBonus)
|
||
}
|
||
// 签到落库与积分发放在同一事务:任一步失败整体回滚,
|
||
// 避免"签到行已写入(当天唯一约束挡住重签)但积分没到账"的中间态
|
||
var balance int
|
||
err := database.DB.Transaction(func(tx *gorm.DB) error {
|
||
sign := model.SignIn{UserID: uid, SignDay: today, Streak: streak, Points: points}
|
||
if err := tx.Create(&sign).Error; err != nil {
|
||
// 唯一索引兜底(并发下重复签到会触发此分支)
|
||
return errors.New("签到失败,请稍后重试")
|
||
}
|
||
b, err := service.ChangePoints(tx, uid, points, model.PointTypeSignIn, sign.ID, remark)
|
||
if err != nil {
|
||
return errors.New("积分发放失败")
|
||
}
|
||
balance = b
|
||
return nil
|
||
})
|
||
if err != nil {
|
||
resp.Fail(c, err.Error())
|
||
return
|
||
}
|
||
resp.OK(c, gin.H{"points": points, "streak": streak, "balance": balance, "vip_bonus": vipBonus})
|
||
}
|