Selaa lähdekoodia

更新后台管理界面支持Model类选择

- 更新CleanupPlanContentController支持Model类选择而非表名
- 列表页面显示Model类信息和实际表名
- 表单支持Model类选择,自动获取表名
- 添加迁移Action帮助用户从表名迁移到Model类
- 添加批量迁移功能处理旧数据
- 增加数据类型筛选器区分Model和表名数据
- 添加JavaScript增强用户体验
- 完善表单验证确保数据完整性
notfff 6 kuukautta sitten
vanhempi
commit
17269b4846

+ 4 - 2
AiWork/now.md

@@ -5,13 +5,15 @@
 ## 🎉 所有任务已完成 ✅
 
 ### 最新完成工作
-**2025年06月17日 13:30** - ✅ **基于Model类清理系统重构全面完成**
+**2025年06月17日 13:50** - ✅ **基于Model类清理系统重构项目圆满完成**
 - 完成所有6个阶段的重构工作,彻底解决用户提出的架构问题
-- 创建全面验证系统,18项测试全部通过
+- 创建全面验证系统,18项测试全部通过,系统完全可用
 - 实际测试Model清理功能,DELETE_ALL和DELETE_BY_TIME类型验证成功
 - 扫描177个Model,成功处理176个,生成完整配置
 - 重构从表扫描改为Model扫描,从表名改为Model类名,从SQL操作改为Model操作
 - 保持向后兼容性,支持软删除等所有Laravel Model特性
+- 完成代码清理和文档更新,标记废弃代码,保持向后兼容
+- 项目状态:✅ **重构全面完成,系统完全可用**
 
 **2025年06月17日 13:00** - ✅ **基于Model类的清理系统重构阶段2-4完成**
 - 成功实现ModelScannerLogic类,扫描177个Model并创建176个配置

+ 125 - 0
app/Module/Cleanup/AdminControllers/Actions/BatchMigrateToModelAction.php

@@ -0,0 +1,125 @@
+<?php
+
+namespace App\Module\Cleanup\AdminControllers\Actions;
+
+use App\Module\Cleanup\Models\CleanupPlanContent;
+use App\Module\Cleanup\Models\CleanupConfig;
+use Dcat\Admin\Grid\BatchAction;
+use Dcat\Admin\Actions\Response;
+use Illuminate\Http\Request;
+
+/**
+ * 批量迁移到Model类Action
+ * 
+ * 批量将使用表名的旧数据迁移为使用Model类
+ */
+class BatchMigrateToModelAction extends BatchAction
+{
+    /**
+     * 按钮标题
+     */
+    protected $title = '批量迁移到Model';
+
+    /**
+     * 处理请求
+     */
+    public function handle(Request $request)
+    {
+        $ids = $this->getKey();
+        
+        if (empty($ids)) {
+            return $this->response()->error('请选择要迁移的记录');
+        }
+
+        $successCount = 0;
+        $failedCount = 0;
+        $errors = [];
+
+        foreach ($ids as $id) {
+            try {
+                $content = CleanupPlanContent::findOrFail($id);
+                
+                // 跳过已经使用Model类的记录
+                if (!empty($content->model_class)) {
+                    continue;
+                }
+
+                // 检查是否有表名
+                if (empty($content->table_name)) {
+                    $errors[] = "ID {$id}: 没有表名";
+                    $failedCount++;
+                    continue;
+                }
+
+                // 查找对应的Model类
+                $config = CleanupConfig::where('table_name', $content->table_name)
+                    ->whereNotNull('model_class')
+                    ->where('model_class', '!=', '')
+                    ->first();
+
+                if (!$config) {
+                    $errors[] = "ID {$id}: 未找到表 {$content->table_name} 对应的Model类";
+                    $failedCount++;
+                    continue;
+                }
+
+                // 验证Model类是否存在
+                if (!class_exists($config->model_class)) {
+                    $errors[] = "ID {$id}: Model类不存在 {$config->model_class}";
+                    $failedCount++;
+                    continue;
+                }
+
+                // 更新为使用Model类
+                $content->update([
+                    'model_class' => $config->model_class,
+                ]);
+
+                $successCount++;
+
+            } catch (\Exception $e) {
+                $errors[] = "ID {$id}: " . $e->getMessage();
+                $failedCount++;
+            }
+        }
+
+        // 构建结果消息
+        $message = "迁移完成!成功: {$successCount} 个,失败: {$failedCount} 个";
+        
+        if (!empty($errors)) {
+            $message .= "\n\n失败详情:\n" . implode("\n", array_slice($errors, 0, 10));
+            if (count($errors) > 10) {
+                $message .= "\n... 还有 " . (count($errors) - 10) . " 个错误";
+            }
+        }
+
+        if ($failedCount > 0) {
+            return $this->response()
+                ->warning($message)
+                ->refresh();
+        } else {
+            return $this->response()
+                ->success($message)
+                ->refresh();
+        }
+    }
+
+    /**
+     * 确认对话框
+     */
+    public function confirm()
+    {
+        return [
+            '确认批量迁移到Model类?',
+            '此操作将查找对应的Model类并批量更新配置,建议在迁移前备份数据。'
+        ];
+    }
+
+    /**
+     * 权限检查
+     */
+    public function allowed()
+    {
+        return true;
+    }
+}

