feat(database): 添加用户角色权限系统及相关监控功能

- 创建用户表(users)包含基本信息和认证字段
- 创建角色表(roles)用于权限控制
- 创建权限表(permissions)定义系统权限
- 创建用户角色关联表(user_roles)建立用户与角色关系
- 创建角色权限关联表(role_permissions)建立角色与权限关系
- 创建迁移记录表(migrations)追踪数据库变更
- 添加AdminController提供管理员面板功能
- 实现系统监控、配置管理、缓存清理等功能
- 添加AOP切面编程支持的各种通知类型
- 实现告警管理AlertManager支持多渠道告警
- 添加文档注解接口规范
This commit is contained in:
Lawson
2026-04-08 17:00:28 +08:00
commit 2782d765fb
270 changed files with 107192 additions and 0 deletions

298
app/Dto/ApiResponseDto.php Normal file
View File

@@ -0,0 +1,298 @@
<?php
declare(strict_types=1);
namespace App\Dto;
/**
* API响应数据传输对象
*/
class ApiResponseDto extends BaseDto
{
private int $code = 0;
private string $message = 'success';
private mixed $data = null;
private string $traceId = '';
private int $timestamp = 0;
private array $meta = [];
public function __construct()
{
$this->timestamp = time();
$this->traceId = \Fendx\Core\Context\Context::getTraceId();
}
public function getCode(): int
{
return $this->code;
}
public function setCode(int $code): self
{
$this->code = $code;
return $this;
}
public function getMessage(): string
{
return $this->message;
}
public function setMessage(string $message): self
{
$this->message = $message;
return $this;
}
public function getData(): mixed
{
return $this->data;
}
public function setData(mixed $data): self
{
$this->data = $data;
return $this;
}
public function getTraceId(): string
{
return $this->traceId;
}
public function setTraceId(string $traceId): self
{
$this->traceId = $traceId;
return $this;
}
public function getTimestamp(): int
{
return $this->timestamp;
}
public function setTimestamp(int $timestamp): self
{
$this->timestamp = $timestamp;
return $this;
}
public function getMeta(): array
{
return $this->meta;
}
public function setMeta(array $meta): self
{
$this->meta = $meta;
return $this;
}
/**
* 添加元数据
*/
public function addMeta(string $key, mixed $value): self
{
$this->meta[$key] = $value;
return $this;
}
/**
* 创建成功响应
*/
public static function success(mixed $data = null, string $message = 'success'): self
{
return (new self())
->setCode(0)
->setMessage($message)
->setData($data);
}
/**
* 创建错误响应
*/
public static function error(string $message, int $code = 400, mixed $data = null): self
{
return (new self())
->setCode($code)
->setMessage($message)
->setData($data);
}
/**
* 创建分页响应
*/
public static function paginate(array $items, int $total, int $page, int $pageSize, string $message = 'success'): self
{
$data = [
'items' => $items,
'pagination' => [
'total' => $total,
'page' => $page,
'page_size' => $pageSize,
'total_pages' => ceil($total / $pageSize),
'has_more' => $page * $pageSize < $total,
'has_prev' => $page > 1,
]
];
return (new self())
->setCode(0)
->setMessage($message)
->setData($data);
}
/**
* 创建未找到响应
*/
public static function notFound(string $message = 'Resource not found'): self
{
return self::error($message, 404);
}
/**
* 创建未授权响应
*/
public static function unauthorized(string $message = 'Unauthorized'): self
{
return self::error($message, 401);
}
/**
* 创建禁止访问响应
*/
public static function forbidden(string $message = 'Forbidden'): self
{
return self::error($message, 403);
}
/**
* 创建验证错误响应
*/
public static function validationError(string $message = 'Validation failed', array $errors = []): self
{
return self::error($message, 422, $errors);
}
/**
* 创建服务器错误响应
*/
public static function serverError(string $message = 'Internal server error'): self
{
return self::error($message, 500);
}
/**
* 检查是否为成功响应
*/
public function isSuccess(): bool
{
return $this->code === 0;
}
/**
* 检查是否为错误响应
*/
public function isError(): bool
{
return $this->code !== 0;
}
/**
* 获取HTTP状态码
*/
public function getHttpStatusCode(): int
{
return match ($this->code) {
0 => 200,
400 => 400,
401 => 401,
403 => 403,
404 => 404,
422 => 422,
500 => 500,
default => 200,
};
}
/**
* 设置分页元数据
*/
public function setPaginationMeta(int $total, int $page, int $pageSize): self
{
return $this->addMeta('total', $total)
->addMeta('page', $page)
->addMeta('page_size', $pageSize)
->addMeta('total_pages', ceil($total / $pageSize));
}
/**
* 设置时间元数据
*/
public function setTimeMeta(float $executionTime = null, string $timezone = null): self
{
if ($executionTime !== null) {
$this->addMeta('execution_time', round($executionTime, 3));
}
if ($timezone !== null) {
$this->addMeta('timezone', $timezone);
}
return $this;
}
/**
* 设置请求元数据
*/
public function setRequestMeta(string $method = null, string $path = null, string $ip = null): self
{
if ($method !== null) {
$this->addMeta('method', $method);
}
if ($path !== null) {
$this->addMeta('path', $path);
}
if ($ip !== null) {
$this->addMeta('ip', $ip);
}
return $this;
}
/**
* 转换为数组(格式化输出)
*/
public function toArray(): array
{
return [
'code' => $this->code,
'message' => $this->message,
'data' => $this->data,
'trace_id' => $this->traceId,
'timestamp' => $this->timestamp,
'meta' => empty($this->meta) ? null : $this->meta,
];
}
/**
* 转换为HTTP响应数组
*/
public function toHttpResponse(): array
{
return [
'status_code' => $this->getHttpStatusCode(),
'headers' => [
'Content-Type' => 'application/json',
'X-Trace-Id' => $this->traceId,
],
'body' => $this->toArray(),
];
}
}

