前后端交互(二)——MongoDB + artTemplate + Express

目录

一.MongoDB 数据库

1.数据库概述及环境搭建  

2.MongoDB 增删改查

二.模板引擎 artTemplate

1.基础概念  

2.语法  

3.学生档案管理

三.Express框架

1.Express框架简介及初体验  

2.Express中间件  

3.Express框架请求处理  

4.express-art-template 模板引擎


一.MongoDB 数据库

1.数据库概述及环境搭建  

  • 使用数据库原因:
  1. 动态网站中的数据都是存储在数据库中的
  2. 数据库可以 持久存储 客户端通过表单收集的用户信息 通过数据库软件本身对数据进行高效管理
  • MongoDB可视化软件:是使用 图形界面操作数据库 的一种方式
  • 数据库软件 可以包含多个数据仓库,每个 数据仓库 中包含多个数据集合,每个 数据集合 中包含多条 文档
  • 使用 Node.js 操作 MongoDB数据库 需要依赖 Node.js第三方包 mongoose
  • 使用 npm install mongoose 命令下载
  • 数据库启动:在命令行工具中运行 net start mongoDB 即可启动 MongoDB,否则 MongoDB 将无法连接
  • 数据库连接:引入 mongoose第三方模块操作数据库,使用 mongoose 提供的 connect()方法 即可连接
  • mongoose.connect().then().catch() ←这个格式说明,数据库连接是 异步函数
const mongoose = require('mongoose'); // 引入第三方模块操作数据库
// { useNewUrlParser: true } 这是链接数据库的过程中 系统提示的代码 需要进行添加 数据库解析问题
mongoose.connect('mongodb://localhost/playground', { useNewUrlParser: true, useUnifiedTopology: true})
    .then(() => console.log('数据库连接成功'))
    .catch(err => console.log(err, '数据库连接失败'));
  • 创建数据库:MongoDB中,不需要显式创建数据库,如果使用的数据库不存在,MongoDB会自动创建 

2.MongoDB 增删改查

2.1 创建集合

  • 创建集合分两步:①设定集合规则 ②创建集合
  • 创建 mongoose.Schema 构造函数的实例 即可创建集合
// 设定集合规则 courseSchema本质上是构造函数
 const courseSchema = new mongoose.Schema({
     name: String,
     author: String,
     tags:hobbies: [String],
     isPublished: Boolean
 });
// 创建集合 集合名字必须大写字母开头 Course本质上是构造函数 
 const Course = mongoose.model('Course', courseSchema); // courses 数据库中自动变化了集合名字 小写+s

2.2 创建文档

  • 创建文档 = 向集合中插入数据
  • 创建文档步骤:①创建集合实例 ②调用实例对象下的 save()方法,将数据保存到数据库中
  // 创建集合实例 方法一(常用)
 const course = new Course({
     name: 'Node.js Course',
     author: '茶茶子',
     tags: ['node', 'backend'],
     isPublished: true
 });
 // 创建集合实例 方法二:集合.create({}).then(result).catch(err) → 这就是个 promise对象格式
Course.create({name: 'mongoDB Course', author: '茶茶子', isPublished: false})
	  .then(result => {
	  	console.log(result)
	  })
 // 调用实例对象下的 save()方法,将数据保存到数据库中
course.save();

2.3 mongoDB 数据库导入数据

  • 找到 mongodb数据库的安装目录,将安装目录下的 bin目录放置在 path环境变量中
  • mongoimport -d 数据库名称 -c 集合名称 --file 要导入的数据文件(注意 file前面两个--)
  • 直接在当前目录 cmd命令窗口 运行该命令会失败 (找不到命令) 
  • 必须去桌面重新打开 cmd命令窗口 逐步 cd到指定目录 再运行该命令

2.4 查询文档

  • .find():查询所有文档
  • .find({属性:}):通过 属性字段 查找文档
  • .findOne():返回一条文档 默认返回当前集合中的第一条文档
  • .find({属性:{$gt:,$lt:}}):查询集合中 年龄字段大于20并且小于40的 文档
  • .find({属性:{$in:['']}}):查询集合中 hobbies字段值包含足球的 文档
  • .find().select(''):选择要查询的字段 空格隔开 不想查的字段 在前面加- 如不想查询_id → -_id
  • .find().sort('+属性'):根据字段进行 升序排列 
  • .find().sort('-属性'):根据字段进行 降序排列 
  • .find().skip(2).limit(3):查询文档跳过前两条结果 限制显示3条结果 用于分页
