*/ use HasFactory, Notifiable, HasApiTokens; /** * The attributes that are mass assignable. * * @var list */ protected $fillable = [ 'name', 'email', 'password', 'phone', 'wechat_openid', 'wechat_unionid', 'avatar', 'status', 'locked_until', 'last_login_at', 'last_login_ip', 'warehouse_ids', ]; /** * The attributes that should be hidden for serialization. * * @var list */ protected $hidden = [ 'password', 'remember_token', ]; /** * Get the attributes that should be cast. * * @return array */ protected function casts(): array { return [ 'email_verified_at' => 'datetime', 'password' => 'hashed', 'locked_until' => 'datetime', 'last_login_at' => 'datetime', ]; } /** * 判断当前用户是否为测试用户(邮箱为 test@example.com) */ public function isTestUser(): bool { return $this->email === 'test@example.com'; } /** * 检查用户是否被锁定 */ public function isLocked(): bool { if ($this->status === 'disabled') { return true; } if ($this->locked_until && $this->locked_until->isFuture()) { return true; } return false; } /** * 获取用户锁定剩余时间(秒) */ public function getLockedRemainingSeconds(): int { if (!$this->locked_until) { return 0; } return max(0, $this->locked_until->diffInSeconds(now())); } /** * 锁定用户 */ public function lock(int $minutes = 30): void { $this->update([ 'status' => 'locked', 'locked_until' => now()->addMinutes($minutes), ]); } /** * 解锁用户 */ public function unlock(): void { $this->update([ 'status' => 'active', 'locked_until' => null, ]); } /** * 禁用用户 */ public function disable(): void { $this->update(['status' => 'disabled']); } /** * 记录登录信息 */ public function recordLogin(string $ip = null): void { $this->update([ 'last_login_at' => now(), 'last_login_ip' => $ip, 'status' => 'active', 'locked_until' => null, ]); } /** * 通过手机号查找用户 */ public static function findByPhone(string $phone): ?self { return static::where('phone', $phone)->first(); } /** * 通过微信 OpenID 查找用户 */ public static function findByWeChatOpenid(string $openid): ?self { return static::where('wechat_openid', $openid)->first(); } /** * 通过微信 UnionID 查找用户 */ public static function findByWeChatUnionid(string $unionid): ?self { return static::where('wechat_unionid', $unionid)->first(); } /** * 验证密码强度 */ public static function validatePasswordStrength(string $password): array { $errors = []; if (strlen($password) < 8) { $errors[] = '密码至少8个字符'; } if (!preg_match('/[A-Z]/', $password)) { $errors[] = '密码必须包含大写字母'; } if (!preg_match('/[a-z]/', $password)) { $errors[] = '密码必须包含小写字母'; } if (!preg_match('/[0-9]/', $password)) { $errors[] = '密码必须包含数字'; } return $errors; } /** * 角色关联 */ public function roles() { return $this->belongsToMany(Role::class, 'user_roles'); } /** * 判断是否有某权限 */ public function hasPermission(string $slug): bool { if ($this->roles->contains('slug', 'super_admin')) { return true; } return $this->roles->flatMap->permissions->pluck('slug')->contains($slug); } /** * 判断是否有某角色 */ public function hasRole(string $slug): bool { return $this->roles->contains('slug', $slug); } /** * 分配角色 */ public function assignRole(Role $role): void { $this->roles()->syncWithoutDetaching([$role->id]); } /** * 移除角色 */ public function removeRole(Role $role): void { $this->roles()->detach([$role->id]); } }