06_包管理工具

image-20230413154422865

1.概念介绍

1.1 包是什么

『包』英文单词是 package ,代表了一组特定功能的源码集合。

1.2 包管理工具

管理『包』的应用软件,可以对「包」进行 下载安装更新删除上传 等操作。

借助包管理工具,可以快速开发项目,提升开发效率。

包管理工具是一个通用的概念,很多编程语言都有包管理工具,所以 掌握好包管理工具非常重要

1.3 常用的包管理工具

下面列举了前端常用的包管理工具

2.npm

npm 全称 Node Package Manager ,翻译为中文意思是『Node 的包管理工具』

npm 是 node.js 官方内置的包管理工具,是 必须要掌握住的工具

2.1 npm 的安装

node.js 在安装时会 自动安装 npm ,所以如果你已经安装了 node.js,可以直接使用 npm

可以通过 npm -v 查看版本号测试,如果显示版本号说明安装成功,反之安装失败

2.2 npm 基本使用

2.2.1 初始化

创建一个空目录,然后以此目录作为工作目录 启动命令行工具 ,执行 npm init

npm init 命令的作用是将文件夹初始化为一个『包』交互式创建 package.json 文件

package.json 是包的配置文件,每个包都必须要有 package.json

package.json 内容示例:

属性翻译:

初始化的过程中还有一些注意事项:

  1. package name ( 包名 ) 不能使用中文、大写,默认值是 文件夹的名称 ,所以文件夹名称也不能使用中文和大写
  2. version ( 版本号 )要求 x.x.x 的形式定义, x 必须是数字,默认值是 1.0.0
  3. ISC 证书与 MIT 证书功能上是相同的,关于开源证书扩展阅读http://www.ruanyifeng.com/blog/2011/05/how_to_choose_free_software_licenses.html
  4. package.json 可以手动创建与修改
  5. 使用 npm init -y 或者 npm init --yes 极速创建 package.json
2.2.2 搜索包

搜索包的方式有两种

  1. 命令行 『npm s/search 关键字』。一般不使用这种方式,因为搜索结果只有文字,不形象。
  2. 网站搜索 网址是 https://www.npmjs.com/。推荐使用这种方式。

经常有同学问,『我怎样才能精准找到我需要的包?最精准、最高效的解决问题。』

这个事儿需要大家在实践中不断的积累,通过看文章,看项目去学习、去积累。

2.2.3 下载安装包

我们可以通过 npm install 或简写方式 npm i 命令安装包

运行之后文件夹下会增加两个资源

比如说,安装 uniq 之后, uniq 就是当前这个包的一个 依赖包(什么叫“当前这个包”?指的就是我使用npm init初始化的这个文件夹,就称为“当前这个包”。也可以理解为我的当前的这个项目) ,有时会简称为 依赖。

举个例子,我们创建一个包名字为 A,A 中安装了包名字是 B,我们就说 B 是 A 的一个依赖包 ,也会说A 依赖 B

2.2.4 require 导入 npm 包基本流程

在nodejs中使用require导入npm包,直接写包名即可(不用写完整路径):

虽然Nodejs和第三方库的导入都直接写包名,但vscode的one dark主题对于导入的模块有不同的配色,这一点很好,方便我看一个模块到底是不是Nodejs的官方模块。当然,这个功能可有可无。

require导入npm包的基本流程如下:

  1. 在当前文件夹下 node_modules 中寻找同名的文件夹
  2. 在上级目录下的 node_modules 中寻找同名的文件夹,直至找到磁盘根目录

2.3 生产环境与开发环境

开发环境是程序员 专门用来写代码 的环境,一般是指程序员的电脑,开发环境的项目一般 只能程序员自己访问

生产环境是项目 代码正式运行 的环境,一般是指正式的服务器电脑,生产环境的项目一般 每个客户都可以访问

2.4 生产依赖与开发依赖

我们可以在安装时设置选项来区分 依赖的类型 ,目前分为两类:

类型命令补充备注
生产依赖npm i -S uniq
npm i --save uniq
-S 等效于 --save,-S 是默认选项
包信息保存在 package.json 文件的dependencies 属性中
开发和生产阶段都需要
开发依赖npm i -D less
npm i --save-dev less
-D 等效于 --save-dev
包信息保存在 package.json 文件的devDependencies 属性中
只在开发阶段需要

举个例子方便大家理解,比如说做蛋炒饭需要 大米 , 油 , 葱 , 鸡蛋 , 锅 , 煤气 , 铲子 等

