![Node.js+Webpack开发实战](https://wfqqreader-1252317822.image.myqcloud.com/cover/131/36862131/b_36862131.jpg)
5.8 博客项目实战
拥有一个个人博客应该可以说是开发者刚入行时最急切的需求了,本节将和大家一起学习如何搭建个人博客。
在本项目中你将学到:
· 用户登录流程以及登录状态维护
· 使用中间件来保护受限资源访问
5.8.1 功能梳理
各位读者或多或少使用过别人提供的博客系统,总体来说,一个简单的博客一般有以下功能:
· 登录/注册(本项目不开放注册,用固定的账号密码)。
· 查看文章列表。
· 查看文章详情。
· 发表文章。
· 编辑文章。
· 删除文章。
· 项目结构。
本项目采用分层的思想构建,项目结构如下:
![](https://epubservercos.yuewen.com/1A2713/19549639501513406/epubprivate/OEBPS/Images/Figure-P110_93124.jpg?sign=1739606572-K2Kz7aqM6lzPNY5joRnmEDQIh40QEOv0-0-b34c375e2fd774ff83fb48281b6d2ecf)
该目录是笔者使用多年Koa经验总结的一套目录,根据职责划分,比较清晰,也比较容易维护。
5.8.2 项目代码
1.middleware/authenticate.js
认证中间件,负责解析cookie,将登录状态挂载到ctx.state上,供后续使用。
// 认证中间件 module.exports = async function (ctx, next) { const logged = ctx.cookies.get('logged', {signed: true}); ctx.state.logged = !!logged; await next(); };
2.routes/post.js
文章相关路由,提供文章发布/编辑/详情/删除功能。
![](https://epubservercos.yuewen.com/1A2713/19549639501513406/epubprivate/OEBPS/Images/Figure-P111_93128.jpg?sign=1739606572-X7yZBEkKiHDx2Pjx11L3MXUnqUWYGQKv-0-87be1bfbda63e21e32365d9c534cfb4d)
3.routes/site.js
网站首页,负责读取文章列表并渲染到HTML上。
![](https://epubservercos.yuewen.com/1A2713/19549639501513406/epubprivate/OEBPS/Images/Figure-P112_93131.jpg?sign=1739606572-hPUcRUZ83EbrpBcPpRmsCPiYQkZfj76S-0-612e9dd70dea6eba2e427bd4f015f415)
4.routes/user.js
用户相关路由,负责登录和退出登录。
![](https://epubservercos.yuewen.com/1A2713/19549639501513406/epubprivate/OEBPS/Images/Figure-P112_93133.jpg?sign=1739606572-Tw2H3NcK44eFpZ8zGXmBevSGbsT0IbHE-0-13fcf4ee9add2284cb6d6db51f36264c)
5.services/post.js
文章相关业务,提供文章发表/删除/展示/编辑功能,由于还没有介绍数据库相关知识,本项目也是将数据保存到内存中,项目重启时数据会丢失。
采用独立的文件处理具体业务是很好的做法,比如后期我们学习完数据库来使用数据库改造本项目时,主要改动在services/post.js,不需要更改其他地方。
![](https://epubservercos.yuewen.com/1A2713/19549639501513406/epubprivate/OEBPS/Images/Figure-P113_93138.jpg?sign=1739606572-518vSyMSHvt83NQjCwAMq4zLo5c9sd7Z-0-6a2411df6965b701b70298350aca371b)
koa-router中ctx.params.xxx获取到的参数类型为字符串,post.js文件中id是数字,需要转换一下。
6.services/user.js
用户业务,负责用户登录检测。账号密码是通过对象保存的,如果需要支持多个账号,直接给user对象添加属性即可。
![](https://epubservercos.yuewen.com/1A2713/19549639501513406/epubprivate/OEBPS/Images/Figure-P115_93142.jpg?sign=1739606572-xyH0Kaj9qYq39uiNiHWlDhEyvjgqOHZ1-0-7c98d7bc01c33eee4a1c5eb27481d4b2)
7.templates/index.ejs
网站首页模板,负责渲染文章列表。
![](https://epubservercos.yuewen.com/1A2713/19549639501513406/epubprivate/OEBPS/Images/Figure-P115_93144.jpg?sign=1739606572-fWsY2FThNYdp45CpjVEML9YGyAr5Tq6W-0-7d0d319301b3cad750dbef720689946a)
8.templates/login.ejs
登录表单页,负责收集用户输入的数据并发送给服务器。
![](https://epubservercos.yuewen.com/1A2713/19549639501513406/epubprivate/OEBPS/Images/Figure-P116_93147.jpg?sign=1739606572-zdmKc8S2GoI3LL6XbIXG2FtsbwdeqCqB-0-4cdf0a228edc6c618dea149be0668c2a)
9.templates/main.ejs
主布局文件,根据登录状态来显示不同的顶部导航。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>博客</title> </head> <body> <% if(logged) { %> <a href="/">首页</a> <a href="/publish">发表文章</a> <a href="/logout">退出登录</a> <% } else { %> <a href="/login">登录</a> <% } %> <%- body %> </body> </html>
10.templates/post.ejs
文章详情页。
<div> <h1><%= post.title %></h1> <time>发表时间: <%= post.time %></time> <hr> <div><%= post.content %></div> </div>
11.templates/publish.ejs
文章发布页,负责收集用户输入的数据并发送给服务器。
![](https://epubservercos.yuewen.com/1A2713/19549639501513406/epubprivate/OEBPS/Images/Figure-P117_93153.jpg?sign=1739606572-vRDr9dxmUd6MPOhvg9rwdyDeSAgg8agS-0-c87abf58a1f168fec7483f1e3029ce22)
12.templates/update.ejs
文章编辑页,负责填充当前文章的数据到输入框中,并收集用户提交的数据发送给服务器。
![](https://epubservercos.yuewen.com/1A2713/19549639501513406/epubprivate/OEBPS/Images/Figure-P117_93155.jpg?sign=1739606572-xtzjsIeuU7JzCvEnbhTqQikp2wO8C8cB-0-f98384521f8739eee71785a9d9655d6a)
13.index.js
入口JS文件,负责挂载中间件、路由、应用配置和启动服务器。
const Koa = require('koa'); const render = require('koa-ejs'); const bodyParser = require('koa-bodyparser'); const authenticate = require('./middlwares/authticate'); // 路由 const siteRoute = require('./routes/site'); const userRoute = require('./routes/user'); const postRoute = require('./routes/post'); const app = new Koa(); app.keys = ['hO0TTQctIjSjNykY']; // 使用中间件 app.use(bodyParser()); app.use(authenticate); render(app, { root: './templates', layout: 'main', viewExt: 'ejs' }); // 挂载路由 app.use(siteRoute.routes()).use(siteRoute.allowedMethods()); app.use(userRoute.routes()).use(userRoute.allowedMethods()); app.use(postRoute.routes()).use(postRoute.allowedMethods()); app.listen(10000, () => { console.log('listen on 10000'); });
5.8.3 效果展示
运行项目之后可以访问http://localhost:10000,页面效果说明如下。
首页(见图5-3)
![](https://epubservercos.yuewen.com/1A2713/19549639501513406/epubprivate/OEBPS/Images/Figure-P118_61016.jpg?sign=1739606572-xg5fn9n5y797dA45uCotpc4upn8QtTu7-0-b3579172b3b4b00ecb94caecb6f8ec34)
图5-3
登录(见图5-4)
![](https://epubservercos.yuewen.com/1A2713/19549639501513406/epubprivate/OEBPS/Images/Figure-P119_61029.jpg?sign=1739606572-Oi0j1cd9V0eT8uGP2q4PKjFj74FpsYe0-0-9713c7ce8cedae985a23e11051ac6ccc)
图5-4
发表文章(见图5-5)
![](https://epubservercos.yuewen.com/1A2713/19549639501513406/epubprivate/OEBPS/Images/Figure-P119_61039.jpg?sign=1739606572-ymePQUjnMwZId4y4t6CNe58kLdtVTPEC-0-174a9aa86432578f790a2f1115a36194)
图5-5
文章列表(首页有文章的情况,见图5-6)
![](https://epubservercos.yuewen.com/1A2713/19549639501513406/epubprivate/OEBPS/Images/Figure-P119_61049.jpg?sign=1739606572-I57UJJ0Vi8I9btSD2Nxx2cx4eNQ6O5j9-0-07c4ccbc83def48be29d761f228c84b9)
图5-6
文章详情(见图5-7)
![](https://epubservercos.yuewen.com/1A2713/19549639501513406/epubprivate/OEBPS/Images/Figure-P119_61059.jpg?sign=1739606572-evnOewKt5VQjcBjSwWRMNM8Atevh2qvY-0-816153c488792631c2ba5139a694679b)
图5-7
文章编辑(见图5-8)
![](https://epubservercos.yuewen.com/1A2713/19549639501513406/epubprivate/OEBPS/Images/Figure-P120_61071.jpg?sign=1739606572-MSIz9mhqkYmKdk9rbEZwgD2edmDllU1Y-0-d0312b2e1875cea1a91d446b2b5dd955)
图5-8
5.8.4 项目小结
本节和大家一起从零开发了一个简单的博客项目,项目采用了规范的文件结构,希望大家可以合理运用该结构进行项目开发。本项目主要使用到的技术有:
· 自定义Koa中间件。
· Koa路由系统。
· Koa模板渲染。
· Koa表单处理。