+ 112 - 0
app/Module/Cleanup/AdminControllers/Actions/MigrateToModelAction.php

@@ -0,0 +1,112 @@
+<?php
+
+namespace App\Module\Cleanup\AdminControllers\Actions;
+
+use App\Module\Cleanup\Models\CleanupPlanContent;
+use App\Module\Cleanup\Models\CleanupConfig;
+use Dcat\Admin\Grid\RowAction;
+use Dcat\Admin\Actions\Response;
+use Illuminate\Http\Request;
+
+/**
+ * 迁移到Model类Action
+ * 
+ * 将使用表名的旧数据迁移为使用Model类
+ */
+class MigrateToModelAction extends RowAction
+{
+    /**
+     * 按钮标题
+     */
+    protected $title = '迁移到Model';
+
+    /**
+     * 按钮图标
+     */
+    protected $icon = 'fa-arrow-up';
+
+    /**
+     * 处理请求
+     */
+    public function handle(Request $request)
+    {
+        $contentId = $this->getKey();
+
+        try {
+            $content = CleanupPlanContent::findOrFail($contentId);
+            
+            // 检查是否已经使用Model类
+            if (!empty($content->model_class)) {
+                return $this->response()
+                    ->error('此记录已经使用Model类,无需迁移');
+            }
+
+            // 检查是否有表名
+            if (empty($content->table_name)) {
+                return $this->response()
+                    ->error('此记录没有表名,无法迁移');
+            }
+
+            // 查找对应的Model类
+            $config = CleanupConfig::where('table_name', $content->table_name)
+                ->whereNotNull('model_class')
+                ->where('model_class', '!=', '')
+                ->first();
+
+            if (!$config) {
+                return $this->response()
+                    ->error('未找到表 ' . $content->table_name . ' 对应的Model类配置');
+            }
+
+            // 验证Model类是否存在
+            if (!class_exists($config->model_class)) {
+                return $this->response()
+                    ->error('Model类不存在:' . $config->model_class);
+            }
+
+            // 更新为使用Model类
+            $content->update([
+                'model_class' => $config->model_class,
+            ]);
+
+            return $this->response()
+                ->success('迁移成功!已更新为使用Model类:' . class_basename($config->model_class))
+                ->refresh();
+
+        } catch (\Exception $e) {
+            return $this->response()
+                ->error('迁移失败:' . $e->getMessage());
+        }
+    }
+
+    /**
+     * 确认对话框
+     */
+    public function confirm()
+    {
+        return [
+            '确认迁移到Model类?',
+            '此操作将查找对应的Model类并更新配置,建议在迁移前备份数据。'
+        ];
+    }
+
+    /**
+     * 权限检查
+     */
+    public function allowed()
+    {
+        // 只对没有Model类的记录显示此按钮
+        $content = $this->row;
+        return empty($content->model_class);
+    }
+
+    /**
+     * 按钮样式
+     */
+    public function html()
+    {
+        return '<a class="btn btn-sm btn-warning ' . $this->getElementClass() . '" title="' . $this->title . '">
+                    <i class="' . $this->icon . '"></i> ' . $this->title . '
+                </a>';
+    }
+}

+ 193 - 9
app/Module/Cleanup/AdminControllers/CleanupPlanContentController.php

@@ -7,6 +7,7 @@ use App\Module\Cleanup\Models\CleanupPlan;
 use App\Module\Cleanup\Models\CleanupConfig;
 use App\Module\Cleanup\Repositories\CleanupPlanContentRepository;
 use App\Module\Cleanup\Enums\CLEANUP_TYPE;
+use App\Module\Cleanup\Logics\ModelScannerLogic;
 use UCore\DcatAdmin\AdminController;
 use Dcat\Admin\Form;
 use Dcat\Admin\Grid;
@@ -46,7 +47,29 @@ class CleanupPlanContentController extends AdminController
             
             // 计划信息
             $grid->column('plan.plan_name', '所属计划')->sortable();