// .find():查询所有文档
User.find().then(result => console.log(result));
// .find({属性:}):通过 属性字段 查找文档
User.find({ _id: '5c09f267aeb04b22f8460968' }).then(result => console.log(result))
// .findOne():返回一条文档 默认返回当前集合中的第一条文档
User.findOne({name: '李四'}).then(result => console.log(result))
// .find({属性:{$gt:,$lt:}}):查询集合中 年龄字段大于20并且小于40的 文档
User.find({age: {$gt: 20, $lt: 40}}).then(result => console.log(result))
// .find({属性:{$in:['']}}):查询集合中 hobbies字段值包含足球的 文档
User.find({hobbies: {$in: ['足球']}}).then(result => console.log(result))
// .find().select(''):选择要查询的字段 空格隔开 不想查的字段 在前面加- 如不想查询_id → -_id
User.find().select('name email -_id').then(result => console.log(result))
// .find().sort('+属性'):根据字段进行 升序排列 
User.find().sort('age').then(result => console.log(result))
// .find().sort('-属性'):根据字段进行 降序排列 
User.find().sort('-age').then(result => console.log(result))
// .find().skip(2).limit(3):查询文档跳过前两条结果 限制显示3条结果 用于分页
User.find().skip(2).limit(3).then(result => console.log(result))

2.5 删除文档

  • .findOneAndDelete({}):查找一条文档并删除 默认返回删除的文档 如果查询条件匹配多个 就删除第一个匹配文档
  • .deleteMany({}):删除多条文档 返回对象{n:删除文档个数}
// .findOneAndDelete({}):查找一条文档并删除 默认返回删除的文档 如果查询条件匹配多个 就删除第一个匹配文档
User.findOneAndDelete({_id: '5c09f267aeb04b22f8460968'}).then(result => console.log(result))
// .deleteMany({}):删除多条文档 返回对象{n:删除文档个数}
User.deleteMany({}).then(result => console.log(result))

2.6 更新文档

  • .updateOne({},{}):更新一个文档
  • .updateMany({},{}):更新多个文档
User.updateOne({name: '李四'}, {age: 120, name: '李狗蛋'}).then(result => console.log(result))
User.updateMany({}, {age: 300}).then(result => console.log(result))

2.7 mongoose验证

  • 在创建集合规则时,可以设置当前字段的验证规则,验证失败就则输入插入失败
const postSchema = new mongoose.Schema({    // 创建集合规则
	title: {
		type: String,
		required: [true, '请传入文章标题'], // 必选字段
		minlength: [2, '文章长度不能小于2'],// 字符串的最小长度
		maxlength: [5, '文章长度最大不能超过5'], // 字符串的最大长度	
		trim: true // 去除字符串两边的空格
	},
	age: {
		type: Number,	
		min: 18, // 数字的最小范围
		max: 100 // 数字的最大范围
	},
	publishDate: {
		type: Date, // 时间类型
		default: Date.now // 默认值
	},
	category: {
		type: String,
		enum: {	// 枚举 列举出当前字段可以拥有的值
			values: ['html', 'css', 'javascript', 'node.js'],
			message: '分类名称要在一定的范围内才可以' // 自定义错误信息
		}},
	author: {
		type: String,
		validate: { // 自定义验证器
			validator: v => { // 返回布尔值 true 验证成功 false 验证失败 v 要验证的值
				return v && v.length > 4 // 存在这个值 并且这个值大于4
			},
			message: '传入的值不符合验证规则' // 自定义错误信息
		}}});
// 创建集合
const Post = mongoose.model('Post', postSchema);
// 相机和中插入值 这是利用了 promise对象 .create({}).then().catch()
Post.create({title:'tea', age: 21, category: 'java', author: 'teameow'})
	.then(result => console.log(result))
	.catch(error => {	
		const err = error.errors; // 获取错误信息对象
		for (var attr in err) {	// 循环错误信息对象
			console.log(err[attr]['message']); // 将错误信息打印到控制台中
		}})

2.8 集合关联

  • 查询文章的所有信息 包括 发表用户详细信息,就用到集合关联
  1. 使用 _id 对集合进行关联:type: mongoose.Schema.Types.ObjectId(集合规则里面书写)
  2. 使用 populate()方法 进行关联集合查询:.find().populate('author').then()(集合里面书写)
const userSchema = new mongoose.Schema({ // 用户集合规则
	name: {
		type: String,
		required: true    }});
const postSchema = new mongoose.Schema({ // 文章集合规则
	title: {
		type: String    },
	author: {
		type: mongoose.Schema.Types.ObjectId, // 作者id 是关联 用户id的 这是固定写法
		ref: 'User' // 关联的集合 就是用户集合 
    }});
