第五章 Node.js进行Web开发

目录

 

5.1 准备

5.2 Express 框架

5.3 模板引擎

5.4 建立微博网站


5.1 准备

5.2 Express 框架

 路由控制;
 模板解析支持;
 动态视图;
 用户会话;
 CSRF 保护;
 静态文件服务;
 错误控制器;
 访问日志;
 缓存;
 插件支持。

  • 安装 Express 

为了使用这个工具,我们需要用全局模式安装Express,因为只有这样我们才能在命令行中使用它。运行以下命令:

$ npm install -g express 

 express --help 查看帮助信息:

Usage: express [options] [path] 
Options: 
 -s, --sessions add session support 
 -t, --template <engine> add template <engine> support (jade|ejs). default=jade 
 -c, --css <engine> add stylesheet <engine> support (stylus). default=plain css 
 -v, --version output framework version 
 -h, --help output help information 

 Express 在初始化一个项目的时候需要指定模板引擎,默认支持Jade和ejs。

  •  建立工程

通过以下命令建立网站基本结构:

express -t ejs microblog 
$ cd microblog && npm install 

其中 dependencies 属性中有express 和ejs。无参数的 npm install 的功能就是检查当前目录下的 package.json,并自动安装所有指定的依赖。

  • 启动服务器

http://localhost:3000

要关闭服务器的话,在终端中按 Ctrl + C。

  • 工程的结构
app.js 是工程的入口,我们先看看其中有什么内容:
/** 
 * Module dependencies. 
 */ 
var express = require('express') 
 , routes = require('./routes'); 
var app = module.exports = express.createServer(); 
// Configuration 
app.configure(function(){ 
 app.set('views', __dirname + '/views'); 
 app.set('view engine', 'ejs'); 
 app.use(express.bodyParser()); 
 app.use(express.methodOverride()); 
 app.use(app.router); 
 app.use(express.static(__dirname + '/public')); 
}); 
app.configure('development', function(){ 
 app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 
}); 
app.configure('production', function(){ 
 app.use(express.errorHandler()); 
}); 
// Routes 
app.get('/', routes.index); 
app.listen(3000); 
console.log("Express server listening on port %d in %s mode", app.address().port, 
app.settings.env); 

app.set 是 Express 的参数设置工具

basepath:基础地址,通常用于 res.redirect() 跳转。
 views:视图文件的目录,存放模板文件。
 view engine:视图模板引擎。
 view options:全局视图参数对象。
 view cache:启用视图缓存。
 case sensitive routes:路径区分大小写。
 strict routing:严格路径,启用后不会忽略路径末尾的“ / ”。
 jsonp callback:开启透明的 JSONP 支持。

/* 
 * GET home page. 
 */ 
exports.index = function(req, res) { 
res.render('index', { title: 'Express' }); 
}; 

这是一个典型的 MVC 架构,浏览器发起请求,由路由控制器接受,根据不同的路径定向到不同的控制器。控制器处理用户的具体请求,可能会访问数据库中的对象,即模型部分。控制器还要访问模板引擎,生成视图的 HTML,最后再由控制器返回给浏览器,完成一次请求。

  • 创建路由规则
  • 路径匹配
  • REST 风格的路由规则

Express 支持 REST 风格的请求方式,在介绍之前我们先说明一下什么是 REST。REST 的意思是 表征状态转移(Representational State Transfer),它是一种基于 HTTP 协议的网络应用的接口风格,充分利用 HTTP 的方法实现统一风格接口的服务。HTTP 协议定义了以下8
种标准的方法。

 GET:请求获取指定资源。
 HEAD:请求指定资源的响应头。
 POST:向指定资源提交数据。
 PUT:请求服务器存储一个资源。
 DELETE:请求服务器删除指定资源。
 TRACE:回显服务器收到的请求,主要用于测试或诊断。
 CONNECT:HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。
 OPTIONS:返回服务器支持的HTTP请求方法。

 GET:获取 
 POST:新增 
 PUT:更新 
 DELETE:删除 

  • 控制权转移

5.3 模板引擎

模板引擎(Template Engine)是一个从页面模板根据一定的规则生成 HTML 的工具。它的发轫可以追溯到 1996 年 PHP 2.0 的诞生。PHP 原本是 Personal Home Page Tools(个人主页工具)的简称,用于取代 Perl 和 CGI 的组合,其功能是让代码嵌入在 HTML 中执行,以产生动态的页面,因此 PHP 堪称是最早的模板引擎的雏形。随后的 ASP、JSP 都沿用了这个模式,即建立一个 HTML 页面模板,插入可执行的代码,运行时动态生成 HTML。

 页面功能逻辑与页面布局样式耦合,网站规模变大以后逐渐难以维护。
 语法复杂,对于非技术的网页设计者来说门槛较高,难以学习。
 功能过于全面,页面设计者可以在页面上编程,不利于功能划分,也使模板解析效
