package models import ( "database/sql/driver" "encoding/json" "errors" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) // ContactInfo 存储用户的多种联系方式 // 使用 jsonb 类型存入数据库 type ContactInfo struct { Email string `json:"email,omitempty"` Phone string `json:"phone,omitempty"` WeChat string `json:"wechat,omitempty"` Feishu string `json:"feishu,omitempty"` } // Scan 实现 sql.Scanner 接口,用于从数据库读取 JSONB 数据 func (ci *ContactInfo) Scan(value interface{}) error { if value == nil { *ci = ContactInfo{} // 如果数据库值为 NULL,则初始化为空结构体 return nil } bytes, ok := value.([]byte) if !ok { return errors.New("type assertion to []byte failed for ContactInfo") } return json.Unmarshal(bytes, ci) } // Value 实现 driver.Valuer 接口,用于将 ContactInfo 写入数据库为 JSONB 数据 func (ci ContactInfo) Value() (driver.Value, error) { return json.Marshal(ci) } // User 代表系统中的用户模型 type User struct { // gorm.Model 内嵌了 ID, CreatedAt, UpdatedAt, 和 DeletedAt // DeletedAt 字段的存在自动为 GORM 开启了软删除模式 gorm.Model // Username 是用户的登录名,应该是唯一的 // 修正了 gorm 标签的拼写错误 (移除了 gorm 后面的冒号) Username string `gorm:"unique;not null" json:"username"` // Password 存储的是加密后的密码哈希,而不是明文 // json:"-" 标签确保此字段在序列化为 JSON 时被忽略,防止密码泄露 Password string `gorm:"not null" json:"-"` // Contact 存储用户的联系方式,以 JSONB 格式存入数据库 Contact ContactInfo `gorm:"type:jsonb" json:"contact"` } // TableName 自定义 User 模型对应的数据库表名 // GORM 默认会使用复数形式 "users",但显式定义是一种好习惯 func (User) TableName() string { return "users" } // --- GORM Hooks --- // BeforeCreate 是一个 GORM 钩子,在创建用户记录前自动调用。 // 这是哈希初始密码最可靠的地方。 func (u *User) BeforeCreate(tx *gorm.DB) (err error) { // 如果密码不为空,则执行哈希 if u.Password != "" { // 使用 bcrypt 对密码进行哈希 hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost) if err != nil { return err } // 将明文密码替换为哈希值 u.Password = string(hashedPassword) } return nil } // --- Helper Methods --- // CheckPassword 用于验证输入的明文密码是否与数据库中存储的哈希匹配 func (u *User) CheckPassword(plainPassword string) bool { // bcrypt.CompareHashAndPassword 会安全地比较哈希和明文,能有效防止时序攻击 err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(plainPassword)) return err == nil }