erp-backend/app/Models/User.php
2026-04-01 17:07:04 +08:00

235 lines
5.0 KiB
PHP
Raw Permalink 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.

<?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]);
}
}