前言
随着互联网的快速发展,博客成为了很多人记录生活、分享心得的一个平台,因此,博客管理系统也成为了一个必不可少的应用。对于前端开发者而言,使用 Hapi+MongoDB 实现博客管理系统是一个不错的选择,然而在实现过程中,我们可能会遇到一些问题,本文将借助实战案例,帮助大家解决 MongoDB 事务处理问题。
Hapi 和 MongoDB 简介
Hapi:Hapi 是一个 Node.js 的 Web 框架,具有强大的插件体系和可扩展性。
MongoDB:MongoDB 是一种基于分布式文件存储的数据库,由 C++ 语言编写,旨在为 Web 应用提供可扩展的高性能数据存储解决方案。
博客管理系统实现
环境准备
Node.js:v10.16.0
MongoDB:v4.0.10
技术栈
Hapi:v18.4.0
Mongoose:v5.4.3
代码结构
- models // 存放数据模型 - article.js - routes // 存放路由 - article.js - index.js // 服务入口文件 - config.js // 配置文件 - package.json
数据库设计
博客系统需要记录文章、分类、标签等信息,因此我们需要设计多个集合:
Article
:文章Category
:分类Tag
:标签
文档结构如下所示:
// javascriptcn.com 代码示例 // Article 文档结构 { "title": "", // 标题 "content": "", // 内容 "category": ObjectId, // 分类 ID "tags": [ObjectId], // 标签 ID 列表 "createTime": "", // 创建时间 "updateTime": "", // 更新时间 } // Category 文档结构 { "name": "", // 名称 "createTime": "", // 创建时间 "updateTime": "" // 更新时间 } // Tag 文档结构 { "name": "", // 名称 "createTime": "", // 创建时间 "updateTime": "" // 更新时间 }
代码实现
数据库连接
在 index.js
中连接数据库,代码如下所示:
// javascriptcn.com 代码示例 const mongoose = require('mongoose') const config = require('./config') mongoose.connect(config.mongoUri, { useNewUrlParser: true }) const db = mongoose.connection db.once('open', () => { console.log('Connected to MongoDB') }) db.on('error', (err) => { console.error('MongoDB connection error:', err) })
其中 config.mongoUri
是 MongoDB 的连接地址。
定义数据模型
在 models/article.js
中定义 Article
数据模型,代码如下所示:
// javascriptcn.com 代码示例 const mongoose = require('mongoose') const articleSchema = new mongoose.Schema({ title: { type: String, required: true }, content: { type: String, required: true }, category: { type: mongoose.Schema.Types.ObjectId, ref: 'Category', required: true }, tags: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Tag', required: true }], createTime: { type: Date, default: Date.now }, updateTime: { type: Date, default: Date.now } }, { versionKey: false, timestamps: { createdAt: 'createTime', updatedAt: 'updateTime' } }) module.exports = mongoose.model('Article', articleSchema)
在 models/category.js
和 models/tag.js
中分别定义 Category
和 Tag
数据模型,代码类似于 models/article.js
。
定义路由
在 routes/article.js
中定义 Article
相关的路由,代码如下所示:
// javascriptcn.com 代码示例 const Joi = require('joi') const Article = require('../models/article') module.exports = [ // 创建文章 { method: 'POST', path: '/articles', async handler (request, h) { const { payload } = request const article = await Article.create(payload) return h.response(article).code(201) }, options: { validate: { payload: Joi.object({ title: Joi.string().required(), content: Joi.string().required(), category: Joi.string().regex(/^[0-9a-fA-F]{24}$/).required(), tags: Joi.array().items(Joi.string().regex(/^[0-9a-fA-F]{24}$/)).required() }) } } }, // 获取文章列表 { method: 'GET', path: '/articles', async handler (request, h) { const { query } = request const conditions = {} if (query.category) { conditions.category = query.category } if (query.tags) { conditions.tags = { $in: query.tags } } const articles = await Article.find(conditions) return h.response(articles) }, options: { validate: { query: Joi.object({ category: Joi.string().regex(/^[0-9a-fA-F]{24}$/), tags: Joi.array().items(Joi.string().regex(/^[0-9a-fA-F]{24}$/)) }) } } } ]
在 routes/category.js
和 routes/tag.js
中分别定义 Category
和 Tag
相关的路由,代码类似于 routes/article.js
。
启动服务
在 index.js
中启动服务,代码如下所示:
// javascriptcn.com 代码示例 const Hapi = require('hapi') const config = require('./config') const articleRoutes = require('./routes/article') const categoryRoutes = require('./routes/category') const tagRoutes = require('./routes/tag') const server = Hapi.server({ port: config.port, host: config.host }) server.route([...articleRoutes, ...categoryRoutes, ...tagRoutes]) module.exports = server
其中 config.port
是服务端口,config.host
是服务主机名。
配置文件
在 config.js
中定义配置信息,代码如下所示:
module.exports = { port: 3000, host: 'localhost', mongoUri: 'mongodb://localhost:27017/blog' }
其中 mongoUri
是 MongoDB 的连接地址。
MongoDB 事务处理问题解决
在实际应用中,我们可能需要在多个集合中进行事务处理,例如当我们创建一篇文章时,需要在 articles
集合中插入文章信息,在 tags
集合中插入标签信息,在 categorys
集合中插入分类信息。这时,如果插入操作中任意一个集合操作失败,我们需要将其他集合的操作也回滚。
在 MongoDB 中,由于不支持跨集合事务,因此我们需要使用 4.0 版本引入的新特性:分布式事务(Distributed Transactions)。
分布式事务
分布式事务支持多个文档或集合、甚至跨多个数据库的事务性写入操作,确保这些操作在应用崩溃或断电时保持 ACID(原子性、一致性、隔离性、持久性)特性。
使用分布式事务的步骤如下:
- 定义分布式事务的 session:
const session = await mongoose.startSession() session.startTransaction()
- 在 session 中执行事务:
// javascriptcn.com 代码示例 const article = new Article({ title: 'title', content: 'content', category: categoryId, tags: tagIds }) await article.save({ session }) for (let i = 0; i < tagIds.length; i++) { const tag = await Tag.findById(tagIds[i]) tag.articles.push(article._id) await tag.save({ session }) } const category = await Category.findById(categoryId) category.articles.push(article._id) await category.save({ session })
- 提交或回滚 session:
await session.commitTransaction() session.endSession()
实战应用
在本次案例中,我们将使用分布式事务解决创建文章时多个表插入操作的事务处理问题。
定义路由
在 routes/article.js
中定义创建文章的路由,代码如下所示:
// javascriptcn.com 代码示例 // 创建文章 { method: 'POST', path: '/articles', async handler (request, h) { const { payload } = request const { title, content, category, tags } = payload // 开启 session const session = await mongoose.startSession() session.startTransaction() try { // 在 session 中执行事务 const article = new Article({ title, content, category, tags }) await article.save({ session }) for (let i = 0; i < tags.length; i++) { const tag = await Tag.findById(tags[i]).session(session) tag.articles.push(article._id) await tag.save({ session }) } const categoryObj = await Category.findById(category).session(session) categoryObj.articles.push(article._id) await categoryObj.save({ session }) // 提交 session await session.commitTransaction() session.endSession() return h.response(article).code(201) } catch (error) { // 回滚 session await session.abortTransaction() session.endSession() // 抛出异常 throw error } }, options: { validate: { payload: Joi.object({ title: Joi.string().required(), content: Joi.string().required(), category: Joi.string().regex(/^[0-9a-fA-F]{24}$/).required(), tags: Joi.array().items(Joi.string().regex(/^[0-9a-fA-F]{24}$/)).required() }) } } }
在 session 中,我们先创建文章信息,然后循环插入标签信息,最后插入分类信息。
在执行事务时,如果出现任何异常,我们需要将操作回滚,并抛出异常。
总结
本文介绍了如何使用 Hapi+MongoDB 实现博客管理系统开发,并提供了解决 MongoDB 事务处理问题的实战案例。在实际应用中,我们需要根据具体场景选择合适的数据库及框架,同时考虑事务处理等问题,保障应用的稳定可靠性。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/6534ac947d4982a6eb9afa56