package model import ( "errors" "github.com/fox/fox/log" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" "time" ) const ( AccountNormal = 1 // 正常 AccountFrozen = 2 // 冻结 AccountBanned = 3 // 封禁 ) // 玩家账户表 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)"` // 邮箱(可选) Phone string `gorm:"type:varchar(20)"` // 手机号(可选) 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:"type:TIMESTAMP;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:"type:TIMESTAMP;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) RegisterNewUser(username, password, ip, deviceID string) (*UserAccount, error) { // 密码加密 hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return nil, err } user := UserAccount{ Username: username, Password: string(hashedPassword), DeviceID: deviceID, RegisterIP: ip, Status: 1, LastLoginIP: ip, LastLoginTime: time.Now(), } 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 } // 记录登录日志 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) { _ = userID _ = username // 这里应该使用JWT库生成实际令牌 // 简化实现,实际项目中请使用安全的JWT实现 return "generated-token-placeholder", nil }