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

View File

@@ -0,0 +1,426 @@
<?php
declare(strict_types=1);
namespace App\Validate;
use Fendx\Web\Request\Request;
/**
* 验证器基类
*/
abstract class BaseValidator
{
/**
* 验证规则
*/
abstract protected function getRules(): array;
/**
* 验证请求数据
*/
public function validate(Request $request): array
{
return $this->validateData($request->all(), $this->getRules());
}
/**
* 验证数组数据
*/
public function validateData(array $data, array $rules): array
{
$errors = [];
foreach ($rules as $field => $rule) {
$value = $data[$field] ?? null;
$fieldErrors = $this->validateField($field, $value, $rule, $data);
if (!empty($fieldErrors)) {
$errors[$field] = $fieldErrors;
}
}
return $errors;
}
/**
* 验证单个字段
*/
protected function validateField(string $field, mixed $value, array $rule, array $data): array
{
$errors = [];
// 必填验证
if ($rule['required'] && ($value === null || $value === '')) {
$errors[] = $rule['message'] ?? "字段 {$field} 必填";
return $errors;
}
// 如果字段不是必填且为空,跳过其他验证
if (!$rule['required'] && ($value === null || $value === '')) {
return $errors;
}
// 类型验证
if (isset($rule['type'])) {
$typeError = $this->validateType($field, $value, $rule['type']);
if ($typeError) {
$errors[] = $typeError;
}
}
// 长度验证
if (isset($rule['min'])) {
$minError = $this->validateMin($field, $value, $rule['min']);
if ($minError) {
$errors[] = $minError;
}
}
if (isset($rule['max'])) {
$maxError = $this->validateMax($field, $value, $rule['max']);
if ($maxError) {
$errors[] = $maxError;
}
}
// 数值范围验证
if (isset($rule['min_value'])) {
$minValueError = $this->validateMinValue($field, $value, $rule['min_value']);
if ($minValueError) {
$errors[] = $minValueError;
}
}
if (isset($rule['max_value'])) {
$maxValueError = $this->validateMaxValue($field, $value, $rule['max_value']);
if ($maxValueError) {
$errors[] = $maxValueError;
}
}
// 正则表达式验证
if (isset($rule['pattern'])) {
$patternError = $this->validatePattern($field, $value, $rule['pattern']);
if ($patternError) {
$errors[] = $patternError;
}
}
// 枚举值验证
if (isset($rule['enum'])) {
$enumError = $this->validateEnum($field, $value, $rule['enum']);
if ($enumError) {
$errors[] = $enumError;
}
}
// 邮箱验证
if (isset($rule['email']) && $rule['email']) {
$emailError = $this->validateEmail($field, $value);
if ($emailError) {
$errors[] = $emailError;
}
}
// URL验证
if (isset($rule['url']) && $rule['url']) {
$urlError = $this->validateUrl($field, $value);
if ($urlError) {
$errors[] = $urlError;
}
}
// 日期验证
if (isset($rule['date']) && $rule['date']) {
$dateError = $this->validateDate($field, $value, $rule['format'] ?? 'Y-m-d');
if ($dateError) {
$errors[] = $dateError;
}
}
// 自定义验证
if (isset($rule['custom']) && is_callable($rule['custom'])) {
$customError = $rule['custom']($value, $data, $field);
if ($customError) {
$errors[] = $customError;
}
}
return $errors;
}
/**
* 类型验证
*/
protected function validateType(string $field, mixed $value, string $type): ?string
{
switch ($type) {
case 'int':
case 'integer':
if (!is_int($value) && !ctype_digit((string)$value)) {
return "字段 {$field} 必须是整数";
}
break;
case 'float':
case 'double':
if (!is_float($value) && !is_numeric($value)) {
return "字段 {$field} 必须是数字";
}
break;
case 'string':
if (!is_string($value)) {
return "字段 {$field} 必须是字符串";
}
break;
case 'bool':
case 'boolean':
if (!is_bool($value) && !in_array($value, [0, 1, '0', '1', 'true', 'false'])) {
return "字段 {$field} 必须是布尔值";
}
break;
case 'array':
if (!is_array($value)) {
return "字段 {$field} 必须是数组";
}
break;
case 'date':
if (!($value instanceof \DateTime) && !strtotime($value)) {
return "字段 {$field} 必须是有效日期";
}
break;
}
return null;
}
/**
* 最小长度验证
*/
protected function validateMin(string $field, mixed $value, int $min): ?string
{
$length = is_array($value) ? count($value) : strlen((string)$value);
if ($length < $min) {
return "字段 {$field} 长度不能少于 {$min}";
}
return null;
}
/**
* 最大长度验证
*/
protected function validateMax(string $field, mixed $value, int $max): ?string
{
$length = is_array($value) ? count($value) : strlen((string)$value);
if ($length > $max) {
return "字段 {$field} 长度不能超过 {$max}";
}
return null;
}
/**
* 最小值验证
*/
protected function validateMinValue(string $field, mixed $value, int|float $min): ?string
{
if (!is_numeric($value) || $value < $min) {
return "字段 {$field} 值不能小于 {$min}";
}
return null;
}
/**
* 最大值验证
*/
protected function validateMaxValue(string $field, mixed $value, int|float $max): ?string
{
if (!is_numeric($value) || $value > $max) {
return "字段 {$field} 值不能大于 {$max}";
}
return null;
}
/**
* 正则表达式验证
*/
protected function validatePattern(string $field, mixed $value, string $pattern): ?string
{
if (!preg_match($pattern, (string)$value)) {
return "字段 {$field} 格式不正确";
}
return null;
}
/**
* 枚举值验证
*/
protected function validateEnum(string $field, mixed $value, array $enum): ?string
{
if (!in_array($value, $enum)) {
return "字段 {$field} 值必须是: " . implode(', ', $enum);
}
return null;
}
/**
* 邮箱验证
*/
protected function validateEmail(string $field, mixed $value): ?string
{
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
return "字段 {$field} 必须是有效邮箱地址";
}
return null;
}
/**
* URL验证
*/
protected function validateUrl(string $field, mixed $value): ?string
{
if (!filter_var($value, FILTER_VALIDATE_URL)) {
return "字段 {$field} 必须是有效URL";
}
return null;
}
/**
* 日期验证
*/
protected function validateDate(string $field, mixed $value, string $format): ?string
{
if ($value instanceof \DateTime) {
return null;
}
$date = \DateTime::createFromFormat($format, $value);
if (!$date || $date->format($format) !== $value) {
return "字段 {$field} 必须是有效日期,格式: {$format}";
}
return null;
}
/**
* 创建验证规则
*/
protected function rule(string $type = 'string', bool $required = false): array
{
return [
'type' => $type,
'required' => $required
];
}
/**
* 添加必填规则
*/
protected function required(string $message = null): array
{
return [
'required' => true,
'message' => $message
];
}
/**
* 添加长度规则
*/
protected function length(int $min = null, int $max = null, string $message = null): array
{
$rule = [];
if ($min !== null) {
$rule['min'] = $min;
}
if ($max !== null) {
$rule['max'] = $max;
}
if ($message) {
$rule['message'] = $message;
}
return $rule;
}
/**
* 添加数值范围规则
*/
protected function range(int|float $min = null, int|float $max = null, string $message = null): array
{
$rule = [];
if ($min !== null) {
$rule['min_value'] = $min;
}
if ($max !== null) {
$rule['max_value'] = $max;
}
if ($message) {
$rule['message'] = $message;
}
return $rule;
}
/**
* 添加正则表达式规则
*/
protected function pattern(string $regex, string $message = null): array
{
return [
'pattern' => $regex,
'message' => $message ?? '格式不正确'
];
}
/**
* 添加枚举规则
*/
protected function enum(array $values, string $message = null): array
{
return [
'enum' => $values,
'message' => $message ?? '值必须在指定范围内'
];
}
/**
* 添加邮箱规则
*/
protected function email(string $message = null): array
{
return [
'email' => true,
'message' => $message ?? '必须是有效邮箱地址'
];
}
/**
* 添加URL规则
*/
protected function url(string $message = null): array
{
return [
'url' => true,
'message' => $message ?? '必须是有效URL'
];
}
/**
* 添加日期规则
*/
protected function date(string $format = 'Y-m-d', string $message = null): array
{
return [
'date' => true,
'format' => $format,
'message' => $message ?? "必须是有效日期,格式: {$format}"
];
}
/**
* 添加自定义验证规则
*/
protected function custom(callable $callback, string $message = null): array
{
return [
'custom' => $callback,
'message' => $message
];
}
}

