require
简单概括以下几点:
- require 可加载 .js、.json 和 .node 后缀的文件
- require 的过程是同步的,所以这样是错误的:
1 | setTimeout(() => { |
require 这个文件得到的是空对象 {}
- require 目录的机制是:
- 如果目录下有 package.json 并指定了 main 字段,则用之
- 如果不存在 package.json,则依次尝试加载目录下的 index.js 和 index.node
- require 过的文件会加载到缓存,所以多次 require 同一个文件(模块)不会重复加载
- 判断是否是程序的入口文件有两种方式:
- require.main === module(推荐)
- module.parent === null
npm
npm i express --save
/npm i express -S
(安装 express,同时将"express": "^4.14.0"
写入 dependencies )npm i express --save-dev
/npm i express -D
(安装 express,同时将"express": "^4.14.0"
写入 devDependencies )npm i express --save --save-exact
(安装 express,同时将"express": "4.14.0"
写入 dependencies )
第三种方式将固定版本号写入 dependencies,建议线上的 Node.js 应用都采取这种锁定版本号的方式,因为你不可能保证第三方模块下个小版本是没有验证 bug 的,即使是很流行的模块。
req
req 包含了请求来的相关信息,res 则用来返回该请求的响应,更多请查阅 express 官方文档。下面介绍几个常用的 req 的属性:
req.query
: 解析后的 url 中的 querystring,如?name=haha
,req.query 的值为{name: 'haha'}
req.params
: 解析 url 中的占位符,如/:name
,访问 /haha,req.params 的值为{name: 'haha'}
req.body
: 解析后请求体,需使用相关的模块,如 body-parser,请求体为{"name": "haha"}
,则 req.body 为{name: 'haha'}
ejs
ejs 有 3 种常用标签:
<% code %>
:运行 JavaScript 代码,不输出<%= code %>
:显示转义后的 HTML内容<%- code %>
:显示原始 HTML 内容
注意:
<%= code %>
和<%- code %>
都可以是 JavaScript 表达式生成的字符串,当变量 code 为普通字符串时,两者没有区别。当 code 比如为<h1>hello</h1>
这种字符串时,<%= code %>
会原样输出<h1>hello</h1>
,而<%- code %>
则会显示 H1 大的 hello 字符串。
更多 ejs 的标签请看 官方文档。
includes
小提示:拆分模板组件通常有两个好处:
- 模板可复用,减少重复代码
- 主模板结构清晰
注意:要用
<%- include('header') %>
而不是<%= include('header') %>
项目开始
目录结构
models
: 存放操作数据库的文件public
: 存放静态文件,如样式、图片等routes
: 存放路由文件views
: 存放模板文件index.js
: 程序主文件package.json
: 存储项目名、描述、作者、依赖等等信息
相关依赖
express
: web 框架express-session
: session 中间件connect-mongo
: 将 session 存储于 mongodb,结合 express-session 使用connect-flash
: 页面通知的中间件,基于 session 实现ejs
: 模板express-formidable
: 接收表单及文件上传的中间件config-lite
: 读取配置文件marked
: markdown 解析moment
: 时间格式化mongolass
: mongodb 驱动objectid-to-timestamp
: 根据 ObjectId 生成时间戳sha1
: sha1 加密,用于密码加密winston
: 日志express-winston
: express 的 winston 日志中间件
配置文件
config/default.js
1 | module.exports = { |
配置释义:
port
: 程序启动要监听的端口号session
: express-session 的配置信息,后面介绍mongodb
: mongodb 的地址,以mongodb://
协议开头,myblog
为 db 名
config-lite 是一个轻量的读取配置文件的模块。config-lite 会根据环境变量(NODE_ENV
)的不同加载 config 目录下不同的配置文件。如果不设置 NODE_ENV
,则读取默认的 default 配置文件,如果设置了 NODE_ENV
,则会合并指定的配置文件和 default 配置文件作为配置,config-lite 支持 .js、.json、.node、.yml、.yaml 后缀的文件。
如果程序以 NODE_ENV=test node app
启动,则 config-lite 会依次降级查找 config/test.js
、config/test.json
、config/test.node
、config/test.yml
、config/test.yaml
并合并 default 配置; 如果程序以 NODE_ENV=production node app
启动,则 config-lite 会依次降级查找 config/production.js
、config/production.json
、config/production.node
、config/production.yml
、config/production.yaml
并合并 default 配置。
config-lite 还支持冒泡查找配置,即从传入的路径开始,从该目录不断往上一级目录查找 config 目录,直到找到或者到达根目录为止。
功能设计 v1.0
路由设计
功能及路由设计如下:
- 注册
- 注册页:
GET /signup
- 注册(包含上传头像):
POST /signup
- 注册页:
- 登录
- 登录页:
GET /signin
- 登录:
POST /signin
- 登录页:
- 登出:
GET /signout
- 查看文章
- 主页:
GET /posts
- 个人主页:
GET /posts?author=xxx
- 查看一篇文章(包含留言):
GET /posts/:postId
- 主页:
- 发表文章
- 发表文章页:
GET /posts/create
- 发表文章:
POST /posts/create
- 发表文章页:
- 修改文章
- 修改文章页:
GET /posts/:postId/edit
- 修改文章:
POST /posts/:postId/edit
- 修改文章页:
- 删除文章:
GET /posts/:postId/remove
- 留言
- 创建留言:
POST /comments
- 删除留言:
GET /comments/:commentId/remove
- 创建留言:
由于我们博客页面是后端渲染的,所以只通过简单的 <a>(GET)
和 <form>(POST)
与后端进行交互,如果使用 jQuery 或者其他前端框架(如 Angular、Vue、React 等等)可通过 Ajax 与后端交互,则 api 的设计应尽量遵循 Restful 风格。
更多阅读:
- ht 大专栏 node-blog记录tp://www.ruanyifeng.com/blog/2011/09/restful
- http://www.ruanyifeng.com/blog/2014/05/restful_api.html
- http://developer.51cto.com/art/200908/141825.htm
- http://blog.jobbole.com/41233/
会话
由于 HTTP 协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识别具体的用户,这个机制就是会话(Session)。
cookie 与 session 的区别
- cookie 存储在浏览器(有大小限制),session 存储在服务端(没有大小限制)
- 通常 session 的实现是基于 cookie 的,session id 存储于 cookie 中
- session 更安全,cookie 可以直接在浏览器查看甚至编辑
更多 session 的资料,参考:https://www.zhihu.com/question/19786827
我们通过引入 express-session 中间件实现对会话的支持:
1 | app.use(session(options)) |
session 中间件会在 req 上添加 session 对象,即 req.session 初始值为 {}
,当我们登录后设置 req.session.user = 用户信息
,返回浏览器的头信息中会带上 set-cookie
将 session id 写到浏览器 cookie 中,那么该用户下次请求时,通过带上来的 cookie 中的 session id 我们就可以查找到该用户,并将用户信息保存到 req.session.user
。
页面通知
我们还需要这样一个功能:当我们操作成功时需要显示一个成功的通知,如登录成功跳转到主页时,需要显示一个 登陆成功
的通知;当我们操作失败时需要显示一个失败的通知,如注册时用户名被占用了,需要显示一个 用户名已占用
的通知。通知只显示一次,刷新后消失,我们可以通过 connect-flash 中间件实现这个功能。
connect-flash 是基于 session 实现的,它的原理很简单:设置初始值 req.session.flash={}
,通过 req.flash(name, value)
设置这个对象下的字段和值,通过 req.flash(name)
获取这个对象下的值,同时删除这个字段,实现了只显示一次刷新后消失的功能。
express-session、connect-mongo 和 connect-flash 的区别与联系
express-session
: 会话(session)支持中间件connect-mongo
: 将 session 存储于 mongodb,需结合 express-session 使用,我们也可以将 session 存储于 redis,如 connect-redisconnect-flash
: 基于 session 实现的用于通知功能的中间件,需结合 express-session 使用
权限控制
不管是论坛还是博客网站,我们没有登录的话只能浏览,登陆后才能发帖或写文章,即使登录了你也不能修改或删除其他人的文章,这就是权限控制。我们也来给博客添加权限控制,如何实现页面的权限控制呢?我们可以把用户状态的检查封装成一个中间件,在每个需要权限控制的路由加载该中间件,即可实现页面的权限控制。
可以看出:
checkLogin
: 当用户信息(req.session.user
)不存在,即认为用户没有登录,则跳转到登录页,同时显示未登录
的通知,用于需要用户登录才能操作的页面checkNotLogin
: 当用户信息(req.session.user
)存在,即认为用户已经登录,则跳转到之前的页面,同时显示已登录
的通知,如已登录用户就禁止访问登录、注册页面
注意:中间件的加载顺序很重要。如上面设置静态文件目录的中间件应该放到 routes(app) 之前加载,这样静态文件的请求就不会落到业务逻辑的路由里;flash 中间件应该放到 session 中间件之后加载,因为 flash 是基于 session 实现的。
页面设计 v2.0
app.locals 和 res.locals
上面的 ejs 模板中我们用到了 blog、user、success、error 变量,我们将 blog 变量挂载到 app.locals
下,将 user、success、error 挂载到 res.locals
下。为什么要这么做呢?app.locals
和 res.locals
是什么?它们有什么区别?
express 中有两个对象可用于模板的渲染:app.locals
和 res.locals
。
可以看出:在调用 res.render
的时候,express 合并(merge)了 3 处的结果后传入要渲染的模板,优先级:res.render
传入的对象> res.locals
对象 > app.locals
对象,所以 app.locals
和 res.locals
几乎没有区别,都用来渲染模板,使用上的区别在于:app.locals
上通常挂载常量信息(如博客名、描述、作者这种不会变的信息),res.locals
上通常挂载变量信息,即每次请求可能的值都不一样(如请求者信息,res.locals.user = req.session.user
)。
连接数据库 v3.0
这里使用的是 mongolass
注册 v4.0
我们使用 express-formidable 处理表单的上传,表单普通字段挂载到 req.fields 上,表单上传后的文件挂载到 req.files 上,文件存储在 public/img 目录下。然后校验了参数,校验通过后将用户信息插入到 MongoDB 中,成功则跳转到主页并显示『注册成功』的通知,失败(如用户名被占用)则跳转回注册页面并显示『用户名已被占用』的通知。
注意:我们使用 sha1 加密用户的密码,sha1 并不是一种十分安全的加密方式,实际开发中可以使用更安全的 bcrypt或 scrypt 加密。 注意:注册失败时(参数校验失败或者存数据库时出错)删除已经上传到 public/img 目录下的头像。
bug - fs.unlink Callback must be a function v4.0注册时删除头像
登出与登录 v5.0
文章 v6.0
留言 v7.0
404简单处理 v8.0
忽略img和logs v9.0
测试 v10.0
修复fs.unlink以及控制台取消打印log v11.0
腾讯公益404 v12.0
1 | git checkout 版本号 # 切换学习时相应的版本 |
启动
1 | 启动本地mongoDB数据库 |
部署
bug
fs.unlink Callback must be a function v4.0注册时删除头像