You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用express-session登录后多设备显示已登录的问题修复求助

问题原因分析
  • 核心问题:全局变量app.locals的误用
    你在首页路由里把登录状态(username、admin)存在了app.locals里,而app.locals是Express应用的全局共享变量,所有客户端的请求都会读取这个变量的值。当一个设备登录后,app.locals.username和app.locals.admin被设置为登录用户的信息,后续所有设备(包括未登录的)请求首页时,模板都会读取这个全局变量,导致错误显示已登录状态,完全和当前请求的req.session无关。

  • 其他潜在问题:

    1. 引入了redisStore但未在session配置中启用,默认使用内存存储,生产环境会存在内存泄漏、重启丢失会话的问题。
    2. 登录接口中,数据库查询结果result是数组,result.admin应该取result[0].admin,否则无法正确获取用户权限。
    3. 登录接口的响应逻辑颠倒:登录成功时未返回SUCCESS,反而在登录失败时返回了该标识。
    4. 每次请求都创建新的数据库连接,没有复用机制,性能损耗大。
解决方案

1. 移除全局变量,改用请求级数据传递

修改首页路由,不再将session值存入全局变量,而是直接把当前请求的session数据传给模板:

app.get('/', function(req, res) {
    let connection = mysql.createConnection(db_config);
    console.log(req.session.loggedin);
    
    // 整理当前请求的登录状态,仅对本次请求有效
    const templateData = {
        result: [],
        loggedin: req.session.loggedin || false,
        username: req.session.username || '',
        isAdmin: req.session.admin === 1 || false
    };

    connection.query('SELECT * FROM posts ORDER by date desc', function (err, result, fields) {
        if (err) {
            console.error(err);
            return res.sendStatus(500);
        }
        if (Object.keys(result).length !== 0) {
            templateData.result = result;
            res.render('templates/index', templateData);
        } else {
            res.sendStatus(404);
        }
        connection.end();
    });
});

在EJS模板中直接使用传入的变量判断登录状态:

<% if (loggedin) { %>
    <p>欢迎回来,<%= username %></p>
    <% if (isAdmin) { %>
        <p>管理员权限</p>
    <% } %>
<% } else { %>
    <p>请登录</p>
<% } %>

2. 修复登录接口的逻辑错误

app.post('/login', function(req, res) {
    var username = req.body.username;
    var password = Buffer.from(req.body.password).toString('base64');
    if (!username || !password) {
        return res.send('Please enter Username and Password!');
    }

    let connection = mysql.createConnection(db_config);
    connection.query('SELECT * FROM users WHERE username = ? AND password = ?', [username, password], function(error, result, fields) {
        if (error) {
            console.error(error);
            return res.send('登录失败,请重试');
        }
        if (result.length > 0) {
            // 正确获取数组中的用户数据
            const user = result[0];
            req.session.loggedin = true;
            req.session.username = user.username;
            req.session.admin = user.admin;
            res.send('SUCCESS'); // 登录成功返回SUCCESS
        } else {
            res.send('用户名或密码错误'); // 登录失败返回错误信息
        }
        connection.end();
    });
});

3. 启用Redis作为Session存储(推荐)

既然已引入redisStore,配置后可解决内存存储的弊端:

// 修正session配置
app.use(session({
    secret: 'your-strong-secret-key', // 替换为更安全的随机密钥
    resave: false, // 避免不必要的session保存
    saveUninitialized: false, // 仅保存已初始化的session
    store: new redisStore({ client: client }), // 绑定Redis客户端
    cookie: {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production', // 生产环境开启HTTPS时设为true
        maxAge: 24 * 60 * 60 * 1000 // 设置会话有效期为1天
    }
}));

4. 优化数据库连接(可选)

使用连接池代替每次创建新连接,提升性能:

// 初始化数据库连接池
const pool = mysql.createPool(db_config);

// 在路由中使用连接池
pool.getConnection(function(err, connection) {
    if (err) throw err;
    connection.query('SELECT * FROM posts ORDER by date desc', function(err, result) {
        connection.release(); // 释放连接回池
        // 处理查询结果
    });
});
验证修改效果
  1. 设备A登录后,仅设备A的页面显示已登录,设备B页面显示未登录。
  2. 设备B登录后,设备B显示自身登录状态,设备A仍保留原登录状态。
  3. 查看各设备的req.session,确认仅登录设备的会话包含loggedin、username等标识。

内容的提问来源于stack exchange,提问作者Moon Wolf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 07:30:39