252
app/Dto/BaseDto.php Normal file
View File

@@ -0,0 +1,252 @@
<?php
declare(strict_types=1);
namespace App\Dto;
/**
* DTO基类
* 所有数据传输对象都应该继承此类
*/
abstract class BaseDto
{
/**
* 数组转DTO
*/
public static function fromArray(array $data): static
{
$dto = new static();
foreach ($data as $key => $value) {
$method = 'set' . str_replace('_', '', ucwords($key, '_'));
if (method_exists($dto, $method)) {
$dto->$method($value);
} elseif (property_exists($dto, $key)) {
$dto->$key = $value;
}
}
return $dto;
}
/**
* DTO转数组
*/
public function toArray(): array
{
$data = [];
$reflection = new \ReflectionClass($this);
foreach ($reflection->getProperties() as $property) {
$propertyName = $property->getName();
$method = 'get' . str_replace('_', '', ucwords($propertyName, '_'));
if (method_exists($this, $method)) {
$data[$propertyName] = $this->$method();
} else {
$data[$propertyName] = $this->$propertyName ?? null;
}
}
return $data;
}
/**
* DTO转JSON
*/
public function toJson(): string
{
return json_encode($this->toArray(), JSON_UNESCAPED_UNICODE);
}
/**
* JSON转DTO
*/
public static function fromJson(string $json): static
{
$data = json_decode($json, true);
if (!is_array($data)) {
throw new \InvalidArgumentException('Invalid JSON data');
}
return static::fromArray($data);
}
/**
* 验证DTO数据
*/
public function validate(): array
{
$errors = [];
$reflection = new \ReflectionClass($this);
foreach ($reflection->getProperties() as $property) {
$propertyName = $property->getName();
$value = $this->$propertyName ?? null;
// 检查必填字段
$attributes = $property->getAttributes('Required');
if (!empty($attributes) && ($value === null || $value === '')) {
$errors[$propertyName] = "Field {$propertyName} is required";
continue;
}
// 检查数据类型
$type = $property->getType();
if ($type && $value !== null) {
$typeName = $type->getName();
if (!$this->validateType($value, $typeName)) {
$errors[$propertyName] = "Field {$propertyName} must be of type {$typeName}";
}
}
}
return $errors;
}
/**
* 验证数据类型
*/
private function validateType(mixed $value, string $type): bool
{
return match ($type) {
'int', 'integer' => is_int($value),
'float', 'double' => is_float($value),
'string' => is_string($value),
'bool', 'boolean' => is_bool($value),
'array' => is_array($value),
'object' => is_object($value),
default => true,
};
}
/**
* 获取所有属性
*/
public function getProperties(): array
{
$reflection = new \ReflectionClass($this);
$properties = [];
foreach ($reflection->getProperties() as $property) {
$properties[$property->getName()] = $this->{$property->getName()} ?? null;
}
return $properties;
}
/**
* 设置属性
*/
public function setProperty(string $name, mixed $value): void
{
$method = 'set' . str_replace('_', '', ucwords($name, '_'));
if (method_exists($this, $method)) {
$this->$method($value);
} elseif (property_exists($this, $name)) {
$this->$name = $value;
}
}
/**
* 获取属性
*/
public function getProperty(string $name): mixed
{
$method = 'get' . str_replace('_', '', ucwords($name, '_'));
if (method_exists($this, $method)) {
return $this->$method();
}
return $this->$name ?? null;
}
/**
* 检查属性是否存在
*/
public function hasProperty(string $name): bool
{
return property_exists($this, $name) || method_exists($this, 'get' . str_replace('_', '', ucwords($name, '_')));
}
/**
* 克隆DTO
*/
public function clone(): static
{
return clone $this;
}
/**
* 合并另一个DTO
*/
public function merge(BaseDto $other): static
{
$newDto = $this->clone();
$otherData = $other->toArray();
foreach ($otherData as $key => $value) {
if ($value !== null) {
$newDto->setProperty($key, $value);
}
}
return $newDto;
}
/**
* 魔术方法:转换为字符串
*/
public function __toString(): string
{
return $this->toJson();
}
/**
* 魔术方法:调试输出
*/
public function __debugInfo(): array
{
return $this->toArray();
}
}
/**
* 必填字段注解
*/
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Required
{
public function __construct(public string $message = '') {}
}
/**
* 字段长度注解
*/
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Length
{
public function __construct(public int $min = 0, public int $max = 255) {}
}
/**
* 字段范围注解
*/
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Range
{
public function __construct(public mixed $min = null, public mixed $max = null) {}
}
/**
* 正则表达式注解
*/
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Pattern
{
public function __construct(public string $regex) {}
}

