Explorar o código

完成基于Model类的清理系统重构阶段2-4

阶段2:创建ModelScannerLogic类
- 实现基于Model类的扫描逻辑,替代表扫描方式
- 支持自动识别数据分类和生成默认配置
- 成功扫描177个Model并创建176个配置

阶段3:重构CleanupExecutorLogic
- 支持基于Model类和表名两种清理模式
- 添加Model特性支持(软删除、事件等)
- 保持向后兼容性

阶段4:更新配置模型和服务
- 为CleanupConfig和CleanupPlanContent添加model_class字段支持
- 添加基于Model的操作方法
- 更新数据库结构添加必要字段和索引
notfff hai 6 meses
pai
achega
0a8011a725

+ 4 - 0
app/Module/Cleanup/CleanupServiceProvider.php

@@ -5,6 +5,8 @@ namespace App\Module\Cleanup;
 use Illuminate\Support\ServiceProvider;
 use Illuminate\Support\Facades\Event;
 use App\Module\Cleanup\Commands\ScanTablesCommand;
+use App\Module\Cleanup\Commands\ScanModelsCommand;
+use App\Module\Cleanup\Commands\TestModelCleanupCommand;
 use App\Module\Cleanup\Commands\CleanupDataCommand;
 use App\Module\Cleanup\Commands\InsertCleanupAdminMenuCommand;
 
