You've already forked think-plugs-recorder
333 lines
10 KiB
PHP
333 lines
10 KiB
PHP
<?php
|
|
|
|
namespace jerryyan\recorder;
|
|
|
|
use think\admin\Plugin;
|
|
use jerryyan\recorder\helper\ViewHelper;
|
|
|
|
/**
|
|
* 操作记录插件服务类
|
|
* Class Service
|
|
* @package jerryyan\recorder
|
|
*/
|
|
class Service extends Plugin
|
|
{
|
|
/**
|
|
* 插件编码
|
|
* @var string
|
|
*/
|
|
protected $appCode = 'recorder';
|
|
|
|
/**
|
|
* 插件名称
|
|
* @var string
|
|
*/
|
|
protected $appName = '操作记录';
|
|
|
|
/**
|
|
* 定义插件菜单
|
|
* @return array
|
|
*/
|
|
public static function menu(): array
|
|
{
|
|
// 暂时不提供后台菜单,仅作为服务插件使用
|
|
return [
|
|
[
|
|
'name' => '操作记录',
|
|
'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='操作记录表';
|
|
";
|
|
}
|
|
} |