235 lines
5.0 KiB
PHP
235 lines
5.0 KiB
PHP
<?php
|
||
|
||
namespace App\Models;
|
||
|
||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||
use Database\Factories\UserFactory;
|
||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||
use Illuminate\Notifications\Notifiable;
|
||
use Laravel\Sanctum\HasApiTokens;
|
||
|
||
class User extends Authenticatable
|
||
{
|
||
/** @use HasFactory<UserFactory> */
|
||
use HasFactory, Notifiable, HasApiTokens;
|
||
|
||
/**
|
||
* The attributes that are mass assignable.
|
||
*
|
||
* @var list<string>
|
||
*/
|
||
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<string>
|
||
*/
|
||
protected $hidden = [
|
||
'password',
|
||
'remember_token',
|
||
];
|
||
|
||
/**
|
||
* Get the attributes that should be cast.
|
||
*
|
||
* @return array<string, string>
|
||
*/
|
||
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]);
|
||
}
|
||
}
|