440
app/Dto/CollectionDto.php Normal file
View File

@@ -0,0 +1,440 @@
<?php
declare(strict_types=1);
namespace App\Dto;
/**
* 数据集合传输对象
*/
class CollectionDto extends BaseDto
{
private array $items = [];
private int $count = 0;
private array $meta = [];
public function __construct(array $items = [])
{
$this->items = $items;
$this->count = count($items);
}
public function getItems(): array
{
return $this->items;
}
public function setItems(array $items): self
{
$this->items = $items;
$this->count = count($items);
return $this;
}
public function getCount(): int
{
return $this->count;
}
public function getMeta(): array
{
return $this->meta;
}
public function setMeta(array $meta): self
{
$this->meta = $meta;
return $this;
}
/**
* 添加项目
*/
public function add(mixed $item): self
{
$this->items[] = $item;
$this->count++;
return $this;
}
/**
* 添加元数据
*/
public function addMeta(string $key, mixed $value): self
{
$this->meta[$key] = $value;
return $this;
}
/**
* 检查是否为空
*/
public function isEmpty(): bool
{
return $this->count === 0;
}
/**
* 检查是否不为空
*/
public function isNotEmpty(): bool
{
return $this->count > 0;
}
/**
* 获取第一个项目
*/
public function first(): mixed
{
return $this->items[0] ?? null;
}
/**
* 获取最后一个项目
*/
public function last(): mixed
{
return $this->items[$this->count - 1] ?? null;
}
/**
* 获取指定索引的项目
*/
public function get(int $index): mixed
{
return $this->items[$index] ?? null;
}
/**
* 映射集合
*/
public function map(callable $callback): self
{
$new = clone $this;
$new->items = array_map($callback, $this->items);
return $new;
}
/**
* 过滤集合
*/
public function filter(callable $callback): self
{
$new = clone $this;
$new->items = array_filter($this->items, $callback);
$new->count = count($new->items);
return $new;
}
/**
* 排序集合
*/
public function sort(callable $callback): self
{
$new = clone $this;
$items = $this->items;
usort($items, $callback);
$new->items = $items;
return $new;
}
/**
* 反转集合
*/
public function reverse(): self
{
$new = clone $this;
$new->items = array_reverse($this->items);
return $new;
}
/**
* 获取唯一的集合
*/
public function unique(): self
{
$new = clone $this;
$new->items = array_unique($this->items);
$new->count = count($new->items);
return $new;
}
/**
* 切片集合
*/
public function slice(int $offset, ?int $length = null): self
{
$new = clone $this;
$new->items = array_slice($this->items, $offset, $length);
$new->count = count($new->items);
return $new;
}
/**
* 限制集合大小
*/
public function take(int $limit): self
{
return $this->slice(0, $limit);
}
/**
* 跳过指定数量
*/
public function skip(int $count): self
{
return $this->slice($count);
}
/**
* 分块集合
*/
public function chunk(int $size): array
{
return array_chunk($this->items, $size);
}
/**
* 求和
*/
public function sum(callable|string $key = null): float
{
if ($key === null) {
return array_sum($this->items);
}
if (is_callable($key)) {
return array_sum(array_map($key, $this->items));
}
return array_sum(array_column($this->items, $key));
}
/**
* 求平均值
*/
public function avg(callable|string $key = null): float
{
if ($this->isEmpty()) {
return 0;
}
return $this->sum($key) / $this->count;
}
/**
* 求最大值
*/
public function max(callable|string $key = null): mixed
{
if ($this->isEmpty()) {
return null;
}
if ($key === null) {
return max($this->items);
}
if (is_callable($key)) {
$values = array_map($key, $this->items);
return max($values);
}
return max(array_column($this->items, $key));
}
/**
* 求最小值
*/
public function min(callable|string $key = null): mixed
{
if ($this->isEmpty()) {
return null;
}
if ($key === null) {
return min($this->items);
}
if (is_callable($key)) {
$values = array_map($key, $this->items);
return min($values);
}
return min(array_column($this->items, $key));
}
/**
* 查找项目
*/
public function find(callable $callback): mixed
{
foreach ($this->items as $item) {
if ($callback($item)) {
return $item;
}
}
return null;
}
/**
* 检查是否存在项目
*/
public function contains(mixed $item): bool
{
return in_array($item, $this->items, true);
}
/**
* 检查是否包含满足条件的项目
*/
public function containsWhere(callable $callback): bool
{
return $this->find($callback) !== null;
}
/**
* 获取所有键
*/
public function keys(): array
{
return array_keys($this->items);
}
/**
* 获取所有值
*/
public function values(): array
{
return array_values($this->items);
}
/**
* 合并其他集合
*/
public function merge(self $other): self
{
$new = clone $this;
$new->items = array_merge($this->items, $other->getItems());
$new->count = count($new->items);
return $new;
}
/**
* 转换为分页对象
*/
public function toPagination(int $page = 1, int $pageSize = 10): PaginationDto
{
$offset = ($page - 1) * $pageSize;
$items = array_slice($this->items, $offset, $pageSize);
return PaginationDto::create($items, $this->count, $page, $pageSize);
}
/**
* 创建空集合
*/
public static function empty(): self
{
return new self([]);
}
/**
* 从数组创建集合
*/
public static function fromArray(array $items): self
{
return new self($items);
}
/**
* 创建包含单个项目的集合
*/
public static function of(mixed $item): self
{
return new self([$item]);
}
/**
* 创建范围集合
*/
public static function range(int $start, int $end): self
{
return new self(range($start, $end));
}
/**
* 转换为数组
*/
public function toArray(): array
{
return [
'items' => $this->items,
'count' => $this->count,
'meta' => empty($this->meta) ? null : $this->meta,
];
}
/**
* 转换为JSON
*/
public function toJson(): string
{
return json_encode($this->toArray(), JSON_UNESCAPED_UNICODE);
}
/**
* 实现ArrayAccess接口
*/
public function offsetExists(mixed $offset): bool
{
return isset($this->items[$offset]);
}
public function offsetGet(mixed $offset): mixed
{
return $this->items[$offset] ?? null;
}
public function offsetSet(mixed $offset, mixed $value): void
{
if ($offset === null) {
$this->items[] = $value;
} else {
$this->items[$offset] = $value;
}
$this->count = count($this->items);
}
public function offsetUnset(mixed $offset): void
{
unset($this->items[$offset]);
$this->count = count($this->items);
}
/**
* 实现Countable接口
*/
public function count(): int
{
return $this->count;
}
/**
* 实现IteratorAggregate接口
*/
public function getIterator(): \ArrayIterator
{
return new \ArrayIterator($this->items);
}
/**
* 实现JsonSerializable接口
*/
public function jsonSerialize(): array
{
return $this->toArray();
}
}

