

前言
今天想分享一个自己动手搭建的、用于记录用户登录日志的小系统。它的核心功能很简单:无论登录成功与否,都能实时捕获用户的登录尝试,并把用户名、密码、来源IP和时间戳等信息规整地记录到 Excel 文件中,方便后续的审计和分析。
本文主要记录一下大体的实现思路和关键代码,希望能给有类似需求的小伙伴提供一个参考。
项目效果
理想状态下,这个系统应该能实时记录所有用户的登录尝试信息,包括输入的用户名、密码、来源IP以及精确的登录时间。所有数据会以 .xlsx 格式保存,结构清晰,可以直接用 Excel 打开查看或进行进一步的数据分析。
技术选型与架构设计
在动手之前,先简单聊聊技术栈的选择:
- Node.js + Express.js:作为后端服务。Node.js 事件驱动、非阻塞 I/O 的特性,应对并发的登录请求比较合适。Express 框架则能快速搭建起 API 服务。
- ExcelJS:用来处理 Excel 文件。最初我试过
xlsx 库,但在实际使用中遇到了些稳定性问题。后来换成了功能更丰富、也更稳定的 ExcelJS 库。
- 原生前端技术:登录界面和日志查看器直接用 HTML、CSS 和 JavaScript 实现。这样做的好处是免去了框架依赖,项目结构简单,加载速度也快。
系统架构
整个系统主要由三个核心部分组成:
- 后端服务器 (server.js):提供记录日志、读取日志文件的 API 接口,并负责 Excel 文件的操作。
- 登录页面:一个模拟的企业登录界面,用于触发登录行为并发送日志数据。
- 日志查看器 (logs-viewer.html):一个简单的 Web 管理界面,用于查看已生成的日志文件列表及其内容。
核心功能实现
整个项目的灵魂,在于几个关键功能的实现。下面我们逐一拆解。
1. 如何准确获取用户的真实 IP 地址?
在网络环境复杂(可能经过代理、CDN)的情况下,直接从请求对象里拿 IP 可能不准确。我们需要优先读取代理服务器转发过来的标准 HTTP 头信息。
function getClientIP(req) {
// 配置Express信任代理
app.set('trust proxy', true);
// 按优先级尝试多种IP头信息
let ip = req.headers['x-forwarded-for']?.split(',').map(x => x.trim()).shift() ||
req.headers['x-real-ip'] ||
req.headers['cf-connecting-ip'] ||
req.ip ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
'未知';
// 处理IPv6映射的IPv4地址
if (ip && ip.startsWith('::ffff:')) {
ip = ip.substring(7);
}
// 本地地址转换
if (ip === '::1') {
ip = '127.0.0.1';
}
return ip;
}

这段代码的逻辑很清晰:按 X-Forwarded-For, X-Real-IP, CF-Connecting-IP 等常见头信息的优先级进行尝试,如果都没有,再降级使用连接本身的远程地址。最后还对 IPv6 映射地址和本地回环地址做了规范化处理。
2. 如何生成并管理 Excel 日志文件?
我们希望日志文件能自动创建、支持追加新记录(而不是覆盖),并且表头样式美观。这对于 ExcelJS 库来说是小菜一碟。
核心思路是:每次收到日志记录请求时,先检查目标 Excel 文件是否存在。如果存在,就读取它并找到(或创建)“登录日志”这个工作表;如果不存在,则新建一个工作簿和工作表。接着,向工作表中追加新的一行数据并保存。
app.post('/api/log-login', async (req, res) => {
try {
const { username, password, loginTime } = req.body;
const clientIP = getClientIP(req);
const logFileName = 'login_logs.xlsx';
const logFilePath = path.join(__dirname, 'logs', logFileName);
let workbook, worksheet;
// 检查文件是否存在
if (fs.existsSync(logFilePath)) {
// 读取现有文件
workbook = new ExcelJS.Workbook();
await workbook.xlsx.read(logFilePath);
worksheet = workbook.getWorksheet('登录日志');
if (!worksheet) {
// 创建新工作表
worksheet = workbook.addWorksheet('登录日志');
setupWorksheetHeaders(worksheet);
}
} else {
// 创建新文件
workbook = new ExcelJS.Workbook();
worksheet = workbook.addWorksheet('登录登录');
setupWorksheetHeaders(worksheet);
}
// 添加新记录
const newRowNumber = worksheet.rowCount + 1;
const newRow = worksheet.getRow(newRowNumber);
newRow.getCell(1).value = username;
newRow.getCell(2).value = password;
newRow.getCell(3).value = loginTime;
newRow.getCell(4).value = clientIP;
// 保存文件
await workbook.xlsx.writeToFile(logFilePath);
res.json({ success: true, message: '登录日志已记录', fileName: logFileName, clientIP: clientIP });
} catch (error) {
console.error('记录登录日志失败:', error);
res.status(500).json({ success: false, message: '记录登录日志失败' });
}
});

