Jde o pomoc ostatním, kteří se mohou ocitnout v podobné situaci jako já. Doufám, že by to šlo standardizovat. Nemyslím si, že bychom měli znovu vynalézat kolo pokaždé, když někdo potřebuje vytvořit aplikaci pro více nájemců.
Tento příklad popisuje strukturu s více nájemci, přičemž každý klient má svou vlastní databázi. Jak jsem řekl, mohl by existovat lepší způsob, jak to udělat, ale protože jsem sám nezískal pomoc, toto bylo moje řešení.
Zde jsou tedy cíle, na které se toto řešení zaměřuje:
- každý klient je identifikován subdoménou, např. client1.application.com,
- aplikace zkontroluje, zda je subdoména platná,
- aplikace vyhledává a získává informace o připojení (adresu URL databáze, přihlašovací údaje atd.) z hlavní databáze,
- aplikace se připojuje k databázi klientů (z velké části předává klientovi),
- aplikace přijímá opatření k zajištění integrity a správy zdrojů (např. použití stejného databázového připojení pro členy stejného klienta namísto vytváření nového připojení).
Zde je kód
ve vašem app.js
soubor
app.use(clientListener()); // checks and identify valid clients
app.use(setclientdb());// sets db for valid clients
Vytvořil jsem dva middleware:
clientListener
- k identifikaci připojujícího se klienta,setclientdb
- po identifikaci klienta získá podrobnosti o klientovi z hlavní databáze a poté naváže spojení s klientskou databází.
prostředí klientListener
Zkontroluji, kdo je klient, kontrolou subdomény z objektu požadavku. Provádím spoustu kontrol, abych se ujistil, že je klient platný (vím, že kód je chaotický a lze jej vyčistit). Po ověření platnosti klienta ukládám informace o klientech do relace. Také zkontroluji, že pokud jsou informace o klientech již uloženy v relaci, není třeba znovu dotazovat databázi. Musíme se jen ujistit, že subdoména požadavku odpovídá subdoméně, která je již uložena v relaci.
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 middleware:
Znovu vše zkontroluji, abych se ujistil, že je klient platný. Poté se otevře připojení k databázi klienta s informacemi získanými z relace.
Také se ujišťuji, že ukládám všechna aktivní připojení do globálního objektu, abych zabránil novým připojením k databázi při každém požadavku (nechceme zahltit všechny klientské mongodb servery připojeními).
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;
V neposlední řadě
Protože používám kombinaci mongoose a nativního mongo, musíme naše modely zkompilovat za běhu. Viz níže
Přidejte toto do 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();
});
Vysvětlení:
Jak jsem již řekl dříve, vytvořil jsem globální objekt pro uložení aktivního objektu připojení k databázi:global.App.activdb
Poté použiji tento objekt připojení k vytvoření (kompilaci) modelu mongoose, poté, co jej uložím do vlastnosti db objektu req:req.db
. Dělám to proto, abych měl přístup ke svým modelům v mém ovladači, například takto.
Příklad mého ovladače Users:
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!');
});
};
Vrátím se a nakonec to uklidím. Pokud mi chce někdo pomoci, budiž.