const User = mongoose.model('User', userSchema); // 用户集合
const Post = mongoose.model('Post', postSchema); // 文章集合
User.create({name: 'itheima'}).then(result => console.log(result)); // 创建用户
// 创建文章 这里的文章作者就要复制 用户id 在数据库中的编号(系统自动生成的)
Post.create({titile: 'wenzhang', author: '5c0caae2...'}).then(result => console.log(result));
// 使用 populate()方法进行关联集合查询
Post.find().populate('author').then(result => console.log(result))

2.9 用户信息增删查改

  • 搭建网站服务器,实现客户端与服务器端的通信
  • 连接数据库,创建用户集合,向集合中插入文档
  • 当用户访问 /list 时,将所有用户信息查询出来
  • 将用户信息和表格html 进行拼接并将拼接结果响应回客户端
  • 当用户访问 /add 时,呈现表单页面,并实现添加用户信息功能
  • 当用户访问 /modify 时,呈现修改页面,并实现修改用户信息功能,修改用户信息分为两大步骤:
  1. 增加页面路由呈现页面:在点击修改按钮的时候 将用户ID传递到当前页面,从数据库中查询当前用户信息 将用户信息展示到页面中
  2. 实现用户修改功能:指定表单的提交地址以及请求方式,接受客户端传递过来的修改信息 找到用户 将用户信息更改为最新的
  • 当用户访问 /delete 时,实现用户删除功能
  • 将数据库连接文件 index.js + User集合创建文件 user.js 这两个和数据库有关的文件,放在 model文件夹下 
  • 在 app.js 中引入 第三方模块 index.js 和 user.js
  • 通过 node app.js  在浏览器中输入 localhost:3000/list 即可访问主页
  • 此方法存在问题:拼接字符串非常不友好
  • app.js:
const http = require('http'); // 搭建网站服务器,实现客户端与服务器端的通信
const app = http.createServer(); // 创建服务器
const url = require('url'); // url.parse():对请求地址进行处理,去除参数
const querystring = require('querystring'); // 将接受的参数字符串转换为对象格式
// 下面这两个模块 是我们自己分出去的 数据库连接模块 和 创建集合模块
require('./model/index.js');
const User = require('./model/user'); // 引入第三方模块的后缀名是可以省略的
// 为服务器对象添加请求事件
app.on('request', async(req, res) => { // 此时使用异步函数 就可以通过返回值拿到 数据库查询结果
    // 当用户访问/list时,将所有用户信息查询出来
    // 实现路由功能:获取请求方式、请求地址,对他们做出判断
    // if (method == 'GET') {} else if (method == 'POST'){}
    // 请求方式
    const method = req.method;
    // 请求地址:req.url 是带有 get 请求参数的请求地址 需要进行处理得到纯粹地址 pathname
    const { pathname, query } = url.parse(req.url, true); // 对象解构获得get请求地址 以及 get请求参数 true:转换为对象
    // 对请求方式、请求地址进行判断
    if (method == 'GET') { // GET:一般用于数据的请求,页面的呈现 注意此处:方式名称 必须大写
        // 呈现用户列表页面
        // 将 html页面 的代码作为模板字符串赋给 list ,请求成功 就让服务器给客户响应此模板字符串
        if (pathname == '/list') {
            // 查询的用户信息:从数据库中查询用户信息 将用户信息展示在列表中
            let users = await User.find(); // 不使用then()的方式拿到返回结果 而是用await 此关键字必用异步函数
            // html字符串,最终 html字符串 应该和 查询的用户信息进行拼接
            let list = `    // ......
					<title>用户列表</title>
                        // ......
						<a href="/add" class="btn btn-primary">添加用户</a>
						<table class="table table-striped table-bordered"><tr>
								<td>用户名</td>
								<td>年龄</td>
                                // ....    </tr>    `;
            // 对数据进行循环操作:users是我们查询到的全部数据数组
            users.forEach(item => { // item:是数组中的每个 对象元素
                list += `
					<tr>
						<td>${item.name}</td> 
						<td>${item.age}</td>
						<td>    `;
                // 在 list字符串中不可以进行循环操作,所以要把list拆开,在中间单独执行循环处理 爱好
                item.hobbies.forEach(item => { // item:是爱好这个选项里的对象元素
                    list += `<span>${item}</span>`;
                })
                list += `</td>
						<td>${item.email}</td>
						<td>
							<a href="/remove?id=${item._id}" class="btn btn-danger btn-xs">删除</a>
							<a href="/modify?id=${item._id}" class="btn btn-success btn-xs">修改</a>
						</td></tr>`;     });
            // list 暂时去除了中间部分的 tr 因为要根据数据库动态添加
            list += `
						</table>
					</div>
				</body>
				</html>    `;
            res.end(list); // 将用户信息和表格HTML进行拼接并将拼接结果响应回客户端
        } else if (pathname == '/add') {
            // 呈现添加用户表单页面
            let add = `
                    // ......
					<title>用户列表</title>
						<h3>添加用户</h3>
						<form method="post" action="/add">
						    <label>用户名</label>
						    <input name="name" type="text" class="form-control" placeholder="请填写用户名">`;     //.....
            res.end(add)}
        // 当用户访问/modify时,呈现修改页面,并实现修改用户信息功能
        else if (pathname == '/modify') {
            // 在数据库中按照ID查询 此时的ID 是通过地址栏返回的 query字符串(被转为对象)拿到的
            let user = await User.findOne({ _id: query.id });
            let hobbies = ['足球', '篮球', '橄榄球', '敲代码', '抽烟', '喝酒', '烫头', '吃饭', '睡觉', '打豆豆']
            console.log(user)
                // 呈现修改用户表单页面
                // <form method="post" action="/modify?id=${user._id}"> 这是 post 和 get 混合使用
            let modify = `
				<!DOCTYPE html>
                // ......
						<h3>修改用户</h3>
						<form method="post" action="/modify?id=${user._id}">
						  <div class="form-group">
						    <label>用户名</label>
						    <input value="${user.name}" name="name" type="text" class="form-control" placeholder="请填写用户名">
						  </div>
                          // ...
						  <div class="form-group">
						    <label>请选择爱好</label>
						    <div>`;
            hobbies.forEach(item => {
                // 判断当前循环项在不在用户的爱好数据组
                let isHobby = user.hobbies.includes(item);
                if (isHobby) {
                    modify += `
						<label class="checkbox-inline">
						  <input type="checkbox" value="${item}" name="hobbies" checked> ${item}</label>`
                } else {
                    modify += `
						<label class="checkbox-inline">
						  <input type="checkbox" value="${item}" name="hobbies">${item}</label>` }})
            modify += `......`;
            res.end(modify)
        } else if (pathname == '/remove') {
            // res.end(query.id)
            await User.findOneAndDelete({ _id: query.id });
            res.writeHead(301, {
                Location: '/list'
            });
            res.end();}
    } else if (method == 'POST') { // POST:一般用于功能实现 如添加数据删除数据
        // 用户添加功能 post接收请求参数通过 data 和 end 事件
        if (pathname == '/add') {
            // 接受用户提交的信息
            let formData = '';
            // 接受post参数:data是一部分一部分传的,formDate最终是将这些一部分拼接起来
            req.on('data', param => {
                    formData += param;
                })
                // post参数接受完毕:此时接受的参数是字符串格式 要想转换为对象 需要用到 queryString.parse()
            req.on('end', async() => { // 此时的函数必须是 异步函数 才能配合下面的 await一起使用
                let user = querystring.parse(formData) //服务器接受的参数 转换为 可以被数据库存储的对象格式
                    // 将用户提交的信息添加到数据库中
                await User.create(user); //create():向数据库中添加数据 参数需是对象
                // 书写响应头,当数据添加成功后指导页面自动跳转到列表页,301 代表重定向    
                res.writeHead(301, {
                    Location: '/list' // location 跳转地址,注意这里是对象格式接收location
                });
                res.end(); //必须写.end(),否则即使重定向了,服务器也无法认定这是回复结束
            })
        } else if (pathname == '/modify') {
            let formData = '';
            req.on('data', param => {
                formData += param;
            })
            req.on('end', async() => {
                let user = querystring.parse(formData)
                    // 此处查询修改用户id 是通过 get中的query得到重定向地址请求参数
                await User.updateOne({ _id: query.id }, user);
                res.writeHead(301, {
                    Location: '/list'
                });
                res.end();
            })}}});
app.listen(3000); // 监听端口

二.模板引擎 artTemplate

1.基础概念  

  • 模板引擎是第三方模块,让开发者以更加友好的方式拼接字符串,使项目代码更加清晰、易于维护
  • art-template 模板引擎:
  • 在命令行工具中使用 npm install art-template 命令进行下载
  • 使用 const template = require('art-template') 引入模板引擎
  • 告诉模板引擎要拼接的数据和模板在哪 const html = template(‘模板路径’, 数据);
  • 使用 模板语法 告诉模板引擎,模板与数据应该如何进行拼接
// 这是 .js文件 在 node命令行中执行,利用的模板是 views文件夹中的 .art文件,本页面的数据对象会传给模板页面
const template = require('art-template');
const path = require('path'); // 进行路径拼接
// 模板文件 .art一般放在 views文件夹中 
// 左边views指的是:模板绝对路径 右边views指的是:views文件夹
const views = path.join(__dirname, 'views', '01.art'); 
// html 指的是拼接完的字符串(页面
const html = template(views, { //  template(‘模板路径’, 数据对象);
	name: '张三',
	content: '<h1>我是标题</h1>' // 模板文件中无法直接解析<h2>标签 需要{{@}} <%-%>
})
console.log(html);