362
app/Dto/PaginationDto.php Normal file
View File

@@ -0,0 +1,362 @@
<?php
declare(strict_types=1);
namespace App\Dto;
/**
* 分页数据传输对象
*/
class PaginationDto extends BaseDto
{
private array $items = [];
#[Required]
private int $total = 0;
#[Required]
private int $page = 1;
#[Required]
private int $pageSize = 10;
private ?int $totalPages = null;
private ?bool $hasMore = null;
private ?bool $hasPrev = null;
private ?bool $hasNext = null;
private ?int $from = null;
private ?int $to = null;
private array $filters = [];
private array $sorts = [];
public function getItems(): array
{
return $this->items;
}
public function setItems(array $items): self
{
$this->items = $items;
$this->calculateDerivedValues();
return $this;
}
public function getTotal(): int
{
return $this->total;
}
public function setTotal(int $total): self
{
$this->total = $total;
$this->calculateDerivedValues();
return $this;
}
public function getPage(): int
{
return $this->page;
}
public function setPage(int $page): self
{
$this->page = max(1, $page);
$this->calculateDerivedValues();
return $this;
}
public function getPageSize(): int
{
return $this->pageSize;
}
public function setPageSize(int $pageSize): self
{
$this->pageSize = max(1, $pageSize);
$this->calculateDerivedValues();
return $this;
}
public function getTotalPages(): ?int
{
return $this->totalPages;
}
public function getHasMore(): ?bool
{
return $this->hasMore;
}
public function getHasPrev(): ?bool
{
return $this->hasPrev;
}
public function getHasNext(): ?bool
{
return $this->hasNext;
}
public function getFrom(): ?int
{
return $this->from;
}
public function getTo(): ?int
{
return $this->to;
}
public function getFilters(): array
{
return $this->filters;
}
public function setFilters(array $filters): self
{
$this->filters = $filters;
return $this;
}
public function getSorts(): array
{
return $this->sorts;
}
public function setSorts(array $sorts): self
{
$this->sorts = $sorts;
return $this;
}
/**
* 添加过滤器
*/
public function addFilter(string $key, mixed $value): self
{
$this->filters[$key] = $value;
return $this;
}
/**
* 添加排序
*/
public function addSort(string $field, string $direction = 'asc'): self
{
$this->sorts[$field] = strtolower($direction);
return $this;
}
/**
* 计算派生值
*/
private function calculateDerivedValues(): void
{
$this->totalPages = $this->pageSize > 0 ? (int) ceil($this->total / $this->pageSize) : 0;
$this->hasMore = $this->page * $this->pageSize < $this->total;
$this->hasPrev = $this->page > 1;
$this->hasNext = $this->page < $this->totalPages;
$this->from = $this->total > 0 ? (($this->page - 1) * $this->pageSize) + 1 : null;
$this->to = min($this->page * $this->pageSize, $this->total);
}
/**
* 获取偏移量
*/
public function getOffset(): int
{
return ($this->page - 1) * $this->pageSize;
}
/**
* 获取限制数量
*/
public function getLimit(): int
{
return $this->pageSize;
}
/**
* 检查是否为第一页
*/
public function isFirstPage(): bool
{
return $this->page === 1;
}
/**
* 检查是否为最后一页
*/
public function isLastPage(): bool
{
return !$this->hasNext;
}
/**
* 获取上一页页码
*/
public function getPrevPage(): ?int
{
return $this->hasPrev ? $this->page - 1 : null;
}
/**
* 获取下一页页码
*/
public function getNextPage(): ?int
{
return $this->hasNext ? $this->page + 1 : null;
}
/**
* 创建分页对象
*/
public static function create(array $items, int $total, int $page = 1, int $pageSize = 10): self
{
return (new self())
->setItems($items)
->setTotal($total)
->setPage($page)
->setPageSize($pageSize);
}
/**
* 从查询结果创建分页对象
*/
public static function fromQuery(array $items, int $total, array $params = []): self
{
$page = (int) ($params['page'] ?? 1);
$pageSize = (int) ($params['page_size'] ?? $params['limit'] ?? 10);
$filters = $params['filters'] ?? [];
$sorts = $params['sorts'] ?? [];
return (new self())
->setItems($items)
->setTotal($total)
->setPage($page)
->setPageSize($pageSize)
->setFilters($filters)
->setSorts($sorts);
}
/**
* 转换为数组
*/
public function toArray(): array
{
return [
'items' => $this->items,
'pagination' => [
'total' => $this->total,
'page' => $this->page,
'page_size' => $this->pageSize,
'total_pages' => $this->totalPages,
'has_more' => $this->hasMore,
'has_prev' => $this->hasPrev,
'has_next' => $this->hasNext,
'from' => $this->from,
'to' => $this->to,
'prev_page' => $this->getPrevPage(),
'next_page' => $this->getNextPage(),
],
'filters' => empty($this->filters) ? null : $this->filters,
'sorts' => empty($this->sorts) ? null : $this->sorts,
];
}
/**
* 获取分页信息数组
*/
public function getPaginationInfo(): array
{
return [
'total' => $this->total,
'page' => $this->page,
'page_size' => $this->pageSize,
'total_pages' => $this->totalPages,
'has_more' => $this->hasMore,
'has_prev' => $this->hasPrev,
'has_next' => $this->hasNext,
'from' => $this->from,
'to' => $this->to,
'prev_page' => $this->getPrevPage(),
'next_page' => $this->getNextPage(),
];
}
/**
* 获取SQL LIMIT子句
*/
public function getSqlLimit(): string
{
return "LIMIT {$this->pageSize} OFFSET " . $this->getOffset();
}
/**
* 验证分页参数
*/
public function validate(): array
{
$errors = parent::validate();
if ($this->page < 1) {
$errors['page'] = 'Page must be greater than 0';
}
if ($this->pageSize < 1 || $this->pageSize > 1000) {
$errors['page_size'] = 'Page size must be between 1 and 1000';
}
if ($this->total < 0) {
$errors['total'] = 'Total must be greater than or equal to 0';
}
return $errors;
}
/**
* 克隆分页对象,修改页码
*/
public function withPage(int $page): self
{
$new = clone $this;
$new->setPage($page);
return $new;
}
/**
* 克隆分页对象,修改页大小
*/
public function withPageSize(int $pageSize): self
{
$new = clone $this;
$new->setPageSize($pageSize);
return $new;
}
/**
* 克隆分页对象,修改过滤器
*/
public function withFilters(array $filters): self
{
$new = clone $this;
$new->setFilters($filters);
return $new;
}
/**
* 克隆分页对象,修改排序
*/
public function withSorts(array $sorts): self
{
$new = clone $this;
$new->setSorts($sorts);
return $new;
}
}

