Files
tyapi-server/internal/domains/user/entities/user.go
2025-07-11 21:05:58 +08:00

276 lines
6.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}