'操作记录', 'subs' => [ [ 'name' => '记录查询', 'icon' => 'layui-icon layui-icon-search', 'node' => 'recorder/index/index' ], [ 'name' => '记录统计', 'icon' => 'layui-icon layui-icon-chart', 'node' => 'recorder/stats/index' ] ] ] ]; } /** * 插件服务注册 */ public function register() { // 注册事件监听器 $this->registerEventListeners(); } /** * 插件启动 */ public function boot(): void { // 插件启动时的初始化操作 $this->initializePlugin(); // 注册模板函数 $this->registerTemplateFunctions(); } /** * 注册模板函数 */ protected function registerTemplateFunctions() { try { // 函数已经通过composer autoload的files自动加载 // 这里只需要确认函数是否正确加载 if (!function_exists('recorder_get_records')) { // 如果通过autoload加载失败,手动加载 require_once __DIR__ . '/functions.php'; } } catch (\Exception $e) { trace("注册模板函数失败: " . $e->getMessage(), 'error'); } } /** * 注册事件监听器 */ protected function registerEventListeners() { // 可以在这里注册模型事件监听器,自动记录CRUD操作 // 例如:监听模型的增删改事件,自动记录操作日志 try { // 注册全局模型事件监听 \think\facade\Event::listen('think\\model\\concern\\ModelEvent', function($event, $model) { $this->handleModelEvent($event, $model); }); } catch (\Exception $e) { trace("注册事件监听器失败: " . $e->getMessage(), 'error'); } } /** * 处理模型事件 * @param string $event * @param $model */ protected function handleModelEvent(string $event, $model) { // 根据配置决定是否自动记录模型操作 $config = \jerryyan\recorder\service\RecorderService::getConfig('auto_record', []); if (!($config['enabled'] ?? false)) { return; } try { $operationMap = [ 'after_insert' => '创建', 'after_update' => '更新', 'after_delete' => '删除', ]; if (isset($operationMap[$event])) { $operationType = $operationMap[$event]; // 检查是否排除此操作类型 $excludeOperations = $config['exclude_operations'] ?? []; if (in_array($operationType, $excludeOperations)) { return; } $tableName = $model->getTable(); $primaryKey = $model->getPk(); $primaryValue = $model->$primaryKey ?? ''; \jerryyan\recorder\service\RecorderService::autoRecord([ 'operation_type' => $operationType, 'operation_desc' => "{$operationType}{$tableName}记录", 'data_type' => $tableName, 'data_id' => (string)$primaryValue, ]); } } catch (\Exception $e) { trace("自动记录模型操作失败: " . $e->getMessage(), 'error'); } } /** * 初始化插件 */ protected function initializePlugin() { // 设置默认配置 $this->setDefaultConfig(); // 检查数据表是否存在 $this->checkDatabaseTables(); } /** * 设置默认配置 */ protected function setDefaultConfig() { $defaultConfig = [ 'enabled' => true, 'auto_record' => [ 'enabled' => false, 'exclude_operations' => ['读取'], 'exclude_controllers' => [], ], 'retention_days' => 90, 'sensitive_operations' => ['删除', '导出'], 'view' => [ 'default_limit' => 10, 'date_format' => 'Y-m-d H:i:s', 'theme' => 'default', 'show_user' => true, 'show_ip' => false, 'compact_mode' => false, ] ]; // 合并用户配置 $userConfig = config('recorder', []); $finalConfig = array_merge($defaultConfig, $userConfig); // 更新配置 config(['recorder' => $finalConfig]); } /** * 检查数据表是否存在 */ protected function checkDatabaseTables() { try { $db = \think\facade\Db::connect(); // 检查操作记录表是否存在 $tables = $db->query("SHOW TABLES LIKE 'jl_recorder_log'"); if (empty($tables)) { trace("操作记录表不存在,请运行数据库迁移脚本:php think migrate:run", 'notice'); } } catch (\Exception $e) { trace("检查数据表失败: " . $e->getMessage(), 'error'); } } /** * 获取插件版本 * @return string */ public function getVersion(): string { return 'v1.0.0'; } /** * 获取插件描述 * @return string */ public function getDescription(): string { return '提供用户操作记录功能,支持手动记录和自动记录,包含完善的查询和视图展示功能'; } /** * 获取插件作者 * @return string */ public function getAuthor(): string { return 'Jerry Yan'; } /** * 插件安装 * @return bool */ public function install(): bool { try { // 运行数据库迁移 $this->runMigrations(); // 初始化配置 $this->setDefaultConfig(); trace("操作记录插件安装成功", 'info'); return true; } catch (\Exception $e) { trace("操作记录插件安装失败: " . $e->getMessage(), 'error'); return false; } } /** * 插件卸载 * @return bool */ public function uninstall(): bool { try { // 这里可以添加卸载时的清理操作 // 注意:通常不建议自动删除数据表,避免数据丢失 trace("操作记录插件卸载成功", 'info'); return true; } catch (\Exception $e) { trace("操作记录插件卸载失败: " . $e->getMessage(), 'error'); return false; } } /** * 运行数据库迁移 */ protected function runMigrations() { // 这里可以调用Phinx迁移命令 // 或者直接执行SQL创建表结构 try { // 示例:直接执行建表SQL $sql = $this->getCreateTableSql(); \think\facade\Db::execute($sql); } catch (\Exception $e) { trace("执行数据库迁移失败: " . $e->getMessage(), 'error'); throw $e; } } /** * 获取建表SQL * @return string */ protected function getCreateTableSql(): string { return " CREATE TABLE IF NOT EXISTS `jl_recorder_log` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '记录ID', `operation_type` varchar(50) NOT NULL DEFAULT '' COMMENT '操作类型', `operation_desc` varchar(500) NOT NULL DEFAULT '' COMMENT '操作说明', `user_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '操作用户ID', `user_nickname` varchar(100) NOT NULL DEFAULT '' COMMENT '操作用户昵称', `data_type` varchar(100) NOT NULL DEFAULT '' COMMENT '操作数据类型', `data_id` varchar(100) NOT NULL DEFAULT '' COMMENT '操作数据ID', `related_type` varchar(100) NOT NULL DEFAULT '' COMMENT '关联数据类型', `related_id` varchar(100) NOT NULL DEFAULT '' COMMENT '关联数据ID', `request_method` varchar(10) NOT NULL DEFAULT '' COMMENT '请求方法', `request_url` varchar(500) NOT NULL DEFAULT '' COMMENT '请求URL', `request_ip` varchar(50) NOT NULL DEFAULT '' COMMENT '请求IP', `user_agent` varchar(500) NOT NULL DEFAULT '' COMMENT '用户代理', `extra_data` text COMMENT '额外数据(JSON格式)', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_operation_type` (`operation_type`), KEY `idx_data_type_id` (`data_type`, `data_id`), KEY `idx_created_at` (`created_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='操作记录表'; "; } }