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

Express+Mongoose多租户分库连接及访问令牌实现问题咨询

解决方案

一、Mongoose多连接实现方案

你之前多次调用mongoose.connect()报错的原因是该方法只会复用Mongoose的默认连接,多次调用会覆盖原有连接配置,Mongoose本身支持多独立连接,需要使用mongoose.createConnection()方法实现,具体实现逻辑如下:

  • 全局维护一个连接缓存对象,key为clientId,value存储对应数据库的连接实例、当前活跃用户数
  • 同clientId下的用户登录时,直接复用已有连接,仅新增活跃用户计数;不存在对应连接时才新建
  • 当clientId对应的活跃用户数归0时,主动断开该数据库连接

代码示例:

1. 公共库连接初始化(server.js)

// 公共库用默认连接即可
const mongoose = require('mongoose');
mongoose.connect('mongodb+srv://user:pass@host/DB_Common');
// 全局连接缓存
global.clientDbConnections = new Map();

2. 客户端连接管理工具(utils/dbManager.js)

const mongoose = require('mongoose');

// 新建/获取客户端连接
async function getClientDbConnection(clientId) {
  // 已存在连接直接返回
  if (global.clientDbConnections.has(clientId)) {
    const connInfo = global.clientDbConnections.get(clientId);
    connInfo.activeUsers += 1;
    return connInfo.connection;
  }
  // 新建连接
  const conn = await mongoose.createConnection(`mongodb+srv://user:pass@host/DB_Client${clientId}`, {
    // 配置自动重连
    autoReconnect: true,
    maxPoolSize: 10
  });
  // 存入缓存
  global.clientDbConnections.set(clientId, {
    connection: conn,
    activeUsers: 1
  });
  return conn;
}

// 断开客户端连接(用户登出时调用)
async function releaseClientDbConnection(clientId) {
  if (!global.clientDbConnections.has(clientId)) return;
  const connInfo = global.clientDbConnections.get(clientId);
  connInfo.activeUsers -= 1;
  // 活跃用户为0时断开连接
  if (connInfo.activeUsers <= 0) {
    await connInfo.connection.close();
    global.clientDbConnections.delete(clientId);
  }
}

module.exports = { getClientDbConnection, releaseClientDbConnection };

3. 专属库模型定义注意

不要用默认的mongoose.model注册专属库的模型,要绑定对应连接实例注册:

// 示例:定义订单模型,绑定到指定的client连接
function getOrderModel(conn) {
  const orderSchema = new mongoose.Schema({
    orderNo: String,
    amount: Number
  });
  // 连接实例不存在模型时才注册,避免重复注册报错
  return conn.models.Order || conn.model('Order', orderSchema);
}

二、访问令牌实现逻辑

采用JWT作为访问令牌,完整逻辑如下:

  1. 登录签发阶段
    用户提交账号密码请求登录,先查询公共库DB_Common校验用户凭证有效性,同时获取用户所属的clientId。校验通过后生成JWT,payload中存入userId、clientId、过期时间戳,签名后返回给前端。
  2. 请求校验中间件
const jwt = require('jsonwebtoken');
const { getClientDbConnection } = require('./utils/dbManager');

async function authMiddleware(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).send('未登录');
  try {
    // 校验token有效性,同时校验是否在黑名单(登出的token加入黑名单)
    const payload = jwt.verify(token, process.env.JWT_SECRET);
    const isBlack = await mongoose.model('TokenBlack').findOne({ token });
    if (isBlack) return res.status(401).send('token已失效');
    // 挂载用户信息到req对象
    req.userId = payload.userId;
    req.clientId = payload.clientId;
    // 预获取对应client的数据库连接,存入req供业务逻辑调用
    req.clientDb = await getClientDbConnection(payload.clientId);
    next();
  } catch (err) {
    return res.status(401).send('token无效');
  }
}
  1. 业务逻辑调用示例
// 查询当前用户所属client的订单列表
app.get('/api/orders', authMiddleware, async (req, res) => {
  const OrderModel = getOrderModel(req.clientDb);
  const orders = await OrderModel.find({ userId: req.userId });
  res.send(orders);
});
  1. 登出逻辑
    用户发起登出请求时,拿到请求携带的token,存入公共库的TokenBlack黑名单集合(设置过期时间和token本身的过期时间一致,自动清理),同时调用releaseClientDbConnection(req.clientId)减少对应连接的活跃用户计数。

注意事项

  • 可以给JWT设置较短的过期时间(比如2小时),搭配刷新令牌实现无感续期,降低token泄露风险
  • 连接缓存可以根据业务需要设置超时自动清理,避免长时间无活跃的连接占用资源
  • 多连接场景下注意连接池大小配置,避免MongoDB连接数超限

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 20:33:03