276 lines
6.8 KiB
Go
276 lines
6.8 KiB
Go
package entities
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"regexp"
|
||
"time"
|
||
|
||
"golang.org/x/crypto/bcrypt"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// User 用户实体
|
||
// 系统用户的核心信息,提供基础的账户管理功能
|
||
// 支持手机号登录,密码加密存储,实现Entity接口便于统一管理
|
||
type User struct {
|
||
// 基础标识
|
||
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"用户唯一标识"`
|
||
Phone string `gorm:"uniqueIndex;type:varchar(20);not null" json:"phone" comment:"手机号码(登录账号)"`
|
||
Password string `gorm:"type:varchar(255);not null" json:"-" comment:"登录密码(加密存储,不返回前端)"`
|
||
|
||
// 时间戳字段
|
||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
|
||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
|
||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
|
||
}
|
||
|
||
// 实现 Entity 接口 - 提供统一的实体管理接口
|
||
// GetID 获取实体唯一标识
|
||
func (u *User) GetID() string {
|
||
return u.ID
|
||
}
|
||
|
||
// GetCreatedAt 获取创建时间
|
||
func (u *User) GetCreatedAt() time.Time {
|
||
return u.CreatedAt
|
||
}
|
||
|
||
// GetUpdatedAt 获取更新时间
|
||
func (u *User) GetUpdatedAt() time.Time {
|
||
return u.UpdatedAt
|
||
}
|
||
|
||
// Validate 验证用户信息
|
||
// 检查用户必填字段是否完整,确保数据的有效性
|
||
func (u *User) Validate() error {
|
||
if u.Phone == "" {
|
||
return NewValidationError("手机号不能为空")
|
||
}
|
||
if u.Password == "" {
|
||
return NewValidationError("密码不能为空")
|
||
}
|
||
|
||
// 验证手机号格式
|
||
if !u.IsValidPhone() {
|
||
return NewValidationError("手机号格式无效")
|
||
}
|
||
|
||
// 验证密码强度
|
||
if err := u.validatePasswordStrength(u.Password); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// ================ 业务方法 ================
|
||
|
||
// ChangePassword 修改密码
|
||
// 验证旧密码,检查新密码强度,更新密码
|
||
func (u *User) ChangePassword(oldPassword, newPassword, confirmPassword string) error {
|
||
// 1. 验证确认密码
|
||
if newPassword != confirmPassword {
|
||
return NewValidationError("新密码和确认新密码不匹配")
|
||
}
|
||
|
||
// 2. 验证旧密码
|
||
if !u.CheckPassword(oldPassword) {
|
||
return NewValidationError("当前密码错误")
|
||
}
|
||
|
||
// 3. 验证新密码强度
|
||
if err := u.validatePasswordStrength(newPassword); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 4. 检查新密码不能与旧密码相同
|
||
if u.CheckPassword(newPassword) {
|
||
return NewValidationError("新密码不能与当前密码相同")
|
||
}
|
||
|
||
// 5. 更新密码
|
||
hashedPassword, err := u.hashPassword(newPassword)
|
||
if err != nil {
|
||
return fmt.Errorf("密码加密失败: %w", err)
|
||
}
|
||
u.Password = hashedPassword
|
||
|
||
return nil
|
||
}
|
||
|
||
// CheckPassword 验证密码是否正确
|
||
func (u *User) CheckPassword(password string) bool {
|
||
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
|
||
return err == nil
|
||
}
|
||
|
||
// SetPassword 设置密码(用于注册或重置密码)
|
||
func (u *User) SetPassword(password string) error {
|
||
// 验证密码强度
|
||
if err := u.validatePasswordStrength(password); err != nil {
|
||
return err
|
||
}
|
||
|
||
// 加密密码
|
||
hashedPassword, err := u.hashPassword(password)
|
||
if err != nil {
|
||
return fmt.Errorf("密码加密失败: %w", err)
|
||
}
|
||
|
||
u.Password = hashedPassword
|
||
return nil
|
||
}
|
||
|
||
// CanLogin 检查用户是否可以登录
|
||
func (u *User) CanLogin() bool {
|
||
// 检查用户是否被删除
|
||
if !u.DeletedAt.Time.IsZero() {
|
||
return false
|
||
}
|
||
|
||
// 检查必要字段是否存在
|
||
if u.Phone == "" || u.Password == "" {
|
||
return false
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
// IsActive 检查用户是否处于活跃状态
|
||
func (u *User) IsActive() bool {
|
||
return u.DeletedAt.Time.IsZero()
|
||
}
|
||
|
||
// IsDeleted 检查用户是否已被删除
|
||
func (u *User) IsDeleted() bool {
|
||
return !u.DeletedAt.Time.IsZero()
|
||
}
|
||
|
||
// ================ 手机号相关方法 ================
|
||
|
||
// IsValidPhone 验证手机号格式
|
||
func (u *User) IsValidPhone() bool {
|
||
return IsValidPhoneFormat(u.Phone)
|
||
}
|
||
|
||
// SetPhone 设置手机号
|
||
func (u *User) SetPhone(phone string) error {
|
||
if !IsValidPhoneFormat(phone) {
|
||
return NewValidationError("手机号格式无效")
|
||
}
|
||
u.Phone = phone
|
||
return nil
|
||
}
|
||
|
||
// GetMaskedPhone 获取脱敏的手机号
|
||
func (u *User) GetMaskedPhone() string {
|
||
if len(u.Phone) < 7 {
|
||
return u.Phone
|
||
}
|
||
return u.Phone[:3] + "****" + u.Phone[len(u.Phone)-4:]
|
||
}
|
||
|
||
// ================ 私有辅助方法 ================
|
||
|
||
// hashPassword 加密密码
|
||
func (u *User) hashPassword(password string) (string, error) {
|
||
hashedBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
return string(hashedBytes), nil
|
||
}
|
||
|
||
// validatePasswordStrength 验证密码强度
|
||
func (u *User) validatePasswordStrength(password string) error {
|
||
if len(password) < 8 {
|
||
return NewValidationError("密码长度至少8位")
|
||
}
|
||
|
||
if len(password) > 128 {
|
||
return NewValidationError("密码长度不能超过128位")
|
||
}
|
||
|
||
// 检查是否包含数字
|
||
hasDigit := regexp.MustCompile(`[0-9]`).MatchString(password)
|
||
if !hasDigit {
|
||
return NewValidationError("密码必须包含数字")
|
||
}
|
||
|
||
// 检查是否包含字母
|
||
hasLetter := regexp.MustCompile(`[a-zA-Z]`).MatchString(password)
|
||
if !hasLetter {
|
||
return NewValidationError("密码必须包含字母")
|
||
}
|
||
|
||
// 检查是否包含特殊字符(可选,可以根据需求调整)
|
||
hasSpecial := regexp.MustCompile(`[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]`).MatchString(password)
|
||
if !hasSpecial {
|
||
return NewValidationError("密码必须包含特殊字符")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// ================ 静态工具方法 ================
|
||
|
||
// IsValidPhoneFormat 验证手机号格式(静态方法)
|
||
func IsValidPhoneFormat(phone string) bool {
|
||
if phone == "" {
|
||
return false
|
||
}
|
||
// 中国手机号验证(11位数字,以1开头)
|
||
pattern := `^1[3-9]\d{9}$`
|
||
matched, _ := regexp.MatchString(pattern, phone)
|
||
return matched
|
||
}
|
||
|
||
// NewUser 创建新用户(工厂方法)
|
||
func NewUser(phone, password string) (*User, error) {
|
||
user := &User{
|
||
ID: "", // 由数据库或调用方设置
|
||
Phone: phone,
|
||
}
|
||
|
||
// 验证手机号
|
||
if err := user.SetPhone(phone); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 设置密码
|
||
if err := user.SetPassword(password); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return user, nil
|
||
}
|
||
|
||
// TableName 指定表名
|
||
func (User) TableName() string {
|
||
return "users"
|
||
}
|
||
|
||
// ValidationError 验证错误
|
||
// 自定义验证错误类型,提供结构化的错误信息
|
||
type ValidationError struct {
|
||
Message string
|
||
}
|
||
|
||
// Error 实现error接口
|
||
func (e *ValidationError) Error() string {
|
||
return e.Message
|
||
}
|
||
|
||
// NewValidationError 创建新的验证错误
|
||
// 工厂方法,用于创建验证错误实例
|
||
func NewValidationError(message string) *ValidationError {
|
||
return &ValidationError{Message: message}
|
||
}
|
||
|
||
// IsValidationError 检查是否为验证错误
|
||
func IsValidationError(err error) bool {
|
||
var validationErr *ValidationError
|
||
return errors.As(err, &validationErr)
|
||
}
|