92 lines
2.8 KiB
Go
92 lines
2.8 KiB
Go
|
|
// Package service 沉淀跨接口复用的业务逻辑:积分变动、站点配置读写
|
|||
|
|
package service
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"errors"
|
|||
|
|
"strconv"
|
|||
|
|
|
|||
|
|
"gorm.io/gorm"
|
|||
|
|
"gorm.io/gorm/clause"
|
|||
|
|
|
|||
|
|
"nl-game-api-gin/internal/database"
|
|||
|
|
"nl-game-api-gin/internal/model"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// ChangePoints 变动用户积分并写流水(事务保证,正数=增加、负数=扣减)
|
|||
|
|
// tx 传 nil 时自动开启新事务;传入已有事务则复用(用于下单等复合操作)
|
|||
|
|
func ChangePoints(tx *gorm.DB, userID, change, ptype, relatedID int, remark string) (int, error) {
|
|||
|
|
run := func(db *gorm.DB) (int, error) {
|
|||
|
|
var user model.User
|
|||
|
|
// 行级锁读取(SELECT ... FOR UPDATE),防止并发变动基于同一余额快照造成丢失更新
|
|||
|
|
// 注意:GORM v2 必须用 clause.Locking,旧的 Set("gorm:query_option") 会被静默忽略
|
|||
|
|
if err := db.Clauses(clause.Locking{Strength: "UPDATE"}).First(&user, userID).Error; err != nil {
|
|||
|
|
return 0, errors.New("用户不存在")
|
|||
|
|
}
|
|||
|
|
newBalance := user.Points + change
|
|||
|
|
if newBalance < 0 {
|
|||
|
|
return 0, errors.New("积分不足")
|
|||
|
|
}
|
|||
|
|
// 更新余额;增加时同步累计 total_points(排行榜依据)
|
|||
|
|
updates := map[string]any{"points": newBalance}
|
|||
|
|
if change > 0 {
|
|||
|
|
updates["total_points"] = user.TotalPoints + change
|
|||
|
|
}
|
|||
|
|
if err := db.Model(&model.User{}).Where("id = ?", userID).Updates(updates).Error; err != nil {
|
|||
|
|
return 0, err
|
|||
|
|
}
|
|||
|
|
// 写积分流水(审计与个人中心展示)
|
|||
|
|
record := model.PointRecord{
|
|||
|
|
UserID: userID, ChangePoints: change, Balance: newBalance,
|
|||
|
|
Type: ptype, RelatedID: relatedID, Remark: remark,
|
|||
|
|
}
|
|||
|
|
if err := db.Create(&record).Error; err != nil {
|
|||
|
|
return 0, err
|
|||
|
|
}
|
|||
|
|
return newBalance, nil
|
|||
|
|
}
|
|||
|
|
// 复用外部事务或自行开启事务
|
|||
|
|
if tx != nil {
|
|||
|
|
return run(tx)
|
|||
|
|
}
|
|||
|
|
var balance int
|
|||
|
|
err := database.DB.Transaction(func(db *gorm.DB) error {
|
|||
|
|
b, err := run(db)
|
|||
|
|
balance = b
|
|||
|
|
return err
|
|||
|
|
})
|
|||
|
|
return balance, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetConfig 读取站点配置,不存在时返回默认值
|
|||
|
|
func GetConfig(key, def string) string {
|
|||
|
|
var conf model.SiteConfig
|
|||
|
|
if err := database.DB.Where("config_key = ?", key).First(&conf).Error; err != nil {
|
|||
|
|
return def
|
|||
|
|
}
|
|||
|
|
return conf.ConfigValue
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetConfigInt 读取整数类型站点配置,解析失败返回默认值
|
|||
|
|
func GetConfigInt(key string, def int) int {
|
|||
|
|
v := GetConfig(key, "")
|
|||
|
|
if v == "" {
|
|||
|
|
return def
|
|||
|
|
}
|
|||
|
|
n, err := strconv.Atoi(v)
|
|||
|
|
if err != nil {
|
|||
|
|
return def
|
|||
|
|
}
|
|||
|
|
return n
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SetConfig 写入站点配置(不存在则创建)
|
|||
|
|
func SetConfig(key, value string) error {
|
|||
|
|
var conf model.SiteConfig
|
|||
|
|
err := database.DB.Where("config_key = ?", key).First(&conf).Error
|
|||
|
|
if err != nil {
|
|||
|
|
conf = model.SiteConfig{ConfigKey: key, ConfigValue: value}
|
|||
|
|
return database.DB.Create(&conf).Error
|
|||
|
|
}
|
|||
|
|
return database.DB.Model(&conf).Update("config_value", value).Error
|
|||
|
|
}
|