17日1230-基于Model类的清理系统重构方案.md 11 KB

基于Model类的清理系统重构方案

任务时间: 2025年06月17日 12:30
任务类型: 架构重构方案
问题分析: 当前扫描数据库表而非Model类的设计缺陷

问题分析

当前设计的问题

1. 扫描方式错误

// 当前错误的做法:直接扫描数据库
private static function getAllTables(): array
{
    $tables = DB::select("SHOW TABLES LIKE 'kku_%'");
    // ...
}

问题

  • 扫描数据库表,而不是代码中的Model类
  • 无法获取Model的业务逻辑信息
  • 无法利用Model的关系定义
  • 无法使用Model的访问器和修改器

2. 存储结构错误

// 当前存储表名
'table_name' => 'kku_farm_users'

// 应该存储Model类名
'model_class' => 'App\Module\Farm\Models\FarmUser'

3. 执行方式错误

// 当前直接操作表
DB::table($tableName)->where(...)->delete();

// 应该使用Model
$modelClass::where(...)->delete();

正确的设计应该是

1. 扫描Model类

  • 扫描 app/Module/*/Models/ 目录下的Model类
  • 分析Model的表名、关系、字段等信息
  • 利用Model的业务逻辑进行清理

2. 存储Model类信息

  • 配置中存储Model类的完整类名
  • 通过Model类获取表名和其他信息
  • 支持Model的软删除、作用域等特性

3. 使用Model执行清理

  • 通过Model类进行数据操作
  • 利用Model的事件和观察者
  • 支持Model的关系级联操作

重构方案

1. 数据库结构调整

修改 cleanup_configs 表

ALTER TABLE kku_cleanup_configs 
ADD COLUMN model_class VARCHAR(255) AFTER table_name,
ADD COLUMN model_info JSON COMMENT 'Model类信息',
ADD INDEX idx_model_class (model_class);

-- 迁移数据后删除 table_name 字段
-- ALTER TABLE kku_cleanup_configs DROP COLUMN table_name;

修改 cleanup_plan_contents 表

ALTER TABLE kku_cleanup_plan_contents 
ADD COLUMN model_class VARCHAR(255) AFTER table_name,
ADD INDEX idx_model_class (model_class);

-- 迁移数据后删除 table_name 字段
-- ALTER TABLE kku_cleanup_plan_contents DROP COLUMN table_name;

2. Model扫描器重构

新的ModelScannerLogic类

class ModelScannerLogic
{
    /**
     * 扫描所有Model类
     */
    public static function scanAllModels(bool $forceRefresh = false): array
    {
        $models = static::getAllModelClasses();
        
        $result = [
            'total_models' => count($models),
            'scanned_models' => 0,
            'new_models' => 0,
            'updated_models' => 0,
            'models' => [],
            'scan_time' => 0,
        ];

        foreach ($models as $modelClass) {
            $modelInfo = static::scanModel($modelClass, $forceRefresh);
            $result['models'][] = $modelInfo;
            // ...
        }

        return $result;
    }

    /**
     * 获取所有Model类
     */
    private static function getAllModelClasses(): array
    {
        $models = [];
        $modulePaths = glob(app_path('Module/*/Models'));
        
        foreach ($modulePaths as $modulePath) {
            $modelFiles = glob($modulePath . '/*.php');
            
            foreach ($modelFiles as $modelFile) {
                $modelClass = static::getModelClassFromFile($modelFile);
                if ($modelClass && static::isValidModel($modelClass)) {
                    $models[] = $modelClass;
                }
            }
        }
        
        return $models;
    }

    /**
     * 从文件路径获取Model类名
     */
    private static function getModelClassFromFile(string $filePath): ?string
    {
        $relativePath = str_replace(app_path(), '', $filePath);
        $relativePath = str_replace('.php', '', $relativePath);
        $relativePath = str_replace('/', '\\', $relativePath);
        
        return 'App' . $relativePath;
    }

    /**
     * 验证是否为有效的Model类
     */
    private static function isValidModel(string $modelClass): bool
    {
        if (!class_exists($modelClass)) {
            return false;
        }

        $reflection = new \ReflectionClass($modelClass);
        
        // 检查是否继承自ModelCore或Eloquent
        return $reflection->isSubclassOf(\UCore\ModelCore::class) ||
               $reflection->isSubclassOf(\Illuminate\Database\Eloquent\Model::class);
    }

    /**
     * 扫描单个Model
     */
    private static function scanModel(string $modelClass, bool $forceRefresh = false): array
    {
        $model = new $modelClass();
        
        // 获取Model信息
        $modelInfo = [
            'class_name' => $modelClass,
            'table_name' => $model->getTable(),
            'module_name' => static::extractModuleName($modelClass),
            'primary_key' => $model->getKeyName(),
            'timestamps' => $model->timestamps,
            'soft_deletes' => static::hasSoftDeletes($model),
            'fillable' => $model->getFillable(),
            'guarded' => $model->getGuarded(),
            'casts' => $model->getCasts(),
            'relations' => static::getModelRelations($model),
        ];

        // 分析数据分类
        $dataCategory = static::detectDataCategory($modelClass, $modelInfo);
        
        // 生成默认配置
        $defaultConfig = static::generateDefaultConfig($dataCategory, $modelInfo);

        // 保存或更新配置
        static::saveModelConfig($modelClass, $modelInfo, $dataCategory, $defaultConfig);

        return $modelInfo;
    }
}

3. 清理执行器重构

使用Model进行清理