-            $grid->column('table_name', '表名')->sortable();
+
+            // Model/表信息
+            $grid->column('model_class', 'Model类')->display(function ($value) {
+                if (!empty($value)) {
+                    $className = class_basename($value);
+                    return "<span class='label label-success'>{$className}</span>";
+                }
+                return '<span class="label label-warning">未设置</span>';
+            })->sortable();
+
+            $grid->column('table_name', '表名')->display(function ($value) {
+                if (!empty($this->model_class)) {
+                    try {
+                        $model = new $this->model_class();
+                        $actualTable = $model->getTable();
+                        return "<span class='text-muted'>{$actualTable}</span>";
+                    } catch (\Exception $e) {
+                        return "<span class='text-danger'>{$value} (Model错误)</span>";
+                    }
+                } else {
+                    return "<span class='text-warning'>{$value} (旧数据)</span>";
+                }
+            })->sortable();
             
             // 清理类型
             $grid->column('cleanup_type', '清理类型')->using([
@@ -87,7 +110,22 @@ class CleanupPlanContentController extends AdminController
                 // 按计划筛选
                 $plans = CleanupPlan::pluck('plan_name', 'id')->toArray();
                 $filter->equal('plan_id', '所属计划')->select($plans);
-                
+
+                // 数据类型筛选
+                $filter->where(function ($query) {
+                    $type = $this->input;
+                    if ($type === 'model') {
+                        $query->whereNotNull('model_class')->where('model_class', '!=', '');
+                    } elseif ($type === 'table') {
+                        $query->where(function($q) {
+                            $q->whereNull('model_class')->orWhere('model_class', '');
+                        });
+                    }
+                }, '数据类型')->select([
+                    'model' => '基于Model类',
+                    'table' => '基于表名(旧数据)',
+                ]);
+
                 // 按清理类型筛选
                 $filter->equal('cleanup_type', '清理类型')->select([
                     1 => '清空表',
@@ -119,6 +157,7 @@ class CleanupPlanContentController extends AdminController
             $grid->batchActions([
                 new \App\Module\Cleanup\AdminControllers\Actions\BatchEnableAction(),
                 new \App\Module\Cleanup\AdminControllers\Actions\BatchDisableAction(),
+                new \App\Module\Cleanup\AdminControllers\Actions\BatchMigrateToModelAction(),
             ]);
 
             // 行操作
@@ -126,6 +165,7 @@ class CleanupPlanContentController extends AdminController
                 $actions->append(new \App\Module\Cleanup\AdminControllers\Actions\EditPlanContentAction());
                 $actions->append(new \App\Module\Cleanup\AdminControllers\Actions\DeletePlanContentAction());
                 $actions->append(new \App\Module\Cleanup\AdminControllers\Actions\TestCleanupAction());
+                $actions->append(new \App\Module\Cleanup\AdminControllers\Actions\MigrateToModelAction());
             });
 
             // 设置每页显示数量
@@ -143,7 +183,28 @@ class CleanupPlanContentController extends AdminController
             
             // 关联信息
             $show->field('plan.plan_name', '所属计划');
-            $show->field('table_name', '表名');
+
+            // Model/表信息
+            $show->field('model_class', 'Model类')->as(function ($value) {
+                if (!empty($value)) {
+                    return $value;
+                } else {
+                    return '未设置(旧数据)';
+                }
+            });
+
+            $show->field('table_name', '表名')->as(function ($value) {
+                if (!empty($this->model_class)) {
+                    try {
+                        $model = new $this->model_class();
+                        return $model->getTable() . ' (从Model获取)';
+                    } catch (\Exception $e) {
+                        return $value . ' (Model错误: ' . $e->getMessage() . ')';
+                    }
+                } else {
+                    return $value . ' (旧数据)';
+                }
+            });
             
             // 清理配置
             $show->field('cleanup_type', '清理类型')->using([
@@ -185,12 +246,37 @@ class CleanupPlanContentController extends AdminController
                 ->options(CleanupPlan::pluck('plan_name', 'id')->toArray())
                 ->required();
 
-            // 表名选择(可以从已有配置中选择或手动输入)
-            $availableTables = CleanupConfig::pluck('table_name', 'table_name')->toArray();
-            $form->select('table_name', '表名')
-                ->options($availableTables)
-                ->required()
-                ->help('选择要清理的数据表');
+            // Model类选择(推荐使用)
+            $availableModels = $this->getAvailableModels();
+            $form->select('model_class', 'Model类')
+                ->options($availableModels)
+                ->help('选择要清理的Model类(推荐使用)')
+                ->when('!=', '', function (Form $form) {
+                    // 当选择了Model类时,自动填充表名
+                    $form->display('auto_table_name', '自动获取表名')->with(function ($value, $model) {
+                        if (!empty($model->model_class)) {
+                            try {
+                                $modelInstance = new $model->model_class();
+                                return $modelInstance->getTable();
+                            } catch (\Exception $e) {
+                                return 'Model错误: ' . $e->getMessage();
+                            }
+                        }
+                        return '请先选择Model类';
+                    });
+                });
+
+            // 表名选择(兼容旧数据)
+            $availableTables = CleanupConfig::whereNull('model_class')
+                ->orWhere('model_class', '')
+                ->pluck('table_name', 'table_name')
+                ->toArray();
+
+            if (!empty($availableTables)) {
+                $form->select('table_name', '表名(兼容旧数据)')
+                    ->options($availableTables)
+                    ->help('仅用于兼容旧数据,建议使用Model类选择');
+            }
 
             // 清理类型
             $form->select('cleanup_type', '清理类型')
@@ -229,9 +315,107 @@ class CleanupPlanContentController extends AdminController
             $form->textarea('notes', '备注说明')
                 ->help('对此清理配置的说明');
 
+            // 表单验证
+            $form->saving(function (Form $form) {
+                // 验证至少选择了Model类或表名
+                if (empty($form->model_class) && empty($form->table_name)) {
+                    return $form->response()->error('请至少选择Model类或表名');
+                }
+
+                // 如果选择了Model类,验证Model是否存在
+                if (!empty($form->model_class)) {
+                    if (!class_exists($form->model_class)) {
+                        return $form->response()->error('选择的Model类不存在:' . $form->model_class);
+                    }
+
+                    // 自动设置表名
+                    try {
+                        $modelInstance = new $form->model_class();
+                        $form->table_name = $modelInstance->getTable();
+                    } catch (\Exception $e) {
+                        return $form->response()->error('Model类实例化失败:' . $e->getMessage());
+                    }
+                }
+            });
+
             // 时间字段
             $form->display('created_at', '创建时间');
             $form->display('updated_at', '更新时间');
+
+            // 添加JavaScript增强用户体验
+            $form->html('
+                <script>
+                $(document).ready(function() {
+                    // 监听Model类选择变化
+                    $(document).on("change", "select[name=model_class]", function() {
+                        var modelClass = $(this).val();
+                        if (modelClass) {
+                            // 显示提示信息
+                            var className = modelClass.split("\\\\").pop();
+                            var moduleName = modelClass.match(/App\\\\Module\\\\([^\\\\]+)\\\\/);
+                            moduleName = moduleName ? moduleName[1] : "Unknown";
+
+                            // 创建提示信息
+                            var infoHtml = "<div class=\"alert alert-info\">" +
+                                "<strong>已选择Model:</strong>" + className + "<br>" +
+                                "<strong>所属模块:</strong>" + moduleName + "<br>" +
+                                "<strong>建议:</strong>使用Model类可以利用Laravel的所有特性,如软删除、事件等" +
+                                "</div>";
+
+                            // 移除旧的提示信息
+                            $(".model-info-alert").remove();
+
+                            // 添加新的提示信息
+                            $(this).closest(".form-group").after("<div class=\"model-info-alert\">" + infoHtml + "</div>");
+                        } else {
+                            $(".model-info-alert").remove();
+                        }
+                    });
+
+                    // 页面加载时如果已有选择,显示提示
+                    var initialModel = $("select[name=model_class]").val();
+                    if (initialModel) {
+                        $("select[name=model_class]").trigger("change");
+                    }
+                });
+                </script>
+            ', '用户体验增强');
         });
     }
+
+    /**
+     * 获取可用的Model类列表
+     */
+    private function getAvailableModels(): array
+    {
+        // 从CleanupConfig中获取已配置的Model类
+        $configuredModels = CleanupConfig::whereNotNull('model_class')
+            ->where('model_class', '!=', '')
+            ->pluck('model_class', 'model_class')
+            ->toArray();
+
+        // 格式化显示名称
+        $formattedModels = [];
+        foreach ($configuredModels as $modelClass) {
+            $className = class_basename($modelClass);
+            $moduleName = $this->extractModuleName($modelClass);
+            $formattedModels[$modelClass] = "{$moduleName} - {$className}";
+        }
+
+        // 按模块和类名排序
+        asort($formattedModels);
+
+        return $formattedModels;
+    }
+
+    /**
+     * 从Model类名中提取模块名
+     */
+    private function extractModuleName(string $modelClass): string
+    {
+        if (preg_match('/App\\\\Module\\\\([^\\\\]+)\\\\Models\\\\/', $modelClass, $matches)) {
+            return $matches[1];
+        }
+        return 'Unknown';
+    }
 }