命令行窗口

  1. 命令行窗口(小黑屏)、CMD窗口、终端、shell
  2. 打开:
    1. 开始 —> 运行 —> CMD —>回车
    2. 左下角搜索 —> CMD —> 回车
    3. 选中一个文件夹的url —> 输入CMD —> 回车
  3. 常用指令:
    1. dir 列出当前目录下的所有文件
    2. cd 目录名 进入到指定的目录, change directory
    3. md 目录名 新建一个文件夹,make directory
    4. rd 目录名 删除一个文件夹,remove d
    5. e: 进入E盘
  4. 目录
    1. . 表示当前目录
      1. 在网络里面可以省略 ./ 访问当前目录的文件
    2. .. 表示上一级目录
  5. 环境变量(windos系统变量)
    1. path ,更改后要重启shell才能在shell里面成功访问
    2. 当在shell里打开一个文件 或调用一个程序时,首先在当前路径找,若无则在环境变量的path的路径中寻找
    3. 在path中的文件和程序可以在任意位置访问

进程和线程

  1. 进程:
  2. 线程:
  3. 操作系统学了的,自己记

Node.js

1. 简介

  • nodejs 是服务器端的JS运行环境,使得JS可以和系统直接进行交互。原来JS在浏览器执行(本地的)。node底层是使用 C++ 编写的
  • node 的中 js 引擎使用的 Chrome 的 V8 引擎
  • Node 是对 ES 标准一个实现,Node也是一个JS引擎,在Node中不包含 DOM 和 BOM
  • 通过Node可以使 js 代码在服务器端执行
  • Node 中可以使用所有的内建对象
    • String Number Boolean Math Date RegExp Function Object Array
    • 而BOM和DOM都不能使用
    • 但是可以使用 console 也可以使用定时器(setTimeout() setInterval())
  • Node可以在后台来编写服务器
    • Node编写服务器都是单线程的服务器
  • 传统的服务器都是多线程的
    • 每进来一个请求,就创建一个线程去处理请求
  • Node的服务器单线程的
    • Node处理请求时是单线程,但是在后台拥有一个I/O线程池
  • node的特点:
    • 非阻塞、异步的I/O
    • 事件和回调函数
    • 单线程(主线程单线程,后台I/O线程池)
    • 跨平台

2. 模块化

  • ES5中没有原生支持模块化,我们只能通过script标签引入js文件来实现模块化
  • 在node中为了对模块管理,引入了CommonJS规范

    模块的引用

  • 使用 require()函数来引入一个模块

  • 例子:
    • var 变量 = require(“模块的标识”); // 模块标志一般为相对路径,以 ... 开头

模块的定义

  • 在node中一个js文件就是一个模块
  • 默认情况下在js文件中编写的内容,都是运行在一个独立的函数中,外部的模块无法访问
  • 在node中有一个全局对象 global ,它的作用和网页的 window 类似,在全局中创建的变量、函数都会作为global的属性、方法保存
  • arguments 对函数的参数进行封装的封装对象
    • 当node在执行模块(一个js文件)中的代码时,它会在其最顶部添加:

