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 // 最后登录时间 Status int `gorm:"type:tinyint;default:1"` // 账号状态 1-正常 2-冻结 3-封禁 RegisterIP string `gorm:"type:varchar(45)"` // 注册IP RegisterTime time.Time `gorm:"default:CURRENT_TIMESTAMP"` // 注册时间 } // 玩家登录记录表 type UserLoginLog struct { gorm.Model PlayerID uint `gorm:"index"` // 关联玩家ID LoginIP string `gorm:"type:varchar(45);not null"` // 登录IP LoginTime time.Time `gorm:"default:CURRENT_TIMESTAMP"` // 登录时间 DeviceInfo string `gorm:"type:varchar(255)"` // 设备信息(JSON格式) 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 } // 记录登录日志 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) { // 这里应该使用JWT库生成实际令牌 // 简化实现,实际项目中请使用安全的JWT实现 return "generated-token-placeholder", nil }