Files
nl-game-api/internal/handler/signin.go
2026-08-14 13:17:03 +08:00

144 lines
5.1 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 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})
}