function (exports, require, module, __filename, __dirname) {

  • module 表示模块本身,且 exportsmodule 的一个属性,即 exports == module.exports

在最底部添加 }

  • 当定义变量不用 varlet 定义 而是直接写 a=10 则其为全局变量
    • 导出变量和函数,将需要暴露给外部的变量或方法设置为exports的属性
  • 使用 exports
  • 例子:

    1. exports.属性 = 属性值;<br /> exports.方法 = 函数;
  • 使用module.exports

  • 例子:

    1. module.exports.属性 = 属性值;<br /> module.exports.方法 = 函数;<br /> module.exports = { };<br />对象拷贝:![image.png](https://cdn.nlark.com/yuque/0/2021/png/495969/1618017531955-29fb8658-7231-4d3b-a32b-6a436f6543c5.png#height=449&id=Vyngv&margin=%5Bobject%20Object%5D&name=image.png&originHeight=356&originWidth=630&originalType=binary&ratio=1&size=45724&status=done&style=shadow&width=795)
  • 不能使用exports = { }的形式,因为exports和module.exports相当于上图的obj和obj2,若使用exports={},则是更改了exports变量(栈内存的值) 而不是更改对象

模块的标识

  • 模块的标识就是模块的名字或路径,node通过模块的标识来寻找模块
  • 对于核心模块(npm中下载的模块),直接使用模块的名字对其进行引入,首先在当前目录的 node_module 中找,若无则去上一级的 node_module 中找,上上一级,直到磁盘根目录

    1. var fs = require("fs");<br /> var express = require("express");<br />
  • 对于自定义的文件模块,需要通过文件的路径来对模块进行引入路径可以是绝对路径,如果是相对路径必须以 ./../ 开头

    1. var router = require("./router");<br />

    包(package)

  • 将多个模块组合为一个完整的功能,就是一个包

  • 包结构

    1. bin,二进制的可执行文件,一般都是一些工具包中才有<br /> libjs文件,库<br /> doc,文档<br /> test,测试代码<br /> package.json,包的描述文件
  • package.json

    • 它是一个json格式的文件,在它里面保存了包各种相关的信息,不能写注释

      1. name 包名<br /> version 版本<br /> dependencies 依赖<br /> main 包的主要的文件<br /> bin 可执行文件

      npm(Node Package Manager node的包管理器)

  • 通过npm可以对node中的包进行上传、下载、搜索等操作

  • npm会在安装完node以后,自动安装,送的
  • npm的常用指令

    1. npm -v 查看自己的npm的版本<br /> npm version 查看所有模块的版本<br /> npm init 初始化项目(创建package.json)<br /> npm i/install 包名, 安装指定的包<br /> **npm i/install 包名 --save**, 安装指定的包并添加依赖,即添加到package.json中<br /> npm i/install 包名 -g 全局安装(一般都是一些工具)<br /> npm i/install 安装当前项目所依赖的包<br /> npm s/search 包名, 搜索包 <br /> npm r/remove 包名, 删除一个包<br /> npm root -g,显示全局安装的位置
  • cnpm npm的淘宝镜像

文件系统(File System)

  • Buffer(缓冲区)
    • Buffer和数组的结构的非常类似,Buffer是用来存储二进制数据的,存储的都是二进制 以16进制显示,数字在输出时转化为10进制
      • buffer中的每一个元素的范围:00 - ff,一个字节。一个汉字占3字节
    • Buffer的方法
      • var buf = Buffer.from(字符串),将一个字符串中内容保存到一个buffer中
      • buf.toString(), 将buffer转换为一个字符串
      • Buffer.alloc(size),创建一个指定大小的buffer对象
      • Buffer.allocUnsafe(size),创建一个指定大小的buffer对象,可以包含敏感数据,没有清空内存之前的数据
  • fs模块(file system)
    • 在Node通过fs模块来对系统中的文件进行操作,fs模块是node中已经继承好了,不需要在使用npm下载,直接引入即可
    • 引入fs,var fs = require(“fs”);
    • fs模块中的大部分操作都提供了两种方法,同步方法和异步方法
      • 同步方法 带sync
      • 异步方法 没有sync,都需要回调函数
  • 写入文件

    1. 1.同步写入<br /> 2.异步写入<br /> 3.简单写入<br /> 4.流式写入
  • 读取文件

    1. 1.同步读取<br /> 2.异步读取<br /> 3.简单读取<br /> 4.流式读取
  • 方法

    • 打开文件

      • flags,打开文件的操作类型,r 只读,w 可写,a 追加 文件不存在则创建
      • mode,文件的操作权限

        1. fs.open(path, flags[, mode], callback),
      • 无返回值

      • callback的参数:err 出错信息,fd 文件标志符

        1. fs.openSync(path, flags[, mode]),
      • 返回值为文件的描述符(通过这个对文件进行操作)

    • 读写文件

      • fd,文件描述符
      • string,要写入的字符

        1. fs.write(fd, string[, position[, encoding]], callback),callback的参数 error<br /> fs.writeSync(fd, string[, position[, encoding]])<br /> fs.read(fd, buffer, offset, length, position, callback)<br /> fs.readSync(fd, buffer, offset, length, position)
    • 关闭文件

      1. fs.close(fd,callback)<br /> fs.closeSync(fd);

      ```javascript // 同步方式 var fs = require(“fs”); var fd = fs.openSync(“hello.txt”,”w”); fs.writeSync(fd, “今天花了150元”); fs.closeSync(fd); // 异步方式 var fs = require(“fs”); fs.open(“hello2.txt”, “w”,function(err, fd){ if(!err){ fs.write(fd, “下周去面试”, function(err){

      1. if(!err)
      2. console.log("写入成功");

      }); fs.close(fd,function(err){

      1. if(!err)
      2. console.log("关闭文件");

      }) } })

  1. - 简单文件读取和写入
  2. fs.writeFile(file, data[, options], callback),文件路径,要写入的数据,选项,写入完成后的回调函数 参数error<br /> fs.writeFileSync(file, data[, options])<br /> fs.readFile(path[, options], callback)<br /> fs.readFileSync(path[, options])
  3. ```javascript
  4. // 写
  5. var fs = require("fs");
  6. fs.writeFile("hello2.txt", "有点紧张",{flag:"a"}, function(err){
  7. if(!err)
  8. console.log("写入成功")
  9. else
  10. console.log(err)
  11. })
  12. // 读
  13. var fs = require("fs");
  14. fs.readFile("hello3.txt", function(err, data){
  15. if(!err){
  16. console.log(data.toString());
  17. }
  18. })
  • 流式文件读取和写入,流式读取和写入适用于一些比较大的文件

    1. fs.createWriteStream(path[, options])<br /> fs.createReadStream(path[, options])

    ```javascript // 写 var fs = require(“fs”); var ws = fs.createWriteStream(“hello3.txt”); ws.write(“写入内容”); ws.write(“写入内容”); // 只要不关闭就可以一直写入 // 通过监听流的open和close来监听流的打开和关闭 ws.once(“open”, function(){ // 流打开了 } ws.once(“close”, function(){ // 流关闭了 } ws.close(); // 或 ws.end();

// 读 var fs = require(“fs”); var rs = fs.createReadStream(“hello2.txt”); rs.once(“open”, function(){ console.log(“可读流打开”) }) rs.once(“close”, function(){ console.log(“可读流关闭”) }) rs.on(“data”, function(data){ console.log(data.toString()); })

// 复制文件 var fs = require(“fs”); var ws = fs.createWriteStream(“hello3.txt”); var rs = fs.createReadStream(“hello2.txt”); rs.pipe(ws);

  1. - `rs.pipe(ws);` 将可读流中的内容 直接输出到可写流中
  2. - on(事件字符串,回调函数), once(事件字符串,回调函数),one()
  3. - fs.existsSync(path) 检查文件是否存在
  4. - fs.stat(path, callback),callback的参数 err stat 状态信息
  5. - fs.statSync(path)
  6. - 获取文件状态
  7. - 返回一个对象,保存了当前对象的状态信息
  8. - fs.size() 文件大小
  9. - fs.ifFile() 是否是文件
  10. - fs.isDirectory() 是否是一个文件夹
  11. - fs.unlink(path, callback) 删除文件
  12. - fs.unlinkSync(path)
  13. - fs.readdir(path, callback) 列出一个目录的目录结构,callback的参数 err files数组 文件名
  14. - fs.readdirSync(path)
  15. <a name="gVxz1"></a>
  16. # Node中核心模块 path os
  17. <a name="jWlKb"></a>
  18. ## path
  19. `path` 处理路径<br />第一步,导入包 `let path = require("path")` <br />`path.extname(str)` 可以得到该路径的扩展名
  20. <a name="HqVQA"></a>
  21. ## os
  22. `os` 有关操作系统的信息<br />`os.cous()` 获取操作系统的CPU信息<br />`os.totalmem()` 获取内存大小
  23. 看官网
  24. <a name="MJqye"></a>
  25. # HTTP模块
  26. 开启一个本地服务器需要Node.jshttp核心模块
  27. 1. http--模块提供了搭建本地服务器的API,首先我们在项目中引入;
  28. ```javascript
  29. let http = require('http')

引入之后我们利用http.createServer()方法得到一个服务器实例。

  1. // createServer()方法返回一个server实例
  2. let server = http.createServer()
  1. 经过以上两步,我们已经搭建好了一个服务器实例,然后我们给服务器实例绑定接收request的事情处理函数,代码如下: ```javascript server.on(‘request’, (req, res) => { console.log(req.url) // 获取到请求的路径(请求路径永远以“/”开头) res.end(‘hello.’) console.log(req.headers) })

// 给服务器绑定接收请求的处理事件,当服务器接收到客户端发送的请求后,会调用后面的处理函数,处理函数接收两个参数:请求信息对象,响应信息对象。

  1. 3. 绑定监听端口号,开启服务器。代码如下:
  2. ```javascript
  3. server.listen(3000, () => {
  4. console.log('服务器开启成功,可以通过访问http://127.0.0.1:3000/来获取数据~~')
  5. })
  6. // server.listen()用来绑定监听的端口号,可以传入第二个参数,当服务器开启成功后,触发后面的回调函数
  1. 最后看到的效果如下图所示:

image.png
image.png

设置相应头

  1. res.writeHead(200, { 'Content-Type': 'text/plain' });
  2. // 或者
  3. res.setHeader('Content-Type', 'text/html');

写入内容

  1. res.write(fileData);

结束响应

  1. res.end();

接下来我们来实现一个需求:

我们实现这个需求,只需要在绑定服务器监听的事件处理函数中获取到用户的请求路径,然后根据不同路径返回不同数据即可,这个也不难。详情代码看下:

  1. let http = require('http')
  2. let server = http.createServer()
  3. server.on('request', (req, res) => {
  4. let url = req.url //得到请求的路径 (请求的路径永远以‘/’开头)
  5. if (url === '/') {
  6. res.end('index page')
  7. } else if (url === '/login') {
  8. res.end('login page')
  9. } else if (url === '/register') {
  10. res.end('register page')
  11. } else if (url === '/product'){
  12. let arr = [
  13. {
  14. name: 'iphone X',
  15. price: 8888
  16. },
  17. {
  18. name: 'iphone 7',
  19. price: 4320
  20. }
  21. ]
  22. // 响应的数据类型必须是字符串或者二进制数据
  23. res.end(JSON.stringify(arr))
  24. } else {
  25. res.end('404 NOT found')
  26. }
  27. })
  28. server.listen(3000, () => {
  29. console.log('服务器启动成功了,,可以访问http://127.0.0.1:3000/啦')
  30. })

这样就可以根据不同的路径访问不同的页面啦~

MongDB数据库

MongDB的数据库中,每一表里面的数据以对象形式存储
对数据库的操作,比如连接、创建、插入等都是异步操作

  • 当在一个函数中写与数据库相关的代码时,需要等这些代码都执行完,再继续向下进行,因此需要在相关语句前加await,再在该函数的定义前加async 如:
    1. const loginHandle = async (req, res) => {
    2. ...
    3. let user = await User.findOne({email});
    4. ...
    5. }
    使用流程
  1. 官网下载,一个是数据库,一个是可视化软件

image.png

  1. 安装他俩
    1. 在安装数据库时,可能会显示没有权限打开数据库之类的错误,可以先忽视,再在计算机的服务里面,找到MongoDB服务,打开其属性->登录->本地系统账号,就OK了
  2. 新建项目,在项目中下载导入mongoose包
  3. 连接数据库 ```javascript const mongoose = require(‘mongoose’)

// connect返回的是一个Promise对象;连接协议为mongodb // 若不存在plaground数据库,会自动创建 mongoose.connect(‘mongodb://localhost/plaground’) .then(() => {console.log(‘数据库连接成功’);}) .catch(err => {console.log(err,’ 数据库连接失败’);});

  1. 5. 创建规则、集合
  2. ```javascript
  3. // 创建集合规则
  4. const courseSchema = new mongoose.Schema({
  5. name: String,
  6. author: String,
  7. isPublished: Boolean
  8. })
  9. // 使用规则创建集合, 相当于数据库中的表
  10. // 1. 集合名词,参数首字母大写,但在数据库中创建的集合名字是courses
  11. // 2. 使用的规则
  12. const Course = mongoose.model('Course', courseSchema)
  1. 创建文档,并保存数据进数据库

    1. 这个不应该叫文档呀,应该是文档中的数据对象嘛

      1. // 创建集合对象(文档)并写入数据
      2. const course = new Course({
      3. name: '深度学习从入门到入土',
      4. author: '一个老师',
      5. isPublished: true
      6. })
      7. // 保存,将创建的文档插入到数据库中
      8. course.save()
    2. 另一种创建文档(数据)的方式

      1. Course.create({name: 'JS', author: 'teacher', isPublished: false}, (err, result) => {
      2. console.log(err);
      3. console.log(result);
      4. })
      5. // 或者这样 Promise的方法
      6. Course.create({name: 'JS', author: 'teacher', isPublished: false})
      7. .then(result => {console.log(result)})
      8. .catch(err => {console.log(err)}) // 当插入文档出错时

      数据库中的层次关系:数据库 -> 集合 -> 文档
      使用mongoimport导入数据文件到数据库
      mongoimport -d 数据库名称 -c 集合名称 --file 要导入的数据文件
      每一条数据都会自动加上一个_id字段

      查询文档

      find,输出文档数组 ```javascript // 查找全部文档 Course.find().then(result => console.log(result))

// 指定查找条件 Course.find({_id: ‘6170ccff6de0c89aacd691a5’}).then(result => console.log(result))

  1. `findOne`,返回一个对象,默认返回查找结果中的第一条文档
  2. ```javascript
  3. Course.findOne({name: 'JS'}).then(result => console.log(result))

**User.countDocuments({name:'ldf'})**统计数据条数

条件查询
查询年龄的范围

  1. // age 后面写一个对象,表示起止范围
  2. User.find({age: {$gt: 20, $lt: 40}}).then(result => console.log(result))

包含

  1. // hobbies字段数组中包含指定元素
  2. User.find({hobbies: {$in: ['足球']}}).then(result => console.log(result))

选择要查询的字段,多个字段以 空格 隔开,会默认查询出id。

  1. User.find().select('name email').then(...)
  • 想除掉默认的id字段时,在字段名前加一个-
    1. User.find().select('name email -_id').then(...)
    排序
    1. User.find().sort('age').then(...);
    2. // 降序
    3. User.find().sort('-age').then(...);
    skip 跳过多少条数据,limit 限制查询数量(可用在分页中)
    1. User.find().skip(2).limit(5).then(...);
    删除文档
    findOneAndDelete,默认删除查找到的第一条数据,并返回该数据
    1. Course.findOneAndDelete({name:'名字'}).then(...)
    deleteMany,若查询条件为空,则删除全部数据,返回一个对象 表示是否删除成功
    1. Course.deleteMany({}).then(...)
    更新文档
    updateOne({},{}),返回一个对象 表示是否更新成功
    1. User.updateOne({name: '11'},{name:'22'}).then()
    updateMany({},{})
    1. // 更改所有
    2. User.updateMany({},{name:'22'}).then()

    设置输入数据的验证条件

    在建立规则时,同时设置每一个字段的一些输入条件,比如是否可以为空 ```javascript const postSchema = new mongoose.Schema({ title: {
    1. type: String,
    required: true // 指示该字段必须输入,不能为空 } })

// 设置自定义错误信息 const postSchema = new mongoose.Schema({ title: { type: String, required: [true, ‘请输入文章标题’], // 指示该字段必须输入,不能为空;出错时的错误信息 minlength: [2, ‘长度要大于2’], maxlength: [5, ‘长度要小于5’], trim: true // 去掉空格 } })

  1. 其他验证:
  2. - `min` `max` Number类型的大小范围
  3. - `default` 设置默认值
  4. - `enum`设置取值的范围,`enum: ['a', 'b', 'c']`
  5. 自定义验证规则
  6. ```javascript
  7. // v 就是用户输入的那个数据值
  8. const postSchema = new mongoose.Schema({
  9. title: {
  10. type: String,
  11. validate: {
  12. validator: v => {
  13. // 对v进行判断,返回一个布尔值
  14. return v && v.length > 4
  15. },
  16. // 自定义错误信息
  17. message: '传入的值不符合验证规则'
  18. }
  19. }
  20. })

集合关联

多个数据集合的数据之间有关系

  • populate('xx')指定通过哪个字段关联
  • lean()将结果转换为普通对象,渲染时防止出错 ```javascript // 建立规则时 const postSchema = new mongoose.Schema({ title: String, author: {
    1. type: mongoose.Schema.Types.ObjectId, // 通过id关联
    2. ref: 'User' // 要关联的集合名称
    } })

// 关联查找 const posts = Post.find().populate(‘author’).lean();

  1. <a name="NlT6I"></a>
  2. ## 修改数据
  3. `update`,根据id修改指定用户数据
  4. ```javascript
  5. await User.updateOne({_id: id}, {
  6. username: username,
  7. email: email,
  8. role: role,
  9. state: state
  10. });

为数据库添加不同种类的账号

没做

设置分页

设置参数

  • 每页显示的条数 limit(数字)
  • 从第几个数据开始显示 skip(数字),默认从0开始 ```javascript // 接收客户端传递过来的当前页参数 let page = req.query.page || 1; // 每一页显示的数据条数 let pageSize = 3; // 查询用户数据的总数 let count = await User.countDocuments({}) // 总页数 let total = Math.ceil(count / pageSize) // 当前页码对应的数据查询开始位置 let start = (page - 1) * pageSize;

// 查询并显示数据库中的用户信息 // limit() 每页显示多少条数据 // skip() 跳到第几条数据开始显示 let users = await User.find({}).limit(pageSize).skip(start) res.render(‘admin/user’, { users, page: page, total: total })

  1. <a name="UyN0i"></a>
  2. # Express框架
  3. 1. 特性
  4. - 提供了方便的路由定义方式
  5. - 简化处理HTTP的请求
  6. - 对模板引擎支持程度高,方便渲染动态HTML页面
  7. - 提供了中间件机制 有效控制HTTP请求
  8. - 拥有大量第三方中间件对功能进行扩展
  9. 对比上面的HTTP协议
  10. ```javascript
  11. // 引入框架
  12. const express = require("express")
  13. // 创建服务器对象
  14. const app = express()
  15. // 监听 get 方式的根目录请求
  16. app.get('/', (req, res) => {
  17. /* send() 做出响应, 好处
  18. 1. 会内部检测响应内容的类型
  19. 2. 会自动设置http状态码
  20. 3. 会自动设置响应内容的类型及编码
  21. 4. 不用自己写end(),会自动结束
  22. */
  23. res.send('good night! 中村桑')
  24. })
  25. app.get('/list',(req, res) => {
  26. res.send({name: '悠一', age: 41})
  27. })
  28. // 监听端口
  29. app.listen(3000, ()=>{
  30. console.log("服务端正在执行,请访问 http://localhost:3000/")
  31. })
  1. 当URL中输入的路径不对时,不会报错,而是在页面中显示Cannot GET /end,友好
  2. 若写的是80端口,则在访问时可以省略不写

中间件

中间件就是一堆方法,可以接收客户端发来的请求、可以对请求做出响应,也可以将请求继续交给下一个中间件继续处理。
Nodejs - 图4
组成:中间件方法、请求处理函数

  • 中间件方法由Express提供,负责拦截请求
  • 请求处理函数由开发人员提供,负责处理请求
  • app.get('请求路径', '处理函数') get()就是一个中间件方法

    next()

    一个请求可以设置多个中间件,使用next()方法将请求的控制权交给下一个中间件,直到遇到结束请求的中间件。
    如:

    1. app.get('/list',(req, res, next) => {
    2. req.name = "悠一";
    3. // 把控制权交给下一个中间件,注意 res.send 在一次连接中只能发送一次
    4. next()
    5. })
    6. app.get('/list',(req, res) => {
    7. res.send(req.name)
    8. })

    next()中只能传入一个字符串作为参数,因此当要传递对象时

  • let str = JSON.stringify(对象) 将对象类型转为字符串类型

  • JSON.parse(str)将字符串转为对象

    app.use()

    app.use 匹配所有的请求方式(当一个路径或多个路径有多种请求方式时),可以直接传入请求处理函数,代表接收后面的所有请求。
    需要把它写在所有请求的最前面。
    1. app.use((req, res, next) => {
    2. ...
    3. next();
    4. })
    app.use 的第一个参数也可以传入请求地址,指定接收哪个路径的全部请求 ```javascript app.use(‘/admin’, (req, res, next) => { … next(); })

// 为什么 get 里面不写send会出错404呢? app.use(‘/admin’, (req, res, next) => { console.log(“app.use /admin”) next() }) app.get(‘/admin’, (req, res, next) => { console.log(“app.get /admin”) res.send(“app.get admin”) next() }) app.post(‘/admin’, (req, res) => { console.log(“app.post /admin”) res.send(“晚上好~~”) })

  1. **应用 作用**
  2. 1. 路由保护,用于判断用户是否已经登录,是则进入页面 显示内容,否则显示提示错误信息
  3. 1. 网站维护公告,阻止用户访问该网站的所有页面,此中间件写在所有中间件函数的最前面
  4. 1. 自定义404页面,此中间件写在所有中间件函数的最后面
  5. ```javascript
  6. app.use((req, res) => {
  7. res.status(404).send("当前页面不存在...")
  8. })

错误处理中间件

  1. 获取错误信息,将信息显示在用户页面。这种只适用于同步代码发生的错误 ```javascript app.get(‘/index’, (req,res)=> { // 自己抛出一个异常 throw new Error(“程序发生了未知错误”) }) app.use((err, req, res, next) => { res.status(500).send(err.message) // 显示 程序发生了未知错误 })

// 这种也可以使用第二种的next()来 app.get(‘/index’, (req,res)=> { throw new Error(“程序发生了未知错误”) }) app.use((err, req, res, next) => { next(err.message) // 也显示 程序发生了未知错误。只是和上面显示的字体不一样,应该原理不同 })

  1. 2. 当程序出错时,调用next()方法,并且将错误信息通过参数的形式传递给next()方法
  2. ```javascript
  3. app.get('/index', (req,res, next)=> {
  4. fs.readFile("./app.js", "utf8", (err, result)=>{
  5. if(err){
  6. // 调用 next方法,将错误信息显示出来
  7. next(err)
  8. }else {
  9. res.send(result)
  10. }
  11. })
  12. })
  1. 可以使用try { }catch(){ }来捕获异步函数错误以及其他同步代码在执行过程中的错误,但是不能捕获其他类型的API发生的错误,比如 Promise

模块化路由

创建访问的二级路径,如下例子可以访问localhost:3000/home/index

  1. const app = express()
  2. // 创建路由对象
  3. const home = express.Router()
  4. // 为路由对象匹配请求路径
  5. app.use('/home', home)
  6. // 创建二级路由
  7. home.get("/index", (req, res) => {
  8. res.send("欢迎 首页")
  9. })

可以把不同模块的设置放在不同的js文件中,通过exportsrequire来使用

  1. // home.js
  2. const express = require("express")
  3. const home = express.Router()
  4. home.get("/index", (req, res)=>{
  5. res.send("welcome to home page")
  6. })
  7. module.exports = home;
  1. // admin.js
  2. const express = require("express")
  3. const admin = express.Router()
  4. admin.get("/index", (req, res)=>{
  5. res.send("welcome to admin page")
  6. })
  7. module.exports = admin;
  1. // app.js
  2. const express = require("express")
  3. const home = require("./home")
  4. const admin = require("./admin")
  5. const app = express()
  6. app.use('/home', home)
  7. app.use('/admin', admin)
  8. app.listen(3000)

请求处理

获取参数

  1. 获取get方式的参数,如localhost:3000/index?name=悠一&gender=男

    1. const app = express()
    2. app.get("/index", (req, res)=>{
    3. res.send(req.query)
    4. })
  2. 接收post方式的参数,借用body-parser

    1. const express = require("express")
    2. const bodyParser = require("body-parser")
    3. const app = express()
    4. app.use(bodyParser.urlencoded({extended: false}))
    5. app.post("/add", (req, res) => {
    6. // 这个body属性是bodyParser自动添加的
    7. res.send(req.body)
    8. })
    9. app.listen(3000)

    设置参数

    指定需要传入的地址参数
    比如输入loclahost:3000/099&luo ```javascript // 这种方式指定url需要传入的参数 app.get(“/dog/:id&:name”, (req,res) => { res.send(req.params) })

  1. 或者是`"/dog/:id/:name"` -> `localhost:3000/007/dogge` <br />`req.params` 则获取输入的参数
  2. <a name="MW2yD"></a>
  3. ## 静态资源的处理
  4. 通过express.static并指定静态资源文件的所在目录,即可访问imgCSShtml文件等。
  5. ```javascript
  6. const express = require("express")
  7. const path = require("path")
  8. const app = express()
  9. app.use(express.static(path.join(__dirname, 'public')))
  10. app.get('/', (req, res) => {
  11. res.send("ni hao")
  12. })
  13. app.listen(3000)

path.join(__dirname, 'public') 指示所在的绝对路径,
就可以通过[http://localhost:3000/static.html](http://localhost:3000/static.html)直接访问public下面的html文件,
可以通过[http://localhost:3000/css/style.css](http://localhost:3000/css/style.css)访问其下css文件夹中的css文件

模板引擎

让开发者更加友好的方式拼接字符串,通过{{data}}来使用数据

  1. 在命令行工具中使用 npm install art-template 命令进行下载
  2. 使用 const template = require('art-template')引入模板引擎
  3. 告诉模板引擎要拼接的数据和模板在哪 const html = tempalte('模板路径', 数据),此处路径为绝对路径
  4. 如: ```javascript // app.js const template = require(‘art-template’); // html 为拼接后的东西 const html = tempalte(‘模板的绝对路径’, { data: { name: ‘ldf’ } })

// index.art

{{data.name}}
  1. 模板文件默认是以`art`为后缀,其内容为html的东西
  2. <a name="IQo3D"></a>
  3. ### 模板语法
  4. **输出**<br />标准语法:`{{ 数据 }}`<br />原始语法:`<%=数据 %>`,输出时必须加上一个等号<br />在模板中可以进行简单的四则运算以及三目运算<br />输出的内容有html的标签时,默认输出时是按字符串输出,想解析出来时,需要加 @ ,如 `{{@ content }}`<br />**条件判断**<br />模板中可以根据条件是否满足来显示响应信息
  5. ```javascript
  6. // 标准语法
  7. {{if age > 18}}
  8. 内容1
  9. {{else if age < 15}}
  10. 内容2
  11. {{else age = 90}}
  12. 内容3
  13. {{/if}}
  14. // 原始语法
  15. <% if(age > 18) { %>
  16. 内容1
  17. <% } else if(age < 15){ %>
  18. 内容2
  19. <% } %>

在原始语法中可以写全部的js代码,大括号必须成对

循环
标准语法:{{ each 数据 }}{{/each}}
原始语法:<% for( ){ %> <% } %>

  1. template(view, {
  2. users: [
  3. {name: 'yuuichi'},
  4. {name: 'sugita'},
  5. {name: 'kamiya'},
  6. {name: 'ono'}
  7. ]
  8. })
  9. <ul>
  10. {{each users}}
  11. <li>{{$index}} {{$value.name}} </li>
  12. {{/each}}
  13. </ul>
  14. <ul>
  15. <% for(var i = 0; i < users.length; i++){ %>
  16. <li><%=i %> <%= users[i].name%></li>
  17. <% }%>
  18. </ul>

标准语法中,循环中的下标为{{$index}},每一个循环的值为{{$value}}

子模版

把模板中公共的部分抽离出来 放到一个单独的文件中,再通过一些语法进入进来

  • 标准语法:{{include '模板'}}
  • 原始语法:<% include('') %>
  • 此处路径为相对路径

模板继承

把html文件的骨架也可以抽离出去。通过继承来使用相同的骨架
在骨架文件中通过挖坑 在具体的子文件中来填坑,显示自己的内容

  1. // layout.art
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  7. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  8. <title>骨架模板</title>
  9. {{block 'head'}} {{/block}}
  10. </head>
  11. <body>
  12. {{block 'main'}} {{/block}}
  13. </body>
  14. </html>
  1. // extend.art
  2. {{extend './layout.art'}}
  3. {{block 'main'}}
  4. <h2>goji sense</h2>
  5. <h2>{{age}}</h2>
  6. {{/block}}

模板配置

  1. 向模板文件中导入一些方法

template.defaults.imports.名字=方法名
在模板中可以调用这个方法,比如:

  1. let add = function (a, b){
  2. return a+b;
  3. }
  4. template.defaults.imports.add = add;
  5. const view = path.join(__dirname, 'views', 'extend.art')
  6. const html = template(view, {})
  7. console.log(html)
  1. <h2>{{ add(2, 20) }}</h2>
  1. 设置模板的根目录

访问模板时,就可以直接写模板的名字

  1. template.defaults.root = path.join(__dirname, 'views')
  1. 设置模板的默认后缀

访问时就不可以不再写.art,也可以自己定义模板的后缀 比如.html

  1. template.defaults.extname = '.art'

express中的模板引擎

需要install art-templateexpress-art-template
大概流程

  1. // 渲染后缀为art的模板,声明使用的是express-art-tenplate引擎
  2. app.engine('art', require('express-art-template'))
  3. // 设置模板存放的目录,前一个views是配置项名字 固定为views;后一个views是目录名
  4. app.set('views', path.join(__dirname, 'views'))
  5. // 设定模板的默认后缀
  6. app.set('view engine', 'art')
  7. app.get('/', (req, res) => {
  8. // 渲染名为indx.art的模板
  9. res.render('index', { }) // 第二个参数 对象,传入模板的数据
  10. }
  11. app.listen(3000)

设置公共属性, 使用app.locals

  • app.locals.users = [{'name' : 'ldf'}]
  • app.locals.currentLink= "ldf"
  • 在前端中通过{{currentLink}}来访问

在模板中使用的外链静态文件 如css文件,链接中的地址为相对路径 相对于浏览器地址栏中的请求路径,而静态文件和模板文件不在同一个目录中,因此需要使用绝对路径来连接静态文件,如'/admin/css/style.css' 则从已设置的静态文件目录public中找

案例项目中涉及的知识点

密码加密 bcryptjs

  1. 哈希加密,只能加密不能解密
  2. 在加密的密码中加入随机字符串 ```javascript const bcrypt = require(“bcryptjs”)

/**

  • 生成随机字符串
  • 参数为一个整数,越大 加密复杂读越高
  • 默认值为10
  • 返回生成的随机字符串 */ // await 关键字只能出现在异步函数中,所以需要定义一个函数来执行这语句 // let salt = await bcrypt.genSalt(10)

async function fun(){ let salt = await bcrypt.genSalt(10) /**

 * 加密密码
 * @param String, 原密码串
 * @param String, 盐
 * @return String, 加密后的密码
 */
let result = await bcrypt.hash('123456', salt)
console.log(salt, result)

} fun()


3. 密码比对 
```javascript
let isValid = await bcrypt.compare("用户输入的明文密码", "数据库中加密的密码")

cookie和session

cookie:浏览器在电脑硬盘开辟的一块空间,主要供服务器存储数据

  • cookie中的数据是以域名的形式进行区分的
  • 其中的数据有过期时间,超过过期时间 数据会被浏览器自动删除
  • 其中的数据会随着请求被自动发送到服务器端

Nodejs - 图5
session:一个对象,存储在服务器端的内存中,在session对象中也可以存储多条数据,每一条数据都有一个唯一标识sessionId
Nodejs - 图6

Joi

用户输入字段的验证
最新Joi的官网

const Joi = require('joi')

const schema = Joi.object({
    username: Joi.string().min(2).max(5).error(new Error('用户名未通过验证')),
    birth: Joi.number().min(1900).max(2014).error(new Error('生日未通过验证')),
      state: Joi.number().valid(0, 1).required().error(new Error('状态值非法'))
});

async function run(){
    try {
        const result = await schema.validateAsync({ username: 'abc', birth: 1899});
    }catch (err) {
        console.log(err.message);
        return;
    }
    console.log('验证通过');
}

run();

各方法解释:

  • error()自定义错误提示信息
  • valid()提供可选的字段
  • required()必填

分页

分页显示数据库中的用户数据

  1. 在HTML中插入<% %>可以写服务器端代码
  2. <%= x %>是赋值语句
  3. page - 0 + 1 意思是 因为减法可以隐式转换 将字符类型转为数值型,而加法不行
<ul class="pagination">
    <li>
        <a href="/admin/user?page=<%=page-1%>">
  <span>&laquo;</span>
</a>
    </li>
    <% for(var i = 1; i <= total; i++) { %>
    <li><a href="/admin/user?page=<%=i%>">{{i}}</a></li>
    <% } %>
    <li>
        <a href="/admin/user?page=<%=page-0+1%>">
  <span>&raquo;</span>
</a>
    </li>
</ul>

使用@进行原文输出

  • **{{@$value._id}}**
  • **{{@user && user._id}}**

formidable

解析表单,支持get、post、文件上传。
注意表单中要上传文件时,在 form 中添加一个属性值**enctype="multipart/form-data"**,指定表单数据的编码类型

  • enctype的默认值为application/x-www-form-urlencoded ```javascript const formidable = require(“formidable”) const path = require(“path”)

// 创建表单解析对象 const form = new formidable.IncomingForm() // 配置上传文件的存放位置 form.uploadDir = path.join(__dirname, ‘../‘, ‘public/uploads’); // 设置保留上传文件的后缀 form.keepExtensions = true; // 解析表单 // fields 保存普通表单数据,对象类型 // files 保存上传文件相关的数据,对象类型 form.parse(req, (err, fields, files)=>{

})


**文件读取 FileReader**<br />在页面上显示用户选择的图片
```javascript
<div class="form-group">
   <label for="exampleInputFile">文章封面</label>
   <input type="file" name="cover" id="file">
   <div class="thumbnail-waper">
       <img class="img-thumbnail" src="" id="preview" style="max-height: 350px;">
   </div>
</div>

// 在用户选择图片后,在页面中显示该图片
let file = document.querySelector('#file');
let preview = document.querySelector('#preview');
file.onchange = function() {
    // 文件读取对象
    let reader = new FileReader();
    reader.readAsDataURL(this.files[0])   // 异步
    reader.onload = function () {
        preview.src = reader.result;
    }
}

开发环境和生产环境

  1. 在计算机环境变量中添加属性NODE_ENV,其值为developmentproduction
  2. 使用process.env获取计算机的系统环境变量及其值
  3. 判断process.env.NODE_ENV的值是哪一个

morgan模块

  1. npm i morgan
  2. const mrogan = require('morgan')
  3. 将客户端发送给服务器端的请求信息打印到控制台中
    1. app.use(morgan('dev'))

config模块
系统内部自动判断当前应用的运行环境,并读取对应的配置信息

  1. npm install config
  2. 在项目的根目录下新建config文件夹
  3. 在config文件夹下面新建 default.json、development.json、production.json文件
  4. 在项目中通过 require方法,将模块导入 **constconfig=require('config')**
  5. 使用模块内部提供的 get 方法获取配置信息 **config.get('title')**

config 可以将敏感的配置信息存储再环境变量中