其中 锅 , 煤气 , 铲子 属于开发依赖,只在制作阶段使用

而 大米 , 油 , 葱 , 鸡蛋 属于生产依赖,在制作与最终食用都会用到

所以 开发依赖 是只在开发阶段使用的依赖包,而 生产依赖 是开发阶段和最终上线运行阶段都用到的依赖包。

怎么区分要安装的包是开发依赖还是生产依赖呢?其实我还是觉得有点难,上面的像less在打包后会转换为css,所以只需要在开发阶段使用,但别的包,特别是没有使用过的包,我就不知道了。可以在安装之前看一下别人的文档,推荐使用的安装命令是什么,照着文档做就行了,如果文档没有,就大胆使用-S,不要在这一点上纠结。

2.5 全局安装

我们可以执行安装选项 -g 进行全局安装

全局安装完成之后就可以在命令行的任何位置运行 nodemon 命令。nodemon可以代替node在命令行中执行命令,可以帮助解决之前的http服务器调试的一个问题:代码更改了之后,需要关闭服务器,重新启动服务器,才能显示最新的效果。而nodemon会自动重启,比如说在命令行运行:nodemon server.js,更改server.js里面的代码并保存,可以看到自动重启了,可以获取最新的代码。

image-20230910093501382

该命令的作用是 自动重启 node 应用程序

说明:

image-20231110111926938

2.5.1 修改 windows 执行策略

windows 默认不允许 npm 全局命令执行脚本文件,所以需要修改执行策略,这样像nodemon这样的全局命令就可以在任意工作目录的命令行里面执行了。

image-20230413160717911

方法一:

1、以 管理员身份 打开 powershell 命令行

image-202304131607049652、键入命令 set-ExecutionPolicy remoteSigned

image-20230413160645263

3、键入 A 然后敲回车 👌

4、如果不生效,可以尝试重启 vscode

方法二:

在cmd中或者在vscode中选择command prompt命令行打开,就可以执行nodemon命令。

2.5.2 环境变量 Path

Path 是操作系统的一个环境变量,可以设置一些文件夹的路径,在当前工作目录下找不到可执行文件时,就会在环境变量 Path 的目录中挨个的查找,如果找到则执行,如果没有找到就会报错。

image-20230413160624136

补充说明:

image-20230910100026656

在cmd中输入code,正常启动:

命令不是只有单独唤起cmd才能使用(此时的cmd所在目录是C:\Users\Administrator),而是在任何目录的cmd或powershell下都可以使用,这就非常方便了,因为我可以将gifcam等常用的程序,都设置Path,在做笔记时我就可以在命令行中直接唤起了,省得我每次都要到处找gifcam。

2.6 安装包的所有依赖

在项目协作中有一个常用的命令就是 npm i ,通过该命令可以依据 package.jsonpackage-lock.json 的依赖声明安装项目依赖。

node_modules 文件夹大多数情况都不会存入项目的git版本库,因为node_modules文件夹太大了,所以一般都会在.gitignore文件里面忽略掉,不让它上传到git仓库中,所以在协作开发的时候,使用了package.jsonpackage-lock.json来记录使用的第三方库的情况,npm i可以很方便的安装这些记录好的依赖。

2.7 安装指定版本的包

项目中可能会遇到版本不匹配的情况,有时就需要安装指定版本的包,可以使用下面的命令。

2.8 删除依赖

项目中可能需要删除某些不需要的包,可以使用下面的命令

2.9 配置命令别名

通过配置命令别名可以更简单的执行命令。比如说在一个项目文件夹里面,我们之前学习的时候,是这样启动服务器的:node server.js,其中server.js是项目的主文件,那么通过配置命令别名,当命令非常长的时候,可以简化一些操作,在学习express时,你将会有所体会,因为启动express的命令,可以配置很多参数。

配置 package.json 中的 scripts 属性

配置完成之后,可以使用别名执行命令

不过 start 别名比较特别,使用时可以省略 run

补充说明:

3.cnpm

3.1 介绍

cnpm 是一个淘宝构建的 npmjs.com 的完整镜像,也称为『淘宝镜像』,网址https://npmmirror.com/

cnpm 服务部署在国内 阿里云服务器上 , 可以提高包的下载速度

官方也提供了一个全局工具包 cnpm ,操作命令与 npm 大体相同

3.2 安装

我们可以通过 npm 来安装 cnpm 工具

3.3 操作命令

功能命令
初始化cnpm init / cnpm init -y
安装包cnpm i uniq
cnpm i -S uniq
cnpm i -D uniq
cnpm i -g nodemon
安装项目依赖cnpm i
删除cnpm r uniq