2.语法  

2.1 模板语法

  • art-template 同时支持两种模板语法:标准语法和原始语法
  • 标准语法可以让模板更容易读写,原始语法具有强大的逻辑处理能力
  • 标准语法: {{ 数据 }}
  • 原始语法:<%=数据  %>
<!DOCTYPE html>
// ...
<body>
	<!-- 标准语法 {{}} -->
	<p>{{ name }}</p> // 传入 .js页面的变量
	<p>{{ 1 + 1 }}</p> // 直接计算结果
	<p>{{ 1 + 1 == 2 ? '相等' : '不相等' }}</p> // 三目运算符
	<p>{{@ content }}</p> // 原文输出@:解析 <h2>标签
	<!-- 原始语法 <%= %> -->
	<p><%= name %></p> // 传入 .js页面的变量
	<p><%= 1 + 2 %></p> // 直接计算结果
	<p><%= 1 + 1 == 2 ? '相等' : '不相等' %></p> // 三目运算符
	<p><%- content%></p> // 原文输出-:解析 <h2>标签
    // ...

2.2 输出

  • 将某项数据输出在模板中,标准语法和原始语法如下:
  • 标准语法:{{ 数据 }}
  • 原始语法:<%=数据 %> 注意 = 表示输出

2.3 原文输出

  • 如果数据中携带 HTML标签,默认模板引擎不会解析标签,会将其转义后输出
  • 标准语法:{{@ 数据 }}
  • 原始语法:<%- 数据 %> 注意是 = 换成了 -

2.4 条件判断 

  • {{/if}} // 一定不要忘记这个 {{/if}}!!!
  • <% } %> // 此处因为没有输出 所以不需要写 = 
<!-- 标准语法 --> 
{{if age > 18}}
	年龄大于18
{{else if age < 15 }}
	年龄小于15
{{else}}
	年龄不符合要求
{{/if}} // 一定不要忘记这个 {{/if}}!!!
 <!-- 原始语法 -->
<% if (age > 18) { %> // 此处因为没有输出 所以不需要写 = 
	年龄大于18
<% } else if (age < 15) { %>
	年龄小于15
<% } else { %>
	年龄不符合要求
<% } %>

2.5 循环

  • 标准语法:{{each 数据}} {{/each}}
  • 原始语法:<% for() { %> <% } %>
// .js文件中 如果串了一个 对象数组 给模板
const html = template(views, {
	users: [{name: '张三', age: 20, sex: '男'},
            {name: '李四', age: 30, sex: '男'},
            {name: '玛丽', age: 15, sex: '女'}]    });

 <!-- 模板文件 遍历标准语法 -->
<ul> {{each users}}    <li> // users是对象数组名
			{{$value.name}} // value是对象数组中的一个对象 不要忘记$ 这是遍历对象的各个属性
			{{$value.age}}
			{{$value.sex}}
	    	</li>        {{/each}} </ul>
  <!-- 模板文件 遍历原始语法 -->
<ul> <% for (var i = 0; i < users.length; i++) { %>    <li>
			<%=users[i].name %>
			<%=users[i].age %>
			<%=users[i].sex %>
		</li>     <% } %>    </ul>

2.6 子模板

  • 使用子模板可以将网站公共区块(头部、底部)抽离到单独的文件中。
  • 标准语法:{{include '模板'}}
  • 原始语法:<%include('模板') %>
const html = template(views, {    msg: '我是首页'    });  // .js文件
// ===========================================================
<!-- 原始语法 -->
<% include('./common/header.art') %> // 我是头部
<div> {{ msg }} </div> // 我是首页
<!-- 标准语法 -->
{{ include './common/footer.art' }} // 我是底部 // .art文件

2.7 模板继承

  • 使用 模板继承 可以将 网站HTML骨架 抽离到 单独文件中,其他页面模板可以继承骨架文件
const html = template(views, {    msg: '首页模板'    }); // .js文件 开发人员要写的信息 

<!DOCTYPE html>    // ./common/layout.art 模板骨架文件 用{{block ''}} {{/block}} 拆坑
<head>
	<meta charset="UTF-8">
	{{block 'link'}} {{/block}}
</head>
<body>    {{block 'content'}} {{/block}}    
</body>

{{extend './common/layout.art'}} // index.art 首页art 继承了模板骨架 {{extend ''}}
{{block 'content'}}    <p> {{ msg }} </p>
{{/block}}
// 用{{block ''}} {{/block}} 填坑 
{{block 'link'}}    <link rel="stylesheet" type="text/css" href="./main.css">
{{/block}}

