// 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 }