3.4 npm 配置淘宝镜像

用 npm 也可以使用淘宝镜像,配置的方式有两种

3.4.1 直接配置

执行如下命令即可完成配置

3.4.2 工具配置

使用 nrm 配置 npm 的镜像地址 npm registry manager

  1. 安装 nrm
  1. 修改镜像
  1. 检查是否配置成功(选做)

检查 registry 地址是否为 https://registry.npmmirror.com/ , 如果 则表明设置成淘宝镜像成功。

  1. 查看有哪些镜像地址

image-20230911145603341

可以使用nrm use npm将镜像转为npm官方地址。

补充说明:

  1. 建议使用第二种方式进行镜像配置,因为后续修改起来会比较方便
  2. 虽然 cnpm 可以提高速度,但是 npm 也可以通过淘宝镜像进行加速,所以 npm 的使用率还是高于 cnpm。建议使用npm配置淘宝镜像,一直使用npm命令。

4.yarn

4.1 yarn 介绍

yarn 是由 Facebook 在 2016 年推出的新的 Javascript 包管理工具,官方网址:https://yarnpkg.com/

4.2 yarn 特点

yarn 官方宣称的一些特点

4.3 yarn 安装

我们可以使用 npm 安装 yarn

4.4 yarn 常用命令

功能命令
初始化yarn init / yarn init -y
安装包yarn add uniq 生产依赖
yarn add less --dev 开发依赖
yarn global add nodemon 全局安装
删除包yarn remove uniq 删除项目依赖包
yarn global remove nodemon 全局删除包
安装项目所有依赖yarn
运行命令别名yarn <别名> 不需要添加run

思考题:

这里有个小问题就是 使用yarn全局安装的包不可用 ,yarn 全局安装包的位置可以通过 yarn global bin 来查看,那你有没有办法使 yarn 全局安装的包能够正常运行?

image-20230910103236810

4.5 yarn 配置淘宝镜像

可以通过如下命令配置淘宝镜像

可以通过 yarn config list 查看 yarn 的配置项:

image-20231215135955680

我在安装从codeSandbox下载的项目依赖时,使用yarn,都会报错:

image-20231215140509959

info There appears to be trouble with your network connection. Retrying...

项目使用的就是yarn,所以没有办法换到npm,怎么解决呢?其实我的yarn配置的就是淘宝源,下载应该很快才对啊?

查了一下:https://blog.csdn.net/weixin_43558117/article/details/130343910,由于yarn.lock文件是通过远程抓取的,而不是本地yarn生成的,所以即使设置了淘宝源,在执行yarn install的时候,走的是别的仓库。

可以通过yarn install --verbose来验证,看是从哪里安装依赖的:

image-20231215142000072

可以看到,安装依赖的时候,走的仍然是官方网址,没有走淘宝源。

怎么解决呢?可以通过忽略lock文件,同时install的时候加registry参数解决:yarn install --no-lockfile --registry https://registry.npmmirror.com/

4.6 npm 和 yarn 选择

大家可以根据不同的场景进行选择

  1. 个人项目

如果是个人项目, 哪个工具都可以 ,可以根据自己的喜好来选择

  1. 公司项目

如果是公司要根据项目代码来选择,可以 通过锁文件判断 项目的包管理工具

包管理工具npm和yarn 千万不要混着用,切记,切记,切记

5.管理发布包

5.1 创建与发布