2.8 模板配置 

  • 向模板中导入变量 template.defaults.imports.变量名 = 变量值;(这个是导入第三方模块中的方法使得 .art模板可以用 第三方模块的方法)
  • 设置模板根目录 template.defaults.root = 模板目录
  • 设置模板默认后缀 template.defaults.extname = '.art'
const template = require('art-template'); // .js文件
const path = require('path');
const dateFormat = require('dateformat'); // 这是一个 格式化时间 的第三方模块
// 模板设置
template.defaults.root = path.join(__dirname, 'views'); // 设置模板的根目录
template.defaults.imports.dateFormat = dateFormat; // 导入模板变量(要用的第三方模块)
template.defaults.extname = '.html'; // 配置模板的默认后缀

const html = template('06.art', {
	time: new Date() // 获取的时间 是不友好的时间,要在 模板.art文件中调用 第三方模块dateformat处理
});
console.log(template('07', {}));    // 这里没写模板后缀,但是会自动添加我们设置的默认后缀
console.log(html);
// dateFormat(.js文件获得的要处理的时间, 时间格式'yyyy-mm-dd')
{{ dateFormat(time, 'yyyy-mm-dd')}} // .art文件 在模板文件中本来不可以调用第三方模块语法,设置后可以

3.学生档案管理

3.1 概述

  • HTTP请求响应、数据库、模板引擎、静态资源访问
  • 制作流程:
  1. 建立项目文件夹并生成项目描述文件:
  2. 创建网站服务器实现客户端和服务器端通信(数据库文件)
  3. 连接数据库并根据需求设计学员信息表(数据库文件)
  4. 创建路由并实现页面模板呈递(路由文件)
  5. 实现静态资源访问(静态资源文件)
  6. 实现学生信息添加功能(路由文件)
  7. 实现学生信息展示功能(路由文件)

3.2 第三方模块 router 实现路由

const getRouter = require('router') // 引入第三方模块 获得方法返回值
const router = getRouter(); // 通过模块方法 获取路由对象
router.get('/add', (req, res) => { // 调用 路由对象 提供的方法 创建路由 (这一步在路由文件中实现)
    res.end('Hello World!')
}) 
server.on('request', (req, res) => { 
    router(req, res) // 启用路由功能 (这一步在入口文件实现)
})

3.3 第三方模块 serve-static 实现静态资源访问服务

const serveStatic = require('serve-static') // 引入serve-static模块获取创建静态资源服务功能的方法
const serve = serveStatic('public') // 调用方法 创建静态资源服务 并指定 静态资源目录
server.on('request', () => { 
    serve(req, res) // 启用 静态资源服务功能
})