setupWorksheetHeaders 函数负责初始化工作表的表头(比如“用户名”、“密码”、“登录时间”、“登录IP”),并可以设置字体加粗、背景色等样式,让生成的表格更专业。
3. 前端如何无缝集成日志记录?
关键在于,前端在提交登录表单时,无论后端验证是否通过,都要先调用日志记录接口。我们可以把日志记录做成一个独立的函数。
// 记录登录日志到Excel
function logLoginToExcel(username, password, loginTime) {
fetch('/api/log-login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: username,
password: password,
loginTime: loginTime
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
console.log('登录日志已记录:', data.fileName, 'IP:', data.clientIP);
} else {
console.error('记录登录日志失败:', data.message);
}
})
.catch(error => {
console.error('发送登录日志失败:', error);
});
}
function login() {
const username = $('#username').val();
const password = $('#password').val();
const loginTime = new Date().toLocaleString('zh-CN');
// 无论登录是否成功都记录日志
logLoginToExcel(username, password, loginTime);
// 继续原有的登录逻辑...
}

这样,日志记录就和业务逻辑解耦了,非常清晰。
4. 如何提供一个便捷的日志管理界面?
一个简单的 Web 界面就能满足基本需求:列出 logs 目录下所有的 .xlsx 文件,点击文件名可以查看文件内容(以 JSON 或表格形式展示),甚至提供下载链接。
// 加载日志文件列表
function loadLogs() {
fetch('/api/logs')
.then(response => response.json())
.then(data => {
if (data.success) {
displayLogs(data.logs);
} else {
container.innerHTML = '<div class=\"no-logs\">获取日志文件失败:' + data.message + '</div>';
}
})
.catch(error => {
container.innerHTML = '<div class=\"no-logs\">获取日志文件失败:' + error.message + '</div>';
});
}
// 查看日志内容
function viewLogContent(filename) {
fetch('/api/logs/' + filename)
.then(response => response.json())
.then(data => {
if (data.success) {
displayLogContent(filename, data.data);
} else {
alert('获取日志内容失败:' + data.message);
}
})
.catch(error => {
alert('获取日志内容失败:' + error.message);
});
}

后端对应需要提供 /api/logs (GET) 和 /api/logs/:filename (GET) 两个 API 接口,分别用于返回文件列表和指定文件的内容。
系统工作流程
把上面的模块串起来,整个系统的工作流就非常清晰了:
-
用户登录流程
用户输入账号密码 → 前端记录日志 → 发送到后端API → 获取真实IP → 写入Excel文件 → 返回成功响应
-
管理员查看日志流程
管理员访问查看器 → 获取文件列表 → 选择文件查看内容 → 下载或预览日志数据
最终实现效果
纸上谈兵终觉浅,我们启动项目来看看实际运行效果。
-
前端登录页面

一个简洁的登录框,用户在此进行登录操作。
-
后端控制台输出

每次有登录请求,后台都会打印出详细的处理日志,包括获取到的IP、文件操作状态等,便于调试和 运维。
-
Web日志查看器

在浏览器中可以直接查看日志文件的内容,以表格形式呈现,非常直观。
-
生成的Excel文件

最终生成的 .xlsx 文件,可以直接用 Microsoft Excel 或 WPS 等软件打开,支持排序、筛选等所有Excel操作。
总结与思考
通过这个实践项目,我们完成了一个功能完整的登录日志记录系统。从准确获取用户IP,到可靠地写入Excel文件,再到前后端的协同,每一步都采用了相对成熟和稳定的技术方案。
当然,这只是一个基础版本。在实际生产环境中,你可能还需要考虑更多问题,比如日志文件的分割(按天/按月)、日志内容的加密脱敏(特别是密码)、以及更高性能的日志写入方式等。但无论如何,这个项目已经搭建起了一个坚实且可扩展的框架。
希望这篇分享能对你有所启发。如果你对 Node.js、Excel 操作或者其他的全栈开发实践感兴趣,欢迎到 云栈社区 来交流讨论,那里有更多开发者分享的实战经验和开源项目。