我们可以将自己开发的工具包发布到 npm 服务上,方便自己和其他开发者使用,操作步骤如下:

  1. 创建文件夹,并创建文件 index.js, 在文件中声明函数,使用 module.exports 暴露
  2. npm 初始化工具包,package.json 填写包的信息 (包的名字是唯一的)
  3. 注册账号 https://www.npmjs.com/signup
  4. 激活账号 (一定要激活账号
  5. npm的镜像修改为npm的官方镜像 (命令行中运行 nrm use npm,这就是为什么老师推荐使用nrm来配置镜像 )
  6. 命令行下 npm login 填写相关用户信息
  7. 命令行下 npm publish 提交包 👌

5.2 更新包

后续可以对自己发布的包进行更新,操作步骤如下

  1. 更新包中的代码
  2. 测试代码是否可用
  3. 修改 package.json 中的版本号
  4. 发布更新

5.3 删除包

执行如下命令删除包。不需要输入包的名称,也不需要登录操作,因为之前在发布的时候已经登录过了,反正先执行这个命令即可,后续操作按照提示来做就行。

删除包需要满足一定的条件,https://docs.npmjs.com/policies/unpublish

6.扩展内容

在很多语言中都有包管理工具,比如:

语言包管理工具
PHPcomposer
Pythonpip
Javamaven
Gogo mod
JavaScriptnpm/yarn/cnpm
RubyrubyGems

除了编程语言领域有包管理工具之外,操作系统层面也存在包管理工具,不过这个包指的是『 软件包 』

操作系统包管理工具网址
Centosyumhttps://packages.debian.org/stable/
Ubuntuapthttps://packages.ubuntu.com/
MacOShomebrewhttps://brew.sh/
Windowschocolateyhttps://chocolatey.org/

 

07_nvm

image-20230413164052707

1.介绍

nvm 全称 Node Version Manager 顾名思义它是用来管理 node 版本的工具,方便切换不同版本的Node.js

2.使用

nvm 的使用非常的简单,跟 npm 的使用方法类似

2.1 下载安装

首先下载 nvm,下载地址 https://github.com/coreybutler/nvm-windows/releases

选择 nvm-setup.exe 下载,无脑安装即可。

nvm配置镜像:

在nvm所在的文件夹中,找到settings.txt,粘贴这两行代码。

这样,nvm安装nodejs就会非常快了。

2.2 常用命令命令

命令说明
nvm list available显示所有可以下载的 Node.js 版本
nvm list显示本地已安装的版本
nvm install 18.21.1安装 18.12.1 版本的 Node.js
nvm install latest安装最新版的 Node.js
nvm uninstall 18.21.1删除某个版本的 Node.js
nvm use 18.12.1切换 18.21.1 的 Node.js

 

08_Express 框架

image-20230413164534306

1.express 介绍

express 是一个基于 Node.js 平台的极简、灵活的 WEB 应用开发框架,官方网址:https://www.expressjs.com.cn/

简单来说,express 是一个封装好的工具包,封装了很多功能,便于我们开发 WEB 应用(HTTP 服务)

2.express 使用

2.1 express 下载

express 本身是一个 npm 包,所以可以通过 npm 安装

2.2 express 初体验

大家可以按照这个步骤进行操作:

  1. 创建 JS 文件(app.js),键入如下代码

结合之前学习的http模块来看express库,创建服务器的基本过程有什么区别?

区别在于express将路由规则单独提取出来了,而http服务器是所有路由都会走创建server的那段代码,先记住基本创建过程,不断复习,才能增强信心。

  1. 命令行下执行该脚本,node app.js
  1. 然后在浏览器就可以访问 http://127.0.0.1:3000/home 👌

小节:

这么简单的几行代码,就可以创建一个服务器,我是真的没有想到会这么简单。在我的印象中,服务端项目最起码要和前端项目一样复杂才对啊,这个想法没有错,但那应该是完整的项目,有很多复杂的功能。

应该说创建服务器本身是很简单的(就像我写一个html网页一样简单,里面可以加上一些基本的css和js),是我把它想的复杂了,面对我一直没有解决的问题,我总是习惯性地夸大难度、找不准方向。难就难在其功能的复杂性,要操作数据库、要保证安全、要验证用户信息、要写各种路由api、要错误兼容等等,把完整功能加上去之后就非常复杂了。不过再复杂,也只是一个服务器而已,记住这句话,抓住这个主线,才能不迷路。

3.express 路由

3.1 什么是路由

官方定义: 路由确定了应用程序如何响应客户端对特定端点的请求

3.2 路由的使用

一个路由的组成由请求方法路径回调函数 组成。

express 中提供了一系列方法,可以很方便的使用路由,使用格式如下:

代码示例:

注意:

下面写响应报文的时候,用到了res.send()方法,这是express的响应方法,express对http模块的一些操作做了兼容,可以使用res.end()方法,下面第四小节讲到了。

3.3 获取请求参数

express 框架封装了一些 API 来方便获取请求报文中的数据,并且兼容原生 HTTP 模块的获取方式。

下面的示例中,直接在url上写一些query参数来测试即可,比如:http://127.0.0.1:9000/request?a=1&b=2

HTTP原生获取参数输出:

image-20230919163044353

express独有获取参数方式输出:

image-20230919162848497

3.4 获取路由参数

路由参数指的是 URL 路径中的参数(数据)

image-20230910160746719

image-20230910160638287

image-20230919163603576

3.4.1 路由参数练习

需求:根据路由参数响应歌手的信息,路径结构:/singer/1.html,显示歌手的姓名和图片(数据在老师的代码中有)。

分析:这个练习还是蛮简单的,只需要在url栏里面更改url就当作发送请求了,使用req.params.id来获取歌手的id。不过导入json文件在这里不使用fs模块(fs读取文件返回的是buffer类型,处理起来很麻烦),而是使用require来导入,因为require导入json文件后,是一个js的对象,可以直接使用这个对象。响应报文的响应体是一个html,并且要对没有找到数据的情况做兼容。

代码:

显示效果:

image-20230919170506703

4.express 响应设置

express 框架封装了一些 API 来方便给客户端响应数据,并且兼容原生 HTTP 模块的获取方式。

兼容HTTP响应方式:

image-20230919171250694

image-20230919171306701

express响应方法的输出:

image-20230920154653919

image-20230920154707215

express的其他响应很牛逼啊,把项目中主要用到的响应都涉及到了,特别是res.json(),我以后应该是用的最多的,而且会对json的内容做一个封装,设置成这种形式:

其中data部分是变化最多的,可以返回多种数据类型,其余部分会随着响应报文是否正常,来显示不同内容。

5.express 中间件

5.1 什么是中间件

中间件(Middleware)本质是一个回调函数

中间件函数 可以像路由回调一样访问 请求对象(request)响应对象(response)

上面这句话是什么意思?

这句话的意思是在使用中间件的地方,中间件可以使用该地方的请求对象和响应对象,而且是在该地方使用请求对象和响应对象之前使用。如果是全局中间件,那么全局中间件可以使用每一个路由规则中的请求对象和响应对象。如果是路由中间件,那么在使用路由中间件的路由规则中,中间件就可以使用此路由中的请求对象和响应对象。把这一点认识清楚非常重要,否则永远搞不懂中间件是怎么运行的

一般情况下(特殊情况我还不知道,按照普通使用方式都应该是这样),如果使用了中间件函数(不管是全局中间件还是路由中间件),那么中间件函数会在具体路由函数之前执行,比如说:

那么这里的checkCodeMiddleware会在路由本身的(req,res)=>{}之前执行,而且是同步的执行,checkCodeMiddleware执行完成之后,(req,res)=>{}才会执行。

什么地方可以看到是同步的执行?中间件函数都有一个next参数,这个参数是一个函数,只有显式地执行了next()之后,才会执行路由本身的(req,res)=>{}

5.2 中间件的作用

中间件的作用 就是 使用函数封装公共操作,简化代码

5.3 中间件的类型

5.3.1 定义全局中间件

每一个请求到达服务端之后 都会执行全局中间件函数

使用方式一:

1、声明中间件函数,函数名可以随便起,但一般都带上Middleware,用于提示这是个中间件。可以写成普通函数,也可以写成箭头函数。

image-20230414105955154

2、应用中间件

使用方式二:

声明时可以直接将匿名函数传递给 app.use()

案例:

需求:记录每个请求的url和IP地址,存放到一个文件当中。

分析:因为要记录每个请求的URL和IP地址,所以就要使用全局中间件,解析里面的request参数,使用fs模块写入一个文件当中。

效果:

查看access.log文件:

image-20230920163455851

5.3.2 多个全局中间件

express 允许使用 app.use() 定义多个全局中间件。

方式一:

方式二:

如果使用多个全局中间件,执行顺序是什么样的?

是按照注册的顺序来执行的。如果是app.use(middleware1,middleware2)这种方式,是按照参数的顺序来执行的。

image-20230920164057297

5.3.3 定义路由中间件

如果 只需要对某一些路由进行功能封装 ,则就需要路由中间件。

还是先定义中间件,然后在路由中调用中间件函数。(就不是使用app.use()来调用了)

调用格式如下:

案例:

需求:针对 /admin 和 /setting 的请求,要求URL携带 code=521 的参数,如未携带提示“暗号错误”

分析:因为是多个路由中(不是全部路由)要处理一些类似的事情,所以用路由中间件。

显示效果:

5.4 静态资源中间件

express 内置处理静态资源的中间件,使用express.static()

注意事项:

  1. index.html 文件为默认打开的资源

image-20230910193038450

即使服务端代码里面没有配置/index.html的路由,那么访问http://127.0.0.1:3000默认打开的就是index.html资源,而如果配置了静态资源中间件,那么系统就会自动到静态资源文件夹中去查找文件。

  1. 如果静态资源与路由规则同时匹配,谁先匹配就响应谁
  2. 路由响应动态资源,静态资源中间件响应静态资源

这个功能相当于我们之前在http模块里面做的,响应不同的静态资源的案例。只不过express用一行代码就全部搞定了,而且会自动设置MIME类型,可以说是非常方便了。但我还是有一个疑问:纯后端express项目,需要静态资源吗?这个问题等我做项目的时候,可以带着问题想一想。

这个还是很有作用的,当我没有tomcat时,可以使用express来做一个简易的服务器,让别人都能够访问我的网站。

有了express的这个功能,创建项目服务器更简单了。

项目可以直接使用http://localhost:9000/来访问,更简单了。

5.4.1 练习

需求:局域网内可以访问尚品汇的网页。尚品汇就是一个编写好的、打包好的前端项目。

image-20230910193738063

分析:其实就是使用express的静态资源中间件,来展示index.html。

5.5 获取请求体数据 body-parser

express 可以使用 body-parser 包处理请求体。

使用案例来学习:

需求:按照要求搭建http服务,GET /login 显示表单网页,POST /login 获取表单中的用户名和密码。

分析:GET路由返回一个HTML页面,使用fs来读取。这个HTML页面里面,使用form表单来提交post请求。在POST路由里面,使用body-parser来获取请求体。

第一步:安装

第二步:导入 body-parser 包

第三步:获取中间件函数

第四步:设置路由中间件,然后使用 request.body 来获取请求体数据。

body-parser可以作为全局中间件使用,也可以作为路由中间件使用。但推荐作为路由中间件使用,因为不是所有路由都需要body-parser来解析请求体的,而且处理请求体的方式有两种,全局设置也不好判断怎么处理。

怎么看使用urlParser还是jsonParser呢?在前后端分离项目中,这其实是后端来定义前端的请求方式的。那么如果遇到实在不知道的情况,可以看前端提交参数的方式,在一个请求里面Payload,view parsed可以看到。

image-20230910200436800

获取到的请求体数据:

不用管前面的[Object: null prototype],这只是一种提示,值是后面的对象。

完整代码:

效果:

image-20230921140639182

5.6 防盗链

定义:防止外部网站盗用本地的资源。原理:禁止其它域名的网站来访问本网站的资源。

比如说:我在百度搜图里面,找几张图,复制它们的链接,粘贴到我的html文件中的img上,打开html文件,可以看到有的图片可以显示,有的图片不能显示,这就说明了有的图片设置了防盗链。

实现防盗链:

通过判断referer请求头是否为网站域名来返回不同结果。referer是访问图片时自动带上的头信息,不需要额外设置。

(在此案例中,使用 127.0.0.1:3000 和 localhost:3000来访问,在index.html里面使用img来测试防盗链。如果是直接在url里面访问public里的文件,还是可以正常访问的:http://localhost:3000/images/logo.png。这种情况的访问在请求头里面没有referer参数,估计还要另外想办法才行,先不用管)

image-20231110161913056

 

先输出req.get("referer")看一下是什么样子的:

image-20230921150333539

测试,直接在url中访问,看不同的网站域名是什么效果。

image-20230921150410987

下面显示使用localhost来访问,是访问不了的。

image-20230921150359619

image-20230921150703868

6.Router

6.1 什么是 Router

express 中的 Router 是一个完整的中间件和路由系统, Router可以看做是一个小型的 app 对象,你看独立router文件里面router的用法,和app.<method>(path,callback)的用法一样,也可以使用router.use()来使用中间件。

6.2 Router 作用

对路由进行模块化,更好的管理路由。

6.3 Router 使用

在public文件夹同层级创建routes文件夹,里面创建独立的 JS 文件(homeRouter.js),使用express.Router()来创建Router。

可以看到,创建router的过程和创建app基本流程是一致的,先记住基本流程的创建。

主文件

注意:

虽然说router就是小型的app,但是如果一个router里面想要使用一个通用的中间件,可不可以这样呢:

这样就可以避免在每一个路由规则里面写同样的中间件了,省事了不少。但实际效果是什么样的呢?

实际效果是不同的routes文件中,所有使用router创建的路由规则,都挂载上了这个中间件,有一些路由规则是不能使用这个中间件的,比如说登录、注册的路由规则。

说明不同的routes文件中的router,是同一个router,只不过挂载上了不同的路由规则而已,暴露出去的也是挂载了不同路由规则的router。

7.EJS 模板引擎

7.1 什么是模板引擎

模板引擎是分离 用户界面和业务数据 的一种技术。可以简单的认为是将html和js文件分离开来,分别进行处理(这里的将HTML和JS分离开来,指的是服务端的项目,在服务端写前端代码,像JSP,Django这种类型的)。

是的,我目前的感觉是,ejs不太重要,因为目前做的项目都是前后端分离,我在vue中写不是简单多了吗?这种模板语法在Django中看过,可以看一下这个文档:https://zhuanlan.zhihu.com/p/448064809。但是以后如果我找remote-developer的工作,不一定就是前后端分离的CRM这类全栈项目,也很有可能是工具类的全栈项目,这些全栈项目需要有一些页面在服务端进行处理,现在我就看到codeSandbox、prisma、vercel等等工具都是这样的,而这些工具类项目也很赚钱啊(提供基本功能的免费版,更多功能则收费,国外的很多公司都是愿意付费的),我如果能够参与进去,那肯定钱都不是问题了。那么这里的ejs还是要重视起来,最起码老师讲的内容要弄清楚,做好笔记。

搜索到的一段话:

EJS最方便的地方就是在于将项目给别人使用的时候,人家不用过多的去了解你的代码,直接修改配置文件就可以达到自己想要的效果。比如说工具类的软件Hexo,普通人想要自己创建博客网站,非常难,即使是程序员,也是很难的,但是使用Hexo只需要进行配置接口。Hexo中的配置都集中在_config.yml这个文件中,你根本不需要去一行一行的浏览源代码,就可以实现修改,达到你想要的效果。

那么我如果做了hexo这个项目,让用户在_config.yml文件里面配置好了之后,执行build命令时我就可以从这个文件里面获取具体的值,填入到ejs文件中,速度会非常快。

7.2 什么是 EJS

EJS 是一个高效的 Javascript 的模板引擎。

官网: https://ejs.co/

中文站:https://ejs.bootcss.com/

7.3 EJS 初体验

下载安装EJS

代码示例

示例:

老师用一步一步的示例来展示出ejs到底可以怎么写:

7.4 EJS 常用语法

执行JS代码

输出转义的数据到模板上

输出非转义的数据到模板上

7.5 ejs的一些语法

7.5.1 ejs列表渲染

需求:有一个数组,将里面的每一个元素渲染为一个li标签。

输出结果:

image-20230917104526877

用ejs实现:

输出结果:

image-20230921155550962

7.5.2 ejs条件渲染

需求:

原生js实现:

ejs实现:

通过改变isLogin的值来看输出:

image-20230917110559202

7.5.3 express中使用ejs

views/home.ejs文件:

启动服务,查看效果:

image-20230917112158330

8. express-generator工具

express-generator可以快速创建一个express应用的骨架。相当于是一个脚手架工具。

安装:npm i -g express-generator

安装完成后,会有一个全局命令express,通过express -v来查看是否安装成功:

image-20230917112654521

创建项目:

express [项目名],如果使用ejs模板,可以输入express [项目名] --view=ejs,简写方式:express -e [项目名]

image-20230921162429249

示例:express -e ejs-template

image-20230917113224336

在项目中需要先安装依赖:npm install

项目结构:

image-20230917113401535

查看package.json里面的命令,将"start": "node ./bin/www"改为"start": "nodemon ./bin/www",便于编写的时候调试。使用npm start将项目启动,浏览器输入127.0.0.1:3000来查看效果:

image-20230917113628281

里面有一些用法可以说一下:

app.js:

image-20230917113925153

image-20230921163530807

9.文件上传

下面的学习依赖上面express-generator创建的项目。

9.1 文件上传报文

app.js里面不做任何更改,直接在indexRouter里面添加路由:

image-20230917144406235

访问/portrait页面时,默认执行的是get请求,所以需要结合ejs来展示页面,新建portrait.ejs文件:

这个好像没有用到ejs的东西,但其实用到了,直接使用res.render了页面,等下一小节看会不会用到。浏览器输入127.0.0.1/3000/portrait进行访问:

因为这个页面在post请求后会跳转,所以需用用fiddle来监听http请求,结果如下:

image-20230917144235058

可以看到,form表单的内容在请求体中。

9.2 处理文件上传

需要用到formidable库,安装npm i formidable,这个包是用来解析form-data的。

image-20230917145607754

image-20230917145725458

但是代码示例有点问题,如果我直接粘贴的话,会报错:formidable is not a function,明明是官方的示例代码,却报错,搜索了一下没有找到答案,就打印formidable看一下是什么,发现里面有一个formidable的function,于是解构赋值,这才行了。

输出效果看一下:

仔细看一下fields和files的值,这个处理结果我很满意啊,我不用特地的分开附件和其余的表单信息,formidable都帮我处理好了:

image-20230917153100892

下一步,一般情况下,服务器在接收到上传的文件后,会把文件保存起来,供用户回显的时候用到。而且应该保存到用户能够很方便就能访问到的位置,所以保存在public/images文件夹里面。只需要对formidable进行配置即可:

image-20230917154002237

上传文件试一试,看图片保存到public/images文件夹里面没有。

image-20230917154131869

保存成功了。

下一步,在保存文件的同时,需要将这个文件的保存路径记录下来,放到数据库中,便于以后的查看。

image-20230917154503366

image-20230917155213270

查看输出:

image-20230917155240179

这一小节真的是把请求最难的部分给解决了,以后不管是上传图片还是上传word、excel等,我都不用怕了,而且把我的一个长久的疑问给解决了:文件上传后,后端是怎么保存的?是需要转为base64保存吗?是把文件保存到数据库中吗?

都不是,是直接保存到public文件夹里面,不需要转换为base64,只需要保存文件的访问url地址即可。这真的是巧妙啊,一个插件就解决了这么难的问题。

而且这个文件夹我看着很眼熟啊:

image-20230917160646851

而且我运行项目代码npm start,实际上nodemon执行的是bin/www文件。

image-20230917160818772

我在NGINX里面看到过,不过一直不知道这是干什么的,现在终于知道是干什么的了,这是部署服务端代码的。嘿嘿。

10.案例实践

做一个记账本项目,有两个界面,一个记账,一个展示账单。

image-20230917160131783

image-20230917160206814

10.1 项目初始化

创建项目:express -e accounts,安装依赖:npm i,将package.json里面的命令别名代码改为"start": "nodemon ./bin/www",方便我们在写代码的时候实时看到效果,将项目运行起来:npm start

10.2 路由配置

只使用一个路由文件,将usersRouter注释掉。在routes/index.js里面创建路由规则。

输入路由地址,看一下有没有问题:

image-20230917161627901

image-20230917161646820

10.3 响应静态页面

在views里面创建list.ejs、create.ejs文件,将老师提供的html页面粘贴进去,注意涉及到导入外部文件的时候,要使用绝对路径。

然后通过路由响应出去:

查看效果:

image-20230917162635606

image-20230917162654038

10.4 获取表单数据

为create.ejs的表单元素添加name属性,这样接口才能获取到具体的数据。同时,为form表单添加method和action。

添加新增记录的路由:

查看效果:

看一下请求体的数据:

image-20230917165443811

10.5 使用lowdb保存账单记录

添加的数据需要保存到数据库中,这样才能方便后续的使用。但目前没有讲到数据库,所以使用一个简单的数据库lowdb。

image-20230917165723888

因为最新版本的lowdb使用了import来引入,而其余的模块是使用require引入,如果将package.json里面改为type:module的话,很多东西都要变,所以使用lowdb@1.0.0版本即可。lowdb的使用很简单,照着文档做就行了。

image-20230917170033166

安装:npm i lowdb@1.0.0

在项目中新建data文件夹,创建db.json文件,里面手动初始化数据库,为什么手动初始化数据库呢?因为如果使用lowdb的方法来初始化的话,比较麻烦,重点不在这里。

image-20230917170829008

在routes/index.js中引入lowdb相关代码:

下一步,使用low的方法,将数据加入到数据库中,为了数据库更完善,需要给每条数据添加一个唯一的id,lowdb推荐使用shortid,安装npm i shortid,注意这个shortid库在npm网页里面已经找不到了,以后就不要使用这种库了,就使用nanoid。

查看一下数据库,非常OK。

image-20230917172102263

10.6 完善成功提醒

在添加记录成功之后,跳转到一个成功信息的页面。老师提供了success.html,我们只需要创建success.ejs文件即可。

image-20230917193404601

查看效果:

10.7 账单列表展示真实数据

先从数据库中取出真实的数据,然后发送到list.ejs文件中。

image-20230917194846292

在list.ejs文件中,使用ejs语法,将数据列表渲染出来。

显示效果:

image-20230917195053502

10.8 删除账单

image-20230917195930926

首先为页面的元素绑定事件,点击可以发起请求。这里用一个a标签来模拟。

image-20230917195624659

添加删除事件的路由:

查看效果:

提示:

一般删除都是用delete方法,以符合restful api的风格。但是这里只是演示,如果设置为router.delete方法,那么就不能使用a标签来发起请求了,后面讲到api时,会讲纯服务端怎么写,先不急。