基于CodeIgniter实现Web应用用户CRUD及全活动记录方案咨询
刚好之前在CodeIgniter项目里做过用户活动日志的功能,结合你提到的GLPI事件表的思路,给你梳理几个可行的实现方案,都是实际项目里验证过的:
一、先搞定数据库表设计(对标GLPI事件表)
首先得把日志表的结构搭好,参考GLPI的事件表逻辑,我们可以创建一个audit_logs表,核心字段如下:
id:主键自增,唯一标识每条日志user_id:操作用户的ID(关联你的用户表,方便追溯是谁操作的)action_type:操作类型,用枚举值比如add/edit/delete/login/logout/export,清晰区分不同操作item_type:操作的业务对象类型,比如ticket/user/asset,对应你系统里的业务表item_id:操作对象的ID(关联对应业务表,比如编辑了ID为12的工单,这里就存12)action_details:操作详情,建议存JSON格式,比如修改了哪些字段、新旧值对比,或者删除的内容快照,灵活存储细节ip_address:用户操作时的IP地址,方便定位操作来源created_at:操作时间(默认自动填充当前时间)user_agent:用户的浏览器/设备信息(可选,辅助排查问题)
二、CodeIgniter里的具体实现方案
1. 用钩子(Hooks)做全局自动监听
这种方式适合批量记录常规的增删改操作,不用每个控制器都写重复代码,一劳永逸。
步骤:
- 先开启钩子:打开
application/config/config.php,把$config['enable_hooks']设为TRUE - 定义钩子:打开
application/config/hooks.php,配置要触发的钩子和对应的处理类 - 编写钩子处理类:在
application/hooks目录下创建AuditLog.php,实现日志记录逻辑
钩子配置示例(hooks.php):
$hook['post_controller'] = [ 'class' => 'AuditLog', 'function' => 'log_activity', 'filename' => 'AuditLog.php', 'filepath' => 'hooks' ];
钩子处理类示例(AuditLog.php):
class AuditLog { public function log_activity() { // 获取CI实例 $CI =& get_instance(); // 排除不需要记录的控制器/方法,比如登录页、静态展示页 $exclude_controllers = ['login', 'welcome']; $exclude_methods = ['index', 'view', 'download']; if (in_array($CI->router->class, $exclude_controllers) || in_array($CI->router->method, $exclude_methods)) { return; } // 未登录用户不记录 $user_id = $CI->session->userdata('user_id'); if (!$user_id) return; // 推断操作类型和业务对象 $action_type = ''; $item_type = $CI->router->class; // 控制器名作为业务对象类型,比如Tickets控制器对应ticket $item_id = $CI->uri->segment(3); // 从URL获取操作对象ID,比如/tickets/edit/12里的12 $action_details = ''; switch($CI->router->method) { case 'add': $action_type = 'add'; // 添加操作后,获取刚插入的记录ID $item_id = $CI->db->insert_id(); $action_details = "新增了{$item_type}记录,ID:{$item_id}"; break; case 'edit': $action_type = 'edit'; // 对比新旧数据,记录修改的字段 $old_data = $CI->db->get_where($item_type, ['id' => $item_id])->row_array(); $new_data = $CI->input->post(); // 过滤掉不需要对比的字段(比如csrf_token) unset($new_data['csrf_token_name']); $changed_fields = array_keys(array_diff_assoc($new_data, $old_data)); $action_details = json_encode([ 'old_data' => $old_data, 'new_data' => $new_data, 'changed_fields' => $changed_fields ]); break; case 'delete': $action_type = 'delete'; // 记录删除前的快照 $deleted_data = $CI->db->get_where($item_type, ['id' => $item_id])->row_array(); $action_details = json_encode(['deleted_data' => $deleted_data]); break; default: // 自定义操作,比如用户修改密码、导出数据 $action_type = 'custom'; $action_details = "执行了自定义操作:{$CI->router->method}"; } // 插入日志表 $log_data = [ 'user_id' => $user_id, 'action_type' => $action_type, 'item_type' => $item_type, 'item_id' => $item_id, 'action_details' => $action_details, 'ip_address' => $CI->input->ip_address(), 'user_agent' => $CI->input->user_agent(), 'created_at' => date('Y-m-d H:i:s') ]; $CI->db->insert('audit_logs', $log_data); } }
2. 封装日志模型,手动调用记录特殊事件
如果有些操作是特殊场景(比如用户登录成功、导出报表),全局钩子不好自动判断,那可以封装一个日志模型,在需要的地方手动调用,灵活性更高。
日志模型示例(application/models/Audit_log_model.php):
class Audit_log_model extends CI_Model { public function add_log($user_id, $action_type, $item_type, $item_id, $action_details = '') { $log_data = [ 'user_id' => $user_id, 'action_type' => $action_type, 'item_type' => $item_type, 'item_id' => $item_id, 'action_details' => $action_details, 'ip_address' => $this->input->ip_address(), 'user_agent' => $this->input->user_agent(), 'created_at' => date('Y-m-d H:i:s') ]; return $this->db->insert('audit_logs', $log_data); } }
控制器里手动调用示例:
class Auth extends CI_Controller { public function login() { // 登录验证逻辑 if ($this->auth->validate_login($username, $password)) { $user_id = $this->session->userdata('user_id'); // 记录登录日志 $this->load->model('audit_log_model'); $this->audit_log_model->add_log( $user_id, 'login', 'user', $user_id, "用户登录成功,IP:{$this->input->ip_address()}" ); redirect('dashboard'); } } }
3. 用自定义基类控制器统一处理
如果你的所有业务控制器都继承自自定义的基类,那可以把日志逻辑封装在基类里,这样代码更统一,维护起来也方便。
自定义基类示例(application/core/MY_Controller.php):
class MY_Controller extends CI_Controller { protected $audit_log_model; public function __construct() { parent::__construct(); // 提前加载日志模型 $this->load->model('audit_log_model'); } /** * 封装日志记录方法,子类直接调用 */ protected function log_activity($action_type, $item_type, $item_id, $action_details = '') { $user_id = $this->session->userdata('user_id'); if (!$user_id) return; // 未登录用户不记录 $this->audit_log_model->add_log($user_id, $action_type, $item_type, $item_id, $action_details); } }
业务控制器调用示例:
class Tickets extends MY_Controller { public function delete($id) { // 获取要删除的工单信息 $ticket_data = $this->db->get_where('tickets', ['id' => $id])->row_array(); // 执行删除操作 $this->db->delete('tickets', ['id' => $id]); // 调用基类的日志方法记录 $this->log_activity( 'delete', 'ticket', $id, json_encode(['deleted_ticket' => $ticket_data]) ); redirect('tickets'); } }
三、几个优化小建议
- 日志展示优化:做一个后台日志查询页面,支持按用户、操作类型、时间范围筛选,把JSON格式的详情解析成友好的对比视图(比如修改前后的字段值高亮显示)
- 避免重复记录:只记录POST请求,排除GET请求(比如页面刷新),防止日志冗余
- 性能优化:如果日志量很大,可以用异步队列(比如结合Redis)来记录日志,避免插入日志拖慢主业务的响应速度
- 敏感数据保护:比如用户密码修改、银行卡信息变更,不要记录明文数据,只记录“修改了密码”这类模糊描述
- 权限控制:日志页面只开放给管理员,避免普通用户查看他人的操作记录
内容的提问来源于stack exchange,提问作者Demian
相关产品推荐
相关产品推荐