3.4 添加学生信息功能步骤分析

  • 在模板的表单中指定请求地址(action="/add")与请求方式(method="post")
  • 为各表单项添加 name属性(这些 name属性 被 post中的 data接收,拼成 formData,转换为对象格式存入数据库)
  • 添加实现学生信息功能路由(router.post('/add', (req, res) => {})
  • 接收客户端传递过来的学生信息(req.on('data', param => {})
  • 将学生信息添加到数据库中(req.on('end', async() => {})
  • 将页面重定向到学生信息列表页面(res.writeHead(301, {Location: '/list'}))

3.5 学生信息列表页面分析

  • 从数据库中将所有的学生信息查询出来(students = await Student.find())
  • 通过模板引擎将学生信息和 HTML模板进行拼接(let html = template('list.art', { students: students})
  • 将拼接好的 HTML模板响应给客户端
<th>{{$value.sex == '0' ? '男' : '女'}}</th> // 此处value指的是对象数组中的各个学生
<th>
	{{each $value.hobbies}} // 此处value指的是对象数组中的各个学生
			<span>{{$value}}</span> // 此处value指的是 爱好这个学生对象属性的值
	{{/each}}
</th>

三.Express框架

1.Express框架简介及初体验  

  • Express是一个基于 Node平台的 Web应用开发框架,可以使用 npm install express 命令下载
  • 框架特性:
  1. 提供了方便简洁的 路由定义方式
  2. 获取HTTP请求参数 进行了简化处理
  3. 模板引擎 支持程度高,方便渲染 动态 HTML页面
  4. 提供了 中间件机制 有效控制 HTTP请求
  • 使用 Express框架 创建 Web服务器:
  • send():内部会检测响应内容的类型、自动设置http状态码、自动设置响应的内容类型及编码:
const express = require('express'); // 引入express框架
const app = express(); // 创建网站服务器
// 当客户端发来 get请求
app.get('/' , (req, res) => {
	// send():内部会检测响应内容的类型、自动设置http状态码、自动设置响应的内容类型及编码
	res.send('Hello. Express');
})
app.listen(3000); // 监听端口
console.log('网站服务器启动成功');

2.Express中间件  

2.1 中间件简介

  • 中间件 = 一堆方法,可以接收客户端请求、可以对请求做出响应,也可以将请求继续交给下一个中间件处理(next)
  • 中间件主要由两部分构成:中间件方法、请求处理函数
  1. 中间件方法由 Express提供,负责拦截请求
  2. 请求处理函数由 开发人员提供,负责处理请求
 app.get('请求路径', '处理函数')   // 接收并处理 get请求
 app.post('请求路径', '处理函数')  // 接收并处理 post请求
  • 可以针对同一个请求设置多个中间件,对同一个请求进行多次处理
  • 默认情况下,请求从上到下依次匹配中间件,一旦匹配成功,终止匹配
  • 可以调用 next()方法 将请求的控制权交给下一个中间件,直到遇到结束请求的中间件
 app.get('/request', (req, res, next) => { // 用不到 next()时,可以省略不写 next参数
     req.name = "张三";
     next(); // 此中间件没有处理完,next()交给下一个中间件 继续处理请求并作出响应
 });
 app.get('/request', (req, res) => {
     res.send(req.name); 
 });

2.2 app.use中间件用法

  • app.use 匹配所有的请求方式,可以直接传入请求处理函数,代表接收所有的请求
  • app.use 第一个参数也可以传入请求地址,代表不论什么请求方式,只要是这个请求地址就接收这个请求
const express = require('express'); // 引入express框架
const app = express(); // 创建网站服务器
// 接收所有请求的中间件 不需要跟请求路径
app.use((req, res, next) => {
	console.log('请求走了app.use中间件');
	next()
})
// 跟请求路径 当客户端访问 /request请求时 走当前中间件
app.use('/request', (req, res, next) => {
	console.log('请求走了app.use / request中间件')
	next()
})
// .....app.get('/list', (req, res) => { .....
app.listen(3000); // 监听端口
console.log('网站服务器启动成功');

2.3 中间件应用

  • 路由保护,客户端在访问需要登录的页面时,用中间件判断用户登录状态,未登录则拦截请求,并直接响应
  • 网站维护公告,在所有路由的最上面定义接收所有请求的中间件,app.use((req, res, next) => {}
  • 为客户端响应 404状态码 及 提示信息 res.status(404).send('')
const express = require('express');
const app = express();
// 1.网站公告
// app.use((req, res, next) => {
// 	res.send('当前网站正在维护...')})
// 2.路由保护
app.use('/admin', (req, res, next) => {
	let isLogin = true;	// 用户没有登录
	if (isLogin) {	// 如果用户登录
		next()// 让请求继续向下执行
	}else { // 如果用户没有登录 直接对客户端做出响应
		res.send('您还没有登录 不能访问 /admin页面')
	}})
app.get('/admin', (req, res) => {
	res.send('您已登录 可以访问当前页面')
})
// 3.响应 404状态码及提示信息 res.status(404).send('')
app.use((req, res, next) => {
	res.status(404).send('当前访问的页面不存在')
})
app.listen(3000);
console.log('网站服务器启动成功');

2.4 错误处理中间件

  • 程序执行时,可能会出现无法预料的错误,比如文件读取失败,数据库连接失败,错误处理中间件集中处理错误
  • 程序出错时,调用next()方法,并且将错误信息通过 参数的形式 传递给 next()方法,触发错误处理中间件
const express = require('express');
const app = express();
const fs = require('fs');
app.get('/index', (req, res, next) => {
	// throw new Error('程序发生了未知错误')
	fs.readFile('./01.js', 'utf8', (err, result) => {
		if (err != null) {
			next(err) // 将错误作为 参数 传递给next(),触发错误处理中间件
		}else {
			res.send(result)
		}})})
// 错误处理中间件
app.use((err, req, res, next) => {
	res.status(500).send(err.message);
})
app.listen(3000);
console.log('网站服务器启动成功');

2.5 捕获错误

  • try{} catch(){}:捕获 异步函数以及其他同步代码 在执行过程中发生的错误,不能捕获 其他类型的API 发生的错误 
  • node.js中,异步API的错误都是通过 回调函数 获取的,支持Promise对象的异步API发生错误,通过 catch捕获
const express = require('express');
const app = express();
const promisify = require('util').promisify; // 将函数 改造为 异步函数
const fs = require('fs'); 
const readFile = promisify(fs.readFile); // 改造 fs模块的 readFile方法 为异步方法

app.get('/index', async (req, res, next) => {
	try { // 捕获 异步函数以及同步代码 执行过程中发生的错误
		await readFile('./aaa.js') // 这个文件不存在
	}catch (ex) {
		next(ex); // catch()捕获到错误,通过 next() 交给下面的 错误处理中间件 处理错误
	}
})
// 错误处理中间件
app.use((err, req, res, next) => {
	res.status(500).send(err.message);
})
app.listen(3000);
console.log('网站服务器启动成功');

3.Express框架请求处理  

3.1 构建模块化路由

const express = require('express');
const app = express(); // 创建网站服务器
const home = express.Router(); // 创建路由对象
app.use('/home', home); // 为 路由对象 匹配请求路径
// 创建二级路由
home.get('/index', (req, res) => {
	res.send('欢迎来到博客首页页面')
})
app.listen(3000);

3.2 GET参数获取

  • Express框架中,使用 req.query 即可获取GET参数,框架内部会将 GET参数 转换为 对象 并返回
 // 接收地址栏中问号后面的参数,例如: http://localhost:3000/?name=zhangsan&age=30
 app.get('/', (req, res) => {
    res.send(req.query)	// 接收 get请求参数
    console.log(req.query); // {"name": "zhangsan", "age": "30"}
 });

3.3 POST参数获取

  • Express框架中,用 req.body 即可接收 POST请求参数,需要借助 第三方包 body-parser
  • extended: false 方法内部使用 querystring模块 处理请求参数的格式(推荐)
  • extended: true 方法内部使用 qs模块 处理请求参数的格式
const express = require('express');
const bodyParser = require('body-parser'); // 接收 POST请求参数 所用的第三方包
const app = express();
// 拦截所有请求 extended: false 方法内部使用 querystring模块 处理请求参数的格式(推荐)
app.use(bodyParser.urlencoded({extended: false})) 
// 接收 post请求参数
app.post('/add', (req, res) => {
	res.send(req.body) 
})
app.listen(3000);

2.4 Express路由参数

  • use()方法:里面可以接受 对象格式的内容 做处理
  • get路由请求参数: /路由/:XXX/:XXX,如:'/index/:id/:name/:age'
  • 获取 get路由请求参数的方法:res.send(req.params)
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(fn ({a: 2})) // fn()方法接受了一个对象参数 {a:2}是实参对象属性
function fn (obj) { // obj是形参对象
	return function (req, res, next) {
		if (obj.a == 1) {
			console.log(req.url)
		}else {
			console.log(req.method)}
		next()    // use()方法处理完成后 交给下一个中间件继续处理请求
	}}
// get路由请求参数 /路由/:XXX/:XXX
app.get('/index/:id/:name/:age', (req, res) => {
	res.send(req.params)// 接收 get路由请求参数
})
app.listen(3000);

2.5 静态资源处理

  • Express内置的 express.static() 托管静态文件,例如:img文件、css文件、js文件等
// 实现静态资源访问功能
app.use('/static',express.static(path.join(__dirname, 'public')))
// 现在,public 目录下的文件就可以访问了
// http://localhost:3000/images/kitten.jpg
// http://localhost:3000/css/style.css
// http://localhost:3000/hello.html 

4.express-art-template 模板引擎

  • 模板引擎官方在 原art-template模板引擎 的基础上封装了 express-art-template,方便二者配合
  • 使用 npm install art-template express-art-template 命令进行安装(两个第三方模块)
const express = require('express');
const path = require('path');
const app = express();

// 模板配置
// 1.告诉express框架 使用 x模板引擎 渲染 x后缀的模板文件
app.engine('art', require('express-art-template'))
// 2.告诉express框架 模板存放的位置 第一个views是拼接后的完整路径 第二个views指文件夹
app.set('views', path.join(__dirname, 'views'))
// 3.告诉express框架 模板默认后缀
app.set('view engine', 'art');
// 渲染模板
app.get('/list', (req, res) => {
	res.render('list', {
		msg: 'list page'
	})})
app.listen(3000);
  • app.locals 对象:
  • 将变量设置到 app.locals对象下面,这个数据在 所有模板中 都可以获取到
const express = require('express');
const path = require('path');
const app = express();
// 模板配置
app.engine('art', require('express-art-template'))
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'art');

app.locals.users = [{
	name: 'zhangsan',
	age: 20
},{
	name: '李四',
	age: 30
}]
app.get('/index', (req, res) => {
	res.render('index', {
		msg: '首页'
	})
});
app.listen(3000);

// index.art 模板文件
{{ msg }} // 首页
<ul>
	{{each users}} // 遍历 app.locals.users对象数组
	<li>
		{{$value.name}}
		{{$value.age}}
	</li>
	{{/each}}
</ul>
发布了29 篇原创文章 · 获赞 21 · 访问量 1642

猜你喜欢

转载自blog.csdn.net/Lyrelion/article/details/105408569