View File

@@ -0,0 +1,303 @@
<?php
declare(strict_types=1);
namespace App\Validate;
use Fendx\Web\Request\Request;
/**
* 用户验证器
*/
class UserValidator
{
/**
* 注册验证规则
*/
public function getRegisterRules(): array
{
return [
'username' => [
'required' => true,
'min' => 2,
'max' => 50,
'pattern' => '/^[a-zA-Z0-9_]+$/',
'message' => '用户名必填2-50位只能包含字母、数字、下划线'
],
'email' => [
'required' => true,
'max' => 100,
'pattern' => '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/',
'message' => '邮箱格式不正确'
],
'password' => [
'required' => true,
'min' => 6,
'max' => 255,
'pattern' => '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{6,}$/',
'message' => '密码至少6位必须包含大小写字母和数字'
],
'nickname' => [
'required' => false,
'max' => 100,
'message' => '昵称最多100位'
],
'phone' => [
'required' => false,
'pattern' => '/^1[3-9]\d{9}$/',
'message' => '手机号格式不正确'
]
];
}
/**
* 登录验证规则
*/
public function getLoginRules(): array
{
return [
'username' => [
'required' => true,
'message' => '用户名必填'
],
'password' => [
'required' => true,
'message' => '密码必填'
]
];
}
/**
* 更新验证规则
*/
public function getUpdateRules(): array
{
return [
'nickname' => [
'required' => false,
'max' => 100,
'message' => '昵称最多100位'
],
'phone' => [
'required' => false,
'pattern' => '/^1[3-9]\d{9}$/',
'message' => '手机号格式不正确'
],
'avatar' => [
'required' => false,
'max' => 255,
'message' => '头像URL最多255位'
]
];
}
/**
* 密码修改验证规则
*/
public function getPasswordRules(): array
{
return [
'old_password' => [
'required' => true,
'message' => '原密码必填'
],
'new_password' => [
'required' => true,
'min' => 6,
'max' => 255,
'pattern' => '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{6,}$/',
'message' => '新密码至少6位必须包含大小写字母和数字'
],
'confirm_password' => [
'required' => true,
'message' => '确认密码必填'
]
];
}
/**
* 验证注册数据
*/
public function validateRegister(Request $request): array
{
return $this->validate($request->all(), $this->getRegisterRules());
}
/**
* 验证登录数据
*/
public function validateLogin(Request $request): array
{
return $this->validate($request->all(), $this->getLoginRules());
}
/**
* 验证更新数据
*/
public function validateUpdate(Request $request): array
{
return $this->validate($request->all(), $this->getUpdateRules());
}
/**
* 验证密码修改数据
*/
public function validatePassword(Request $request): array
{
$data = $request->all();
$errors = $this->validate($data, $this->getPasswordRules());
// 验证确认密码
if (!isset($errors['new_password']) && !isset($errors['confirm_password'])) {
if ($data['new_password'] !== $data['confirm_password']) {
$errors['confirm_password'] = '两次输入的密码不一致';
}
}
return $errors;
}
/**
* 通用验证方法
*/
private function validate(array $data, array $rules): array
{
$errors = [];
foreach ($rules as $field => $rule) {
$value = $data[$field] ?? null;
// 必填验证
if ($rule['required'] && ($value === null || $value === '')) {
$errors[$field] = $rule['message'] ?? "字段 {$field} 必填";
continue;
}
// 如果字段不是必填且为空,跳过其他验证
if (!$rule['required'] && ($value === null || $value === '')) {
continue;
}
// 最小长度验证
if (isset($rule['min']) && strlen($value) < $rule['min']) {
$errors[$field] = $rule['message'] ?? "字段 {$field} 长度不能少于 {$rule['min']}";
}
// 最大长度验证
if (isset($rule['max']) && strlen($value) > $rule['max']) {
$errors[$field] = $rule['message'] ?? "字段 {$field} 长度不能超过 {$rule['max']}";
}
// 正则表达式验证
if (isset($rule['pattern']) && !preg_match($rule['pattern'], $value)) {
$errors[$field] = $rule['message'] ?? "字段 {$field} 格式不正确";
}
// 自定义验证方法
if (isset($rule['custom']) && is_callable($rule['custom'])) {
$customError = $rule['custom']($value, $data);
if ($customError) {
$errors[$field] = $customError;
}
}
}
return $errors;
}
/**
* 验证用户名唯一性
*/
public function validateUniqueUsername(string $username, ?int $excludeId = null): ?string
{
// 这里应该调用UserService检查用户名唯一性
// $userService = app(UserService::class);
// if ($userService->existsByUsername($username, $excludeId)) {
// return '用户名已存在';
// }
return null;
}
/**
* 验证邮箱唯一性
*/
public function validateUniqueEmail(string $email, ?int $excludeId = null): ?string
{
// 这里应该调用UserService检查邮箱唯一性
// $userService = app(UserService::class);
// if ($userService->existsByEmail($email, $excludeId)) {
// return '邮箱已存在';
// }
return null;
}
/**
* 验证手机号唯一性
*/
public function validateUniquePhone(string $phone, ?int $excludeId = null): ?string
{
if (empty($phone)) {
return null;
}
// 这里应该调用UserService检查手机号唯一性
// $userService = app(UserService::class);
// if ($userService->existsByPhone($phone, $excludeId)) {
// return '手机号已存在';
// }
return null;
}
/**
* 完整的注册验证(包含唯一性检查)
*/
public function validateRegisterFull(Request $request): array
{
$data = $request->all();
$errors = $this->validateRegister($request);
if (empty($errors)) {
// 检查用户名唯一性
$usernameError = $this->validateUniqueUsername($data['username']);
if ($usernameError) {
$errors['username'] = $usernameError;
}
// 检查邮箱唯一性
$emailError = $this->validateUniqueEmail($data['email']);
if ($emailError) {
$errors['email'] = $emailError;
}
// 检查手机号唯一性(如果提供)
if (!empty($data['phone'])) {
$phoneError = $this->validateUniquePhone($data['phone']);
if ($phoneError) {
$errors['phone'] = $phoneError;
}
}
}
return $errors;
}
/**
* 完整的更新验证(包含唯一性检查)
*/
public function validateUpdateFull(Request $request, int $userId): array
{
$data = $request->all();
$errors = $this->validateUpdate($request);
if (empty($errors)) {
// 检查手机号唯一性(如果提供)
if (!empty($data['phone'])) {
$phoneError = $this->validateUniquePhone($data['phone'], $userId);
if ($phoneError) {
$errors['phone'] = $phoneError;
}
}
}
return $errors;
}
}