class CleanupExecutorLogic
{
    /**
     * 执行表清理(使用Model)
     */
    private static function executeModelCleanup(CleanupPlanContent $content, int $taskId): array
    {
        $modelClass = $content->model_class;
        
        if (!class_exists($modelClass)) {
            throw new \Exception("Model类不存在: {$modelClass}");
        }

        $model = new $modelClass();
        $tableName = $model->getTable();

        // 记录清理前的数量
        $beforeCount = $modelClass::count();

        // 根据清理类型执行不同的操作
        $deletedRecords = match($content->cleanup_type) {
            CLEANUP_TYPE::TRUNCATE->value => static::truncateModel($modelClass),
            CLEANUP_TYPE::DELETE_ALL->value => static::deleteAllModel($modelClass),
            CLEANUP_TYPE::DELETE_BY_TIME->value => static::deleteByTimeModel($modelClass, $content->conditions),
            CLEANUP_TYPE::DELETE_BY_USER->value => static::deleteByUserModel($modelClass, $content->conditions),
            CLEANUP_TYPE::DELETE_BY_CONDITION->value => static::deleteByConditionModel($modelClass, $content->conditions),
            default => throw new \Exception("不支持的清理类型: {$content->cleanup_type}")
        };

        // 记录清理后的数量
        $afterCount = $modelClass::count();

        // 记录清理日志
        CleanupLogLogic::createLog([
            'task_id' => $taskId,
            'model_class' => $modelClass,
            'table_name' => $tableName,
            'cleanup_type' => $content->cleanup_type,
            'before_count' => $beforeCount,
            'after_count' => $afterCount,
            'deleted_records' => $deletedRecords,
            'conditions' => $content->conditions,
        ]);

        return [
            'success' => true,
            'deleted_records' => $deletedRecords,
            'before_count' => $beforeCount,
            'after_count' => $afterCount,
        ];
    }

    /**
     * 按时间删除(使用Model)
     */
    private static function deleteByTimeModel(string $modelClass, array $conditions): int
    {
        $timeField = $conditions['time_field'] ?? 'created_at';
        $beforeTime = static::parseTimeCondition($conditions['before'] ?? '30_days_ago');

        $query = $modelClass::where($timeField, '<', $beforeTime);

        // 如果Model支持软删除,可以选择强制删除
        if (isset($conditions['force_delete']) && $conditions['force_delete']) {
            return $query->forceDelete();
        }

        return $query->delete();
    }

    /**
     * 按用户删除(使用Model)
     */
    private static function deleteByUserModel(string $modelClass, array $conditions): int
    {
        $userField = $conditions['user_field'] ?? 'user_id';
        $userCondition = $conditions['user_condition'] ?? 'in';
        $userValues = $conditions['user_values'] ?? [];

        $query = $modelClass::query();

        switch ($userCondition) {
            case 'in':
                $query->whereIn($userField, $userValues);
                break;
            case 'not_in':
                $query->whereNotIn($userField, $userValues);
                break;
            case 'null':
                $query->whereNull($userField);
                break;
            case 'not_null':
                $query->whereNotNull($userField);
                break;
        }

        return $query->delete();
    }

    /**
     * 清空表(使用Model)
     */
    private static function truncateModel(string $modelClass): int
    {
        $model = new $modelClass();
        $beforeCount = $modelClass::count();
        
        // 使用Model的truncate方法
        $modelClass::truncate();
        
        return $beforeCount;
    }
}

4. 配置模型重构

新的CleanupConfig模型

class CleanupConfig extends ModelCore
{
    protected $fillable = [
        'model_class',
        'module_name', 
        'data_category',
        'default_cleanup_type',
        'default_conditions',
        'model_info',
        // ...
    ];

    /**
     * 获取Model实例
     */
    public function getModelInstance()
    {
        if (!class_exists($this->model_class)) {
            throw new \Exception("Model类不存在: {$this->model_class}");
        }

        return new $this->model_class();
    }

    /**
     * 获取表名
     */
    public function getTableName(): string
    {
        return $this->getModelInstance()->getTable();
    }

    /**
     * 获取记录数量
     */
    public function getRecordCount(): int
    {
        $modelClass = $this->model_class;
        return $modelClass::count();
    }

    /**
     * 检查Model是否支持软删除
     */
    public function supportsSoftDeletes(): bool
    {
        $model = $this->getModelInstance();
        return method_exists($model, 'trashed');
    }
}

实施步骤

阶段1:数据库结构调整

  1. 添加新字段 model_classmodel_info
  2. 保持 table_name 字段用于兼容性
  3. 创建数据迁移脚本

阶段2:扫描器重构

  1. 创建新的 ModelScannerLogic
  2. 实现Model类扫描功能
  3. 保持向后兼容性

阶段3:执行器重构

  1. 重构 CleanupExecutorLogic 使用Model
  2. 支持Model的特性(软删除、事件等)
  3. 保持API兼容性

阶段4:清理旧代码

  1. 移除 table_name 字段
  2. 删除旧的表扫描逻辑
  3. 更新文档和测试

优势分析

1. 更好的架构设计

  • 基于代码而非数据库结构
  • 利用Model的业务逻辑
  • 支持Laravel的所有Model特性

2. 更强的功能支持

  • 软删除支持
  • 模型事件和观察者
  • 关系级联操作
  • 访问器和修改器

3. 更好的维护性

  • 代码和配置保持同步
  • 类型安全的操作
  • 更好的错误处理

4. 更灵活的扩展

  • 支持自定义Model方法
  • 支持复杂的查询条件
  • 支持Model的作用域

这个重构方案将彻底解决当前设计的架构问题,使清理系统更加健壮和灵活。