准备事项
安装Nodejs,express,git,mongodb以及各种依赖库的过程略去不提。使用eslint规范代码。
npm install -g eslint
也可以 –save-dev 选择本地依赖安装 。懒人必备,直接init一个eslint规则。
eslint --init
这会提供几个可选选项,根据你的选择创建规范规则文件。放张图。
express通过mongoose库配置mongodb连接。app.js文件添加
1 | var mongoose =require("mongoose"); |
package.json修改script项实现自动重启服务,此处需要node-dev包。
1 | "scripts": { |
新建git仓库。
git init
新建 .gitignore 文件忽略node_modules文件夹。之后add文件,add . 匹配所有文件。
git add .
commit提交初始的环境配置。
git commit -m "init"
github上create a new repository后关联到远程仓库
git remote add origin git remote add origin https://github.com/flycatrix/express-blog.git
将本地仓库的内容推到远程库上
git push -u origin master
本地和远程分支合并
git pull origin master:master
准备OK。
数据库操作
mongoose(猫鼬)中间件
博客的功能很大程度上依赖数据库操作,使用mongoose库(猫鼬)简化mongodb操作。mongoose使用对象模型的方法对mongodb数据库进行操作,使用schema方法规范模型的变量个数,变量类型等。吐槽官方文档,一个技术文档写的那么萌。。
model文件夹存放要使用到的数据模型。简单的user模型
1 | var mongoose =require("mongoose"); |
建立模型需要引用mongoose模块,Schema构造函数将写好的数据模型obj包装成Schema对象,最终导出的model对象将继承Schema构造函数的原型中所定义的 访问和操作mongodb 的方法。
schema有十种数据类型,string是其中之一,MongoDB期望用户能按固定的规律存放数据,但实际上用户可以在MongoDB中的任一集合内存放任一类型的数据。mongoose采取事先说明好数据结构和类型的方式,规范用户写入MongoDB中的数据。同时,Schema也能在定义模型时定义MongoDB的索引,支持指定索引(index)或者唯一索引(unique)。对mongoose模型的使用将分散在各路由页面说明。
关于Schema的更多用法和解释可移步mongoose的文档,写文档的一定是个爱猫的程序员。
session - cookie用户验证
关于session
用户的的登录状态和权限等级的确认均需要用到cookie机制,然而用户在浏览器端可以轻易更改、伪造cookie。就如xss攻击可以在用户不知情的情况下获取记录在用户浏览器中的无httponly属性的cookie信息,攻击者一旦成功伪造cookie登入,便可操作受害人的资料数据等。然而xss有绕过httponly的方法,且httponly对中间人攻击无效。一年以前曾经测试过中间人攻击,直接登入受害人QQ邮箱。。不过cookie的secure属性可以只在https通信中传递cookie,可以有效防范中间人,嗅探什么的。。emm废话不说了。
此外,cookie在每次请求的时候都会被附加在请求头中,大量的cookie必然会影响传输效率。那么,session应运而生。
session是存储在服务器中的数据,通过session_id运作。当收到请求时,取出保存在cookie中的session,在服务端查看其是否存在登录属性,借此判断用户的登录状态。
express-session中间件
express-session中间件件实现会话。先npm install express-session –save-dev,然后app.js中添加
1 | var session = require("express-session"); |
当用户首次访问页面时,就为用户分配一个cookie,其中保存对应的sessionid。
登录逻辑:若用户登录成功,那么为这个session新添一个属性,当下次请求来临时,取得用户session中新增的属性字段,若为空则是未登录用户,若不为空则是已登录用户。
注销逻辑:使用destroy方法摧毁当前会话,在摧毁后会重新生成一个新的没有登录状态的session会话。
登录与注册功能
在express添加新路由需要三步。
- app.js 中require 路由模块(存放在routes文件夹下)
- app.js 中use注册路由
- routes文件夹中添加新路由模块文件并设置路由规则
register - 注册页
register.ejs模版部分
views文件夹新建register.ejs模版。页面部件偷懒扒取自bootstrap。
1 | <!DOCTYPE html> |
页面头部的导航栏写成独立的header.ejs文件,在使用到header的ejs中include一下,还可以在include的同时向header.ejs传入变量。想起来之前用php写公共部分再include,通过js改变页面内容的做法,简直不要太傻。
register.js路由模块部分
路由模块中设置路由规则,也承担根据用户输入连接和操作数据库的任务。
1 | var express = require('express'); |
路由文件中引入我们事先写好的userModel(mongoose模块),以便按userModel中规定的模型检索数据库。在register.js路由模块中,get方法负责渲染页面,post方法在二级路由中实现数据库查询以及写入的操作。通过find方法检索用户提交的用户名,在回调函数中处理查询结果,error用于抛出一个错误信息,docs为查询后得出的文档数组,若docs长度为0,表示这是一个未被注册的用户名,因此调用create方法写入一个新用户信息。若docs的长度不为0,则显示 “用户名已被注册” 的消息。
login - 登录页
login的ejs模版和register.ejs大同小异,不再贴出赘述。
login.js路由模块部分
login部分的路由,需要判断用户名和密码是否能在数据库中成功匹配。判断成功后,还应该为用户设置一条新的session属性,以便根据新添加的属性判断用户的登录状态,在数据库中取得用户信息。
login.js路由模块
1 | var express = require('express'); |
get方法渲染登录页面,post请求中接收用户输入,若用户登录成功,那么为这个session新添一个属性userInfo并跳转至首页,若登录失败则在页面上显示错误消息。
博客的增删改查
准备博客模型
在用户登录之后,可以撰写、发表博客。博客内容和信息存储在MongoDB中,在model文件夹中建立mongoose模型
1 | // article model |
author,title,content,createTime为博客的基本信息,category为博客添加分类信息。
首页显示博客列表
首页提供对未登录或非博主用户展示博客列表功能,对博主提供删改接口。此外,添加销毁路由的操作。
index.js路由模块部分
index路由判断用户登录状态及用户权限,读取保存在数据库中的博客列表传输给index.ejs模版,最终显示在用户界面上。
1 | var express = require('express'); |
get方法中首先读取用户登录状态,若存在则显示欢迎标签,同时在页面上增加对当前用户博客内容的删改功能,若是未登录用户,则仅能查看当前博客列表。
index.ejs模版部分
1 | <!DOCTYPE html> |
在此指出,index.ejs模版中使用ejs语法中的for循环创建表格,同时将当次循环中的博客的id写入对应的tr的id属性以及删除按钮的href属性中去,这样当用户点击删除时才能告知服务器具体需要删除哪篇博客。后面查寻博客时则通过事件代理取tr的id值确定路由。
article模版展示编写博客的页面,新建博客和更新博客共用此模版。
article.ejs模版部分
1 | <!DOCTYPE html> |
增 - 编写博客
在routes文件夹中新建article.js路由模块,在views文件夹下新建article.ejs模版文件,生成编写博客页面。
article.js路由模块部分
1 | var express = require('express'); |
get方法中,先通过session的userInfo属性判断用户是否登入,若为已登录用户则加载article页面,若为未登录用户则跳转登录页。
post方法中,接收用户输入,将用户编写的博客内容保存到MongoDB中。
删 - 删除博客
使用动态路由的方式完成删除博客的操作。首页对已登录博主用户提供删除博客接口,当点击删除时,将被点击的博客的id当作路由地址通过get方式传给remove.js路由模块,在remove.js中通过deleteOne()方法安全删除博客。
remove.js路由模块部分
1 | var express = require('express'); |
在express中,获取不同方式提交的数据的方法分以下几种。
- req.params获取动态路由字段。
- req.query获取get查询字段。
- req.body获取post字段。
在删除路由模块后,需要再执行一次find()操作,否则首页的博客列表将为空。
改 - 更新博客
更新博客也使用动态路由的方法。和删除操作很类似。需要注意的是更新博客需要获取原有的博客填充进article.ejs模版中。
update.js路由模块部分
1 | var express = require('express'); |
更改之后仍使用编写博客时的方式提交博客,博客的createtime属性也将更新为最后一次提交更新的时间。
查 - 查看博客
在index.ejs中对展示博客列表的table标签添加事件代理。监听每个tr的点击事件跳转到check路由上。
index.ejs添加
1 | <script type="text/javascript"> |
在for循环输出博客列表时,事先给每个tr的id属性都设置成了博客的id。
check.js路由模块部分
1 | var express = require('express'); |
这应该是最简单的一个模块吧。find一下然后传给check.ejs模版就好了。
总结
至此,已经实现了一个简单的可以登录注册增删改查博文的小博客,但还缺少分类、评论等功能。第一次记叙小项目的实现过程,行文不免凌乱缺乏条理,章节安排参考了@liuxing的github项目node-blog,项目代码已上传我的github express-blog。此外,由于对代码的理解较浅,表述不当之处,还望不吝赐教。