基于MEAN栈与Mongoose实现多站点数据隔离方案问询
我来给你梳理下实现多站点数据隔离的核心思路和具体步骤,基于你用的MEAN栈+Mongoose,这个方案既简洁又能保证数据安全隔离:
核心思路:给所有业务集合添加站点关联字段
本质是让每个数据条目(用户、项目、设置等)都绑定到具体的站点,通过**siteId**字段关联站点集合的主键。这样不管是查询、创建还是修改数据,都能通过siteId过滤,确保每个站点只能访问自己的数据。
1. 设计Mongoose数据模型
首先要创建一个Site集合存储站点基础信息,然后其他业务集合(User、Project、Setting等)都添加siteId字段关联到Site的ObjectId。
示例:Site模型
const mongoose = require('mongoose'); const siteSchema = new mongoose.Schema({ sitePath: { type: String, required: true, unique: true }, // 对应URL里的路径,比如"new-website" name: { type: String, required: true }, // 站点名称 adminId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, // 关联创建站点的管理员 createdAt: { type: Date, default: Date.now } }); module.exports = mongoose.model('Site', siteSchema);
示例:User模型(其他业务模型同理)
const userSchema = new mongoose.Schema({ username: { type: String, required: true }, email: { type: String, required: true, unique: true }, password: { type: String, required: true }, siteId: { type: mongoose.Schema.Types.ObjectId, ref: 'Site', required: true, index: true // 加索引提升查询性能 }, role: { type: String, enum: ['admin', 'member'], default: 'member' }, createdAt: { type: Date, default: Date.now } }); module.exports = mongoose.model('User', userSchema);
2. 实现站点识别中间件
因为每个站点对应唯一的URL路径(比如www.mywebsite/new-website),我们需要一个Express中间件从URL中提取sitePath,查询对应的Site文档,然后把站点信息挂载到req对象上,后续所有业务接口都用这个站点信息过滤数据。
示例:站点识别中间件
// middleware/siteMiddleware.js const Site = require('../models/Site'); async function identifySite(req, res, next) { // 从URL参数提取sitePath(假设路由格式为 /:sitePath/*) const sitePath = req.params.sitePath; try { const site = await Site.findOne({ sitePath }); if (!site) { return res.status(404).json({ message: '站点不存在' }); } // 将站点对象挂载到req,后续接口直接使用 req.site = site; next(); } catch (err) { res.status(500).json({ message: '站点识别失败', error: err.message }); } } module.exports = identifySite;
路由中使用中间件
const express = require('express'); const router = express.Router(); const identifySite = require('./middleware/siteMiddleware'); const userController = require('./controllers/userController'); const projectController = require('./controllers/projectController'); // 所有站点相关路由都先经过站点识别 router.use('/:sitePath', identifySite); // 创建用户(仅站点管理员可操作) router.post('/:sitePath/users', userController.createUser); // 获取当前站点所有用户 router.get('/:sitePath/users', userController.getSiteUsers); // 创建项目 router.post('/:sitePath/projects', projectController.createProject); // 获取我的账户信息 router.get('/:sitePath/my-account', userController.getMyAccount);
3. 业务逻辑层强制数据过滤
所有CRUD操作必须带上当前站点的siteId,确保只操作该站点的数据,绝对不能让接口接收外部传入的siteId(避免恶意跨站点访问)。
示例:用户控制器
// controllers/userController.js const User = require('../models/User'); // 创建用户(自动绑定当前站点siteId) exports.createUser = async (req, res) => { try { // 从req.site获取siteId,不允许前端传入 const userData = { ...req.body, siteId: req.site._id }; const newUser = await User.create(userData); res.status(201).json(newUser); } catch (err) { res.status(400).json({ message: '创建用户失败', error: err.message }); } }; // 获取当前站点所有用户 exports.getSiteUsers = async (req, res) => { try { const users = await User.find({ siteId: req.site._id }); res.status(200).json(users); } catch (err) { res.status(500).json({ message: '获取用户失败', error: err.message }); } }; // 获取当前登录用户的账户信息 exports.getMyAccount = async (req, res) => { // 假设已通过认证中间件拿到当前用户的_id const userId = req.user._id; try { // 同时过滤siteId和userId,确保用户只能访问自己站点的账户 const user = await User.findOne({ _id: userId, siteId: req.site._id }); if (!user) { return res.status(404).json({ message: '账户不存在' }); } res.status(200).json(user); } catch (err) { res.status(500).json({ message: '获取账户信息失败', error: err.message }); } };
4. 权限控制与站点初始化
- 站点初始化:管理员创建站点时,自动为该站点创建一个默认管理员用户(或关联创建站点的管理员账号),确保新站点有可操作的管理员。
- 权限验证:添加权限中间件,确保只有当前站点的管理员才能执行创建用户、修改设置等敏感操作。
示例:权限验证中间件
// middleware/authMiddleware.js const User = require('../models/User'); async function checkSiteAdmin(req, res, next) { const userId = req.user._id; const siteId = req.site._id; try { const user = await User.findOne({ _id: userId, siteId }); if (!user || user.role !== 'admin') { return res.status(403).json({ message: '无权限执行此操作' }); } next(); } catch (err) { res.status(500).json({ message: '权限验证失败', error: err.message }); } } module.exports = checkSiteAdmin;
路由中使用权限中间件
// 只有站点管理员才能创建用户 router.post('/:sitePath/users', checkSiteAdmin, userController.createUser);
5. 额外优化建议
- 给所有业务集合的
siteId字段添加索引,提升查询性能。 - 站点删除时,通过Mongoose的
pre中间件或手动批量删除该站点下的所有用户、项目等数据,避免垃圾数据。 - 站点设置可以单独创建
Setting集合,每个站点对应一条记录,同样用siteId关联。
内容的提问来源于stack exchange,提问作者PK2995
相关产品推荐
相关产品推荐