324
app/Dto/UserDto.php Normal file
View File

@@ -0,0 +1,324 @@
<?php
declare(strict_types=1);
namespace App\Dto;
/**
* 用户数据传输对象
*/
class UserDto extends BaseDto
{
#[Required]
private ?int $id = null;
#[Required]
#[Length(min: 2, max: 50)]
private string $username = '';
#[Required]
#[Length(min: 6, max: 100)]
private string $email = '';
#[Required]
#[Length(min: 6, max: 255)]
private string $password = '';
#[Length(max: 100)]
private string $nickname = '';
#[Length(max: 20)]
private string $phone = '';
#[Length(max: 255)]
private string $avatar = '';
private ?int $status = null;
private ?int $roleId = null;
private ?string $roleName = '';
private ?\DateTime $createdAt = null;
private ?\DateTime $updatedAt = null;
private ?\DateTime $lastLoginAt = null;
private array $permissions = [];
private array $roles = [];
public function getId(): ?int
{
return $this->id;
}
public function setId(int $id): self
{
$this->id = $id;
return $this;
}
public function getUsername(): string
{
return $this->username;
}
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
public function getEmail(): string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
public function getNickname(): string
{
return $this->nickname;
}
public function setNickname(string $nickname): self
{
$this->nickname = $nickname;
return $this;
}
public function getPhone(): string
{
return $this->phone;
}
public function setPhone(string $phone): self
{
$this->phone = $phone;
return $this;
}
public function getAvatar(): string
{
return $this->avatar;
}
public function setAvatar(string $avatar): self
{
$this->avatar = $avatar;
return $this;
}
public function getStatus(): ?int
{
return $this->status;
}
public function setStatus(int $status): self
{
$this->status = $status;
return $this;
}
public function getRoleId(): ?int
{
return $this->roleId;
}
public function setRoleId(int $roleId): self
{
$this->roleId = $roleId;
return $this;
}
public function getRoleName(): ?string
{
return $this->roleName;
}
public function setRoleName(string $roleName): self
{
$this->roleName = $roleName;
return $this;
}
public function getCreatedAt(): ?\DateTime
{
return $this->createdAt;
}
public function setCreatedAt(\DateTime $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
public function getUpdatedAt(): ?\DateTime
{
return $this->updatedAt;
}
public function setUpdatedAt(\DateTime $updatedAt): self
{
$this->updatedAt = $updatedAt;
return $this;
}
public function getLastLoginAt(): ?\DateTime
{
return $this->lastLoginAt;
}
public function setLastLoginAt(\DateTime $lastLoginAt): self
{
$this->lastLoginAt = $lastLoginAt;
return $this;
}
public function getPermissions(): array
{
return $this->permissions;
}
public function setPermissions(array $permissions): self
{
$this->permissions = $permissions;
return $this;
}
public function getRoles(): array
{
return $this->roles;
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* 添加权限
*/
public function addPermission(string $permission): self
{
if (!in_array($permission, $this->permissions)) {
$this->permissions[] = $permission;
}
return $this;
}
/**
* 添加角色
*/
public function addRole(string $role): self
{
if (!in_array($role, $this->roles)) {
$this->roles[] = $role;
}
return $this;
}
/**
* 检查是否有指定权限
*/
public function hasPermission(string $permission): bool
{
return in_array($permission, $this->permissions);
}
/**
* 检查是否有指定角色
*/
public function hasRole(string $role): bool
{
return in_array($role, $this->roles);
}
/**
* 获取用于API响应的数据隐藏敏感信息
*/
public function toApiResponse(): array
{
$data = $this->toArray();
// 移除敏感信息
unset($data['password']);
// 格式化日期
if ($this->createdAt) {
$data['created_at'] = $this->createdAt->format('Y-m-d H:i:s');
}
if ($this->updatedAt) {
$data['updated_at'] = $this->updatedAt->format('Y-m-d H:i:s');
}
if ($this->lastLoginAt) {
$data['last_login_at'] = $this->lastLoginAt->format('Y-m-d H:i:s');
}
return $data;
}
/**
* 创建用于登录的用户DTO
*/
public static function forLogin(string $username, string $password): self
{
return (new self())
->setUsername($username)
->setPassword($password);
}
/**
* 创建用于注册的用户DTO
*/
public static function forRegister(string $username, string $email, string $password): self
{
return (new self())
->setUsername($username)
->setEmail($email)
->setPassword($password);
}
/**
* 验证邮箱格式
*/
public function validateEmail(): bool
{
return filter_var($this->email, FILTER_VALIDATE_EMAIL) !== false;
}
/**
* 验证手机号格式
*/
public function validatePhone(): bool
{
return preg_match('/^1[3-9]\d{9}$/', $this->phone) === 1;
}
/**
* 验证用户名格式
*/
public function validateUsername(): bool
{
return preg_match('/^[a-zA-Z0-9_]{2,50}$/', $this->username) === 1;
}
}