审计追踪功能实现细节与避坑指南

迷人的艳艳 安全 阅读 1,717
赞 25 收藏
二维码
手机扫码查看
反馈

项目初期的技术选型

最近刚结束了一个企业后台管理系统,需求里有一条特别重要:所有用户操作都要记录下来,方便后续审计和问题追溯。说白了就是要做审计追踪(Audit Trail)。我一开始觉得这事儿不复杂,不就是加个日志嘛?但真正做起来才发现,这里面的坑还真不少。

审计追踪功能实现细节与避坑指南

在技术选型上,我们团队讨论了好几次。最开始想用后端统一处理,毕竟后端对数据的掌控力更强。但后来发现前端也有很多需要记录的操作,比如用户点击了某个按钮、修改了表单内容等,这些如果都交给后端来做,通信成本太高。最后决定前后端协作:前端负责捕获用户行为,后端负责存储和管理数据。

核心代码就这几行

先简单介绍一下我们的实现思路。前端通过监听用户交互事件(如点击、输入等),将关键信息发送到后端接口。后端接收后存入数据库,供后续查询和分析。这里贴一下前端的核心代码:

// 定义一个通用的日志记录函数
function logUserAction(actionType, details = {}) {
  const userId = localStorage.getItem('userId'); // 获取当前用户ID
  const timestamp = new Date().toISOString(); // 记录时间戳

  const logData = {
    userId,
    actionType,
    details,
    timestamp,
  };

  // 发送日志到后端接口
  fetch('https://jztheme.com/api/log', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(logData),
  }).catch((err) => {
    console.error('日志记录失败:', err);
  });
}

// 示例:监听按钮点击事件
document.querySelectorAll('.audit-track').forEach((button) => {
  button.addEventListener('click', (event) => {
    const actionType = event.target.dataset.action || 'unknown_action';
    logUserAction(actionType, { elementId: event.target.id });
  });
});

上面这段代码看起来挺简单,但实际上踩了不少坑,后面我会详细讲。

最大的坑:性能问题

刚开始的时候,我天真地以为只要把所有用户交互都记录下来就好了。结果上线测试时,发现页面卡得不行。尤其是那些高频操作,比如表单输入或者滚动事件,直接导致页面响应变慢。

后来才意识到,问题出在日志发送的频率上。每次用户触发事件,都会调用 logUserAction 函数,然后发起一次 HTTP 请求。这种频繁的网络请求不仅拖慢了页面性能,还给后端服务器带来了巨大压力。

解决这个问题的办法是引入**节流(throttle)**和**防抖(debounce)**机制。对于高频事件,比如表单输入,我们采用了防抖策略:

// 防抖函数
function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

// 监听输入框变化
document.querySelectorAll('.audit-input').forEach((input) => {
  input.addEventListener(
    'input',
    debounce((event) => {
      logUserAction('input_change', { value: event.target.value });
    }, 500)
  );
});

而对于一些低频但重要的操作,比如按钮点击,我们就直接记录,不需要额外的优化。

又踩坑了:数据一致性

另一个让我头疼的问题是数据一致性。有些操作涉及到多个步骤,比如提交表单时,前端会先验证数据,然后再调用后端接口保存。如果在这过程中用户突然刷新页面,或者网络中断,就会导致日志记录不完整。

为了解决这个问题,我们在后端增加了一个状态字段,用来标记每条日志是否完成。比如用户点击“提交”按钮时,前端会先发送一条“开始提交”的日志,等后端返回成功后再发送一条“提交完成”的日志。如果中途出现问题,后端可以根据状态字段判断哪些操作未完成。

这部分逻辑主要是在后端实现的,前端只需要配合调整日志格式:

logUserAction('form_submit_start', { formId: 'user_profile' });

fetch('https://jztheme.com/api/submit', {
  method: 'POST',
  body: formData,
})
  .then((response) => {
    if (response.ok) {
      logUserAction('form_submit_success', { formId: 'user_profile' });
    } else {
      logUserAction('form_submit_failed', { formId: 'user_profile' });
    }
  })
  .catch(() => {
    logUserAction('form_submit_error', { formId: 'user_profile' });
  });

最终效果还算满意

经过几轮优化,最终的效果还算不错。系统能够准确记录大部分用户操作,而且性能影响也在可接受范围内。不过还是有一些小问题没完全解决:

  • 某些复杂的交互场景下,日志记录不够细致。比如拖拽排序操作,目前只能记录最终结果,无法还原整个过程。
  • 日志数据量增长很快,后端查询性能逐渐成为瓶颈。虽然加了索引,但对于超大规模数据集来说,仍然有点吃力。

总的来说,这个功能达到了预期目标,但也暴露了一些不足之处。以后有机会的话,可能会尝试更高效的日志存储方案,比如使用专门的日志系统(如 ELK Stack)。

回顾与反思

这次做审计追踪功能,最大的收获是对用户体验和系统性能之间的平衡有了更深的理解。刚开始的时候,总是想着“能记多少记多少”,结果差点把系统搞崩。后来才明白,有些操作其实没必要记录,比如鼠标的移动轨迹,或者用户反复修改表单内容的过程。

另一个教训是,技术选型一定要结合实际需求。如果一开始就考虑到前后端协作的复杂性,可能就不会走那么多弯路了。

以上是我个人对这个审计追踪功能的完整讲解,有更优的实现方式欢迎评论区交流。这个技巧的拓展用法还有很多,后续会继续分享这类博客。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论