率降低。

  • 使用模板引擎

ejs 的标签系统非常简单,它只有以下3种标签。
 <% code %>:JavaScript 代码。
 <%= code %>:显示替换过 HTML 特殊字符的内容。
 <%- code %>:显示原始 HTML 内容。

  • 页面布局
  • 片段视图

Express 的视图系统还支持片段视图(partials),它就是一个页面的片段,通常是重复的内容,用于迭代显示。通过它你可以将相对独立的页面块分割出去,而且可以避免显式地使用 for 循环。

5.4 建立微博网站

  • 功能分析
    开发中的一个大忌就是没有想清楚要做什么就开始动手,因此我们准备在动手实践之前先规划一下网站的功能,即使是出于学习目的也不例外。首先,微博应该以用户为中心,因此需要有用户的注册和登录功能。微博网站最核心的功能是信息的发表,这个功能涉及许多方面,包括数据库访问、前端显示等。一个完整的微博系统应该支持信息的评论、转发、圈点用户等功能,但出于演示目的,我们不能一一实现所有功能,只是实现一个微博社交网站的雏形。
  • 路由规划
  1.  /:首页 
  2.  /u/[user]:用户的主页
  3.  /post:发表信息 
  4.  /reg:用户注册 
  5.  /login:用户登录 
  6.  /logout:用户登出 
app.get('/', routes.index); 
app.get('/u/:user', routes.user); 
app.post('/post', routes.post); 
app.get('/reg', routes.reg); 
app.post('/reg', routes.doReg); 
app.get('/login', routes.login); 
app.post('/login', routes.doLogin); 
app.get('/logout', routes.logout); 
exports.index = function(req, res) { 
 res.render('index', { title: 'Express' }); 
}; 
exports.user = function(req, res) { 
}; 
exports.post = function(req, res) { 
}; 
exports.reg = function(req, res) { 
}; 
exports.doReg = function(req, res) { 
}; 
exports.login = function(req, res) { 
}; 
exports.doLogin = function(req, res) { 
}; 
exports.logout = function(req, res) { 
}; 
  • 界面设计
  • 使用 Bootstrap
  • 连接数据库
  • 会话支持
  •  用户模型
  • 视图交互
app.get('/login', function(req, res) { 
 res.render('login', { 
 title: '用户登入', 
 }); 
}); 
app.post('/login', function(req, res) { 
 //生成口令的散列值
 var md5 = crypto.createHash('md5'); 
 var password = md5.update(req.body.password).digest('base64'); 
 
 User.get(req.body.username, function(err, user) { 
 if (!user) { 
 req.flash('error', '用户不存在'); 
 return res.redirect('/login'); 
 } 
 if (user.password != password) { 
 req.flash('error', '用户口令错误'); 
 return res.redirect('/login'); 
 } 
 req.session.user = user; 
 req.flash('success', '登入成功'); 
 res.redirect('/'); 
 }); 
}); 
app.get('/logout', function(req, res) { 
 req.session.user = null; 
 req.flash('success', '登出成功'); 
 res.redirect('/'); 
}); 
  • 页面权限控制
