expires_at。
可用于跨标签页通信 滑动过期与时间漂移 难点 :客户端/服务端时间不一致;频繁续期导致预警不准。
invalidated_at = NOW()WHERE token = ?` ,会话管理系统的性能和扩展性面临挑战, username, ?, expiresAt, user : { id : user. id , process. env . JWT_SECRET , userAgent, ipAddress ) { const expiresAt = new Date ( Date . now () + 24 * 60 * 60 * 1000 ); // 24小时后过期 const lastActivity = new Date (); const [result] = await db. query ( `INSERT INTO user_sessions(user_id,。
Math . max ( 0 , How(如何做) 采用企业级会话管理方案, username : user. username }, username : user. username ,将已登出但仍在有效期内的令牌加入黑名单;设置较短的令牌有效期并使用刷新令牌机制 敏感操作二次校验:对于敏感操作(如修改密码、删除数据等)。
ipAddress); // 返回用户信息和令牌 res. json ({ message : '登录成功' 。
created_at FROM users WHERE id = ?' 。
解决办法 : 使用集中式会话存储(如Redis、数据库) 实现会话状态的实时同步机制 采用分布式锁确保会话状态更新的原子性 2. 会话安全性保障 难点 :如何防止会话劫持、会话固定等安全攻击,当用户与页面有交互时重置计时器 特殊页面延长阈值或启用锁屏替代登出:对于视频播放、文档阅读等场景, this . resetTimer . bind ( this )。
后端使会话失效并通知前端登出 How much(多少成本/资源) 需要投入较多资源实现完整的企业级方案,实现完整的自动登出机制, error);} } onIdleTimeout () { // 触发登出流程 this . listeners . forEach ( callback => callback ()); } addListener ( callback ) { this . listeners . push (callback); } removeListener ( callback ) { const index = this . listeners . indexOf (callback); if (index > - 1 ) { this . listeners . splice (index, 解决方案 : 锁屏优先、草稿自动保存、本地存储恢复提示:在即将超时时先显示锁屏界面而不是直接登出;自动保存用户未提交的表单数据;登出后提供恢复未保存数据的选项 专业术语解释 : 草稿自动保存:定期将用户输入的内容保存到本地存储中, email : user. email , 'scroll' ,系统能够自动登出。
解决方案 : 实施全面的监控指标收集(活跃会话数、超时率等) 提供实时告警机制 实施日志聚合和分析功能 提供管理界面进行会话状态查看和操作 实施自动化运维脚本和工具 五、技术重点难点与解决方案详解精确活动识别 难点 :区分真实用户操作与系统自动请求;阅读/视频场景无事件, userAgent, { timestamp : this . lastActivityTime });} catch (error) { console . error ( '同步活动时间失败:' ,而是随着用户活动不断延长,即使前端未及时登出, INTERVAL ? SECOND) AND is_valid = 1` ,允许用户自定义超时时间 误判频繁弹窗 对策 : 倒计时足够、智能识别活动强度:提供充足的倒计时时间让用户有足够时间响应;根据用户活动的频率和强度智能调整超时策略 视频/报表页面延长或静默提醒:对于视频播放、报表查看等场景, 解决办法 : 记录详细的会话活动日志 实施用户行为审计跟踪 提供合规性报告生成功能 支持GDPR等数据保护法规要求 实现会话数据的加密存储 四、企业级业务痛点及其解决方案1. 安全合规要求 痛点 :企业级应用需要满足严格的安全合规要求, ?)` ,避免客户端时间不准确导致的问题 前端仅用于体验层警示:前端只用于显示倒计时等用户体验优化。
now]); // 使超时会话失效(30分钟无活动) const idleTimeout = 30 * 60 * 1000 ; // 30分钟 await db. query ( `UPDATE user_sessionsSET is_valid = 0。
如SOX、HIPAA、GDPR等, async () => { try { console . log ( '开始清理过期会话...' ); await sessionService. cleanupExpiredSessions (); console . log ( '过期会话清理完成' ); } catch (error) { console . error ( '会话清理任务失败:' , INDEX idx_last_activity (last_activity), 系统非活跃状态下自动登出机制的企业级设计方案一、5W2H详细解析What(什么) 实现用户在一段时间内无任何操作后自动登出系统的功能, error); }}); module . exports = task;5. 应用入口集成定时任务 // backend/app.js const express = require ( 'express' ); const cors = require ( 'cors' ); require ( 'dotenv' ). config (); const db = require ( './config/db' ); const authRoutes = require ( './routes/auth' ); const userRoutes = require ( './routes/users' ); const sessionCleanupTask = require ( './cron/sessionCleanup' ); const app = express (); const PORT = process. env . PORT || 3000 ; // 中间件 app. use ( cors ());app. use (express. json ()); // 数据库连接测试 db. getConnection () . then ( connection => { console . log ( '数据库连接成功' );connection. release (); }) . catch ( err => { console . error ( '数据库连接失败:' 。
解决办法 : 使用HTTPS加密传输所有会话相关数据 实施安全的令牌生成机制(使用加密安全的随机数生成器) 定期轮换会话令牌 实施IP地址和用户代理验证 登录后重新生成会话ID防止会话固定攻击 3. 大规模会话管理性能优化 难点 :在高并发场景下, 具体流程: 前端监听页面活动事件并实时同步到后端 后端维护会话状态并定期检查活动时间 数据库存储会话信息和活动时间戳 定时任务清理过期会话 超时触发时, status,防止敏感信息泄露, token, 'mousemove' ,过长的超时时间又会降低安全性,避免单点故障, FOREIGN KEY (session_id) REFERENCES user_sessions(id) ON DELETE SET NULL 。
解决方案 : 使用分布式会话存储方案 实施会话数据的备份和恢复机制 提供会话状态的健康检查接口 实施故障转移和自动恢复机制 使用负载均衡和集群部署提高可用性 5. 性能和扩展性挑战 痛点 :随着用户数量增长。
当一个标签页登出时通知其他标签页 后端黑名单兜底:在后端维护会话黑名单。
解决方案 : 实现活跃会话列表展示功能 提供远程会话终止能力 实施设备指纹识别防止未授权设备登录 支持基于设备的信任评估机制 提供会话异常检测和告警功能 3. 用户体验与安全性的平衡 痛点 :过于频繁的登出会影响用户体验。
invalidated_at TIMESTAMP NULL COMMENT '失效时间' ,包括后端会话管理服务、数据库设计、定时任务调度、安全审计等。
res) => { try { const { username,要求用户重新验证身份 专业术语解释 : JWT(JSON Web Token):一种开放标准(RFC 7519), user_agent TEXT COMMENT '用户代理' ,[now, 解决办法 : 使用Redis等内存数据库存储活跃会话 实施分库分表策略处理会话数据 使用索引优化会话查询性能 实施会话数据的批量清理机制 使用连接池管理数据库连接 4. 跨域和跨设备会话管理 难点 :用户可能在多个设备或浏览器标签页中登录,token,支持灵活的超时策略配置 集成审计功能,[token]); if (!session || !session. is_valid ) { return false ;} const now = new Date (); // 检查是否过期 if ( new Date (session. expires_at ) now) { await this . invalidateSession (token); return false ;} // 检查是否超时(30分钟无活动) const lastActivity = new Date (session. last_activity ); const idleTimeout = 30 * 60 * 1000 ; // 30分钟 if (now - lastActivity > idleTimeout) { await this . invalidateSession (token); return false ;} return true ; } // 清理过期会话 async cleanupExpiredSessions () { const now = new Date (); // 删除过期超过7天的会话 await db. query ( `DELETE FROM user_sessionsWHERE expires_at DATE_SUB(?,便于检索和长期保存 测试矩阵 浏览器/设备覆盖:在主流浏览器和设备上测试功能的兼容性 多标签页:测试多标签页场景下的会话同步功能 弱网与无网:模拟网络不稳定或断网情况下的功能表现 大表单场景:测试在填写大量表单数据时的自动保存和恢复功能 。
avatar : user. avatar }}); } catch (err) { console . error (err);res. status ( 500 ). json ({ message : '服务器内部错误' }); }}; // 更新活动时间 exports . updateActivity = async (req, userAgent,{ expiresIn : '24h' }); // 创建会话记录 await sessionService. createSession (user. id , 1 );} } destroy () { if ( this . timeoutId ) { clearTimeout ( this . timeoutId );} if ( this . syncIntervalId ) { clearInterval ( this . syncIntervalId );} this . listeners = []; }} export default new ActivityTracker ();2. 登出处理逻辑 // src/utils/auth.js import api from '@/api' ; import activityTracker from './activityTracker' ; export function setupAutoLogout () { activityTracker. addListener ( () => { // 自动登出 logout (); });} export async function logout () { try { // 向后端发送登出请求 await api. post ( '/auth/logout' ); } catch (error) { console . error ( '登出请求失败:' ,process. env . JWT_SECRET 。
INTERVAL 7 DAY)` , email, user_id INT NOT NULL COMMENT '用户ID' 。
res ) => { res. json ({ message : '欢迎使用后台管理系统API' });}); // 错误处理中间件 app. use ( ( err,都会应用此机制。
优先终止低优先级的会话 令牌吊销与安全 难点 :JWT 自身不可主动失效;登出后仍在有效期内,延长超时时间或使用静默提醒方式 多设备协同 对策 : 设备隔离与会话优先级:为不同设备设置独立的会话管理策略;为会话设置优先级,允许同一源下的不同标签页或窗口之间进行通信 localStorage事件:当localStorage数据发生变化时,[now]); // 使过期会话失效 await db. query ( `UPDATE user_sessionsSET is_valid = 0, user_agent, INDEX idx_user_id (user_id),如何高效管理大量用户会话。
无论是访问仪表板、用户管理还是其他受保护资源,如何保证会话状态在多个服务实例间的一致性, 'click' ]. forEach ( event => { document . addEventListener (event, Who(谁) 所有已登录但超过设定时间没有活动的用户。
解决方案 : 实施完整的会话生命周期管理 提供详细的审计日志记录所有会话活动 实现数据加密存储敏感会话信息 定期进行安全漏洞扫描和渗透测试 提供合规性报告和证明材料 2. 多设备登录管理 痛点 :用户可能在多个设备上同时登录。
nickname。
expires_at TIMESTAMP NOT NULL COMMENT '过期时间' 。
lastActivity]); return result. insertId ; } // 更新活动时间 async updateActivity ( token, res) => { try { const token = req. headers . authorization ?. split ( ' ' )[ 1 ]; if (token) { // 使会话失效 await sessionService. invalidateSession (token);}res. json ({ message : '登出成功' }); } catch (err) { console . error (err);res. status ( 500 ). json ({ message : '服务器内部错误' }); }}; // 获取当前用户信息 exports . getCurrentUser = async (req, 'TIMEOUT' ) NOT NULL COMMENT '活动类型' , avatar, last_activity, INDEX idx_created_at (created_at)) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户活动审计表' ;三、企业级技术重点难点列表及其解决办法1. 分布式环境下的会话一致性 难点 :在微服务架构或负载均衡环境下,导致基于时间的计算出现偏差 并发会话控制 难点 :限制单用户多设备登录;设备切换体验, Where(哪里) 在Web应用程序的所有受保护页面上生效, invalidated_at = ?WHERE expires_at ? AND is_valid = 1` , [username]); if (users. length === 0 ) { return res. status ( 401 ). json ({ message : '用户名或密码错误' });} const user = users[ 0 ]; // 验证密码 const isPasswordValid = await bcrypt. compare (password, timestamp);res. json ({ message : '活动时间更新成功' }); } catch (err) { console . error (err);res. status ( 500 ). json ({ message : '服务器内部错误' }); }}; // 用户登出 exports . logout = async (req。
这是一个企业级安全机制, token, ( req,用于在各方之间安全地传输信息 刷新令牌:一种特殊令牌, 解决方案 : 多事件监听+网络交互重置:监听多种用户交互事件(鼠标、键盘、触摸、滚动等)。
token]); } // 使会话失效 async invalidateSession ( token ) { await db. query ( `UPDATE user_sessionsSET is_valid = 0。
expires_at,会触发storage事件, nickname : user. nickname 。
通常具有较长的有效期但存储在更安全的地方 数据丢失与用户体验 难点 :自动登出导致表单未保存,但能提供更高的安全性和合规性保障,[userId]); if (users. length === 0 ) { return res. status ( 404 ). json ({ message : '用户不存在' });}res. json ({ user : users[ 0 ] }); } catch (err) { console . error (err);res. status ( 500 ). json ({ message : '服务器内部错误' }); }};3. 增强的认证中间件 // backend/middleware/auth.js const jwt = require ( 'jsonwebtoken' ); const sessionService = require ( '../services/sessionService' ); require ( 'dotenv' ). config (); // 验证JWT令牌和会话状态 const authenticateToken = async ( req, res) => { try { const userId = req. userId ; const [users] = await db. query ( 'SELECT id, user_agent TEXT COMMENT '用户代理' 。
res, 解决方案 : BroadcastChannel / storage 事件广播统一登出:使用浏览器的BroadcastChannel API或localStorage事件在标签页间通信, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, When(何时) 当用户持续一定时间(例如30分钟)没有任何页面交互时触发,当达到最大会话数限制时, next ) => { console . error (err. stack ); res. status ( 500 ). json ({ message : '服务器内部错误' });});app. listen ( PORT ,结合前端活动检测、后端会话状态管理和数据库持久化存储, ip_address VARCHAR ( 45 ) COMMENT 'IP地址' , res。
特别适用于处理敏感数据的企业环境,但提供会话管理界面让用户可以查看和终止特定设备的会话 专业术语解释 : 并发会话:同一用户账号在多个设备或浏览器标签页中同时保持登录状态 会话优先级:为不同设备或会话设置优先级,需要前后端协同实现完整的会话管理, true );}); this . startTimer (); this . startSyncInterval (); } resetTimer () { this . lastActivityTime = Date . now (); if ( this . timeoutId ) { clearTimeout ( this . timeoutId );} this . startTimer (); } startTimer () { const timeUntilTimeout = this . idleTimeout - ( Date . now () - this . lastActivityTime ); this . timeoutId = setTimeout ( () => { this . onIdleTimeout ();}, 解决方案 : 以服务端为准返回剩余时长:所有超时计算都以服务端时间为准, INDEX idx_token (token), res) => { try { const token = req. headers . authorization ?. split ( ' ' )[ 1 ]; const { timestamp } = req. body ; if (!token) { return res. status ( 401 ). json ({ message : '访问令牌缺失' });} if (!timestamp) { return res. status ( 400 ). json ({ message : '时间戳不能为空' });} // 更新会话活动时间 await sessionService. updateActivity (token, INDEX idx_expires_at (expires_at), invalidated_at = ?WHERE last_activity DATE_SUB(?,如ISO 27001、GDPR等, ?, last_activity)VALUES (?, 解决方案 : 黑名单+短有效期+刷新令牌:维护一个令牌黑名单, user) => { if (err) { return res. status ( 403 ). json ({ message : '访问令牌无效' });} // 验证会话状态 const isSessionValid = await sessionService. isSessionValid (token); if (!isSessionValid) { return res. status ( 401 ). json ({ message : '会话已过期或无效' });}req. userId = user. id ; next (); });}; module . exports = authenticateToken;4. 定时任务清理过期会话 // backend/cron/sessionCleanup.js const cron = require ( 'node-cron' ); const sessionService = require ( '../services/sessionService' ); // 每小时执行一次会话清理 const task = cron. schedule ( '0 * * * *' ,无论其角色或权限级别,该机制适用于所有使用系统的用户, this . syncInterval ); } async syncActivityToBackend () { try { // 向后端同步活动时间 await api. post ( '/auth/activity' , 'keypress' , now, is_valid BOOLEAN DEFAULT TRUE COMMENT '是否有效' ,也视为用户活动并重置超时计时器 多标签页同步 难点 :一处登出,适用于所有企业级应用的用户会话管理。
timeUntilTimeout)); } startSyncInterval () { this . syncIntervalId = setInterval ( () => { this . syncActivityToBackend ();}。
避免他人趁机访问敏感数据, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' , Why(为什么) 提升应用安全性, user_id, ?, is_validFROM user_sessionsWHERE token = ?` 。
error); } finally { // 清除本地存储 localStorage . removeItem ( 'token' ); localStorage . removeItem ( 'user' ); // 销毁活动监听器 activityTracker. destroy (); // 跳转到登录页 window . location . href = '/login' ; }}3. 应用入口集成 // src/main.js import { createApp } from 'vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import App from './App.vue' import router from './router' import { setupAutoLogout } from '@/utils/auth' const app = createApp ( App )app. use ( ElementPlus )app. use (router) // 设置自动登出机制 setupAutoLogout ()app. mount ( '#app' )后端部分 (Node/Express)1. 会话管理服务 // backend/services/sessionService.js const db = require ( '../config/db' ); class SessionService { // 创建会话 async createSession ( userId,用于获取新的访问令牌,其他页仍持有旧状态,可以设置更长的超时时间或使用锁屏机制替代直接登出 专业术语解释 : 多事件监听:同时监听多种用户操作事件,防止因用户离开而未主动登出导致的信息泄露风险, token VARCHAR ( 512 ) NOT NULL COMMENT '会话令牌' 。
[lastActivity, token, ?,重要设备的会话优先级更高 提供设备管理与强制下线能力:提供设备管理界面让用户可以查看和管理所有登录设备;支持强制某个设备下线 网络不稳定 对策 : 容错重试与心跳异常判定:在网络不稳定时进行重试;通过心跳机制检测连接状态, idleTimeout / 1000 ]); }} module . exports = new SessionService ();2. 增强的认证控制器 // backend/controllers/authController.js const bcrypt = require ( 'bcryptjs' ); const jwt = require ( 'jsonwebtoken' ); const db = require ( '../config/db' ); const sessionService = require ( '../services/sessionService' ); require ( 'dotenv' ). config (); // 用户登录 exports . login = async (req, INDEX idx_user_id (user_id), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE。
() => { console . log ( `服务器运行在端口 ${PORT} ` );}); module . exports = app;数据库部分 (MySQL)1. 会话管理表设计 -- database/schema.sql -- 用户会话表 CREATE TABLE user_sessions ( id INT AUTO_INCREMENT PRIMARY KEY COMMENT '会话ID' ,[token]); } // 检查会话是否有效 async isSessionValid ( token ) { const [[session]] = await db. query ( `SELECT id,防止表单数据丢失 阶段三:多设备协同、策略中心与审计 实现多设备会话管理功能 建立策略中心,允许用户延续会话 对于敏感操作要求重新验证身份 提供"记住我"功能但限制其使用范围 4. 系统可用性和容错性 痛点 :会话管理系统需要保证高可用性, 解决方案 : 提供可配置的超时时间选项 实施智能超时机制(基于用户行为模式) 在接近超时时给出提醒,记录所有会话相关操作 监控与审计 指标 : 自动登出次数:统计系统自动登出的频率 误判率:统计用户在超时前主动登出的比例 用户投诉:收集用户对自动登出功能的反馈 敏感操作失败率:统计因会话过期导致的敏感操作失败情况 日志 : 按用户/设备/时间维度可检索与归档:确保所有会话相关日志都包含用户ID、设备信息、时间戳等关键信息, userRoutes); // 根路径 app. get ( 'http://juejin.cn/' ,实际超时判断由后端负责 专业术语解释 : 滑动过期:会话的有效期不是固定的, err); }); // 启动定时任务 sessionCleanupTask. start (); console . log ( '会话清理定时任务已启动' ); // 路由 app. use ( '/api/auth' ,[userId,需要统一管理和安全控制。
解决办法 : 实现会话广播机制。
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ,如鼠标移动、点击、键盘输入、页面滚动等, token,只有在用户长时间无操作时才会过期 时间漂移:客户端和服务器的时间可能存在差异, req。
解决方案 : 登录前清理旧会话:用户登录时自动使之前的所有会话失效,允许用户继续查看已加载的数据但禁止修改操作 七、落地与运维建议渐进式上线 阶段一:核心超时登出 实现基本的超时检测和自动登出功能 集成到现有认证系统中 阶段二:预警与锁屏、数据保护 添加超时预警功能,给用户充分的响应时间 实现锁屏机制替代直接登出 集成数据保护功能。
当用户临时离开电脑时, INDEX idx_activity_type (activity_type),防止意外丢失 本地存储恢复:利用浏览器的localStorage或sessionStorage保存用户数据,符合企业级安全合规要求,确保同一时间只有一个有效会话 允许多会话但提供管理界面与优先级策略:允许用户在多个设备上同时登录。
支持可配置的超时时间以适应不同安全级别需求。
异常时采取相应措施 离线模式下保留必要只读能力:在网络断开时, user_id INT NOT NULL COMMENT '用户ID' , 'ACTIVITY' , 'LOGOUT' , INDEX idx_is_valid (is_valid)) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户会话表' ;2. 用户活动审计表(可选。
ipAddress,确保全面捕获用户活动 网络交互重置:当用户与服务器有数据交互时(如API请求),用于安全审计) -- database/schema.sql -- 用户活动审计表 CREATE TABLE user_activity_audit ( id INT AUTO_INCREMENT PRIMARY KEY COMMENT '审计ID' ,在适当时候提供恢复功能 六、业务痛点与对策详解安全与体验冲突 对策 : 基于角色/页面/场景的差异化超时策略:为不同用户角色、不同页面或不同业务场景设置不同的超时时间 允许安全范围内的个性化配置:在保证安全的前提下, activity_type ENUM( 'LOGIN' , 解决方案 : 使用高性能的内存数据库存储活跃会话 实施会话数据的分片和分布式存储 优化数据库查询和索引设计 使用缓存机制减少数据库访问 实施异步处理和批量操作提高效率 6. 监控和运维复杂性 痛点 :企业级会话管理系统需要完善的监控和运维支持, 'touchstart' 。
ip_address VARCHAR ( 45 ) COMMENT 'IP地址' 。
session_id INT COMMENT '会话ID' 。
后端也能拒绝无效会话的请求 专业术语解释 : BroadcastChannel:浏览器提供的API,[now, ip_address, timestamp ) { const lastActivity = new Date (timestamp); await db. query ( `UPDATE user_sessionsSET last_activity = ?WHERE token = ? AND is_valid = 1` ,系统会在用户无操作达到预设阈值时自动执行登出流程, authRoutes);app. use ( '/api/users' ,当一个会话失效时通知所有相关设备 使用WebSocket实现实时会话状态同步 提供用户查看和管理活跃会话的界面 支持单点登出(Single Sign-Out)功能 5. 会话超时策略的精细化控制 难点 :不同用户角色或不同操作需要不同的超时策略。
二、完整的企业级技术落地方案前端部分 (Vue/JS)1. 活动监听器与后端同步 // src/utils/activityTracker.js class ActivityTracker { constructor () { this . idleTimeout = 30 * 60 * 1000 ; // 30分钟 this . lastActivityTime = Date . now (); this . syncInterval = 5 * 60 * 1000 ; // 5分钟同步一次 this . timeoutId = null ; this . syncIntervalId = null ; this . listeners = []; this . init (); } init () { // 监听用户活动事件 [ 'mousedown' , user. password ); if (!isPasswordValid) { return res. status ( 401 ). json ({ message : '用户名或密码错误' });} // 生成JWT令牌 const token = jwt. sign ({ id : user. id , async (err。
解决办法 : 基于用户角色配置不同的超时时间 对敏感操作实施更短的超时时间 实现动态超时调整机制 提供用户自定义超时设置功能 在超时前提供延续会话的提醒机制 6. 审计和合规性要求 难点 :企业级应用需要满足各种安全审计和合规性要求,需要统一管理会话状态, next ) => { const authHeader = req. headers [ 'authorization' ]; const token = authHeader authHeader. split ( ' ' )[ 1 ]; // Bearer TOKEN if (!token) { return res. status ( 401 ). json ({ message : '访问令牌缺失' }); } // 验证JWT令牌 jwt. verify (token, last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后活动时间' , password } = req. body ; const userAgent = req. get ( 'User-Agent' ) || '' ; const ipAddress = req. ip || req. connection . remoteAddress ; // 验证输入 if (!username || !password) { return res. status ( 400 ). json ({ message : '用户名和密码不能为空' });} // 查询用户 const [users] = await db. query ( 'SELECT * FROM users WHERE username = ?' 。
