2025-05-29 00:17:18 +08:00
|
|
|
|
package model
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
|
|
|
|
"github.com/fox/fox/log"
|
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
AccountNormal = 0 // 正常
|
|
|
|
|
AccountFrozen = 1 // 冻结
|
|
|
|
|
AccountBanned = 2 // 封禁
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 玩家账户表
|
|
|
|
|
type UserAccount struct {
|
|
|
|
|
gorm.Model
|
|
|
|
|
Username string `gorm:"type:varchar(32);uniqueIndex;not null"` // 用户名
|
|
|
|
|
Password string `gorm:"type:varchar(255);not null"` // 密码哈希
|
|
|
|
|
Email string `gorm:"type:varchar(100);uniqueIndex"` // 邮箱(可选)
|
|
|
|
|
Phone string `gorm:"type:varchar(20);uniqueIndex"` // 手机号(可选)
|
|
|
|
|
DeviceID string `gorm:"type:varchar(64);index"` // 设备ID
|
|
|
|
|
LastLoginIP string `gorm:"type:varchar(45)"` // 最后登录IP(支持IPv6)
|
|
|
|
|
LastLoginTime time.Time // 最后登录时间
|
2025-05-30 23:08:20 +08:00
|
|
|
|
Status int `gorm:"type:tinyint;default:1"` // 账号状态 1-正常 2-冻结 3-封禁
|
|
|
|
|
RegisterIP string `gorm:"type:varchar(45)"` // 注册IP
|
|
|
|
|
RegisterTime time.Time `gorm:"type:TIMESTAMP;default:CURRENT_TIMESTAMP"` // 注册时间
|
2025-05-29 00:17:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 玩家登录记录表
|
|
|
|
|
type UserLoginLog struct {
|
|
|
|
|
gorm.Model
|
2025-05-30 23:08:20 +08:00
|
|
|
|
PlayerID uint `gorm:"index"` // 关联玩家ID
|
|
|
|
|
LoginIP string `gorm:"type:varchar(45);not null"` // 登录IP
|
|
|
|
|
LoginTime time.Time `gorm:"type:TIMESTAMP;default:CURRENT_TIMESTAMP"` // 登录时间
|
|
|
|
|
DeviceInfo string `gorm:"type:varchar(255)"` // 设备信息(JSON格式)
|
2025-05-29 00:17:18 +08:00
|
|
|
|
LoginResult bool // 登录结果 true-成功 false-失败
|
|
|
|
|
FailReason string `gorm:"type:varchar(100)"` // 失败原因
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type UserLoginOp struct {
|
|
|
|
|
db *gorm.DB
|
|
|
|
|
logDb *gorm.DB
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewUserLoginOp() *UserLoginOp {
|
|
|
|
|
return &UserLoginOp{db: UserDB, logDb: LogDB}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
ErrUserOrPassword = errors.New("user or password was error")
|
|
|
|
|
ErrAccountFrozen = errors.New("account frozen")
|
|
|
|
|
ErrAccountBanned = errors.New("account banned")
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func (s *UserLoginOp) Login(username, password, ip, deviceID string) (*UserAccount, error) {
|
|
|
|
|
var user UserAccount
|
|
|
|
|
err := s.db.Where("username = ?", username).First(&user).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
// 验证密码
|
|
|
|
|
if err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
|
|
|
|
|
s.recordLoginLog(user.ID, ip, deviceID, false, ErrUserOrPassword.Error())
|
|
|
|
|
return nil, ErrUserOrPassword
|
|
|
|
|
}
|
|
|
|
|
// 检查账号状态
|
|
|
|
|
switch user.Status {
|
|
|
|
|
case AccountNormal:
|
|
|
|
|
|
|
|
|
|
case AccountFrozen:
|
|
|
|
|
s.recordLoginLog(user.ID, ip, deviceID, false, ErrAccountFrozen.Error())
|
|
|
|
|
return nil, ErrAccountFrozen
|
|
|
|
|
case AccountBanned:
|
|
|
|
|
s.recordLoginLog(user.ID, ip, deviceID, false, ErrAccountBanned.Error())
|
|
|
|
|
return nil, ErrAccountBanned
|
|
|
|
|
}
|
|
|
|
|
// 更新最后登录信息
|
|
|
|
|
user.LastLoginIP = ip
|
|
|
|
|
user.LastLoginTime = time.Now()
|
|
|
|
|
_ = s.db.Save(&user).Error
|
|
|
|
|
|
|
|
|
|
// 记录成功登录日志
|
|
|
|
|
s.recordLoginLog(user.ID, ip, deviceID, true, "")
|
|
|
|
|
|
|
|
|
|
// 6. 生成访问令牌
|
|
|
|
|
token, err := generateToken(user.ID, user.Username)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
user.Password = token
|
|
|
|
|
return &user, err
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-29 09:54:43 +08:00
|
|
|
|
// 注册新用户
|
|
|
|
|
func (s *UserLoginOp) RegisterNewUser(username, password, ip, deviceID string) (*UserAccount, error) {
|
|
|
|
|
// 密码加密
|
|
|
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
user := UserAccount{
|
2025-05-30 23:08:20 +08:00
|
|
|
|
Username: username,
|
|
|
|
|
Password: string(hashedPassword),
|
|
|
|
|
DeviceID: deviceID,
|
|
|
|
|
RegisterIP: ip,
|
|
|
|
|
Status: 1,
|
|
|
|
|
LastLoginIP: ip,
|
|
|
|
|
LastLoginTime: time.Now(),
|
2025-05-29 09:54:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := s.db.Create(&user).Error; err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s.recordLoginLog(user.ID, ip, deviceID, true, "")
|
|
|
|
|
|
|
|
|
|
// 生成访问令牌
|
|
|
|
|
token, err := generateToken(user.ID, user.Username)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
user.Password = token
|
|
|
|
|
|
|
|
|
|
return &user, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-29 00:17:18 +08:00
|
|
|
|
// 记录登录日志
|
|
|
|
|
func (s *UserLoginOp) recordLoginLog(userID uint, ip, deviceID string, success bool, failReason string) {
|
|
|
|
|
logEntry := UserLoginLog{
|
|
|
|
|
PlayerID: userID,
|
|
|
|
|
LoginIP: ip,
|
|
|
|
|
DeviceInfo: deviceID,
|
|
|
|
|
LoginResult: success,
|
|
|
|
|
FailReason: failReason,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := s.logDb.Create(&logEntry).Error; err != nil {
|
|
|
|
|
log.ErrorF("记录登录日志失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 生成JWT令牌(简化版)
|
|
|
|
|
func generateToken(userID uint, username string) (string, error) {
|
2025-05-29 09:54:43 +08:00
|
|
|
|
_ = userID
|
|
|
|
|
_ = username
|
2025-05-29 00:17:18 +08:00
|
|
|
|
// 这里应该使用JWT库生成实际令牌
|
|
|
|
|
// 简化实现,实际项目中请使用安全的JWT实现
|
|
|
|
|
return "generated-token-placeholder", nil
|
|
|
|
|
}
|