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

基于请求来源用Express.js&Prisma动态切换数据库遇this.DB未定义问题

问题分析与解决方案

问题背景

我想根据请求来源动态切换数据库,于是写了全局中间件处理,但设置完数据库实例后,控制器里的this.DB始终是undefined。

相关代码

全局中间件:

// middlwares/global.middleware.js
import DBController from "../controllers/db.controller.js";
import { db1, db2 } from "../prisma/prismaClient.js";
export default (req, res, next) => {
  const dbcontroller = DBController();
  const domain = req.get("origin");
  switch (domain) {
    case "http://localhost:3000":
      dbcontroller.setDB(db1);
      break;
    case "http://localhost:3001":
      dbcontroller.setDB(db2);
      break;
  }
  next();
};

DB控制器基类:

// controller/db.controller.js
import autoBind from "auto-bind";
class DBController {
  constructor() {
    this.DB;
    autoBind(this);
  }
  setDB(prismaClient) {
    this.DB = prismaClient;
  }
}
export default DBController;

业务控制器基类:

// conrtoller/controller.js
import { generateResponse } from "./../util/public.util.js";
import DBController from "./db.controller.js";
import autoBind from "auto-bind";
import createError from "http-errors";
class Controller extends DBController {
  constructor() {
    super();
    this.generateResponse = generateResponse;
    this.createError = createError;
    autoBind(this);
  }
}
export default Controller;

文章控制器:

// controller/article.controller.js
import Controller from "./controller.js";
class ArticleController extends Controller {
  async get(req, res, next) {
    try {
      const articles = await this.DB.article.findMany(); // this.DB 是 undefined
      const response = this.generateResponse("success", articles);
      res.send(response);
    } catch (error) {
      next(error);
    }
  }
}
export default new ArticleController();

核心原因

你遇到的问题本质是实例不共享:

  • ArticleController是提前实例化(export default new ArticleController())的,整个应用生命周期里只有这一个实例。
  • 全局中间件每次处理请求时都会创建一个全新的DBController实例(哪怕你写的DBController()改成new DBController(),依然是新实例),你在这个新实例上设置DB,和控制器里的实例完全无关,所以控制器里的this.DB自然取不到值。

解决方案

方案一:将DB实例挂载到req对象(推荐)

每个请求的req对象是独立的,把DB实例挂载到req上能完美避免并发冲突,实现请求级别的动态切换:

  1. 修改全局中间件:
// middlwares/global.middleware.js
import { db1, db2 } from "../prisma/prismaClient.js";
export default (req, res, next) => {
  const domain = req.get("origin");
  // 根据来源设置对应的DB实例到req上
  switch (domain) {
    case "http://localhost:3000":
      req.db = db1;
      break;
    case "http://localhost:3001":
      req.db = db2;
      break;
    default:
      // 可设置默认DB或抛出错误
      req.db = db1;
  }
  next();
};
  1. 修改文章控制器,从req中获取DB实例:
// controller/article.controller.js
import Controller from "./controller.js";
class ArticleController extends Controller {
  async get(req, res, next) {
    try {
      const articles = await req.db.article.findMany();
      const response = this.generateResponse("success", articles);
      res.send(response);
    } catch (error) {
      next(error);
    }
  }
}
export default new ArticleController();
  1. 简化DB控制器(不需要setDB逻辑了):
// controller/db.controller.js
import autoBind from "auto-bind";
class DBController {
  constructor() {
    autoBind(this);
  }
}
export default DBController;

方案二:使用单例模式的DBController

如果坚持要用DBController的方式,可以改成单例模式,确保中间件和控制器共享同一个实例,但要注意并发请求可能覆盖DB实例的问题:

  1. 修改DB控制器为单例:
// controller/db.controller.js
import autoBind from "auto-bind";
class DBController {
  constructor() {
    this.DB;
    autoBind(this);
  }

  // 单例方法:全局只创建一个实例
  static getInstance() {
    if (!DBController.instance) {
      DBController.instance = new DBController();
    }
    return DBController.instance;
  }

  setDB(prismaClient) {
    this.DB = prismaClient;
  }

  getDB() {
    return this.DB;
  }
}
export default DBController;
  1. 修改中间件使用单例:
// middlwares/global.middleware.js
import DBController from "../controllers/db.controller.js";
import { db1, db2 } from "../prisma/prismaClient.js";
export default (req, res, next) => {
  const dbcontroller = DBController.getInstance(); // 获取全局唯一实例
  const domain = req.get("origin");
  switch (domain) {
    case "http://localhost:3000":
      dbcontroller.setDB(db1);
      break;
    case "http://localhost:3001":
      dbcontroller.setDB(db2);
      break;
  }
  next();
};
  1. 修改业务控制器基类,绑定单例实例:
// conrtoller/controller.js
import { generateResponse } from "./../util/public.util.js";
import DBController from "./db.controller.js";
import autoBind from "auto-bind";
import createError from "http-errors";
class Controller extends DBController {
  constructor() {
    super();
    this.generateResponse = generateResponse;
    this.createError = createError;
    this.dbInstance = DBController.getInstance(); // 绑定单例
    autoBind(this);
  }
}
export default Controller;
  1. 修改文章控制器调用方式:
// controller/article.controller.js
import Controller from "./controller.js";
class ArticleController extends Controller {
  async get(req, res, next) {
    try {
      const articles = await this.dbInstance.getDB().article.findMany();
      const response = this.generateResponse("success", articles);
      res.send(response);
    } catch (error) {
      next(error);
    }
  }
}
export default new ArticleController();

注意:单例模式存在并发风险,如果两个请求同时处理,后一个请求的setDB会覆盖前一个的设置,导致前一个请求用错数据库,因此更推荐方案一。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 01:47:08