如何在Node.js Sequelize中实现请求级单用户单数据库切换
实现Node.js + Sequelize 按请求切换用户独立数据库
核心思路
先通过固定的主数据库存储用户与对应独立数据库的映射关系,每次请求时先完成用户认证,从主库获取该用户的数据库配置,再动态切换/复用Sequelize连接实例,最后将实例挂载到请求对象供业务逻辑使用。
具体实现步骤
1. 初始化主数据库连接
先创建主库的Sequelize实例,用来存储用户信息(用户ID、用户名、密码、对应数据库名等)。这个连接是全局固定的,不需要动态切换。
// config/main-db.js const { Sequelize } = require('sequelize'); // 主库配置 const mainSequelize = new Sequelize({ dialect: 'mysql', // 根据你的数据库类型调整 host: 'localhost', username: 'main_db_user', password: 'main_db_pass', database: 'main_database' }); // 定义用户模型 const User = mainSequelize.define('User', { userId: { type: Sequelize.STRING, primaryKey: true }, username: Sequelize.STRING, password: Sequelize.STRING, // 建议存哈希值 dbName: Sequelize.STRING, // 对应用户的独立数据库名,比如UserDb1 dbUser: Sequelize.STRING, // 独立库的用户名(如果和主库不同) dbPass: Sequelize.STRING // 独立库的密码(如果和主库不同) }); // 同步模型(首次运行时执行) mainSequelize.sync(); module.exports = { mainSequelize, User };
2. 实现用户认证与数据库切换中间件
这一步是核心:每次请求先验证用户身份(比如从token里解析userId),然后从主库查询用户的数据库配置,再创建/复用该用户的Sequelize实例,挂载到req对象上。
为了避免重复创建连接,我们用一个对象缓存已初始化的用户数据库实例:
// middleware/db-switch.js const { User } = require('../config/main-db'); const { Sequelize } = require('sequelize'); // 缓存用户数据库实例,key为userId const userDbCache = {}; async function switchUserDb(req, res, next) { try { // 1. 从请求中获取当前登录用户的userId(示例:从JWT token解析) const userId = req.user.userId; // 假设你已经有认证中间件处理了用户信息 // 2. 从主库查询用户对应的数据库配置 const user = await User.findByPk(userId); if (!user) { return res.status(404).json({ message: '用户不存在' }); } // 3. 检查缓存中是否已有该用户的数据库实例,没有则创建 if (!userDbCache[userId]) { userDbCache[userId] = new Sequelize({ dialect: 'mysql', // 和主库一致或根据实际调整 host: 'localhost', // 如果独立库在不同服务器,这里要改 username: user.dbUser || 'default_db_user', // 用用户配置的或默认值 password: user.dbPass || 'default_db_pass', database: user.dbName }); // 可选:测试连接是否正常 await userDbCache[userId].authenticate(); } // 更新实例的最后使用时间,用于后续缓存清理 userDbCache[userId].lastUsed = Date.now(); // 4. 将用户数据库实例挂载到req对象,供后续路由使用 req.userDb = userDbCache[userId]; next(); } catch (err) { console.error('切换用户数据库失败:', err); return res.status(500).json({ message: '数据库连接失败' }); } } module.exports = switchUserDb;
3. 在业务路由中使用用户数据库实例
后续的业务逻辑直接通过req.userDb来操作当前用户的独立数据库,比如定义模型或执行查询:
// routes/user-data.js const express = require('express'); const router = express.Router(); const switchUserDb = require('../middleware/db-switch'); const { DataTypes } = require('sequelize'); // 应用数据库切换中间件 router.use(switchUserDb); // 示例:获取用户独立库中的数据 router.get('/my-data', async (req, res) => { try { // 动态定义用户库中的模型(如果模型结构统一,也可以提前封装) const UserData = req.userDb.define('UserData', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, content: DataTypes.TEXT }); // 同步模型(如果需要) await UserData.sync(); // 查询数据 const data = await UserData.findAll(); res.json(data); } catch (err) { res.status(500).json({ message: '查询失败', error: err.message }); } }); module.exports = router;
4. 可选:缓存清理机制
如果用户量很大,缓存的连接实例会占用过多内存,可以定时清理闲置的连接:
// 在middleware/db-switch.js中添加定时清理逻辑 // 每小时清理一次超过2小时未使用的连接 setInterval(() => { const now = Date.now(); for (const userId in userDbCache) { if (now - userDbCache[userId].lastUsed > 2 * 60 * 60 * 1000) { // 关闭连接 userDbCache[userId].close(); // 从缓存中删除 delete userDbCache[userId]; } } }, 60 * 60 * 1000);
注意事项
- 连接池配置:可以给Sequelize实例配置连接池(
pool选项),优化连接复用效率。 - 模型复用:如果所有用户的独立数据库表结构一致,可以提前封装模型,每次直接传入用户的Sequelize实例初始化,避免重复定义。
- 错误处理:要处理数据库连接失败、用户不存在等异常情况,避免请求挂起。
- 安全性:不要在前端暴露用户的数据库信息,所有数据库配置都从主库后端查询获取。
内容的提问来源于stack exchange,提问作者Chetan Rathod
相关产品推荐
相关产品推荐

