dynamic数据库连接到来自nodejs的mongodb或mongoose

我正在尝试创build一个多租户应用(saas),每个客户都有自己的数据库。

我的情况是:

我创build了一个中间件,可以确定哪个客户端基于子域,然后从通用数据库中检索客户端的数据库连接信息。 我不知道如何build立这个客户端的连接对象,以便能够在我的控制器中使用。 我应该在中间件还是控制器中执行此操作? 如果它在模型中,我如何传递连接string和参数(我可以使用会话,但是我不知道如何从模型中访问会话)。

我如何做到以下几点?

  1. 组织:我在哪里dynamic创build客户端数据库连接?
  2. 将连接参数注入/传递给控制器​​或模型(在进行连接定义的情况下)
  3. dynamic连接完成后,我如何在全球范围内访问该客户端?

这是我的中间件的一个例子,我想创build一个mongoose的连接,我想dynamic(通过客户端的连接信息):

function clientlistener() { return function (req, res, next) { console.dir('look at my sub domain ' + req.subdomains[0]); // console.log(req.session.Client.name); if (req.session.Client && req.session.Client.name === req.subdomains[0]) { var options = session.Client.options; var url = session.Client.url var conn = mongoose.createConnection(url, options); next(); } } } 

如何从控制器内部访问这个连接对象? 还是从模型?

谢谢。

这是为了帮助那些可能发现自己和我一样处境相似的人。 我希望它可以标准化。 我不认为每次有人需要制作多租户应用程序时,我们都不得不重新发明轮子。

这个例子描述了一个多租户结构,每个客户拥有自己的数据库。 就像我说的那样,可能有更好的办法,但是因为我自己没有得到帮助,所以这是我的解决scheme。

所以这个解决scheme的目标是:

  • 每个客户端由子域标识,例如client1.application.com,
  • 应用程序检查子域是否有效,
  • 应用程序查找并从主数据库获取连接信息(数据库url,凭证等)
  • 应用程序连接到客户端数据库(几乎交给客户端),
  • 应用程序采取措施确保完整性和资源pipe理(例如,为同一客户端的成员使用相同的数据库连接,而不是build立新的连接)。

这是代码

在你的app.js文件中

 app.use(clientListener()); // checks and identify valid clients app.use(setclientdb());// sets db for valid clients 

我创build了两个中间件:

  • clientListener – 识别客户端连接,
  • setclientdb – 在识别客户端之后,从主数据库获取客户端详细信息,然后build立与客户端数据库的连接。

clientListener中间件

我通过检查请求对象中的子域来检查客户端是谁。 我做了一堆检查,以确保客户端是有效的(我知道代码是凌乱的,可以做得更干净)。 确保客户端有效后,我将客户端信息存储在会话中。 我还检查,如果客户端信息已存储在会话中,则不需要再次查询数据库。 我们只需要确保请求子域名与在会话中已存储的子域名相匹配。

 var Clients = require('../models/clients'); var basedomain = dbConfig.baseDomain; var allowedSubs = {'admin':true, 'www':true }; allowedSubs[basedomain] = true; function clientlistener() { return function(req, res, next) { //console.dir('look at my sub domain ' + req.subdomains[0]); // console.log(req.session.Client.name); if( req.subdomains[0] in allowedSubs || typeof req.subdomains[0] === 'undefined' || req.session.Client && req.session.Client.name === req.subdomains[0] ){ //console.dir('look at the sub domain ' + req.subdomains[0]); //console.dir('testing Session ' + req.session.Client); console.log('did not search database for '+ req.subdomains[0]); //console.log(JSON.stringify(req.session.Client, null, 4)); next(); } else{ Clients.findOne({subdomain: req.subdomains[0]}, function (err, client) { if(!err){ if(!client){ //res.send(client); res.send(403, 'Sorry! you cant see that.'); } else{ console.log('searched database for '+ req.subdomains[0]); //console.log(JSON.stringify(client, null, 4)); //console.log(client); // req.session.tester = "moyo cow"; req.session.Client = client; return next(); } } else{ console.log(err); return next(err) } }); } } } module.exports = clientlistener; 

setclientdb中间件:

我再次检查一切,确保客户端是有效的。 然后,打开从会话中检索到的信息与客户端数据库的连接。

我也确保将所有活动连接存储到一个全局对象中,以防止在每个请求(我们不希望每个客户端连接超载的mongodb服务器)上连接到数据库。

 var mongoose = require('mongoose'); //var dynamicConnection = require('../models/dynamicMongoose'); function setclientdb() { return function(req, res, next){ //check if client has an existing db connection /*** Check if client db is connected and pooled *****/ if(/*typeof global.App.clientdbconn === 'undefined' && */ typeof(req.session.Client) !== 'undefined' && global.App.clients[req.session.Client.name] !== req.subdomains[0]) { //check if client session, matches current client if it matches, establish new connection for client if(req.session.Client && req.session.Client.name === req.subdomains[0] ) { console.log('setting db for client ' + req.subdomains[0]+ ' and '+ req.session.Client.dbUrl); client = mongoose.createConnection(req.session.Client.dbUrl /*, dbconfigoptions*/); client.on('connected', function () { console.log('Mongoose default connection open to ' + req.session.Client.name); }); // When the connection is disconnected client.on('disconnected', function () { console.log('Mongoose '+ req.session.Client.name +' connection disconnected'); }); // If the Node process ends, close the Mongoose connection process.on('SIGINT', function() { client.close(function () { console.log(req.session.Client.name +' connection disconnected through app termination'); process.exit(0); }); }); //If pool has not been created, create it and Add new connection to the pool and set it as active connection if(typeof(global.App.clients) === 'undefined' || typeof(global.App.clients[req.session.Client.name]) === 'undefined' && typeof(global.App.clientdbconn[req.session.Client.name]) === 'undefined') { clientname = req.session.Client.name; global.App.clients[clientname] = req.session.Client.name;// Store name of client in the global clients array activedb = global.App.clientdbconn[clientname] = client; //Store connection in the global connection array console.log('I am now in the list of active clients ' + global.App.clients[clientname]); } global.App.activdb = activedb; console.log('client connection established, and saved ' + req.session.Client.name); next(); } //if current client, does not match session client, then do not establish connection else { delete req.session.Client; client = false; next(); } } else { if(typeof(req.session.Client) === 'undefined') { next(); } //if client already has a connection make it active else{ global.App.activdb = global.App.clientdbconn[req.session.Client.name]; console.log('did not make new connection for ' + req.session.Client.name); return next(); } } } } module.exports = setclientdb; 

最后但是同样重要的

由于我使用了mongoose和本地mongo的组合,我们必须在运行时编译我们的模型。 请看下面

添加到您的app.js

 // require your models directory var models = require('./models'); // Create models using mongoose connection for use in controllers app.use(function db(req, res, next) { req.db = { User: global.App.activdb.model('User', models.agency_user, 'users') //Post: global.App.activdb.model('Post', models.Post, 'posts') }; return next(); }); 

说明:

就像我之前所说的,我创build了一个全局对象来存储活动数据库连接对象: global.App.activdb

然后我使用这个连接对象来创build(编译)mongoose模型,在我将它存储在req对象的db属性中之后: req.db 。 我这样做,以便我可以像我这样访问我的控制器模型。

我的用户控制器的示例:

 exports.list = function (req, res) { req.db.User.find(function (err, users) { res.send("respond with a resource" + users + 'and connections ' + JSON.stringify(global.App.clients, null, 4)); console.log('Worker ' + cluster.worker.id + ' running!'); }); }; 

我会回来清理这个最终。 如果有人想帮助我,那很好。

大家好,这是一个更新的解决scheme。

所以这个解决scheme的目标是:

  • 每个客户端由子域标识,例如client1.application.com,
  • 应用程序检查子域是否有效,
  • 应用程序查找并从主数据库获取连接信息(数据库url,凭证等)
  • 应用程序连接到客户端数据库(几乎交给客户端),
  • 应用程序采取措施确保完整性和资源pipe理(例如,为同一客户端的成员使用相同的数据库连接,而不是build立新的连接)。

更新

  • 使用承诺,
  • 自动导入和编译模型
  • 新的中间件; modelsinit(用于自动导入和编译mongoose模型)
  • 清理中间件(setclientdb,clientlistener,modelsInit)

请参阅下面的一些解释

**

modelsInit中间件

** 特征

  • testing模型是否已经被编译。 如果是这样,跳过。
  • testing以查看请求是否不是租户请求; 即(请求应用程序主页,pipe理页面等)

     'use strict'; /** * Created by moyofalaye on 3/17/14. */ var path = require('path'); var config = require('../../config/config'); // Globbing model files config.getGlobbedFiles('./app/models/*.js').forEach(function (modelPath) { require(path.resolve(modelPath)); }); function modelsInit() { return function (req, res, next) { //console.log(req.subdomains[0]); switch (req.subdomains[0]) { case 'www': case undefined: return next(); break; case 'admin': return next(); break; // default: // return } var clientname = req.session.Client.name; // test if models are not already compiled if so, skip if (/*typeof req.db === 'undefined' && */ typeof global.App.clientModel[clientname] === 'undefined') { req.db = {}; //Get files from models directory config.getGlobbedFiles('./app/models/clientmodels/**/*.js').forEach(function (modelPath) { console.log('the filepath is ' + modelPath); //Deduce/ extrapulate model names from the file names //Im not very good with regxp but this is what i had to do, to get the names from the filename eg users.server.models.js (this is my naming convention, so as not to get confused with server side models and client side models var filename = modelPath.replace(/^.*[\\\/]/, ''); var fullname = filename.substr(0, filename.lastIndexOf('.')); var endname = fullname.indexOf('.'); var name = fullname.substr(0, endname); req.db[name] = require(path.resolve(modelPath))(global.App.activdb); console.log('the filename is ' + name); }); global.App.clientModel[clientname] = req.db; console.log(global.App.clients); return next(); } // since models exist, pass it to request.db for easy consumption in controllers req.db = global.App.clientModel[clientname]; return next(); }; } module.exports = modelsInit; 

待办事项:进一步的解释

ClientListener.js

 var config = require('../../config/config'); var Clients = require('../models/clients'); var basedomain = config.baseDomain; var allowedSubs = {'admin': true, 'www': true}; allowedSubs[basedomain] = true; //console.dir(allowedSubs); function clientlistener() { return function (req, res, next) { //check if client has already been recognized if (req.subdomains[0] in allowedSubs || typeof req.subdomains[0] == 'undefined' || req.session.Client && req.session.Client.name === req.subdomains[0]) { console.log('did not search database for ' + req.subdomains[0]); //console.log(JSON.stringify(req.session.Client, null, 4)); return next(); } //look for client in database else { Clients.findOne({subdomain: req.subdomains[0]}, function (err, client) { if (!err) { //if client not found if (!client) { //res.send(client); res.status(403).send('Sorry! you cant see that.'); console.log(client); } // client found, create session and add client else { console.log('searched database for ' + req.subdomains[0]); req.session.Client = client; return next(); } } else { console.log(err); return next(err) } }); } } } module.exports = clientlistener; 

setclientdb.js

 var client; var clientname; var activedb; var Promise = require("bluebird"); Promise.promisifyAll(require("mongoose")); //mongoose = require('mongoose'); function setclientdb() { return function (req, res, next) { //check if client is not valid if (typeof(req.session.Client) === 'undefined' || req.session.Client && req.session.Client.name !== req.subdomains[0]) { delete req.session.Client; client = false; return next(); } //if client already has an existing connection make it active else if (global.App.clients.indexOf(req.session.Client.name) > -1) { global.App.activdb = global.App.clientdbconn[req.session.Client.name]; //global.App.clientdbconnection is an array of or established connections console.log('did not make new connection for ' + req.session.Client.name); return next(); } //make new db connection else { console.log('setting db for client ' + req.subdomains[0] + ' and ' + req.session.Client.dbUrl); client = mongoose.createConnection(req.session.Client.dbUrl /*, dbconfigoptions*/); client.on('connected', function () { console.log('Mongoose default connection open to ' + req.session.Client.name); //If pool has not been created, create it and Add new connection to the pool and set it as active connection if (typeof(global.App.clients) === 'undefined' || typeof(global.App.clients[req.session.Client.name]) === 'undefined' && typeof(global.App.clientdbconn[req.session.Client.name]) === 'undefined') { clientname = req.session.Client.name; global.App.clients.push(req.session.Client.name);// Store name of client in the global clients array activedb = global.App.clientdbconn[clientname] = client; //Store connection in the global connection array and set it as the current active database console.log('I am now in the list of active clients ' + global.App.clients[clientname]); global.App.activdb = activedb; console.log('client connection established, and saved ' + req.session.Client.name); return next(); } }); // When the connection is disconnected client.on('disconnected', function () { console.log('Mongoose ' + req.session.Client.name + ' connection disconnected'); }); // If the Node process ends, close the Mongoose connection process.on('SIGINT', function () { client.close(function () { console.log(req.session.Client.name + ' connection disconnected through app termination'); process.exit(0); }); }); } } } module.exports = setclientdb; 

进一步的解释即将到来