var crypto = require('crypto'); 
var User = require('../models/user.js'); 
module.exports = function(app) { 
 app.get('/', function(req, res) { 
 res.render('index', { 
 title: '首页' 
 }); 
 }); 
 
 app.get('/reg', checkNotLogin); 
 app.get('/reg', function(req, res) { 
 res.render('reg', { 
 title: '用户注册', 
 }); 
 }); 
 
 app.post('/reg', checkNotLogin); 
 app.post('/reg', function(req, res) { 
 //检验用户两次输入的口令是否一致
 if (req.body['password-repeat'] != req.body['password']) { 
 req.flash('error', '两次输入的口令不一致'); 
 return res.redirect('/reg'); 
 } 
 
 //生成口令的散列值
 var md5 = crypto.createHash('md5'); 
 var password = md5.update(req.body.password).digest('base64'); 
 
 var newUser = new User({ 
 name: req.body.username, 
 password: password, 
 });
//检查用户名是否已经存在
 User.get(newUser.name, function(err, user) { 
 if (user) 
 err = 'Username already exists.'; 
 if (err) { 
 req.flash('error', err); 
 return res.redirect('/reg'); 
 } 
 //如果不存在则新增用户
 newUser.save(function(err) { 
 if (err) { 
 req.flash('error', err); 
 return res.redirect('/reg'); 
 } 
 req.session.user = newUser; 
 req.flash('success', '注册成功'); 
 res.redirect('/'); 
 }); 
 }); 
 }); 
 
 app.get('/login', checkNotLogin); 
 app.get('/login', function(req, res) { 
 res.render('login', { 
 title: '用户登入', 
 }); 
 }); 
 
 app.post('/login', checkNotLogin); 
 app.post('/login', function(req, res) { 
 //生成口令的散列值
 var md5 = crypto.createHash('md5'); 
 var password = md5.update(req.body.password).digest('base64'); 
 
 User.get(req.body.username, function(err, user) { 
 if (!user) { 
 req.flash('error', '用户不存在'); 
 return res.redirect('/login'); 
 } 
 if (user.password != password) { 
 req.flash('error', '用户口令错误'); 
 return res.redirect('/login'); 
 } 
 req.session.user = user; 
 req.flash('success', '登入成功'); 
 res.redirect('/'); 
 }); 
 });  
app.get('/logout', function(req, res) { 
 req.session.user = null; 
 req.flash('success', '登出成功'); 
 res.redirect('/'); 
 }); 
}; 
function checkLogin(req, res, next) { 
 if (!req.session.user) { 
 req.flash('error', '未登入'); 
 return res.redirect('/login'); 
 } 
 next(); 
} 
function checkNotLogin(req, res, next) { 
 if (req.session.user) { 
 req.flash('error', '已登入'); 
 return res.redirect('/'); 
 } 
 next(); 
} 
  • 发表微博
  •  微博模型
// models/post.js
var mongodb = require('./db'); 
function Post(username, post, time) { 
 this.user = username; 
 this.post = post; 
if (time) { 
 this.time = time; 
 } else { 
 this.time = new Date(); 
 } 
}; 
module.exports = Post; 
Post.prototype.save = function save(callback) { 
 // 存入 Mongodb 的文档
 var post = { 
 user: this.user, 
 post: this.post, 
 time: this.time, 
 }; 
 mongodb.open(function(err, db) { 
 if (err) { 
 return callback(err); 
 } 
 // 读取 posts 集合
 db.collection('posts', function(err, collection) { 
 if (err) { 
 mongodb.close(); 
 return callback(err); 
 } 
 // 为 user 属性添加索引
 collection.ensureIndex('user'); 
 // 写入 post 文档
 collection.insert(post, {safe: true}, function(err, post) { 
 mongodb.close(); 
 callback(err, post); 
 }); 
 }); 
 }); 
}; 
Post.get = function get(username, callback) { 
 mongodb.open(function(err, db) { 
 if (err) { 
 return callback(err); 
 } 
 // 读取 posts 集合
 db.collection('posts', function(err, collection) { 
 if (err) { 
 mongodb.close(); 
 return callback(err); 
 }
// 查找 user 属性为 username 的文档,如果 username 是 null 则匹配全部
 var query = {}; 
 if (username) { 
 query.user = username; 
 } 
 collection.find(query).sort({time: -1}).toArray(function(err, docs) { 
 mongodb.close(); 
 if (err) { 
 callback(err, null); 
 } 
 // 封装 posts 为 Post 对象
 var posts = []; 
 docs.forEach(function(doc, index) { 
 var post = new Post(doc.user, doc.post, doc.time); 
 posts.push(post); 
 }); 
 callback(null, posts); 
 }); 
 }); 
 }); 
};  
  • 发表微博
app.post('/post', checkLogin); 
app.post('/post', function(req, res) { 
 var currentUser = req.session.user; 
 var post = new Post(currentUser.name, req.body.post); 
 post.save(function(err) { 
 if (err) { 
 req.flash('error', err); 
 return res.redirect('/'); 
 } 
 req.flash('success', '发表成功'); 
 res.redirect('/u/' + currentUser.name); 
 }); 
}); 
  • 用户页面
app.get('/u/:user', function(req, res) { 
 User.get(req.params.user, function(err, user) { 
 if (!user) { 
 req.flash('error', '用户不存在'); 
 return res.redirect('/'); 
 } 
 Post.get(user.name, function(err, posts) { 
 if (err) { 
 req.flash('error', err); 
 return res.redirect('/'); 
 } 
 res.render('user', { 
 title: user.name, 
 posts: posts, 
 }); 
 }); 
 }); 
}); 

猜你喜欢

转载自blog.csdn.net/boss2967/article/details/82995310