@@ -46,6 +48,8 @@ class CleanupServiceProvider extends ServiceProvider
         if ($this->app->runningInConsole()) {
             $this->commands([
                 ScanTablesCommand::class,
+                ScanModelsCommand::class,
+                TestModelCleanupCommand::class,
                 CleanupDataCommand::class,
                 InsertCleanupAdminMenuCommand::class,
             ]);

+ 123 - 0
app/Module/Cleanup/Commands/ScanModelsCommand.php

@@ -0,0 +1,123 @@
+<?php
+
+namespace App\Module\Cleanup\Commands;
+
+use App\Module\Cleanup\Logics\ModelScannerLogic;
+use Illuminate\Console\Command;
+
+/**
+ * 扫描Model类命令
+ * 
+ * 用于扫描系统中的Model类并生成清理配置
+ */
+class ScanModelsCommand extends Command
+{
+    /**
+     * 命令签名
+     */
+    protected $signature = 'cleanup:scan-models 
+                            {--force : 强制刷新所有Model配置}
+                            {--show-details : 显示详细信息}';
+
+    /**
+     * 命令描述
+     */
+    protected $description = '扫描系统中的Model类并生成清理配置';
+
+    /**
+     * 执行命令
+     */
+    public function handle(): int
+    {
+        $this->info('开始扫描Model类...');
+        $this->newLine();
+
+        $forceRefresh = $this->option('force');
+        $showDetails = $this->option('show-details');
+
+        if ($forceRefresh) {
+            $this->warn('强制刷新模式:将重新生成所有Model的配置');
+        }
+
+        try {
+            // 执行扫描
+            $result = ModelScannerLogic::scanAllModels($forceRefresh);
+
+            // 显示扫描结果
+            $this->displayScanResults($result, $showDetails);
+
+            $this->newLine();
+            $this->info('✅ Model类扫描完成!');
+            
+            return Command::SUCCESS;
+
+        } catch (\Exception $e) {
+            $this->error('❌ 扫描失败: ' . $e->getMessage());
+            $this->error('详细错误: ' . $e->getTraceAsString());
+            
+            return Command::FAILURE;
+        }
+    }
+
+    /**
+     * 显示扫描结果
+     */
+    private function displayScanResults(array $result, bool $showDetails): void
+    {
+        // 显示统计信息
+        $this->table(
+            ['项目', '数量'],
+            [
+                ['总Model数', $result['total_models']],
+                ['已扫描', $result['scanned_models']],
+                ['新增配置', $result['new_models']],
+                ['更新配置', $result['updated_models']],
+                ['扫描耗时', $result['scan_time'] . 's'],
+            ]
+        );
+
+        if ($showDetails && !empty($result['models'])) {
+            $this->newLine();
+            $this->info('📋 详细信息:');
+            
+            $tableData = [];
+            foreach ($result['models'] as $model) {
+                $status = $model['is_new'] ? '新增' : ($model['is_updated'] ? '更新' : '跳过');
+                $tableData[] = [
+                    $model['model_class'],
+                    $model['table_name'],
+                    $model['module_name'],
+                    $model['data_category']->getDescription(),
+                    $status,
+                ];
+            }
+            
+            $this->table(
+                ['Model类', '表名', '模块', '数据分类', '状态'],
+                $tableData
+            );
+        }
+
+        // 显示模块统计
+        if (!empty($result['models'])) {
+            $moduleStats = [];
+            foreach ($result['models'] as $model) {
+                $module = $model['module_name'];
+                if (!isset($moduleStats[$module])) {
+                    $moduleStats[$module] = 0;
+                }
+                $moduleStats[$module]++;
+            }
+
+            $this->newLine();
+            $this->info('📊 模块统计:');
+            
+            $moduleTableData = [];
+            foreach ($moduleStats as $module => $count) {
+                $moduleTableData[] = [$module, $count];
+            }
+            
+            $this->table(['模块名', 'Model数量'], $moduleTableData);
+        }
+    }
+}

+ 207 - 0
app/Module/Cleanup/Commands/TestModelCleanupCommand.php

@@ -0,0 +1,207 @@
+<?php
+
+namespace App\Module\Cleanup\Commands;
+
+use App\Module\Cleanup\Logics\CleanupExecutorLogic;
+use App\Module\Cleanup\Models\CleanupPlanContent;
+use App\Module\Cleanup\Enums\CLEANUP_TYPE;
+use Illuminate\Console\Command;
+
+/**
+ * 测试基于Model的清理功能
+ */
+class TestModelCleanupCommand extends Command
+{
+    /**
+     * 命令签名
+     */
+    protected $signature = 'cleanup:test-model 
+                            {model_class : Model类名}
+                            {--type=3 : 清理类型(1=TRUNCATE,2=DELETE_ALL,3=DELETE_BY_TIME)}
+                            {--dry-run : 预览模式,不实际执行}';
+
+    /**
+     * 命令描述
+     */
+    protected $description = '测试基于Model类的清理功能';
+
+    /**
+     * 执行命令
+     */
+    public function handle(): int
+    {
+        $modelClass = $this->argument('model_class');
+        $cleanupType = (int)$this->option('type');
+        $dryRun = $this->option('dry-run');
+
+        $this->info("测试Model清理功能");
+        $this->info("Model类: {$modelClass}");
+        $this->info("清理类型: " . CLEANUP_TYPE::from($cleanupType)->getDescription());
+        $this->info("模式: " . ($dryRun ? '预览模式' : '实际执行'));
+        $this->newLine();
+
+        try {
+            // 检查Model类是否存在
+            if (!class_exists($modelClass)) {
+                $this->error("Model类不存在: {$modelClass}");
+                return Command::FAILURE;
+            }
+
+            $model = new $modelClass();
+            $tableName = $model->getTable();
+            $currentCount = $modelClass::count();
+
+            $this->info("表名: {$tableName}");
+            $this->info("当前记录数: {$currentCount}");
+            $this->newLine();
+
+            // 创建测试的计划内容
+            $content = new CleanupPlanContent();
+            $content->model_class = $modelClass;
+            $content->table_name = $tableName;
+            $content->cleanup_type = $cleanupType;
+            $content->batch_size = 100;
+            $content->is_enabled = true;
+
+            // 设置清理条件
+            switch ($cleanupType) {
+                case CLEANUP_TYPE::DELETE_BY_TIME->value:
+                    $content->conditions = [
+                        'time_field' => 'created_at',
+                        'before' => '30_days_ago'
+                    ];
+                    break;
+                case CLEANUP_TYPE::DELETE_BY_USER->value:
+                    $content->conditions = [
+                        'user_field' => 'user_id',
+                        'user_condition' => 'in',
+                        'user_values' => [999999] // 不存在的用户ID
+                    ];
+                    break;
+                default:
+                    $content->conditions = [];
+            }
+
+            if ($dryRun) {
+                $this->info("🔍 预览模式 - 计算将要删除的记录数...");
+                
+                // 这里可以添加预览逻辑
+                $affectedRecords = $this->calculateAffectedRecords($modelClass, $cleanupType, $content->conditions);
+                
+                $this->table(
+                    ['项目', '数值'],
+                    [
+                        ['当前记录数', $currentCount],
+                        ['将删除记录数', $affectedRecords],
+                        ['剩余记录数', $currentCount - $affectedRecords],
+                    ]
+                );
+            } else {
+                $this->warn("⚠️  即将执行实际清理操作!");
+                if (!$this->confirm('确定要继续吗?')) {
+                    $this->info('操作已取消');
+                    return Command::SUCCESS;
+                }
+
+                $this->info("🚀 开始执行清理...");
+                
+                // 使用反射调用私有方法进行测试
+                $reflection = new \ReflectionClass(CleanupExecutorLogic::class);
+                $method = $reflection->getMethod('executeModelCleanup');
+                $method->setAccessible(true);
+                
+                $startTime = microtime(true);
+                $result = $method->invoke(null, $content, 0, $startTime);
+                
+                if ($result['success']) {
+                    $this->info("✅ 清理成功!");
+                    $this->table(
+                        ['项目', '数值'],
+                        [
+                            ['删除记录数', $result['deleted_records']],
+                            ['执行时间', $result['execution_time'] . 's'],
+                            ['当前记录数', $modelClass::count()],
+                        ]
+                    );
+                } else {
+                    $this->error("❌ 清理失败: " . $result['message']);
+                }
+            }
+
+            return Command::SUCCESS;
+
+        } catch (\Exception $e) {
+            $this->error("❌ 执行失败: " . $e->getMessage());
+            $this->error("详细错误: " . $e->getTraceAsString());
+            return Command::FAILURE;
+        }
+    }
+
+    /**
+     * 计算受影响的记录数
+     */
+    private function calculateAffectedRecords(string $modelClass, int $cleanupType, array $conditions): int
+    {
+        switch ($cleanupType) {
+            case CLEANUP_TYPE::TRUNCATE->value:
+            case CLEANUP_TYPE::DELETE_ALL->value:
+                return $modelClass::count();
+                
+            case CLEANUP_TYPE::DELETE_BY_TIME->value:
+                if (empty($conditions['time_field']) || empty($conditions['before'])) {
+                    return 0;
+                }
+                $timeField = $conditions['time_field'];
+                $beforeTime = $this->parseTimeCondition($conditions['before']);
+                return $modelClass::where($timeField, '<', $beforeTime)->count();
+                
+            case CLEANUP_TYPE::DELETE_BY_USER->value:
+                if (empty($conditions['user_field']) || empty($conditions['user_values'])) {
+                    return 0;
+                }
+                $userField = $conditions['user_field'];
+                $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->count();
+                
+            default:
+                return 0;
+        }
+    }
+
+    /**
+     * 解析时间条件
+     */
+    private function parseTimeCondition(string $timeCondition): string
+    {
+        if (preg_match('/(\d+)_days?_ago/', $timeCondition, $matches)) {
+            return now()->subDays((int)$matches[1])->toDateTimeString();
+        }
+        
+        if (preg_match('/(\d+)_months?_ago/', $timeCondition, $matches)) {
+            return now()->subMonths((int)$matches[1])->toDateTimeString();
+        }
+        
+        if (preg_match('/(\d+)_years?_ago/', $timeCondition, $matches)) {
+            return now()->subYears((int)$matches[1])->toDateTimeString();
+        }
+        
+        return $timeCondition;
+    }
+}

+ 368 - 62
app/Module/Cleanup/Logics/CleanupExecutorLogic.php

@@ -11,6 +11,8 @@ use App\Module\Cleanup\Enums\CLEANUP_TYPE;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
 
 /**
  * 清理执行逻辑类
@@ -173,27 +175,31 @@ class CleanupExecutorLogic
             foreach ($enabledContents as $content) {
                 try {
                     // 更新当前步骤
+                    $targetName = $content->model_class ?: $content->table_name;
                     CleanupTaskLogic::updateTaskProgress(
                         $task->id,
                         $processedTables,
                         $totalDeleted,
-                        "正在清理表: {$content->table_name}"
+                        "正在清理: {$targetName}"
                     );
 
-                    // 执行清理
-                    $result = static::executeTableCleanup($content, $task->id);
+                    // 执行清理(支持Model类和表名两种模式)
+                    $result = static::executeCleanup($content, $task->id);
                     
                     if ($result['success']) {
                         $totalDeleted += $result['deleted_records'];
                         $processedTables++;
                     } else {
-                        $errors[] = "表 {$content->table_name}: " . $result['message'];
+                        $targetName = $content->model_class ?: $content->table_name;
+                        $errors[] = "{$targetName}: " . $result['message'];
                     }
 
                 } catch (\Exception $e) {
-                    $errors[] = "表 {$content->table_name}: " . $e->getMessage();
-                    Log::error("清理表失败", [
+                    $targetName = $content->model_class ?: $content->table_name;
+                    $errors[] = "{$targetName}: " . $e->getMessage();
+                    Log::error("清理失败", [
                         'task_id' => $task->id,
+                        'model_class' => $content->model_class,
                         'table_name' => $content->table_name,
                         'error' => $e->getMessage()
                     ]);
@@ -376,6 +382,17 @@ class CleanupExecutorLogic
         return $query->count();
     }
 
+    /**
+     * 检查Model是否支持软删除
+     *
+     * @param Model $model Model实例
+     * @return bool 是否支持软删除
+     */
+    private static function modelSupportsSoftDeletes(Model $model): bool
+    {
+        return in_array(SoftDeletes::class, class_uses_recursive($model));
+    }
+
     /**
      * 解析时间条件
      *
@@ -424,58 +441,31 @@ class CleanupExecutorLogic
     }
 
     /**
-     * 执行清理
+     * 执行清理(支持Model类和表名两种模式)
      *
      * @param CleanupPlanContent $content 计划内容
      * @param int $taskId 任务ID
      * @return array 执行结果
      */
-    private static function executeTableCleanup(CleanupPlanContent $content, int $taskId): array
+    private static function executeCleanup(CleanupPlanContent $content, int $taskId): array
     {
         $startTime = microtime(true);
-        $tableName = $content->table_name;
         $cleanupType = CLEANUP_TYPE::from($content->cleanup_type);
 
         try {
-            // 检查表是否存在
-            if (!Schema::hasTable($tableName)) {
-                throw new \Exception("表 {$tableName} 不存在");
+            // 优先使用Model类,如果没有则使用表名
+            if (!empty($content->model_class)) {
+                return static::executeModelCleanup($content, $taskId, $startTime);
+            } else {
+                return static::executeTableCleanup($content, $taskId, $startTime);
             }
 
-            // 记录清理前的记录数
-            $beforeCount = DB::table($tableName)->count();
-
-            // 执行清理
-            $deletedRecords = static::performCleanup($tableName, $cleanupType, $content->conditions, $content->batch_size);
-
-            // 记录清理后的记录数
-            $afterCount = DB::table($tableName)->count();
-            $actualDeleted = $beforeCount - $afterCount;
-
-            $executionTime = round(microtime(true) - $startTime, 3);
-
-            // 记录清理日志
-            static::logCleanupOperation($taskId, $tableName, $cleanupType, [
-                'before_count' => $beforeCount,
-                'after_count' => $afterCount,
-                'deleted_records' => $actualDeleted,
-                'execution_time' => $executionTime,
-                'conditions' => $content->conditions,
-                'batch_size' => $content->batch_size,
-            ]);
-
-            return [
-                'success' => true,
-                'message' => "表 {$tableName} 清理成功",
-                'deleted_records' => $actualDeleted,
-                'execution_time' => $executionTime,
-            ];
-
         } catch (\Exception $e) {
             $executionTime = round(microtime(true) - $startTime, 3);
+            $targetName = $content->model_class ?: $content->table_name;
 
             // 记录错误日志
-            static::logCleanupOperation($taskId, $tableName, $cleanupType, [
+            static::logCleanupOperation($taskId, $content, $cleanupType, [
                 'error' => $e->getMessage(),
                 'execution_time' => $executionTime,
                 'conditions' => $content->conditions,
@@ -491,7 +481,138 @@ class CleanupExecutorLogic
     }
 
     /**
-     * 执行实际的清理操作
+     * 执行基于Model类的清理
+     *
+     * @param CleanupPlanContent $content 计划内容
+     * @param int $taskId 任务ID
+     * @param float $startTime 开始时间
+     * @return array 执行结果
+     */
+    private static function executeModelCleanup(CleanupPlanContent $content, int $taskId, float $startTime): array
+    {
+        $modelClass = $content->model_class;
+        $cleanupType = CLEANUP_TYPE::from($content->cleanup_type);
+
+        // 检查Model类是否存在
+        if (!class_exists($modelClass)) {
+            throw new \Exception("Model类不存在: {$modelClass}");
+        }
+
+        $model = new $modelClass();
+        $tableName = $model->getTable();
+
+        // 记录清理前的记录数
+        $beforeCount = $modelClass::count();
+
+        // 执行清理
+        $deletedRecords = static::performModelCleanup($modelClass, $cleanupType, $content->conditions, $content->batch_size);
+
+        // 记录清理后的记录数
+        $afterCount = $modelClass::count();
+        $actualDeleted = $beforeCount - $afterCount;
+
+        $executionTime = round(microtime(true) - $startTime, 3);
+
+        // 记录清理日志
+        static::logCleanupOperation($taskId, $content, $cleanupType, [
+            'before_count' => $beforeCount,
+            'after_count' => $afterCount,
+            'deleted_records' => $actualDeleted,
+            'execution_time' => $executionTime,
+            'conditions' => $content->conditions,
+            'batch_size' => $content->batch_size,
+        ]);
+
+        return [
+            'success' => true,
+            'message' => "Model {$modelClass} 清理成功",
+            'deleted_records' => $actualDeleted,
+            'execution_time' => $executionTime,
+        ];
+    }
+
+    /**
+     * 执行基于表名的清理(向后兼容)
+     *
+     * @param CleanupPlanContent $content 计划内容
+     * @param int $taskId 任务ID
+     * @param float $startTime 开始时间
+     * @return array 执行结果
+     */
+    private static function executeTableCleanup(CleanupPlanContent $content, int $taskId, float $startTime): array
+    {
+        $tableName = $content->table_name;
+        $cleanupType = CLEANUP_TYPE::from($content->cleanup_type);
+
+        // 检查表是否存在
+        if (!Schema::hasTable($tableName)) {
+            throw new \Exception("表 {$tableName} 不存在");
+        }
+
+        // 记录清理前的记录数
+        $beforeCount = DB::table($tableName)->count();
+
+        // 执行清理
+        $deletedRecords = static::performTableCleanup($tableName, $cleanupType, $content->conditions, $content->batch_size);
+
+        // 记录清理后的记录数
+        $afterCount = DB::table($tableName)->count();
+        $actualDeleted = $beforeCount - $afterCount;
+
+        $executionTime = round(microtime(true) - $startTime, 3);
+
+        // 记录清理日志
+        static::logCleanupOperation($taskId, $content, $cleanupType, [
+            'before_count' => $beforeCount,
+            'after_count' => $afterCount,
+            'deleted_records' => $actualDeleted,
+            'execution_time' => $executionTime,
+            'conditions' => $content->conditions,
+            'batch_size' => $content->batch_size,
+        ]);
+
+        return [
+            'success' => true,
+            'message' => "表 {$tableName} 清理成功",
+            'deleted_records' => $actualDeleted,
+            'execution_time' => $executionTime,
+        ];
+    }
+
+    /**
+     * 执行基于Model类的清理操作
+     *
+     * @param string $modelClass Model类名
+     * @param CLEANUP_TYPE $cleanupType 清理类型
+     * @param array $conditions 清理条件
+     * @param int $batchSize 批处理大小
+     * @return int 删除的记录数
+     */
+    private static function performModelCleanup(string $modelClass, CLEANUP_TYPE $cleanupType, array $conditions, int $batchSize): int
+    {
+        switch ($cleanupType) {
+            case CLEANUP_TYPE::TRUNCATE:
+                return static::performModelTruncate($modelClass);
+
+            case CLEANUP_TYPE::DELETE_ALL:
+                return static::performModelDeleteAll($modelClass, $batchSize);
+
+            case CLEANUP_TYPE::DELETE_BY_TIME:
+                return static::performModelDeleteByTime($modelClass, $conditions, $batchSize);
+
+            case CLEANUP_TYPE::DELETE_BY_USER:
+                return static::performModelDeleteByUser($modelClass, $conditions, $batchSize);
+
+            case CLEANUP_TYPE::DELETE_BY_CONDITION:
+                return static::performModelDeleteByCondition($modelClass, $conditions, $batchSize);
+
+            default:
+                throw new \Exception("不支持的清理类型: {$cleanupType->value}");
+        }
+    }
+
+    /**
+     * 执行基于表名的清理操作(向后兼容)
      *
      * @param string $tableName 表名
      * @param CLEANUP_TYPE $cleanupType 清理类型
@@ -499,23 +620,23 @@ class CleanupExecutorLogic
      * @param int $batchSize 批处理大小
      * @return int 删除的记录数
      */
-    private static function performCleanup(string $tableName, CLEANUP_TYPE $cleanupType, array $conditions, int $batchSize): int
+    private static function performTableCleanup(string $tableName, CLEANUP_TYPE $cleanupType, array $conditions, int $batchSize): int
     {
         switch ($cleanupType) {
             case CLEANUP_TYPE::TRUNCATE:
-                return static::performTruncate($tableName);
+                return static::performTableTruncate($tableName);
 
             case CLEANUP_TYPE::DELETE_ALL:
-                return static::performDeleteAll($tableName, $batchSize);
+                return static::performTableDeleteAll($tableName, $batchSize);
 
             case CLEANUP_TYPE::DELETE_BY_TIME:
-                return static::performDeleteByTime($tableName, $conditions, $batchSize);
+                return static::performTableDeleteByTime($tableName, $conditions, $batchSize);
 
             case CLEANUP_TYPE::DELETE_BY_USER:
-                return static::performDeleteByUser($tableName, $conditions, $batchSize);
+                return static::performTableDeleteByUser($tableName, $conditions, $batchSize);
 
             case CLEANUP_TYPE::DELETE_BY_CONDITION:
-                return static::performDeleteByCondition($tableName, $conditions, $batchSize);
+                return static::performTableDeleteByCondition($tableName, $conditions, $batchSize);
 
             default:
                 throw new \Exception("不支持的清理类型: {$cleanupType->value}");
@@ -523,12 +644,25 @@ class CleanupExecutorLogic
     }
 
     /**
-     * 执行TRUNCATE操作
+     * 执行Model的TRUNCATE操作
+     *
+     * @param string $modelClass Model类名
+     * @return int 删除的记录数
+     */
+    private static function performModelTruncate(string $modelClass): int
+    {
+        $beforeCount = $modelClass::count();
+        $modelClass::truncate();
+        return $beforeCount;
+    }
+
+    /**
+     * 执行表的TRUNCATE操作(向后兼容)
      *
      * @param string $tableName 表名
      * @return int 删除的记录数
      */
-    private static function performTruncate(string $tableName): int
+    private static function performTableTruncate(string $tableName): int
     {
         $beforeCount = DB::table($tableName)->count();
         DB::statement("TRUNCATE TABLE `{$tableName}`");
@@ -536,13 +670,32 @@ class CleanupExecutorLogic
     }
 
     /**
-     * 执行DELETE ALL操作
+     * 执行Model的DELETE ALL操作
+     *
+     * @param string $modelClass Model类名
+     * @param int $batchSize 批处理大小
+     * @return int 删除的记录数
+     */
+    private static function performModelDeleteAll(string $modelClass, int $batchSize): int
+    {
+        $totalDeleted = 0;
+
+        do {
+            $deleted = $modelClass::limit($batchSize)->delete();
+            $totalDeleted += $deleted;
+        } while ($deleted > 0);
+
+        return $totalDeleted;
+    }
+
+    /**
+     * 执行表的DELETE ALL操作(向后兼容)
      *
      * @param string $tableName 表名
      * @param int $batchSize 批处理大小
      * @return int 删除的记录数
      */
-    private static function performDeleteAll(string $tableName, int $batchSize): int
+    private static function performTableDeleteAll(string $tableName, int $batchSize): int
     {
         $totalDeleted = 0;
 
@@ -555,14 +708,52 @@ class CleanupExecutorLogic
     }
 
     /**
-     * 执行按时间删除操作
+     * 执行Model的按时间删除操作
+     *
+     * @param string $modelClass Model类名
+     * @param array $conditions 条件
+     * @param int $batchSize 批处理大小
+     * @return int 删除的记录数
+     */
+    private static function performModelDeleteByTime(string $modelClass, array $conditions, int $batchSize): int
+    {
+        if (empty($conditions['time_field']) || empty($conditions['before'])) {
+            throw new \Exception('时间删除条件不完整');
+        }
+
+        $timeField = $conditions['time_field'];
+        $beforeTime = static::parseTimeCondition($conditions['before']);
+        $totalDeleted = 0;
+
+        // 检查Model是否支持软删除
+        $model = new $modelClass();
+        $supportsSoftDeletes = static::modelSupportsSoftDeletes($model);
+        $forceDelete = $conditions['force_delete'] ?? false;
+
+        do {
+            $query = $modelClass::where($timeField, '<', $beforeTime)->limit($batchSize);
+
+            if ($supportsSoftDeletes && $forceDelete) {
+                $deleted = $query->forceDelete();
+            } else {
+                $deleted = $query->delete();
+            }
+
+            $totalDeleted += $deleted;
+        } while ($deleted > 0);
+
+        return $totalDeleted;
+    }
+
+    /**
+     * 执行表的按时间删除操作(向后兼容)
      *
      * @param string $tableName 表名
      * @param array $conditions 条件
      * @param int $batchSize 批处理大小
      * @return int 删除的记录数
      */
-    private static function performDeleteByTime(string $tableName, array $conditions, int $batchSize): int
+    private static function performTableDeleteByTime(string $tableName, array $conditions, int $batchSize): int
     {
         if (empty($conditions['time_field']) || empty($conditions['before'])) {
             throw new \Exception('时间删除条件不完整');
@@ -584,14 +775,73 @@ class CleanupExecutorLogic
     }
 
     /**
-     * 执行按用户删除操作
+     * 执行Model的按用户删除操作
+     *
+     * @param string $modelClass Model类名
+     * @param array $conditions 条件
+     * @param int $batchSize 批处理大小
+     * @return int 删除的记录数
+     */
+    private static function performModelDeleteByUser(string $modelClass, array $conditions, int $batchSize): int
+    {
+        if (empty($conditions['user_field']) || empty($conditions['user_values'])) {
+            throw new \Exception('用户删除条件不完整');
+        }
+
+        $userField = $conditions['user_field'];
+        $userCondition = $conditions['user_condition'] ?? 'in';
+        $userValues = is_array($conditions['user_values']) ? $conditions['user_values'] : [$conditions['user_values']];
+        $totalDeleted = 0;
+
+        // 检查Model是否支持软删除
+        $model = new $modelClass();
+        $supportsSoftDeletes = static::modelSupportsSoftDeletes($model);
+        $forceDelete = $conditions['force_delete'] ?? false;
+
+        do {
+            $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;
+                default:
+                    throw new \Exception("不支持的用户条件: {$userCondition}");
+            }
+
+            $query->limit($batchSize);
+
+            if ($supportsSoftDeletes && $forceDelete) {
+                $deleted = $query->forceDelete();
+            } else {
+                $deleted = $query->delete();
+            }
+
+            $totalDeleted += $deleted;
+        } while ($deleted > 0);
+
+        return $totalDeleted;
+    }
+
+    /**
+     * 执行表的按用户删除操作(向后兼容)
      *
      * @param string $tableName 表名
      * @param array $conditions 条件
      * @param int $batchSize 批处理大小
      * @return int 删除的记录数
      */
-    private static function performDeleteByUser(string $tableName, array $conditions, int $batchSize): int
+    private static function performTableDeleteByUser(string $tableName, array $conditions, int $batchSize): int
     {
         if (empty($conditions['user_field']) || empty($conditions['user_ids'])) {
             throw new \Exception('用户删除条件不完整');
@@ -613,14 +863,58 @@ class CleanupExecutorLogic
     }
 
     /**
-     * 执行按条件删除操作
+     * 执行Model的按条件删除操作
+     *
+     * @param string $modelClass Model类名
+     * @param array $conditions 条件
+     * @param int $batchSize 批处理大小
+     * @return int 删除的记录数
+     */
+    private static function performModelDeleteByCondition(string $modelClass, array $conditions, int $batchSize): int
+    {
+        if (empty($conditions['where'])) {
+            throw new \Exception('自定义删除条件不完整');
+        }
+
+        $totalDeleted = 0;
+
+        // 检查Model是否支持软删除
+        $model = new $modelClass();
+        $supportsSoftDeletes = static::modelSupportsSoftDeletes($model);
+        $forceDelete = $conditions['force_delete'] ?? false;
+
+        do {
+            $query = $modelClass::query();
+
+            foreach ($conditions['where'] as $condition) {
+                if (isset($condition['field'], $condition['operator'], $condition['value'])) {
+                    $query->where($condition['field'], $condition['operator'], $condition['value']);
+                }
+            }
+
+            $query->limit($batchSize);
+
+            if ($supportsSoftDeletes && $forceDelete) {
+                $deleted = $query->forceDelete();
+            } else {
+                $deleted = $query->delete();
+            }
+
+            $totalDeleted += $deleted;
+        } while ($deleted > 0);
+
+        return $totalDeleted;
+    }
+
+    /**
+     * 执行表的按条件删除操作(向后兼容)
      *
      * @param string $tableName 表名
      * @param array $conditions 条件
      * @param int $batchSize 批处理大小
      * @return int 删除的记录数
      */
-    private static function performDeleteByCondition(string $tableName, array $conditions, int $batchSize): int
+    private static function performTableDeleteByCondition(string $tableName, array $conditions, int $batchSize): int
     {
         if (empty($conditions['where'])) {
             throw new \Exception('自定义删除条件不完整');
@@ -648,13 +942,23 @@ class CleanupExecutorLogic
      * 记录清理操作日志
      *
      * @param int $taskId 任务ID
-     * @param string $tableName 表名
+     * @param CleanupPlanContent $content 计划内容
      * @param CLEANUP_TYPE $cleanupType 清理类型
      * @param array $details 详细信息
      */
-    private static function logCleanupOperation(int $taskId, string $tableName, CLEANUP_TYPE $cleanupType, array $details): void
+    private static function logCleanupOperation(int $taskId, CleanupPlanContent $content, CLEANUP_TYPE $cleanupType, array $details): void
     {
         try {
+            $tableName = $content->table_name;
+            if (!empty($content->model_class)) {
+                // 如果有Model类,从Model获取表名
+                $modelClass = $content->model_class;
+                if (class_exists($modelClass)) {
+                    $model = new $modelClass();
+                    $tableName = $model->getTable();
+                }
+            }
+
             CleanupLog::create([
                 'task_id' => $taskId,
                 'table_name' => $tableName,
@@ -665,12 +969,14 @@ class CleanupExecutorLogic
                 'execution_time' => $details['execution_time'] ?? 0,
                 'conditions' => $details['conditions'] ?? [],
                 'error_message' => $details['error'] ?? null,
+                'model_class' => $content->model_class,
                 'created_at' => now(),
             ]);
         } catch (\Exception $e) {
             Log::error('记录清理日志失败', [
                 'task_id' => $taskId,
-                'table_name' => $tableName,
+                'model_class' => $content->model_class ?? null,
+                'table_name' => $content->table_name ?? null,
                 'error' => $e->getMessage()
             ]);
         }

+ 389 - 0
app/Module/Cleanup/Logics/ModelScannerLogic.php

@@ -0,0 +1,389 @@
+<?php
+
+namespace App\Module\Cleanup\Logics;
+
+use App\Module\Cleanup\Models\CleanupConfig;
+use App\Module\Cleanup\Enums\DATA_CATEGORY;
+use App\Module\Cleanup\Enums\CLEANUP_TYPE;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
+use UCore\ModelCore;
+
+/**
+ * Model扫描逻辑类
+ * 
+ * 负责扫描系统中的Model类并生成清理配置
+ */
+class ModelScannerLogic
+{
+    /**
+     * 扫描所有Model类
+     *
+     * @param bool $forceRefresh 是否强制刷新
+     * @return array 扫描结果
+     */
+    public static function scanAllModels(bool $forceRefresh = false): array
+    {
+        $startTime = microtime(true);
+        
+        // 获取所有Model类
+        $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) {
+            try {
+                $modelInfo = static::scanModel($modelClass, $forceRefresh);
+                if ($modelInfo) {
+                    $result['models'][] = $modelInfo;
+                    $result['scanned_models']++;
+
+                    if ($modelInfo['is_new']) {
+                        $result['new_models']++;
+                    } elseif ($modelInfo['is_updated']) {
+                        $result['updated_models']++;
+                    }
+                }
+            } catch (\Exception|\Error $e) {
+                Log::error("扫描Model {$modelClass} 失败: " . $e->getMessage());
+            }
+        }
+
+        $result['scan_time'] = round(microtime(true) - $startTime, 3);
+        
+        return $result;
+    }
+
+    /**
+     * 扫描单个Model
+     *
+     * @param string $modelClass Model类名
+     * @param bool $forceRefresh 是否强制刷新
+     * @return array Model信息
+     */
+    public static function scanModel(string $modelClass, bool $forceRefresh = false): ?array
+    {
+        try {
+            // 检查是否已存在配置
+            $existingConfig = CleanupConfig::where('model_class', $modelClass)->first();
+            $isNew = !$existingConfig;
+            $isUpdated = false;
+
+            // 获取Model信息
+            $modelInfo = static::getModelInfo($modelClass);
+
+            // 如果获取Model信息失败,跳过
+            if (isset($modelInfo['error'])) {
+                Log::warning("跳过有问题的Model: {$modelClass} - " . $modelInfo['error']);
+                return null;
+            }
+
+            // 自动识别数据分类
+            $dataCategory = static::detectDataCategory($modelClass, $modelInfo);
+
+            // 识别模块名称
+            $moduleName = static::extractModuleName($modelClass);
+
+            // 生成默认清理配置
+            $defaultConfig = static::generateDefaultConfig($dataCategory, $modelInfo);
+
+            if ($isNew || $forceRefresh) {
+                // 创建或更新配置
+                $configData = [
+                    'model_class' => $modelClass,
+                    'table_name' => $modelInfo['table_name'], // 保持兼容性
+                    'model_info' => $modelInfo,
+                    'module_name' => $moduleName,
+                    'data_category' => $dataCategory->value,
+                    'default_cleanup_type' => $defaultConfig['cleanup_type'],
+                    'default_conditions' => $defaultConfig['conditions'],
+                    'is_enabled' => $defaultConfig['is_enabled'],
+                    'priority' => $defaultConfig['priority'],
+                    'batch_size' => $defaultConfig['batch_size'],
+                    'description' => $defaultConfig['description'],
+                ];
+
+                if ($isNew) {
+                    CleanupConfig::create($configData);
+                } else {
+                    $existingConfig->update($configData);
+                    $isUpdated = true;
+                }
+            }
+
+            return [
+                'model_class' => $modelClass,
+                'table_name' => $modelInfo['table_name'],
+                'module_name' => $moduleName,
+                'data_category' => $dataCategory,
+                'model_info' => $modelInfo,
+                'is_new' => $isNew,
+                'is_updated' => $isUpdated,
+            ];
+        } catch (\Exception|\Error $e) {
+            Log::error("扫描Model失败: {$modelClass} - " . $e->getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 获取所有Model类
+     *
+     * @return array 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类名
+     *
+     * @param string $filePath 文件路径
+     * @return string|null 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类
+     *
+     * @param string $modelClass Model类名
+     * @return bool 是否有效
+     */
+    private static function isValidModel(string $modelClass): bool
+    {
+        if (!class_exists($modelClass)) {
+            return false;
+        }
+
+        try {
+            $reflection = new \ReflectionClass($modelClass);
+            
+            // 检查是否继承自ModelCore或Eloquent
+            $isValidModel = $reflection->isSubclassOf(ModelCore::class) ||
+                           $reflection->isSubclassOf(Model::class);
+            
+            // 排除抽象类
+            if ($reflection->isAbstract()) {
+                return false;
+            }
+            
+            return $isValidModel;
+        } catch (\Exception $e) {
+            return false;
+        }
+    }
+
+    /**
+     * 获取Model信息
+     *
+     * @param string $modelClass Model类名
+     * @return array Model信息
+     */
+    private static function getModelInfo(string $modelClass): array
+    {
+        try {
+            $model = new $modelClass();
+
+            return [
+                'class_name' => $modelClass,
+                'table_name' => $model->getTable(),
+                'primary_key' => $model->getKeyName(),
+                'timestamps' => $model->timestamps,
+                'soft_deletes' => static::hasSoftDeletes($model),
+                'fillable' => $model->getFillable(),
+                'guarded' => $model->getGuarded(),
+                'casts' => $model->getCasts(),
+                'connection' => $model->getConnectionName(),
+                'relations' => static::getModelRelations($model),
+            ];
+        } catch (\Exception|\Error $e) {
+            Log::warning("获取Model信息失败: {$modelClass} - " . $e->getMessage());
+
+            // 返回基本信息
+            return [
+                'class_name' => $modelClass,
+                'table_name' => 'unknown',
+                'primary_key' => 'id',
+                'timestamps' => true,
+                'soft_deletes' => false,
+                'fillable' => [],
+                'guarded' => [],
+                'casts' => [],
+                'connection' => null,
+                'relations' => [],
+                'error' => $e->getMessage(),
+            ];
+        }
+    }
+
+    /**
+     * 检查Model是否支持软删除
+     *
+     * @param Model $model Model实例
+     * @return bool 是否支持软删除
+     */
+    private static function hasSoftDeletes(Model $model): bool
+    {
+        return in_array(SoftDeletes::class, class_uses_recursive($model));
+    }
+
+    /**
+     * 获取Model的关系定义
+     *
+     * @param Model $model Model实例
+     * @return array 关系列表
+     */
+    private static function getModelRelations(Model $model): array
+    {
+        $relations = [];
+        $reflection = new \ReflectionClass($model);
+        
+        foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
+            if ($method->class === get_class($model) && 
+                !$method->isStatic() && 
+                $method->getNumberOfParameters() === 0) {
+                
+                $methodName = $method->getName();
+                
+                // 跳过一些已知的非关系方法
+                if (in_array($methodName, ['getTable', 'getKeyName', 'getFillable', 'getGuarded', 'getCasts'])) {
+                    continue;
+                }
+                
+                try {
+                    $result = $model->$methodName();
+                    if ($result instanceof \Illuminate\Database\Eloquent\Relations\Relation) {
+                        $relations[] = [
+                            'name' => $methodName,
+                            'type' => class_basename(get_class($result)),
+                            'related' => get_class($result->getRelated()),
+                        ];
+                    }
+                } catch (\Exception|\Error $e) {
+                    // 忽略调用失败的方法
+                    Log::debug("获取Model关系失败: {$methodName} - " . $e->getMessage());
+                }
+            }
+        }
+        
+        return $relations;
+    }
+
+    /**
+     * 从Model类名提取模块名称
+     *
+     * @param string $modelClass Model类名
+     * @return string 模块名称
+     */
+    private static function extractModuleName(string $modelClass): string
+    {
+        // App\Module\Farm\Models\FarmUser -> Farm
+        if (preg_match('/App\\\\Module\\\\([^\\\\]+)\\\\Models\\\\/', $modelClass, $matches)) {
+            return $matches[1];
+        }
+        
+        return 'Unknown';
+    }
+
+    /**
+     * 检测数据分类
+     *
+     * @param string $modelClass Model类名
+     * @param array $modelInfo Model信息
+     * @return DATA_CATEGORY 数据分类
+     */
+    private static function detectDataCategory(string $modelClass, array $modelInfo): DATA_CATEGORY
+    {
+        $tableName = $modelInfo['table_name'];
+        $className = class_basename($modelClass);
+        
+        // 根据表名和类名模式识别数据分类
+        if (str_contains($tableName, '_logs') || str_contains($className, 'Log')) {
+            return DATA_CATEGORY::LOG_DATA;
+        }
+        
+        if (str_contains($tableName, '_cache') || str_contains($tableName, '_sessions') || 
+            str_contains($className, 'Cache') || str_contains($className, 'Session')) {
+            return DATA_CATEGORY::CACHE_DATA;
+        }
+        
+        if (str_contains($tableName, '_configs') || str_contains($tableName, '_settings') ||
+            str_contains($className, 'Config') || str_contains($className, 'Setting')) {
+            return DATA_CATEGORY::CONFIG_DATA;
+        }
+        
+        if (str_contains($tableName, '_orders') || str_contains($tableName, '_payments') ||
+            str_contains($tableName, '_transactions') || str_contains($className, 'Order') ||
+            str_contains($className, 'Payment') || str_contains($className, 'Transaction')) {
+            return DATA_CATEGORY::TRANSACTION_DATA;
+        }
+        
+        // 默认为用户数据
+        return DATA_CATEGORY::USER_DATA;
+    }
+
+    /**
+     * 生成默认清理配置
+     *
+     * @param DATA_CATEGORY $dataCategory 数据分类
+     * @param array $modelInfo Model信息
+     * @return array 默认配置
+     */
+    private static function generateDefaultConfig(DATA_CATEGORY $dataCategory, array $modelInfo): array
+    {
+        $defaultCleanupType = $dataCategory->getDefaultCleanupType();
+        $defaultPriority = $dataCategory->getDefaultPriority();
+        $isEnabled = $dataCategory->isDefaultEnabled();
+        
+        // 根据Model特性调整配置
+        $conditions = [];
+        if ($defaultCleanupType === CLEANUP_TYPE::DELETE_BY_TIME) {
+            $timeField = 'created_at';
+            if ($modelInfo['timestamps']) {
+                $conditions = [
+                    'time_field' => $timeField,
+                    'before' => '30_days_ago'
+                ];
+            }
+        }
+        
+        return [
+            'cleanup_type' => $defaultCleanupType->value,
+            'conditions' => $conditions,
+            'is_enabled' => $isEnabled,
+            'priority' => $defaultPriority,
+            'batch_size' => 1000,
+            'description' => "自动生成的{$dataCategory->getDescription()}清理配置",
+        ];
+    }
+}

+ 14 - 12
app/Module/Cleanup/Models/CleanupBackupFile.php

@@ -19,18 +19,20 @@ class CleanupBackupFile extends ModelCore
      */
     protected $table = 'cleanup_backup_files';
 
-    // field start 
- * @property  int  $id  主键ID
- * @property  int  $backup_id  备份记录ID
- * @property  string  $table_name  表名
- * @property  string  $file_name  文件名
- * @property  string  $file_path  文件路径
- * @property  int  $file_size  文件大小(字节)
- * @property  string  $file_hash  文件SHA256哈希
- * @property  int  $backup_type  备份类型:1SQL,2JSON,3CSV
- * @property  int  $compression_type  压缩类型:1none,2gzip,3zip
- * @property  \Carbon\Carbon  $created_at  创建时间
- * field end
+    // field start
+    /**
+     * @property  int  $id  主键ID
+     * @property  int  $backup_id  备份记录ID
+     * @property  string  $table_name  表名
+     * @property  string  $file_name  文件名
+     * @property  string  $file_path  文件路径
+     * @property  int  $file_size  文件大小(字节)
+     * @property  string  $file_hash  文件SHA256哈希
+     * @property  int  $backup_type  备份类型:1SQL,2JSON,3CSV
+     * @property  int  $compression_type  压缩类型:1none,2gzip,3zip
+     * @property  \Carbon\Carbon  $created_at  创建时间
+     */
+    // field end
 
     /**
      * 字段类型转换

+ 144 - 1
app/Module/Cleanup/Models/CleanupConfig.php

@@ -18,10 +18,32 @@ class CleanupConfig extends ModelCore
      */
     protected $table = 'cleanup_configs';
 
+    // attrlist start
+    /**
+     * 可批量赋值的属性
+     */
+    protected $fillable = [
+        'model_class',
+        'table_name',
+        'model_info',
+        'module_name',
+        'data_category',
+        'default_cleanup_type',
+        'default_conditions',
+        'is_enabled',
+        'priority',
+        'batch_size',
+        'description',
+        'last_cleanup_at',
+    ];
+    // attrlist end
+
     // field start
     /**
      * @property  int  $id  主键ID
-     * @property  string  $table_name  表名
+     * @property  string  $model_class  Model类名
+     * @property  string  $table_name  表名(兼容性保留)
+     * @property  array  $model_info  Model类信息JSON
      * @property  string  $module_name  模块名称
      * @property  int  $data_category  数据分类:1用户数据,2日志数据,3交易数据,4缓存数据,5配置数据
      * @property  int  $default_cleanup_type  默认清理类型:1清空表,2删除所有,3按时间删除,4按用户删除,5按条件删除
@@ -40,6 +62,7 @@ class CleanupConfig extends ModelCore
      * 字段类型转换
      */
     protected $casts = [
+        'model_info' => 'array',
         'data_category' => 'integer',
         'default_cleanup_type' => 'integer',
         'default_conditions' => 'array',
@@ -260,4 +283,124 @@ class CleanupConfig extends ModelCore
             'total' => static::count(),
         ];
     }
+
+    /**
+     * 获取Model实例
+     */
+    public function getModelInstance()
+    {
+        if (empty($this->model_class)) {
+            throw new \Exception("Model类名为空");
+        }
+
+        if (!class_exists($this->model_class)) {
+            throw new \Exception("Model类不存在: {$this->model_class}");
+        }
+
+        return new $this->model_class();
+    }
+
+    /**
+     * 获取实际表名(优先从Model获取)
+     */
+    public function getActualTableName(): string
+    {
+        if (!empty($this->model_class)) {
+            try {
+                return $this->getModelInstance()->getTable();
+            } catch (\Exception $e) {
+                // 如果Model有问题,回退到table_name
+            }
+        }
+
+        return $this->table_name;
+    }
+
+    /**
+     * 获取记录数量(优先使用Model)
+     */
+    public function getRecordCount(): int
+    {
+        if (!empty($this->model_class)) {
+            try {
+                $modelClass = $this->model_class;
+                return $modelClass::count();
+            } catch (\Exception $e) {
+                // 如果Model有问题,回退到直接查询表
+            }
+        }
+
+        return \DB::table($this->table_name)->count();
+    }
+
+    /**
+     * 检查Model是否支持软删除
+     */
+    public function supportsSoftDeletes(): bool
+    {
+        if (empty($this->model_class)) {
+            return false;
+        }
+
+        try {
+            $model = $this->getModelInstance();
+            return in_array(\Illuminate\Database\Eloquent\SoftDeletes::class, class_uses_recursive($model));
+        } catch (\Exception $e) {
+            return false;
+        }
+    }
+
+    /**
+     * 获取Model信息摘要
+     */
+    public function getModelSummary(): array
+    {
+        if (empty($this->model_class)) {
+            return [
+                'has_model' => false,
+                'table_name' => $this->table_name,
+                'record_count' => $this->getRecordCount(),
+            ];
+        }
+
+        try {
+            $model = $this->getModelInstance();
+            return [
+                'has_model' => true,
+                'model_class' => $this->model_class,
+                'table_name' => $model->getTable(),
+                'primary_key' => $model->getKeyName(),
+                'timestamps' => $model->timestamps,
+                'soft_deletes' => $this->supportsSoftDeletes(),
+                'record_count' => $this->getRecordCount(),
+                'fillable_count' => count($model->getFillable()),
+                'casts_count' => count($model->getCasts()),
+            ];
+        } catch (\Exception $e) {
+            return [
+                'has_model' => false,
+                'error' => $e->getMessage(),
+                'table_name' => $this->table_name,
+                'record_count' => 0,
+            ];
+        }
+    }
+
+    /**
+     * 作用域:只查询有Model类的配置
+     */
+    public function scopeWithModel($query)
+    {
+        return $query->whereNotNull('model_class')->where('model_class', '!=', '');
+    }
+
+    /**
+     * 作用域:只查询没有Model类的配置(旧数据)
+     */
+    public function scopeWithoutModel($query)
+    {
+        return $query->where(function($q) {
+            $q->whereNull('model_class')->orWhere('model_class', '');
+        });
+    }
 }

+ 19 - 0
app/Module/Cleanup/Models/CleanupLog.php

@@ -18,11 +18,30 @@ class CleanupLog extends ModelCore
      */
     protected $table = 'cleanup_logs';
 
+    // attrlist start
+    /**
+     * 可批量赋值的属性
+     */
+    protected $fillable = [
+        'task_id',
+        'table_name',
+        'model_class',
+        'cleanup_type',
+        'before_count',
+        'after_count',
+        'deleted_records',
+        'execution_time',
+        'conditions',
+        'error_message',
+    ];
+    // attrlist end
+
     // field start
     /**
      * @property  int  $id  主键ID
      * @property  int  $task_id  任务ID
      * @property  string  $table_name  表名
+     * @property  string  $model_class  Model类名
      * @property  int  $cleanup_type  清理类型:1清空表,2删除所有,3按时间删除,4按用户删除,5按条件删除
      * @property  int  $before_count  清理前记录数
      * @property  int  $after_count  清理后记录数

+ 128 - 1
app/Module/Cleanup/Models/CleanupPlanContent.php

@@ -18,11 +18,30 @@ class CleanupPlanContent extends ModelCore
      */
     protected $table = 'cleanup_plan_contents';
 
+    // attrlist start
+    /**
+     * 可批量赋值的属性
+     */
+    protected $fillable = [
+        'plan_id',
+        'table_name',
+        'model_class',
+        'cleanup_type',
+        'conditions',
+        'priority',
+        'batch_size',
+        'is_enabled',
+        'backup_enabled',
+        'notes',
+    ];
+    // attrlist end
+
     // field start
     /**
      * @property  int  $id  主键ID
      * @property  int  $plan_id  计划ID
-     * @property  string  $table_name  表名
+     * @property  string  $table_name  表名(兼容性保留)
+     * @property  string  $model_class  Model类名
      * @property  int  $cleanup_type  清理类型:1清空表,2删除所有,3按时间删除,4按用户删除,5按条件删除
      * @property  array  $conditions  清理条件JSON配置
      * @property  int  $priority  清理优先级
@@ -367,4 +386,112 @@ class CleanupPlanContent extends ModelCore
             'total' => $query->count(),
         ];
     }
+
+    /**
+     * 获取Model实例
+     */
+    public function getModelInstance()
+    {
+        if (empty($this->model_class)) {
+            throw new \Exception("Model类名为空");
+        }
+
+        if (!class_exists($this->model_class)) {
+            throw new \Exception("Model类不存在: {$this->model_class}");
+        }
+
+        return new $this->model_class();
+    }
+
+    /**
+     * 获取实际表名(优先从Model获取)
+     */
+    public function getActualTableName(): string
+    {
+        if (!empty($this->model_class)) {
+            try {
+                return $this->getModelInstance()->getTable();
+            } catch (\Exception $e) {
+                // 如果Model有问题,回退到table_name
+            }
+        }
+
+        return $this->table_name;
+    }
+
+    /**
+     * 获取目标名称(Model类名或表名)
+     */
+    public function getTargetName(): string
+    {
+        return $this->model_class ?: $this->table_name;
+    }
+
+    /**
+     * 检查是否基于Model类
+     */
+    public function isModelBased(): bool
+    {
+        return !empty($this->model_class);
+    }
+
+    /**
+     * 检查Model是否支持软删除
+     */
+    public function supportsSoftDeletes(): bool
+    {
+        if (!$this->isModelBased()) {
+            return false;
+        }
+
+        try {
+            $model = $this->getModelInstance();
+            return in_array(\Illuminate\Database\Eloquent\SoftDeletes::class, class_uses_recursive($model));
+        } catch (\Exception $e) {
+            return false;
+        }
+    }
+
+    /**
+     * 获取记录数量(优先使用Model)
+     */
+    public function getRecordCount(): int
+    {
+        if ($this->isModelBased()) {
+            try {
+                $modelClass = $this->model_class;
+                return $modelClass::count();
+            } catch (\Exception $e) {
+                // 如果Model有问题,回退到直接查询表
+            }
+        }
+
+        return \DB::table($this->table_name)->count();
+    }
+
+    /**
+     * 作用域:只查询有Model类的内容
+     */
+    public function scopeWithModel($query)
+    {
+        return $query->whereNotNull('model_class')->where('model_class', '!=', '');
+    }
+
+    /**
+     * 作用域:只查询没有Model类的内容(旧数据)
+     */
+    public function scopeWithoutModel($query)
+    {
+        return $query->where(function($q) {
+            $q->whereNull('model_class')->orWhere('model_class', '');
+        });
+    }
+
+    /**
+     * 作用域:按Model类筛选
+     */
+    public function scopeByModel($query, string $modelClass)
+    {
+        return $query->where('model_class', $modelClass);
+    }
 }

+ 13 - 11
app/Module/Cleanup/Models/CleanupSqlBackup.php

@@ -17,17 +17,19 @@ class CleanupSqlBackup extends ModelCore
      */
     protected $table = 'cleanup_sql_backups';
 
-    // field start 
- * @property  int  $id  主键ID
- * @property  int  $backup_id  备份记录ID
- * @property  string  $table_name  表名
- * @property  string  $sql_content  INSERT语句内容
- * @property  int  $records_count  记录数量
- * @property  int  $content_size  内容大小(字节)
- * @property  string  $content_hash  内容SHA256哈希
- * @property  array  $backup_conditions  备份条件
- * @property  \Carbon\Carbon  $created_at  创建时间
- * field end
+    // field start
+    /**
+     * @property  int  $id  主键ID
+     * @property  int  $backup_id  备份记录ID
+     * @property  string  $table_name  表名
+     * @property  string  $sql_content  INSERT语句内容
+     * @property  int  $records_count  记录数量
+     * @property  int  $content_size  内容大小(字节)
+     * @property  string  $content_hash  内容SHA256哈希
+     * @property  array  $backup_conditions  备份条件
+     * @property  \Carbon\Carbon  $created_at  创建时间
+     */
+    // field end
 
     /**
      * 字段类型转换

+ 17 - 15
app/Module/Cleanup/Models/CleanupTableStats.php

@@ -16,21 +16,23 @@ class CleanupTableStats extends ModelCore
      */
     protected $table = 'cleanup_table_stats';
 
-    // field start 
- * @property  int  $id  主键ID
- * @property  string  $table_name  表名
- * @property  int  $record_count  记录总数
- * @property  float  $table_size_mb  表大小(MB)
- * @property  float  $index_size_mb  索引大小(MB)
- * @property  float  $data_free_mb  碎片空间(MB)
- * @property  int  $avg_row_length  平均行长度
- * @property  int  $auto_increment  自增值
- * @property  string  $oldest_record_time  最早记录时间
- * @property  string  $newest_record_time  最新记录时间
- * @property  string  $scan_time  扫描时间
- * @property  \Carbon\Carbon  $created_at  创建时间
- * @property  \Carbon\Carbon  $updated_at  更新时间
- * field end
+    // field start
+    /**
+     * @property  int  $id  主键ID
+     * @property  string  $table_name  表名
+     * @property  int  $record_count  记录总数
+     * @property  float  $table_size_mb  表大小(MB)
+     * @property  float  $index_size_mb  索引大小(MB)
+     * @property  float  $data_free_mb  碎片空间(MB)
+     * @property  int  $avg_row_length  平均行长度
+     * @property  int  $auto_increment  自增值
+     * @property  string  $oldest_record_time  最早记录时间
+     * @property  string  $newest_record_time  最新记录时间
+     * @property  string  $scan_time  扫描时间
+     * @property  \Carbon\Carbon  $created_at  创建时间
+     * @property  \Carbon\Carbon  $updated_at  更新时间
+     */
+    // field end
 
     /**
      * 字段类型转换