2025-11-10 10:45:43 +08:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace App\Model;
|
|
|
|
|
|
2026-03-09 10:12:35 +08:00
|
|
|
use Hyperf\Database\Model\Relations\BelongsTo;
|
2026-03-05 10:22:54 +08:00
|
|
|
use Hyperf\Database\Model\Relations\HasMany;
|
2025-11-10 10:45:43 +08:00
|
|
|
use Hyperf\DbConnection\Model\Model;
|
2026-03-06 16:55:46 +08:00
|
|
|
use OpenApi\Attributes as OA;
|
2025-11-10 10:45:43 +08:00
|
|
|
use Qbhy\HyperfAuth\Authenticatable;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @property int $id
|
|
|
|
|
* @property string $username
|
|
|
|
|
* @property string $password
|
|
|
|
|
* @property string $email
|
|
|
|
|
* @property int $status
|
2026-03-09 10:12:35 +08:00
|
|
|
* @property int $role_id
|
|
|
|
|
* @property bool $api_key_enabled
|
2025-11-10 10:45:43 +08:00
|
|
|
* @property array $ext
|
|
|
|
|
* @property string $refresh_token
|
|
|
|
|
* @property \Carbon\Carbon $refresh_token_expires_at
|
|
|
|
|
* @property \Carbon\Carbon $created_at
|
|
|
|
|
* @property \Carbon\Carbon $updated_at
|
|
|
|
|
*/
|
2026-03-06 16:55:46 +08:00
|
|
|
#[OA\Schema(
|
|
|
|
|
schema: 'User',
|
|
|
|
|
type: 'object',
|
|
|
|
|
properties: [
|
|
|
|
|
new OA\Property(property: 'id', type: 'integer', example: 1),
|
|
|
|
|
new OA\Property(property: 'username', type: 'string', example: 'user_1234'),
|
|
|
|
|
new OA\Property(property: 'email', type: 'string', example: 'user@example.com'),
|
|
|
|
|
new OA\Property(property: 'status', type: 'integer', example: 1),
|
2026-03-09 10:12:35 +08:00
|
|
|
new OA\Property(property: 'role_id', type: 'integer', nullable: true, example: 1),
|
|
|
|
|
new OA\Property(property: 'api_key_enabled', type: 'boolean', example: false),
|
2026-03-06 16:55:46 +08:00
|
|
|
new OA\Property(property: 'ext', type: 'object', nullable: true, example: ['nickname' => 'user']),
|
|
|
|
|
new OA\Property(property: 'refresh_token_expires_at', type: 'string', format: 'date-time', nullable: true),
|
|
|
|
|
new OA\Property(property: 'created_at', type: 'string', format: 'date-time'),
|
|
|
|
|
new OA\Property(property: 'updated_at', type: 'string', format: 'date-time'),
|
|
|
|
|
]
|
|
|
|
|
)]
|
2025-11-10 10:45:43 +08:00
|
|
|
class User extends Model implements Authenticatable
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* The table associated with the model.
|
|
|
|
|
*/
|
|
|
|
|
protected ?string $table = 'users';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The attributes that are mass assignable.
|
|
|
|
|
*/
|
|
|
|
|
protected array $fillable = [
|
|
|
|
|
'username',
|
|
|
|
|
'password',
|
|
|
|
|
'email',
|
|
|
|
|
'status',
|
2026-03-09 10:12:35 +08:00
|
|
|
'role_id',
|
|
|
|
|
'api_key_enabled',
|
2025-11-10 10:45:43 +08:00
|
|
|
'ext',
|
|
|
|
|
'refresh_token',
|
|
|
|
|
'refresh_token_expires_at',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The attributes that should be hidden for serialization.
|
|
|
|
|
*/
|
|
|
|
|
protected array $hidden = [
|
|
|
|
|
'password',
|
|
|
|
|
'refresh_token',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The attributes that should be cast to native types.
|
|
|
|
|
*/
|
|
|
|
|
protected array $casts = [
|
|
|
|
|
'id' => 'integer',
|
|
|
|
|
'status' => 'integer',
|
2026-03-09 10:12:35 +08:00
|
|
|
'role_id' => 'integer',
|
|
|
|
|
'api_key_enabled' => 'boolean',
|
2025-11-10 10:45:43 +08:00
|
|
|
'ext' => 'array',
|
|
|
|
|
'refresh_token_expires_at' => 'datetime',
|
|
|
|
|
'created_at' => 'datetime',
|
|
|
|
|
'updated_at' => 'datetime',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the name of the unique identifier for the user.
|
|
|
|
|
*/
|
|
|
|
|
public function getId()
|
|
|
|
|
{
|
|
|
|
|
return $this->id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Retrieve a user by their unique identifier.
|
|
|
|
|
*/
|
|
|
|
|
public static function retrieveById($key): ?Authenticatable
|
|
|
|
|
{
|
|
|
|
|
return static::query()->find($key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Set the user's password (auto-hash).
|
|
|
|
|
*/
|
|
|
|
|
public function setPasswordAttribute($value): void
|
|
|
|
|
{
|
|
|
|
|
$this->attributes['password'] = password_hash($value, PASSWORD_DEFAULT);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Verify the user's password.
|
|
|
|
|
*/
|
|
|
|
|
public function verifyPassword(string $password): bool
|
|
|
|
|
{
|
|
|
|
|
return password_verify($password, $this->password);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-09 10:12:35 +08:00
|
|
|
/**
|
|
|
|
|
* 角色关联
|
|
|
|
|
*/
|
|
|
|
|
public function role(): BelongsTo
|
|
|
|
|
{
|
|
|
|
|
return $this->belongsTo(Role::class, 'role_id');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 用户的 API Keys
|
|
|
|
|
*/
|
|
|
|
|
public function apiKeys(): HasMany
|
|
|
|
|
{
|
|
|
|
|
return $this->hasMany(ApiKey::class, 'user_id');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function isAdministrator(): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->role?->name === 'administrator';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function isDeveloper(): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->role?->name === 'developer';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function isAccessor(): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->role?->name === 'accessor';
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-05 10:22:54 +08:00
|
|
|
public function developedPlatforms(): HasMany
|
|
|
|
|
{
|
|
|
|
|
return $this->hasMany(Platform::class, 'developer_id');
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-06 15:29:04 +08:00
|
|
|
// @TODO 重新实现删除用户时平台归属转移逻辑(Hyperf 不支持 static::deleting 事件绑定)
|
2026-03-05 10:22:54 +08:00
|
|
|
|
2025-11-10 10:45:43 +08:00
|
|
|
/**
|
|
|
|
|
* Check if refresh token is valid.
|
|
|
|
|
*/
|
|
|
|
|
public function isRefreshTokenValid(): bool
|
|
|
|
|
{
|
|
|
|
|
if (!$this->refresh_token || !$this->refresh_token_expires_at) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->refresh_token_expires_at->isFuture();
|
|
|
|
|
}
|
|
|
|
|
}
|