简介:这是一套面向计算机相关专业学生与Web开发初学者的博客管理系统完整源码,基于Node.js、Express、MongoDB与Vue构建,可作为毕业设计、课程设计作业或全栈练手项目使用。压缩包共675个文件,约121.62MB,其中241个js文件承载前后端核心逻辑,82个json与7个yml负责依赖与配置,7个html、6个css及layui相关样式文件构成页面骨架,另有大量md说明、gif演示与license授权文件,目录结构清晰,便于按模块阅读与二次开发。项目界面美观、功能齐全,涵盖用户管理、文章发布、评论互动等博客常见模块,并附带sql脚本与依赖锁文件,经过测试可快速部署运行。目前已有163人学习下载,适合需要完整赛题方案、全栈代码参考与排错思路的读者,能帮助快速理解Express路由、MongoDB数据建模与Vue组件化开发的协作方式。
1. 从一份能跑通的 Node.js 博客系统说起
很多同学做毕业设计时,最头疼的不是写不出代码,而是环境装不上、数据库连不通、前后端对不上。这份Nodejs+Express+MongoDB实现博客管理系统.zip就是冲着这个痛点来的:它是一套完整可运行的博客后台源码,技术栈锁定在 Node.js + Express + MongoDB,覆盖文章发布、分类管理、用户登录、评论互动这些博客系统最核心的模块。拿到手之后,你不需要从零搭架子,只要把环境配好、依赖装上、数据库跑起来,就能看到一个能登录、能发文、能评论的完整系统。它适合三类人:正在做毕业设计需要参考完整项目结构的同学、想学 Express 分层写法但缺练手项目的后端新手、以及需要一套轻量 CMS 底座做二次开发的从业者。下面我按实际拆包和跑通的顺序,把这份资源从环境到代码再到踩坑讲清楚。
2. 环境准备:Node.js、MongoDB 与依赖安装的完整链路
2.1 为什么先锁版本再装环境
Node.js 和 MongoDB 这两个东西,版本选错就是一连串玄学问题。我一般会先看项目里的package.json,确认它用的 Express 是 4.x 还是 5.x,Mongoose 是 6.x 还是 7.x,然后反推 Node 版本。常见做法是 Node 用 16 或 18 的 LTS 版本,MongoDB 用 5.0 或 6.0 的社区版。原因很简单:Express 4 对 Node 18 兼容良好,Mongoose 6 要求 Node 12 以上,而 MongoDB 6.0 的驱动对老版本 Node 支持会变差。如果你装的是 Node 20 配 Mongoose 5,大概率会在连接数据库时报MongoServerSelectionError,这不是代码问题,是驱动和数据库协议对不上。
安装 Node.js 时,Windows 用户直接下 LTS 的 msi 包,Mac 用户用 pkg 或 brew 都行。装完在终端敲node -v和npm -v,两个都能出版本号才算成功。MongoDB 安装稍微麻烦一点,Windows 下要手动建数据目录,Mac 下用 brew 装完要启动服务。这里有个高频翻车点:Windows 上 npm 报无法加载文件 npm.ps1,因为在此系统上禁止运行脚本,这是 PowerShell 执行策略的问题,不是 Node 没装好。解决办法是以管理员身份打开 PowerShell,执行Set-ExecutionPolicy RemoteSigned,然后选 Y 确认。这个坑几乎每个 Windows 新手都会踩一次。
2.2 数据库连接与配置文件怎么改
MongoDB 装好之后,默认监听mongodb://127.0.0.1:27017。这份博客系统一般会在根目录放一个config文件夹或者.env文件,里面写着数据库地址和端口。你需要确认三件事:数据库服务是否在跑、连接字符串是否写对、数据库名是否和代码里一致。常见做法是先用 MongoDB Compass 或者命令行mongosh连一下,能连上再改项目配置。
# 启动 MongoDB 服务(Mac 用 brew) brew services start mongodb-community # Windows 下用命令行启动,指定数据目录 mongod --dbpath "C:\data\db" # 验证连接,进入 mongosh mongosh # 在 mongosh 里执行 show dbs上面这段命令的逻辑是:先确保数据库进程活着,再用客户端验证。--dbpath指向的目录必须提前建好,否则 mongod 启动会直接退出。show dbs能列出数据库,说明服务正常。如果这一步报错,先别动项目代码,把数据库本身跑通再说。
项目里的数据库配置通常长这样:
// config/db.js 常见写法 const mongoose = require('mongoose'); const connectDB = async () => { try { // 连接字符串里的 blog 是数据库名,可自定义 await mongoose.connect('mongodb://127.0.0.1:27017/blog', { useNewUrlParser: true, useUnifiedTopology: true, }); console.log('MongoDB Connected'); } catch (err) { console.error(err.message); process.exit(1); // 连接失败直接退出,避免后续报错难排查 } }; module.exports = connectDB;这段代码的关键参数是useNewUrlParser和useUnifiedTopology,Mongoose 6 之后这两个参数已经默认开启,写了也不报错,但如果你用的是 Mongoose 5,不写就会出警告。process.exit(1)是故意让进程退出的,这样你能立刻看到连接失败,而不是等到某个接口报错才发现数据库根本没连上。
2.3 依赖安装与启动脚本
依赖安装就一条命令,但背后有几个细节值得说。进入项目根目录,执行npm install,它会读package.json把所有依赖拉到node_modules。如果卡在某个包上不动,先换 npm 源,再删掉node_modules和package-lock.json重来。启动脚本一般在package.json的scripts里,常见的是npm start或npm run dev。dev通常用 nodemon 做热重载,改代码不用重启。
# 进入项目目录 cd blog-system # 安装依赖,建议加 --registry 指定国内源 npm install --registry=https://registry.npmmirror.com # 启动开发模式 npm run dev # 如果 scripts 里只有 start npm start安装完成后,终端应该输出类似Server running on port 3000和MongoDB Connected两行。看到这两行,说明后端已经活了。接下来打开浏览器访问http://localhost:3000,如果项目带前端页面,应该能看到博客首页;如果只做后端 API,就用 Postman 或 Apifox 测接口。这一步的验证标准很简单:服务能起、数据库能连、页面或接口有响应,三者缺一不可。
3. 代码结构拆解:路由、模型与控制器怎么配合
3.1 MVC 分层在 Express 里的落地方式
这份博客系统用的是典型的 MVC 变体:models放 Mongoose Schema,routes放路由定义,controllers放业务逻辑,views或前端静态文件放展示层。很多人写 Express 喜欢把逻辑全塞在路由回调里,项目一小还能忍,一旦模块多了就是灾难。这份源码把三者拆开,好处是改一个功能只需要动对应文件,不会牵一发动全身。
先看模型层。博客系统至少需要用户、文章、分类、评论四个 Schema。以文章为例:
// models/Post.js const mongoose = require('mongoose'); const PostSchema = new mongoose.Schema({ title: { type: String, required: true }, // 标题必填 content: { type: String, required: true }, // 正文必填 author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, // 关联用户 category: { type: String, default: '未分类' }, // 分类,给默认值 createdAt: { type: Date, default: Date.now }, // 创建时间自动生成 }); module.exports = mongoose.model('Post', PostSchema);这里author字段用ObjectId加ref指向 User 模型,是 MongoDB 做关联查询的标准做法。default让分类和创建时间有兜底值,避免插入时报空。如果你把required去掉,前端传空也能存进去,后期数据清洗会很痛苦,所以必填字段一定要在 Schema 层卡住。
3.2 路由与控制器的职责边界
路由层只做一件事:把 URL 和 HTTP 方法映射到控制器函数。控制器才负责取参数、调模型、返响应。这样拆分之后,你想加一个中间件做鉴权,只需要在路由上加一行,不用改控制器。
// routes/posts.js const express = require('express'); const router = express.Router(); const postController = require('../controllers/postController'); const auth = require('../middleware/auth'); // 鉴权中间件 router.get('/', postController.getAllPosts); // 获取文章列表 router.get('/:id', postController.getPostById); // 获取单篇 router.post('/', auth, postController.createPost); // 发布需登录 router.put('/:id', auth, postController.updatePost); // 更新需登录 router.delete('/:id', auth, postController.deletePost); // 删除需登录 module.exports = router;auth中间件放在createPost前面,意味着只有带合法 token 的请求才能进控制器。如果你把auth写在控制器里,每个函数都要重复判断,改起来容易漏。控制器的写法通常是async/await配try/catch,把数据库操作包起来,出错统一返回 500。
// controllers/postController.js 片段 exports.createPost = async (req, res) => { try { const { title, content, category } = req.body; // req.user 由 auth 中间件注入 const post = await Post.create({ title, content, category, author: req.user.id, }); res.status(201).json(post); } catch (err) { console.error(err.message); res.status(500).json({ msg: '服务器错误' }); } };这段代码里req.user.id来自鉴权中间件解析 token 后的结果,所以auth必须放在前面。status(201)表示资源创建成功,比统一返 200 更规范。如果你发现发布文章时author是 null,先检查auth中间件有没有正确挂载,再看 token 解析后有没有把用户信息塞进req。
3.3 前端页面与接口的对接方式
如果这份资源带 Vue 或静态页面,前端一般通过 axios 调后端接口。常见做法是把 baseURL 配成http://localhost:3000/api,然后在请求拦截器里带上 token。这里最容易出的问题是跨域:前端跑在 8080,后端跑在 3000,浏览器直接拦请求。解决办法是在 Express 里加 cors 中间件,或者用代理。
// app.js 里开启跨域 const cors = require('cors'); app.use(cors()); // 开发阶段允许所有来源,上线要收紧 app.use(express.json()); // 解析 JSON 请求体 app.use('/api/posts', require('./routes/posts'));cors()不传参数就是允许所有来源,开发时省事,上线必须改成白名单。express.json()不加的话,req.body会是 undefined,前端传的 JSON 根本读不到。这两个中间件的顺序也有讲究:先 cors 再 json,最后挂路由。如果你把路由挂在 json 前面,请求体解析不到,控制器里拿到的就是空对象。
4. 避坑与排查:从环境到接口的高频翻车记录
4.1 数据库连不上,先看服务再看字符串
现象是启动项目后终端一直报MongoServerSelectionError或connect ECONNREFUSED。原因通常有两个:MongoDB 服务根本没启动,或者连接字符串的端口写错。Windows 下很多人装完 MongoDB 以为它自动跑,其实要手动启动服务或运行 mongod。解决方法是先用mongosh或 Compass 连一次,确认服务活着,再检查项目里的连接字符串是不是127.0.0.1:27017。如果用了 Docker 或远程数据库,地址和认证信息都要对应改。
4.2 npm 脚本被禁止运行
现象是 Windows PowerShell 里执行npm install报无法加载文件 npm.ps1,因为在此系统上禁止运行脚本。原因是 PowerShell 默认执行策略是 Restricted,不允许跑脚本。解决办法是以管理员身份打开 PowerShell,执行Set-ExecutionPolicy RemoteSigned,选 Y。改完之后npm -v能正常出版本号。这个坑和 Node 本身无关,纯粹是系统策略,但新手很容易误以为 Node 装坏了,反复重装浪费时间。
4.3 接口返回 401 但 token 明明带了
现象是前端请求头里加了Authorization,后端还是返 401。原因多半是中间件解析 token 的方式和前端传的格式不一致。常见做法是前端传Bearer xxxxx,后端用req.header('Authorization').split(' ')[1]取。如果前端只传了 token 没加 Bearer,或者后端直接拿整个 header 去 verify,都会失败。解决方法是打印一下req.header('Authorization'),看实际传过来的是什么,再调整解析逻辑。另外 token 过期也会返 401,别只盯着格式。
4.4 文章列表查出来是空数组
现象是数据库里明明有数据,接口返回[]。原因可能是查询条件写错,比如按category过滤时传了不存在的分类,或者分页参数page和limit算错导致跳过全部数据。还有一种情况是集合名对不上:Mongoose 默认把模型名复数化,Post对应posts集合,如果你手动建了post集合,查不到很正常。解决方法是先在 mongosh 里db.posts.find()确认数据在哪个集合,再检查控制器的查询条件和分页计算。
4.5 修改代码后页面没变化
现象是改了控制器逻辑,刷新接口还是老结果。原因是启动用的是npm start而不是npm run dev,没有热重载,进程还是旧的。解决方法是确认package.json里dev脚本用了 nodemon,然后用npm run dev启动。如果已经用了 nodemon 还不生效,检查是不是有多个 node 进程占着端口,用lsof -i :3000或netstat -ano | findstr 3000找到进程杀掉再重启。
5. 进阶用法:把这份源码改成你自己的毕业设计
5.1 从能跑到能讲清楚,差的是这几步
拿到一份能跑的源码只是起点,毕业设计答辩时老师不会只看你跑起来,还会问架构、问数据流、问为什么这么设计。我的习惯是先把核心链路画出来:用户登录拿 token,请求带 token 过中间件,控制器查 Mongoose 模型,模型操作 MongoDB,结果逐层返回。这条链路里每个环节你都要能说清楚输入输出是什么。比如登录接口,输入是用户名密码,中间做密码比对和 token 签发,输出是 token 和用户信息。能把这个讲顺,比背代码强得多。
5.2 加一个搜索功能来验证你理解了分层
想检验自己有没有真懂这套结构,最简单的办法是加一个文章搜索接口。按分层来:路由加router.get('/search', postController.searchPosts),控制器里用Post.find({ title: new RegExp(keyword, 'i') })做模糊匹配,前端加个输入框调这个接口。整个过程你只需要动三个文件,不用碰其他模块,这就是分层的好处。
// controllers/postController.js 里新增 exports.searchPosts = async (req, res) => { try { const { keyword } = req.query; if (!keyword) { return res.status(400).json({ msg: '请输入关键词' }); } // i 表示不区分大小写 const posts = await Post.find({ title: { $regex: keyword, $options: 'i' }, }).populate('author', 'username'); // 只取作者的用户名 res.json(posts); } catch (err) { console.error(err.message); res.status(500).json({ msg: '搜索失败' }); } };$regex是 MongoDB 的正则查询,$options: 'i'让匹配忽略大小写。populate把author字段从 ObjectId 展开成用户对象,第二个参数指定只取username,避免把密码等敏感字段带出来。这个接口加完,你对路由、控制器、模型三层的配合就有了实际手感。
5.3 上线前必须改的几个默认配置
开发环境能跑不等于能上线。这份源码里至少有三处默认配置要改:cors 从允许所有来源改成白名单、数据库连接字符串从本地改成线上地址并加认证、token 密钥从硬编码改成环境变量。我一般会在根目录建.env文件,用dotenv加载,把MONGO_URI、JWT_SECRET、PORT都抽出来。这样代码里不出现敏感信息,换环境只改.env就行。
# .env 示例 PORT=3000 MONGO_URI=mongodb://127.0.0.1:27017/blog JWT_SECRET=your_random_secret_here// app.js 顶部加载 require('dotenv').config(); const PORT = process.env.PORT || 3000;dotenv必须在其他模块读取环境变量之前加载,所以放在入口文件最上面。JWT_SECRET不要用简单字符串,随便生成一串随机值都行,但别提交到代码仓库。从那以后我每次拿到新项目,第一件事就是找硬编码的密钥和数据库地址,先抽成环境变量再动业务代码,这个习惯帮我省过很多次后悔药。希望这份拆解能帮你把这份博客系统真正跑起来、讲清楚、改得动。
本文还有配套的精品资源,点击获取