目录
一.MongoDB 数据库
1.数据库概述及环境搭建
- 使用数据库原因:
- 动态网站中的数据都是存储在数据库中的
- 数据库可以 持久存储 客户端通过表单收集的用户信息 通过数据库软件本身对数据进行高效管理
- 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 集合关联
- 查询文章的所有信息 包括 发表用户详细信息,就用到集合关联
- 使用 _id 对集合进行关联:type: mongoose.Schema.Types.ObjectId(集合规则里面书写)
- 使用 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 时,呈现修改页面,并实现修改用户信息功能,修改用户信息分为两大步骤:
- 增加页面路由呈现页面:在点击修改按钮的时候 将用户ID传递到当前页面,从数据库中查询当前用户信息 将用户信息展示到页面中
- 实现用户修改功能:指定表单的提交地址以及请求方式,接受客户端传递过来的修改信息 找到用户 将用户信息更改为最新的
- 当用户访问 /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请求响应、数据库、模板引擎、静态资源访问
- 制作流程:
- 建立项目文件夹并生成项目描述文件:
- 创建网站服务器实现客户端和服务器端通信(数据库文件)
- 连接数据库并根据需求设计学员信息表(数据库文件)
- 创建路由并实现页面模板呈递(路由文件)
- 实现静态资源访问(静态资源文件)
- 实现学生信息添加功能(路由文件)
- 实现学生信息展示功能(路由文件)
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 命令下载
- 框架特性:
- 提供了方便简洁的 路由定义方式
- 对 获取HTTP请求参数 进行了简化处理
- 对 模板引擎 支持程度高,方便渲染 动态 HTML页面
- 提供了 中间件机制 有效控制 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)
- 中间件主要由两部分构成:中间件方法、请求处理函数
- 中间件方法由 Express提供,负责拦截请求
- 请求处理函数由 开发人员提供,负责处理请求
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>