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,552 @@
<?php
declare(strict_types=1);
namespace Fendx\CLI\Generator;
class ModelGenerator extends CodeGenerator
{
protected function loadTemplates(): void
{
$this->templates = [
'model' => $this->getModelTemplate(),
'property' => $this->getPropertyTemplate(),
'method' => $this->getMethodTemplate(),
'migration' => $this->getMigrationTemplate(),
'factory' => $this->getFactoryTemplate(),
'seeder' => $this->getSeederTemplate()
];
}
public function generate(string $name, array $options = []): bool
{
if (!$this->validateName($name)) {
return false;
}
$className = $this->getClassName($name);
$tableName = $options['table'] ?? $this->getTableName($name);
$fields = $options['fields'] ?? [];
$timestamps = $options['timestamps'] ?? true;
$softDeletes = $options['soft_deletes'] ?? false;
$relationships = $options['relationships'] ?? [];
$generateMigration = $options['migration'] ?? true;
$generateFactory = $options['factory'] ?? false;
$generateSeeder = $options['seeder'] ?? false;
if (!empty($fields)) {
if (is_string($fields)) {
$fields = $this->parseFields($fields);
}
}
// 生成模型
$modelContent = $this->generateModel($className, $tableName, $fields, $timestamps, $softDeletes, $relationships);
$modelPath = $this->getNamespacePath('Model');
$modelFilePath = $modelPath . '/' . $className . '.php';
if (!$this->createFile($modelFilePath, $modelContent)) {
return false;
}
// 生成迁移文件
if ($generateMigration) {
$this->generateMigration($className, $tableName, $fields, $timestamps, $softDeletes);
}
// 生成工厂文件
if ($generateFactory) {
$this->generateFactory($className, $fields);
}
// 生成种子文件
if ($generateSeeder) {
$this->generateSeeder($className);
}
$this->showMessage('success', "Model '{$className}' generated successfully!");
return true;
}
private function generateModel(string $className, string $tableName, array $fields, bool $timestamps, bool $softDeletes, array $relationships): string
{
$useStatements = [];
$properties = '';
$methods = '';
// 基础use语句
$useStatements[] = 'use Fendx\\ORM\\Model;';
if ($softDeletes) {
$useStatements[] = 'use Fendx\\ORM\\Traits\\SoftDeletes;';
}
// 生成属性
foreach ($fields as $field) {
$properties .= $this->generateProperty($field);
}
// 生成关系方法
foreach ($relationships as $relationship) {
$methods .= $this->generateRelationshipMethod($relationship);
}
// 生成访问器和修改器
foreach ($fields as $field) {
$methods .= $this->generateAccessorMutator($field);
}
$useBlock = implode("\n", array_unique($useStatements)) . "\n\n";
return $this->renderTemplate('model', [
'namespace' => $this->getFullNamespace('Model'),
'use_block' => $useBlock,
'class_name' => $className,
'table_name' => $tableName,
'timestamps' => $timestamps ? 'true' : 'false',
'soft_deletes' => $softDeletes ? 'use SoftDeletes;' : '',
'properties' => $properties,
'methods' => $methods
]);
}
private function generateProperty(array $field): string
{
$name = $field['name'];
$type = $this->getPhpType($field['type']);
$nullable = isset($field['options']['nullable']);
$docBlock = $this->generatePropertyDocBlock($field);
return $this->renderTemplate('property', [
'doc_block' => $docBlock,
'property_name' => $name,
'property_type' => $type,
'nullable' => $nullable ? '?' : ''
]);
}
private function generatePropertyDocBlock(array $field): string
{
$type = $this->getPhpType($field['type']);
$comment = '';
if (isset($field['options']['unique'])) {
$comment .= ' (unique)';
}
if (isset($field['options']['default'])) {
$comment .= ' (default: ' . $field['options']['default'] . ')';
}
if (isset($field['options']['nullable'])) {
$comment .= ' (nullable)';
}
return "/**\n * @var {$type}{$comment}\n */";
}
private function generateRelationshipMethod(array $relationship): string
{
$type = $relationship['type'];
$relatedModel = $relationship['model'];
$foreignKey = $relationship['foreign_key'] ?? null;
$localKey = $relationship['local_key'] ?? null;
switch ($type) {
case 'hasOne':
return $this->generateHasOneMethod($relatedModel, $foreignKey, $localKey);
case 'hasMany':
return $this->generateHasManyMethod($relatedModel, $foreignKey, $localKey);
case 'belongsTo':
return $this->generateBelongsToMethod($relatedModel, $foreignKey, $localKey);
case 'belongsToMany':
return $this->generateBelongsToManyMethod($relatedModel, $foreignKey, $localKey);
default:
return '';
}
}
private function generateHasOneMethod(string $relatedModel, ?string $foreignKey, ?string $localKey): string
{
$methodName = $this->getVariableName($relatedModel);
$params = '';
if ($foreignKey) {
$params .= ", '{$foreignKey}'";
}
if ($localKey) {
$params .= ", '{$localKey}'";
}
return <<<PHP
/**
* Get the associated {$relatedModel}.
*/
public function {$methodName}(): HasOne
{
return \$this->hasOne({$relatedModel}::class{$params});
}
PHP;
}
private function generateHasManyMethod(string $relatedModel, ?string $foreignKey, ?string $localKey): string
{
$methodName = $this->getPluralName($this->getVariableName($relatedModel));
$params = '';
if ($foreignKey) {
$params .= ", '{$foreignKey}'";
}
if ($localKey) {
$params .= ", '{$localKey}'";
}
return <<<PHP
/**
* Get the associated {$relatedModel} records.
*/
public function {$methodName}(): HasMany
{
return \$this->hasMany({$relatedModel}::class{$params});
}
PHP;
}
private function generateBelongsToMethod(string $relatedModel, ?string $foreignKey, ?string $localKey): string
{
$methodName = $this->getVariableName($relatedModel);
$params = '';
if ($foreignKey) {
$params .= ", '{$foreignKey}'";
}
if ($localKey) {
$params .= ", '{$localKey}'";
}
return <<<PHP
/**
* Get the associated {$relatedModel}.
*/
public function {$methodName}(): BelongsTo
{
return \$this->belongsTo({$relatedModel}::class{$params});
}
PHP;
}
private function generateBelongsToManyMethod(string $relatedModel, ?string $foreignKey, ?string $localKey): string
{
$methodName = $this->getPluralName($this->getVariableName($relatedModel));
$params = '';
if ($foreignKey) {
$params .= ", '{$foreignKey}'";
}
if ($localKey) {
$params .= ", '{$localKey}'";
}
return <<<PHP
/**
* Get the associated {$relatedModel} records.
*/
public function {$methodName}(): BelongsToMany
{
return \$this->belongsToMany({$relatedModel}::class{$params});
}
PHP;
}
private function generateAccessorMutator(array $field): string
{
$name = $field['name'];
$type = $this->getPhpType($field['type']);
$methods = '';
// 生成访问器
$accessorName = 'get' . $this->getClassName($name) . 'Attribute';
$methods .= <<<PHP
/**
* Get the {$name} attribute.
*/
public function {$accessorName}(): {$type}
{
return \$this->attributes['{$name}'];
}
PHP;
// 生成修改器
$mutatorName = 'set' . $this->getClassName($name) . 'Attribute';
$methods .= <<<PHP
/**
* Set the {$name} attribute.
*/
public function {$mutatorName}({$type} \$value): void
{
\$this->attributes['{$name}'] = \$value;
}
PHP;
return $methods;
}
private function generateMigration(string $className, string $tableName, array $fields, bool $timestamps, bool $softDeletes): void
{
$migrationName = 'create_' . $tableName . '_table';
$migrationClassName = $this->getClassName($migrationName);
$content = $this->generateMigrationContent($migrationClassName, $tableName, $fields, $timestamps, $softDeletes);
$migrationPath = $this->getNamespacePath('../database/migrations');
$timestamp = date('Y_m_d_His');
$filename = $timestamp . '_' . $migrationName . '.php';
$filePath = $migrationPath . '/' . $filename;
$this->createFile($filePath, $content);
}
private function generateMigrationContent(string $className, string $tableName, array $fields, bool $timestamps, bool $softDeletes): string
{
$upMethods = '';
$downMethods = '';
// 生成字段定义
foreach ($fields as $field) {
$upMethods .= $this->generateMigrationField($field);
}
// 生成时间戳
if ($timestamps) {
$upMethods .= " \$table->timestamps();\n";
$downMethods .= " \$table->dropTimestamps();\n";
}
// 生成软删除
if ($softDeletes) {
$upMethods .= " \$table->softDeletes();\n";
$downMethods .= " \$table->dropSoftDeletes();\n";
}
return $this->renderTemplate('migration', [
'class_name' => $className,
'table_name' => $tableName,
'up_methods' => $upMethods,
'down_methods' => $downMethods
]);
}
private function generateMigrationField(array $field): string
{
$name = $field['name'];
$type = $field['type'];
$options = $field['options'] ?? [];
$method = "\$table->{$type}('{$name}')";
if (isset($options['nullable'])) {
$method .= '->nullable()';
}
if (isset($options['default'])) {
$default = $options['default'];
if (is_string($default)) {
$default = "'{$default}'";
}
$method .= "->default({$default})";
}
if (isset($options['unique'])) {
$method .= '->unique()';
}
return " {$method};\n";
}
private function generateFactory(string $className, array $fields): void
{
$factoryClassName = $className . 'Factory';
$definition = $this->generateFactoryDefinition($className, $fields);
$content = $this->renderTemplate('factory', [
'namespace' => $this->getFullNamespace('Database\\Factories'),
'class_name' => $factoryClassName,
'model_class' => $className,
'definition' => $definition
]);
$factoryPath = $this->getNamespacePath('../database/factories');
$filePath = $factoryPath . '/' . $factoryClassName . '.php';
$this->createFile($filePath, $content);
}
private function generateFactoryDefinition(string $className, array $fields): string
{
$definition = '';
foreach ($fields as $field) {
$name = $field['name'];
$type = $field['type'];
$faker = $this->getFakerMethod($type, $field['options'] ?? []);
$definition .= " '{$name}' => {$faker},\n";
}
return $definition;
}
private function getFakerMethod(string $type, array $options): string
{
$fakerMap = [
'string' => '$this->faker->sentence()',
'varchar' => '$this->faker->word()',
'text' => '$this->faker->paragraph()',
'int' => '$this->faker->numberBetween(1, 1000)',
'integer' => '$this->faker->numberBetween(1, 1000)',
'bigint' => '$this->faker->numberBetween(1, 1000000)',
'float' => '$this->faker->randomFloat(2, 0, 1000)',
'double' => '$this->faker->randomFloat(2, 0, 1000)',
'bool' => '$this->faker->boolean()',
'boolean' => '$this->faker->boolean()',
'date' => '$this->faker->date()',
'datetime' => '$this->faker->datetime()',
'timestamp' => '$this->faker->datetime()',
'email' => '$this->faker->email()',
'url' => '$this->faker->url()'
];
return $fakerMap[$type] ?? '$this->faker->word()';
}
private function generateSeeder(string $className): void
{
$seederClassName = $className . 'Seeder';
$modelVariable = $this->getVariableName($className);
$content = $this->renderTemplate('seeder', [
'namespace' => $this->getFullNamespace('Database\\Seeders'),
'class_name' => $seederClassName,
'model_class' => $className,
'model_variable' => $modelVariable,
'model_namespace' => $this->getFullNamespace('Model')
]);
$seederPath = $this->getNamespacePath('../database/seeders');
$filePath = $seederPath . '/' . $seederClassName . '.php';
$this->createFile($filePath, $content);
}
private function getModelTemplate(): string
{
return <<<PHP
<?php
declare(strict_types=1);
namespace {{namespace}};
{{use_block}}class {{class_name}} extends Model
{
protected string \$table = '{{table_name}}';
protected bool \$timestamps = {{timestamps}};
{{soft_deletes}}
{{properties}}
{{methods}}
}
PHP;
}
private function getPropertyTemplate(): string
{
return <<<PHP
{{doc_block}}
private {{nullable}}{{property_type}} \${{property_name}};
PHP;
}
private function getMethodTemplate(): string
{
return <<<PHP
{{method_content}}
PHP;
}
private function getMigrationTemplate(): string
{
return <<<PHP
<?php
declare(strict_types=1);
use Fendx\Database\Migration;
use Fendx\Database\Schema\Blueprint;
class {{class_name}} extends Migration
{
public function up(): void
{
\$this->schema->create('{{table_name}}', function (Blueprint \$table) {
{{up_methods}});
}
public function down(): void
{
\$this->schema->dropIfExists('{{table_name}}');
}
}
PHP;
}
private function getFactoryTemplate(): string
{
return <<<PHP
<?php
declare(strict_types=1);
namespace {{namespace}};
use Fendx\Database\Factories\Factory;
use {{model_namespace}}\{{model_class}};
class {{class_name}} extends Factory
{
protected string \$model = {{model_class}}::class;
public function definition(): array
{
return [
{{definition}};
}
}
PHP;
}
private function getSeederTemplate(): string
{
return <<<PHP
<?php
declare(strict_types=1);
namespace {{namespace}};
use Fendx\Database\Seeder;
use {{model_namespace}}\{{model_class}};
class {{class_name}} extends Seeder
{
public function run(): void
{
{{model_class}}::factory()->count(10)->create();
}
}
PHP;
}
}