引: 一起来学node.js
目录
利用node.js实现一个包括路由控制,页面模板,数据库访问,用户注册,登录,会话功能的系统。
1. 安装mangodb数据库
- key1:创建数据库文件存放位置:D:\db\data\db
- key2:启动服务:进入\bin文件运行 mongod --dbpath D:\db\data\db
浏览器键入:http://localhost:27017 则可。配置本地服务 :环境变量中添加数据位置。
启动数据库:
mongod --dbpath "D:\db\data\db"
启动项目:
npm start
2. know more
- http模块:能够实现POST请求,以及Cookie、会话的管理。
- express框架:Web开发框架。
安装:
$ npm i -g express //安装express
$ npm install -g express-generator //安装命令工具
$ express --v //检查版本
项目优点:
- MVC的设计模式,实现业务逻辑,数据操作视图的分离。
介绍基本内容:
- require 可同步加载 .js、.json 和 .node 后缀的文件
- 如果目录下有 package.json 并指定了 main 字段,则用之
- 如果不存在 package.json,则依次尝试加载目录下的 index.js 和 index.node
2. exports 和 module.exports 用来导出代码
- module.exports 初始值为一个空对象 {}
- exports 是指向的 module.exports 的引用
- require() 返回的是 module.exports 而不是 exports
由于模块导出的时候,真正导出的执行是module.exports,而不是exports,所以经常看到这类写法:
exports = module.exports = {...}
原理很简单:module.exports 指向新的对象时,exports 断开了与 module.exports 的引用,那么通过 exports = module.exports 让 exports 重新指向 module.exports。
3. npm安装模块是不会写入package.json中
第三种方式用于固定版本号,适合线上Node.js 版本:
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 )
4. 设置路由,渲染模板
index.js
const express = require('express')
const app = express()
const indexRouter = require('./routes/index')
app.use('/', indexRouter)
app.listen(3000)
生成一个 express(web框架) 实例 app,挂载了一个根路由控制器,然后监听 3000 端口并启动程序。
routes/index.js
const express = require('express')
const router = express.Router()
router.get('/', function (req, res) {
res.send('hello, express')
})
module.exports = router
将 /
的路由放到了 routes/index.js 。路由文件通过生成一个 express.Router 实例 router 并导出,通过 app.use
挂载到不同的路径。
index.js : 模板引擎ejs
app.set('views', path.join(__dirname, 'views'))// 设置存放模板文件的目录
app.set('view engine', 'ejs')// 设置模板引擎为 ejs
通过 app.set
设置模板引擎为 ejs 和存放模板的目录。在 myblog 下新建 views 文件夹,在 views 下新建 users.ejs.
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
body {padding: 50px;font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;}
</style>
</head>
<body>
<h1><%= name.toUpperCase() %></h1>
<p>hello, <%= name %></p>
</body>
</html>
ejs 常用标签:
<% code %>
:运行 JavaScript 代码,不输出<%= code %>
:显示转义后的 HTML内容<%- code %>
:显示原始 HTML 内容
routes/user.js
router.get('/:name', function (req, res) {
res.render('users', {
name: req.params.name
})
})
通过调用 res.render
函数渲染 ejs 模板,res.render 第一个参数是模板的名字,这里是 users 则会匹配 views/users.ejs,第二个参数是传给模板的数据,这里传入 name,则在 ejs 模板中可使用 name。
5. 中间件与next
express 中的中间件(middleware)就是用来处理请求的,当一个中间件处理完,可以通过调用 next()
传递给下一个中间件,如果没有调用 next()
,则请求不会往下传递
const express = require('express')
const app = express()
app.use(function (req, res, next) {
console.log('1')
next()
})
app.use(function (req, res, next) {
console.log('2')
res.status(200).end()
})
app.listen(3000)
// 输出 1
// 2
6. node创建一个博客:包含登录、注册、发表文章等功能
目录结构:
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-lite: 是一个轻量的读取配置文件的模块
config/default.js
module.exports = {
port: 3000,
session: {
secret: 'myblog',
key: 'myblog',
maxAge: 2592000000
},
mongodb: 'mongodb://localhost:27017/myblog'
}
配置释义:
port
: 程序启动要监听的端口号session
: express-session 的配置信息,后面介绍mongodb
: mongodb 的地址,以mongodb://
协议开头,myblog
为 db 名
1. 会话
cookie 与 session 的区别
- cookie 存储在浏览器(有大小限制),session 存储在服务端(没有大小限制)
- 通常 session 的实现是基于 cookie 的,session id 存储于 cookie 中
- session 更安全,cookie 可以直接在浏览器查看甚至编辑
通过引入 express-session 中间件实现对会话的支持:
const session = require('express-session')
app.use(session(options))
2. 页面通知
当我们操作成功时需要显示一个成功的通知,如登录成功跳转到主页时,需要显示一个 登陆成功
的通知;当我们操作失败时需要显示一个失败的通知。
connect-flash 是基于 session 实现的,它的原理很简单:
req.session.flash={} //设置初始值
req.flash(name, value) //设置这个对象下的字段和值
req.flash(name) //获取这个对象下的值,同时删除这个字段,实现了只显示一次刷新后消失的功能。
3. 权限控制
我们可以把用户状态的检查封装成一个中间件,在每个需要权限控制的路由加载该中间件,即可实现页面的权限控制。
middlewares/check.js
module.exports = {
checkLogin: function checkLogin (req, res, next) {
if (!req.session.user) {
req.flash('error', '未登录')
return res.redirect('/signin')
}
next()
},
checkNotLogin: function checkNotLogin (req, res, next) {
if (req.session.user) {
req.flash('error', '已登录')
return res.redirect('back')// 返回之前的页面
}
next()
}
}
routes/signout.js
const checkLogin = require('../middlewares/check').checkLogin
// GET /signout 登出
router.get('/', checkLogin, function (req, res, next) {
res.send('登出')
})
index.js
const path = require('path')
const express = require('express')
const session = require('express-session')
const MongoStore = require('connect-mongo')(session)
const flash = require('connect-flash')
const config = require('config-lite')(__dirname)
const routes = require('./routes')
const app = express()
// session 中间件
app.use(session({
name: config.session.key, // 设置 cookie 中保存 session id 的字段名称
secret: config.session.secret, // 通过设置 secret 来计算 hash 值并放在 cookie 中,使产生的 signedCookie 防篡改
resave: true, // 强制更新 session
saveUninitialized: false, // 设置为 false,强制创建一个 session,即使用户未登录
cookie: {
maxAge: config.session.maxAge// 过期时间,过期后 cookie 中的 session id 自动删除
},
store: new MongoStore({// 将 session 存储到 mongodb
url: config.mongodb// mongodb 地址
})
}))
// flash 中间件,用来显示通知
app.use(flash())
4. 变量:app.locals 和 res.locals
app.locals
上通常挂载常量信息(如博客名、描述、作者这种不会变的信息),res.locals
上通常挂载变量信息,即每次请求可能的值都不一样(如请求者信息,res.locals.user = req.session.user
)。
index.js, 在 routes(app)
上一行添加如下代码:
// 设置模板全局常量
app.locals.blog = {
title: pkg.name,
description: pkg.description
}
// 添加模板必需的三个变量
app.use(function (req, res, next) {
res.locals.user = req.session.user
res.locals.success = req.flash('success').toString()
res.locals.error = req.flash('error').toString()
next()
})
这样在调用 res.render
的时候就不用传入这四个变量了,express 为我们自动 merge 并传入了模板,所以我们可以在模板中直接使用这四个变量。
5. mongolass
我们使用 Mongolass 这个模块操作 mongodb 进行增删改查。
lib/mongo.js:
const config = require('config-lite')(__dirname) //配置文件
const Mongolass = require('mongolass')
const mongolass = new Mongolass()
mongolass.connect(config.mongodb)
用户模型设计:
exports.User = mongolass.model('User', {
name: { type: 'string', required: true },
password: { type: 'string', required: true },
avatar: { type: 'string', required: true },
gender: { type: 'string', enum: ['m', 'f', 'x'], default: 'x' },
bio: { type: 'string', required: true }
})
exports.User.index({ name: 1 }, { unique: true }).exec()// 根据用户名找到用户,用户名全局唯一
定义了用户表的 schema,生成并导出了 User 这个 model,同时设置了 name 的唯一索引,保证用户名是不重复的
6. 注册与文件上传
index.js
使用 express-formidable 处理 form 表单(包括文件上传):
// 处理表单及文件上传的中间件
app.use(require('express-formidable')({
uploadDir: path.join(__dirname, 'public/img'), // 上传文件目录
keepExtensions: true// 保留后缀
}))
models/users.js
const User = require('../lib/mongo').User
module.exports = {
// 注册一个用户
create: function create (user) {
return User.create(user).exec()
}
}
6. 404页面
router/index.js
// 404 page
app.use(function (req, res) {
if (!res.headersSent) {
res.status(404).render('404')
}
})
views/404.ejs: 用了腾讯公益的 404 页面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><%= blog.title %></title>
<script type="text/javascript" src="http://www.qq.com/404/search_children.js" charset="utf-8"></script>
</head>
<body></body>
</html>
7. 复用页面通知
将错误信息用页面通知展示的功能,刷新页面将会跳转到主页并显示『权限不足』的红色通知
index.js:
app.use(function(err,req,res,next) {
console.error(err)
req.flash('error', err.message)
res.redirect('/posts')
})
8. 记录日志
我们使用 winston 和 express-winston 记录日志。(Winston@2.4.3)
winston 将正常请求的日志打印到终端并写入了 logs/success.log
,将错误请求的日志打印到终端并写入了 logs/error.log
。
注意:记录正常请求日志的中间件要放到
routes(app)
之前,记录错误请求日志的中间件要放到routes(app)
之后。
const winston = require('winston')
const expressWinston = require('express-winston')
// 正常请求的日志
app.use(expressWinston.logger({
transports: [
new (winston.transports.Console)({
json: true,
colorize: true
}),
new winston.transports.File({
filename: 'logs/success.log'
})
]
}))
// 路由
routes(app)
// 错误请求的日志
app.use(expressWinston.errorLogger({
transports: [
new winston.transports.Console({
json: true,
colorize: true
}),
new winston.transports.File({
filename: 'logs/error.log'
})
]
}))
9. .gitignore 托管时忽略部分文件
线上配置、本地调试的 logs 以及 node_modules 添加到 git 的版本控制中,这个时候就需要 .gitignore 文件了
.gitignore
config/*
!config/default.*
npm-debug.log
node_modules
coverage
.pubilc/img/.gitignore
git 会忽略 public/img 目录下所有上传的头像,而不忽略 public/img
# Ignore everything in this directory
*
# Except this file
!.gitignore