nodeJS笔记

113 阅读17分钟

一、node.js 介绍

1、异步I/O

2、单线程

3、事件驱动

4、学习成本低

天使轮 小团队,几十万,PPT A轮 一百万 项目初步搭建,烧钱 B轮 千万 验证项目是否可以盈利 ,烧钱 C轮 亿, 项目收支平衡 D轮 十亿,项目有盈利前景,上市前夕

上市

二、安装配置

nodejs.org/zh-cn/

三、环境变量

环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数,如:临时文件夹位置和系统文件夹位置等。通过合理地配置环境变量可以增加访问文件或文件夹的方式

image-20210610104647927

在path中添加node的安装路径后,我们就可以在任意的目录下调用node程序。

四、CMD命令行

第1种:windows + R  -> 输入 cmd -> 敲回车

第2种:打开任意文件夹  -> 在地址栏输入cmd  -> 敲回车

常用命令

● cls 清屏

● dir 列出当前目录下的所有文件

● cd 进入到指定的目录 cd..   退回到上一级目录

● md 创建指定的目录

● rd 删除一个文件夹(只能删除空文件夹

● rm 删除文件

五、第一个node程序

// node中的顶级对象
console.logglobal );
// 当前js文件的目录
console.log( __dirname );
// 当前js文件的路径
console.log( __filename );

六、node的文件模块

node通过文件模块(fs   filesystem)实现对文件或目录的读、写、改、新建等操作。

6.1 文件操作步骤(了解)

1)打开文件    fs.openSync()

2)输入内容 fs.writeSync()

3)关闭文件 fs.closeSync()

模式说明
a文件用于追加。 如果文件不存在,则创建该文件
ax类似于a,但如果路径存在,则失败
a+打开文件用于读取和追加。 如果文件不存在,则创建该文件
ax+类似于 'a+',但如果路径存在,则失败
as打开文件用于追加(在同步模式中)。 如果文件不存在,则创建该文件
as+打开文件用于读取和追加(在同步模式中)。 如果文件不存在,则创建该文件
r+打开文件用于读取和写入。 如果文件不存在,则会发生异常
rs+打开文件用于读取和写入(在同步模式中)。 指示操作系统绕过本地的文件系统缓存
w打开文件用于写入。 如果文件不存在则创建文件,如果文件存在则截断文件
wx类似于 'w',但如果路径存在,则失败
let fs = require("fs");
let fd = fs.openSync("./ok.txt","a");
fs.writeSync(fd,"床前明月光");
fs.closeSync(fd);

6.2 文件操作命令

1)fs.writeFile() 写入    fs.appendFile() 追加内容
//同步
fs.writeFileSync("./ok.txt","床前明月光");
//异步写法
fs.writeFile("./ok.txt","床前明月光",(err)=>{
 console.log("第一句写入完成");
})
2)  fs.readFile(路径, callback ) 读文件
// 同步写法。
// 同步写法特征是声明变量保存同步函数的return 值
let data = fs.readFileSync("./ok.txt");
console.log( data.toString() );

// 异步写法。
// 回调函数中两个形参:err为错误消息内容;data 为读取文件内容
fs.readFile("./ok.txt",(err,data)=>{
 console.log( data.toString() );
})
3)  fs.unlink(路径,callback) 删除文件
//同步
// fs.unlinkSync("./ok.txt");
//异步
fs.unlink("./ok.txt",(err)=>{
 if(!err){
   console.log"删除成功");
 }
})
4)fs.stat(路径,callback )  查看文件属性
let stats = fs.statSync("./ok.txt");
if( stats.isFile() ){
 console.log("是文件");
}else{
 console.log("不是文件");
}

fs.stat("../案例",(err,stats)=>{
 console.log( stats );
 // isFile()返回布尔值表示是否是文件,是文件返回true。
 console.log( stats.isFile() );
 // isDirectory()返回布尔值表示是否是目录, 是目录返回true
 console.log(stats.isDirectory() );
})
5)  fs.rename(老路径,新路径,callback ) 修改文件名
// 修改文件名
fs.renameSync("./ok.txt","./hello.txt");

6.3 对目录操作

1) 创建目录 fs.mkdir()

fs.mkdirSync("./ok");
  1. 删除目录 fs.rmdir()
// 删除目录,只能删除空目录
fs.rmdirSync("./ok");
  1. 读取目录 fs.readdir()
// 读目录, 将目录下的文件名及子目录名作为数组返回
let files = fs.readdirSync("../案例");
console.log( files );

小练习:读取指定目录下的所有文件(包括子目录下的),将文件名打印输出

let fs = require("fs");
// 读取指定的目录,遍历返回的数组,遍历过程中判断是否文件,如果是文件则打印输出,如果是目录递归调用函数
function readdir( url ){
 let files = fs.readdirSync(url);
 console.log( files );
 // 第一次返回数组:[ '01.txt', 'aa', 'hello.txt' ]
 // 第二次返回数组:[ '02.txt', '03.txt', 'bb']
 // 第三次返回数组:[ '04.txt', '05.txt']
 for(let item of files){
   // 遍历时判断数组中每一项是文件还是目录
   console.log( url+"/"+item );
   let stats = fs.statSync( url+"/"+item );
   if( stats.isFile() ){
     // 是文件
     console.log( item );
   }else{
     // 是目录,则递归调用读取子目录
     readdir( url+"/"+item );
   }
 }
}
readdir( __dirname+"/ok")

6.4  二进制

二极管 通电1  断电0

0

1

10

11

110

十进制 1998   二进制  0111 1100 1110

计算机上的任意文件类型其实都是以二进制形式存储在计算机的硬盘上。

node引入buffer可以直接读取操作二进制内容。Buffer对象有 toString() 方法可以实现将二进制转为其他编码。可选的参数有 ascII \ base64 \  uft8 \  hex 等等。

1个二进制    1bit (比特)

8bit 1byte(字节)

1024b 1Kb

1024Kb 1Mb

1024Mb  1Gb

1024Gb 1Tb

6.5 ASCII编码

ascII 介绍:

ASCII 码使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符。标准ASCII 码也叫基础ASCII码,使用7 位二进制数(剩下的1位二进制为0)来表示所有的大写和小写字母,数字0 到9、标点符号,以及在美式英语中使用的特殊控制字符

baike.baidu.com/item/ASCII/…

ascii编码1

汉字编码: GB2312 BIG5  

微软公司  UTF(utf8\ utf16\ utfmb3) 万国码

编码转换工具:tool.chinaz.com/Tools/Unico…

七、递归读取指定目录下所有文件

作业:

递归删除指定的非空目录(只能使用同步语法)

一、node中的模块化

随着JS项目的越来越复杂,就必须考虑模块化。没有实现模块化的弊端:

1、全局变量重复、全局污染

2、js的加载顺序

3、不方便管理。开发者自己去下载,写代码加载。

模块化的好处?

最大的好处是大大提高了代码的可维护性。

其次,编写代码不必从零开始。当一个模块编写完毕,就可以 被其他地方引用。我们在编写程序的时候,也经常引用其他模块,包括Node.js内置的模块和来自第三方的模块。

使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中,因此, 我们自己在编写模块时,不必考虑名字会与其他模块冲突。但是也要注意,尽量不要与内置函数名字冲突。

二、node中的自定义模块

node中一个js文件就是一个模块。用户自己写的js就是自定义模块。

2.1  require谁就运行谁

require() 加载谁就执行加载的js模块。 require()属于同步语法。

2.2 模块天生具有隔离作用域

每个模块就像铜墙铁壁,其中声明的变量和函数别的模块无法使用。

2.3  使用 exports.xxx = xxx的语法向外暴露

test.js

let name = "张三";
function fn(){
 console.log( "hello");
}
// 使用exports.xxx = xxx 向外暴露。实际是将变量或函数包在一个对象中输出
exports.name = name;
exports.fn = fn;

01.js  接收 test.js 暴露的内容

// 加载自定义模块时,一定要添加路径
let test = require("./test.js")

2.4  使用module.exports = xxx的语法向外暴露

module.exports = {
 name,
 fn,
 demo
}

2.5  exports与 module.exports的区别

module.exports 使用上更灵活,可单独暴露一个函数或变量。

require() 实际加载的是module.exports,而不是exports。就是说两个语法同时用,exports不起作用。

2.6 module.exports在向外暴露class时更方便

例如向外暴露一个class: Person.js

class Person{
 constructor(name){
   this.name = name
 }
 say(){

 }
}

exports.Person = Person;
// 或
module.exports = Person;

那么在01.js 中引入后,如果是 exports.xxx=xxx的暴露方法,实例化的写法有点怪异:

let p1 = new Person.Person("张三");

如果是 module.exports = xxx 的暴露方法,加载后实例化写法比较正常:

let p1 = new Person("李四");

综上所述,更多是采用 module.exports = xxx 的写法。

2.7  练习:自定义node模块,实现去除字符中前后的空格

创建 trim.js 的自定义模块

function trimLeft(str){
 let reg = /^\s+/;
 return str.replace(reg,"")
}

function trimRight(str){
 let reg = /\s+$/;
 return str.replace(reg,"");
}
// 向外暴露
module.exports = { trimLeft, trimRight }

使用自定义模块时,需先加载。

let trim = require("./trim.js");

//使用模块
let str = trim.trimRight("   zhangsan   ")

三、node中的内置模块

node官方开发的内置的功能模块

3.1 用于url处理的内置模块

url

let url = require("url");
let str = "http://www.jd.com:8080/hello/index.html?id=19&name=jack&sex=男#abc123";

console.log( url.parse(str,true) ); // url.parse()将url字符串转为对象
let {query:{id,name,sex}} = url.parse(str,true);
console.log( id,name,sex);

3.2 path模块

path.join(路径1,路径2,...) 拼接路径;将路径片段拼接为完整的符合操作系统规范的相对路径

console.log( path.join("./hello""ok""index.html") ); // ./hello/ok/index.html

path.resolve(路径1,路径2,...)  拼接路径;将路径片段拼接为完整的符合操作系统规范的绝对路径。可识别./ ../ /相对路径并转换为绝对路径。

console.log( path.resolve("../笔记","node中的模块化.md") ); //D:\0531北京\day05\笔记\node中的模块化.md

path.extname() 从资源文件中提取扩展名

path.extname("index.jpg?id=12"// .jpg?id=12

path.dirname() 从url中提取资源路径

console.log( path.dirname(str) ); // http://www.jd.com:8080/hello

四、node的第三方模块

包(package):包含至少一个js文件的文件夹。

4.1  node package manager(NPM)包管理器

从npm网站上下载使用包的步骤:

1) 创建一个目录(目录名不要和常见的关键词重复,不能是汉字),然后进入该目录命令行下

2) 初始化命令,生成一个安装包的配置文件,必须要有package.json才可以从npm网站上下载包

npm init

必须包含 name,version,main   完整属性如下表:

属性名说明
name包(项目)的名称
version包(项目)的版本号
description包(项目)的描述
main包(项目)入口文件
scripts定义快捷脚本命令
keywords项目关键词
author作者
license协议
dependencies包(项目)依赖的模块
devDependencies包(项目)开发依赖的模块

3)执行npm 安装命令

npm i ( npm install ) 表示安装包

安装完成后,项目目录下会多出 package-lock.json 和 node_modules 文件夹。下载的包统一存在在node_modules 下面

4) 使用包,根据包作者的教程

小练习:安装使用包 time-stamp

4.2 设置淘宝下载镜像网站

npm网站官网服务器位于国外,下载速度比较慢而且不稳定。一般国内开发者需要将npm 的下载地址修改为为国内的淘宝镜像网站。

命令: 完成后会在C:/users/adminstrator/目录下生产.npmrc文件

npm config set registry https://registry.npm.taobao.org

验证命令

npm config get registry

五、npm 加载机制

node的内置模块和npm上的第三方模块加载时不需要写路径,node会自动根据 module.paths 去查找 node_modules目录。

node中内置了 module.paths 的数组常量,包含了node_module目录所有的的路径。当执行require('time-stamp')时,会自动根据数组的地址从上到下依次查询。

'D:\0531北京\day05\案例\myApp01\node_modules''D:\0531北京\day05\案例\node_modules''D:\0531北京\day05\node_modules''D:\0531北京\node_modules''D:\node_modules']

其他的加载机制有:

a) 可以不写扩展名,node 会按照 .js、.json、.node的顺序来分析

b) 如果没有找到对应的文件,找到了一个目录,那么将会将其当作是一个包来处理

c) 首先找是否含有 package.json,如果有,则分析它的 main 属性,找到 main 属性对应 的那个文件

d) 如果没有 package.json 或者是 main 解析失败了,那么就找文件名为 index 的文件,依次从 index. js、index.json、index.node 查找。

以上都失败,则会提示“can not find modules”

小练习:分析一下下面的require命令分别加载的是谁?

require("./a.js")    当前目录下的a.js

require("a.js")    找node_modules目录下a.js

require("a")  找node_modules目录下 a 目录下 package.json 内的main属性对应的文件名

require("./a")  找当前目录下的a目录下的 index.js  

六、 package.json文件  和 package-lock.json

package.json 文件是npm的创造性发明。每次安装包后,都会在package.json中添加 dependencies (依赖)。那么其他开发者可根据package.json知道你的项目需要依赖那些第三方的包才可以运行。

lock意味锁定,package-lock.json 保存了指定的第三方的包的版本号。当开发者的程序因为第三方的包升级后不能运行了,可根据package-lock.json中保存的老的版本的地址重新去下载。从而使项目可以运行。

七、常用的npm 命令

npm 官网:www.npmjs.cn/

安装包 npm i 包名    或者     npm  install  包名

卸载包   npm  uninstall  包名

清除缓存:npm cache clean --force 当反复安装失败时,调用该命令后再重新安装

安装到依赖( 项目运行时需要的模块)

npm  i   包名  --save (--save为默认选项,意思为添加到package.json 的dependencies 里面)

安装到开发依赖(只是开发过程中需要的模块)

npm i   包名   --save-dev (--save-dev ,意思为添加到package.json 中的 devDependencies 里面 )

开发依赖指项目的开发过程中需要使用的包,像less编译为css, 语法检查模块等。

八、本地与全局安装

本地安装,即安装到您的项目的node_modules目录下。本地安装的包只能在本项目内使用。

全局安装,即将包安装到npm的安装目录下。可通过   npm  root -g 来查看。全局安装的包在所有项目中都可以调用。

全局安装命令:

npm i webpack -g

作业:

实现一个自定义的模块,将指定的目录下的文件名(包括子目录下)保存在数组中并返回。

一、node与服务器应用

1.1 什么是上网

服务器1

客户端:

1)发送请求(在浏览器的地址栏输入一个url回车)

  1.  接收服务端返回的结果,将页面显示(如果是html则浏览器直接渲染,如果是json需要使用js渲染后更新网页)。

服务器:

1)已经运行的服务端程序,接收了用户的请求

2)分析的用户的请求,搞清楚用户需要的资源,去读取相应的资源。

3)或者去数据库实现对数据的增删改查

4) 将操作的结果返回给客户端(结果有两种:html 或 json)

1.2  服务器与客户端

在此说的服务器默认指 web服务器(www服务器),这种服务器为用户提供上网服务。服务器上内容主要是网站开发者上传的网站源码,用户使用浏览器可以访问你的网站。

服务器:配置比较高的电脑

硬件配置:多线程处理器CPU, 超大内存、超大硬盘、带宽

软件配置:

操作系统:linux、Unix

应用软件:web服务器上常用软件:

Apache(音译为阿帕奇)是世界使用排名第一的Web服务器软件。它可以运行在几乎所有广泛使用的计算机平台上,由于其跨平台和安全性被广泛使用,是最流行的Web服务器端软件之一

Nginx是一款轻量级Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东新浪网易腾讯淘宝

数据库软件:Oracle、mysql、SQLserver

编程语言:Java、PHP、Python

服务器2

node既是平台又是编程语言

node

客户端:

可以用来上网的设备(pc、手机、pad、电视、智能手表、物联网设备)。一般来说都是通过浏览器实现上网,因此我们也可将浏览器称为客户端。

三、IP地址与域名

2012110903575056504

IP地址:是服务器在互联网上的唯一标识。ip地址范围:IPv4标准: 0.0.0.0 ~ 255.255.255.255

内网IP:只能在局域网内互相访问,外网IP不能直接访问内网IP。内网IP也不能直接访问互联网,需要通过路由器实现。

外网IP:直接可连上互联网的IP地址。

直接通过IP地址访问服务器不容易记忆,因此人们又发明了域名。注册域名后,需要将服务器的IP地址捆绑(解析),解析生效后,就可以通过域名访问网站了。提供解析转换服务的服务器称为根服务器。

特殊IP地址与域名:

127.0.0.1 操作系统中默认的指向本地服务器(本地主机)

localhost 操作系统中默认的指向本地服务器(本地主机)

四、互联网传输协议

协议( Protocol)是指双方为了完成一个目标结果所必须遵守的规则和约定。通俗的理解:双方采用约定好的格式来做某种事情,这种事先约定好的格式,就叫做协议。

2-15现实中的协议

4.1 互联网协议的分类

http 协议: 超文本传输协议

ftp协议: 文件传输协议。客户端通过FTP协议访问位于FTP服务器上的资源。由于FTP传输效率非常高,在网络上传输大的文件时,一般也采用该协议。(断点续传)

smtp协议:电子邮件传输协议

五、http协议发展历史

clipboard

1) http /0.9 非常简单,只有一个get方法。只支持html。不支持图片、声音等多媒体资源。

2) http/1.0 增加了get/post等多种请求方法,服务器也可以响应图片、mp3等电脑上常见的文件格式。但是一次请求只能一个响应,传输数据效率很低。

TCP: 客户端与服务器端之间建立的通道,可比喻为高速公路

HTTP:可理解为高速公路上的卡车。卡车上装载的“货物”就是 HTML

HTML:超文本标记语言

HTTP1.0 缺陷:

一条高速公路上同时只能跑一辆车。即一次请求返回一个响应就断开tcp连接。导致如果html中有很多外链文件(.css,  .jpg  .js)的话,全部显示完整页面需要很长时间。

3) HTTP 1.1 主要是添加一个keep-live( 长连接 )功能。现在一个连接通道可以同时跑多辆卡车。即一个响应结束后不断开连接,可继续发送请求及响应。大大提高了使用效率。除此以外,还支持put、delete、propfind等,用来创建RESTful API。

HTTP1.1 缺陷:请求响应必须依照次序进行。如果有大量的请求密集发送,容易造成网络堵塞。解决办法:合并图片(精灵图),合并css、合并JS,减少请求次数。

http1.1

4) http2 更安全、更方便,功能更强。例如:压缩传输、安全性、复路多用等。目前新版的浏览器都支持http2, 但服务器端正在普及中。

六、http协议的特点

1、简单、快速。

2、灵活,可以传输系统中多数类型的文件。jpg 、gif\png\doc\rar\pdf\mp3\mp4....

3、无状态、无连接。http协议不能记忆用户的各种状态。例如用户的登录状态、头像、订单信息...,因此人们又发明了 cookie、session等技术帮助实现记忆用户状态的功能。无连接指服务完成后会自动断开连接,释放资源。

七、请求与响应

7.1 请求

一个请求返回一个响应。普通意义的请求:浏览器地址栏输入url回车,另外,浏览器在解析html代码时碰到href、src等外链资源时,会自动发送请求;提交表单; 使用JavaScript程序自动提交请求;

每次发送请求时,提交的内容:

1) 请求行:包含请求方法(GET/POST/PUT) URI   http版本

2) 请求头: 包含了请求时提交的信息,例如cookie等

3) 请求体:一般为空。当表单提交时并且表单的 method 等于 post时,表单中的控件的值保存在请求体中。

wps2

例如:

GET /gjgwy/kaoshi/2960/ HTTP/1.1
Host: www.offcn.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://www.offcn.com/
Connection: keep-alive
Cookie: Hm_lvt_e2aae86521497cb530b3ddd615555b4f=1628489508; Hm_lpvt_e2aae86521497cb530b3ddd615555b4f=1628489508; NTKF_T2D_CLIENTID=guest4B6BBD94-BC75-41B9-84D4-995298BCF65F; Hm_lvt_a6adf98bf5f7dd3d72872cf8b3535543=1626069966,1628480923,1628489414; Hm_lvt_4ec9d2a15f26f718712c06950b0b3861=1626069966,1628480923,1628489414; nTalk_CACHE_DATA={uid:kf_10353_ISME9754_guest4B6BBD94-BC75-41,tid:1628489413889269}; Hm_lpvt_4ec9d2a15f26f718712c06950b0b3861=1628489425; Hm_lpvt_a6adf98bf5f7dd3d72872cf8b3535543=1628489508
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0

7.2 响应

  1. 状态行: 状态码与描述

2) 响应头:包含了此次响应的信息。例如 content-type,浏览器必须依照这个指定的类型去渲染页面。

3) 响应体: 包含了此次响应的数据,服务器返回给客户端的内容。可能是html代码、js代码、图片、mp3...

wps3

7.3  状态码与状态码描述:

状态代码有三位数字组成,第一个数字定义了响应的类别,共分五种类别:

1xx:指示信息--表示请求已接收,继续处理

2xx:成功--表示请求已被成功接收、理解、接受(200仅仅表示http层面的成功,也就说请求到了服务器,服务器也给客户端响应了,但是未必能得到预期的结果数据,这要前后端好好对接一下)

3xx:重定向--要完成请求必须进行更进一步的操作

4xx:客户端错误--请求有语法错误或请求无法实现

5xx:服务器端错误--服务器未能实现合法的请求

常见状态码:

200 OK            //客户端请求成功

400 Bad Request     //客户端请求有语法错误,不能被服务器所理解

401 Unauthorized    //请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用

403 Forbidden       //服务器收到请求,但是拒绝提供服务

404 Not Found       //请求资源不存在,eg:输入了错误的URL

500 Internal Server Error   //服务器发生不可预期的错误

503 Server Unavailable    //服务器当前不能处理客户端的请求,一段时间后可能恢复正常

八、使用node原生语法创建一个http服务器程序

let http = require("http");
let fs = require("fs");

let num = 0;
// 创建http服务
let server = http.createServer((request,response)=>{
 console.log( request.url );
 // num++;
 // console.log(`第${num}位同学已访问`);
 // 读取当前目录下的index.html
 let data = fs.readFileSync("./index.html");
 // 响应给客户端
 response.end( data.toString("utf-8") );
})
// 启动http服务
// 参数1:端口号,一个web服务器上可以挂载多个网站,每个网站可以用不同的端口号区分。范围 0~60000之间
server.listen(3002,()=>{
 console.log("http服务已启动在3002端口");
})

九、node的顶层路由

路由:url到函数的映射。

路由

路由:由开发人员事先设置好的“url字符串”,当用户发送请求时和 url字符串匹配上,就可以得到相应的响应服务。

node顶层路由:node的http服务器程序,可以很容易“伪造”一个不存在的网页,通过修改路由代码可以实现正常访问。也就是说,node变的http服务器程序,路由与物理文件不是一一对应关系,这种叫node的顶层路由设计,目前只有node和python可以实现顶层路由。

优点:可以将路由设置的比较简洁,就可以访问路径较复杂的网页

if (urlObj.pathname == "/69383") {
   let data = fs.readFileSync("./gjgwy/2020/1015/69383.html");
   response.end(data.toString("utf-8"));
 }

缺点:所有的静态资源(.css  .jpg  .js)也需要写路由才可以响应。但是可以使用express框架解决。

十、表单的get和 post

如果是GET请求,可使用 url模块来解析表单的数据。

例如:表单代码:

 <form action="/login" method="get">
   姓名:<input type="text" name="username">
   <input type="submit" value="提交">
 </form>
let urlObj = url.parse(request.url, true);
let {query:{username}} = urlObj;

如果是POST请求,使用node原生语法解析表单数据太繁琐,后期使用express框架来解决。

一、express简介

基于 Node.js 平台,快速、开放、极简的 Web 开发框架,可以快速开发网站或者提供API的后台服务程序。

www.expressjs.com.cn/

二、express的安装使用

安装步骤:

1) 快速创建package.json

npm  init -y
  1. 安装 express
npm i express
  1. 使用express 创建http服务应用程序
let express = require("express");
let app = express();
let path = require("path");

// 添加路由
app.get("/", (request,response)=>{
 // send() 相当于 setHeader() 与 end() 的封装
 // response.send("<h2>欢迎访问首页</h2>");
 response.sendFile( path.resolve("./index.html") );
})
app.get("/music", (request,response)=>{
 response.send("音乐频道");
})
// 启动http服务
app.listen(3000,()=>{
 console.log("http服务已启动在3000端口");
})

express路由语法总结:

app.请求方法(路由,回调函数 )

三、静态资源下载服务

因为node的顶层路由设计原因,所有的静态资源(css\图片\js)也都需要有路由响应,express提供了 内置中间件( express.static() )来提供静态资源下载服务。

1) 创建public 目录,将所有静态资源放到此目录下

2) 添加一个中间件配置

// 使用express内置中间件实现将public指定为静态资源下载目录
// 放在public目录下的资源文件,只要路径和文件名正确,就可以直接访问,不需要额外写路由。
app.use( express.static("./public") );

四、动态路由

http://127.0.0.1:3000/student/10002 查看学号为10002号的学生信息

http://127.0.0.1:3000/student/10003

http://127.0.0.1:3000/student/18888

。。。。

4.1 使用正则表达式匹配:

//student/(\d{5})/

// 正则匹配路由
app.get(//student/(\d{5})/, (request,response)=>{
 let num = request.params[0];
 response.send( `学号为${num}号的学生信息` )
})

4.2  冒号引导变量

// 冒号引导变量
app.get("/student/:num", (request,response)=>{
 let num = request.params.num;
 response.send`学号为${num}号的学生信息` )
})

小练习:

http://127.0.0.1:3000/book/平凡的世界/author/路遥

http://127.0.0.1:3000/book/呐喊/author/鲁迅

五、路由模块化

当网站的路由特别多时,所有的路由写在入口文件代码很臃肿,一般将同类型的路由写到一个模块中,并向外暴露,然后在入口文件中引用并注册即可。

1) 新建 routes 目录,再新建 index.js 模块,将所有的路由放到index.js 中,然后向外暴露router

index.js代码:

let express = require("express");
let router = express.Router();

//把路由定义在router对象上

module.exports = router;

2) 在入口文件中引入 routes/index.js 模块,并进行注册

//加载路由模块
let indexRouter = require("./routes/index.js");

//注册
app.use("/", indexRouter)
  1. 访问

http://localhost:3000/music/chinese

http://localhost:3000/music/jp

http://localhost:3000/music/en

。。。

http://localhost:3000/phone/apple

http://localhost:3000/phone/huawei

http://localhost:3000/phone/xiaomi

。。。

六、热更新中间件

nodemon 是一个热更新中间件,作用可以自动监听项目文件的改动,代码被修改而且保存后,nodemon会自动帮我们重启项目,节约开发调试的时间。

  1. 全局安装nodemon , 全局安装模式将包安装在node.js 的安装目录下,全局安装只需要安装一次,以后在所有的项目中都可以调用nodemon 程序了。
npm i nodemon -g

全局安装目录在 C:\Users\samsung\AppData\Roaming\npm\node_modules 目录下。

2) nodemon 的配置:

项目根目录下新建一个 nodemon.json 文件:

{
 "restartable": "rs",
 "ignore": [".git", ".svn", "node_modules /**/ node_modules"],
 "verbose": true,
 "execMap": {"js": "node --harmony"},
 "watch": [],
 "env": {"NODE_ENV": "development"
 },
 "ext": "js json"
}
  1. 使用热更新启动项目
nodemon index

部分系统会因为权限问题报错:

cts-210616162420114

是因为PowerShell执行策略的问题。

解决方法:

  1. 以管理员身份运行vscode;
  2. 执行:get-ExecutionPolicy,显示Restricted,表示状态是禁止的;
  3. 执行:set-ExecutionPolicy RemoteSigned;
  4. 这时再执行get-ExecutionPolicy,就显示RemoteSigned;

原文链接:blog.csdn.net/larpland/ar…

七、 RESTclient

作用:REST Client是接口调试工具,方便在开发接口时候进行调试接口,解决当调试post请求时必须写一个表单文件或ajax的麻烦。它是基于vscode的一个插件,需要在vscode工具上安装。

在项目根目录下创建 .http  或  .rest 文件:

@localhost = localhost
@port = 3000
@host = {{localhost}}:{{port}}
###
GET http://{{host}}/game HTTP/1.1

###
GET HTTP://{{host}}/hello HTTP/1.1

每个路由用 ### 分隔;注意变量声明不要有引号和分号等。

八、next 继续匹配

express中路由的匹配从上至下进行,默认情况下,一旦有一个可以匹配上,就不再向下继续匹配了。不过可以添加next参数实现继续向下匹配

router.get("/game", (request,response,next)=>{
 // response.send("游戏频道aaa");
 console.log"游戏频道aaa" );
 next();
})
router.get("/game", (request,response)=>{
 response.send("游戏频道bbb");
})

一、next 向下匹配

express中路由从上至下匹配,有匹配上的就不再继续向下了。但是为了增长程序灵活性,可以使用next() 实现继续向下匹配。

二、中间件原理

菜 =》 采购流程 =》 洗菜流程  =》切菜流程 =》炒菜流程 =》 成品

3-5中间件原理-炼钢车间

中间件: 封装了请求对象与响应对象的能够实现特定功能的函数。

作用:中间件指业务处理过程中中间环节,例如一家工厂,从原料到生产出成品,中间需要多个加工环节。项目的开发类似,当用户的数据提交后,经过多个中间件函数处理,最后给用户一个响应的结果。

3-6程序中的中间件执行流程

三、中间件案例:

利用中间件给所有的请求对象添加一个时间属性。

经过以上的中间件处理后,req请求对象上就多了time 属性。

let time = require("time-stamp");
// 自定义中间件,为req对象添加一个time属性
app.use((req,res,next)=>{
 req.time = time("YYYY年MM月DD日 HH:mm:ss");
 next();
})

经过这个中间件处理后,以后使用req对象时就有了time属性。

router.get("/", (req,res)=>{
 res.send("欢迎访问首页,当前时间是:"+ req.time );
})

四、中间件前缀

如果某个中间件只是针对某种请求起作用,可以针对性的添加前缀。

中间件总结:

1) 一般是在路由之前使用中间件(错误消息处理中间件除外)

2) 可以连续使用中间件处理客户端请求

3)中间件中必需有next()

  1.  多个中间件的请求与响应对象是共享的(同一个)。

app.use("/game",(req, res, next) => {

 req.time = time("YYYY年MM月DD日 HH:mm:ss");

 next();

})

有game只对以game前缀开头的请求起作用 去掉game前缀之后所有都可以起作用

五、错误处理中间件

4.1 404错误中间件。一般放在所有路由的下面,当请求匹配不了所有路由时,由错误中间件负责给用户提示一个消息。

app.use((req,res)=>{
 res.send("<h2>对不起,找不到网页</h2>")
})

4.2 逻辑错误处理中间件

在项目代码中,所有的不满足条件的消息可以统一由一个逻辑错误处理中间件来向用户提示错误消息:

在模块的逻辑处理代码中,如果需要给用户提示错误消息,可以使用next("消息")

image-20210811114113393

那么 next() 会自动去寻找逻辑错误消息处理中间件(一般放在所有路由下部),并将 错误消息填充到中间件函数的第一个参数err 中。

// 逻辑错误消息处理中间件
app.use((err,req,res,next)=>{
 res.send( err );
})

六、express内置的中间件

1)  express.static(路径)  提供静态资源下载服务
2)express.urlencode()  解析表单提交时content-type 为application/x-www-form-urlencoded 格式的数据
// 内置中间件 express.urlencode() 用来解析表单 post 方法提交的 content-type为 application/x-www-form-urlencoded 的表单数据
app.use( express.urlencoded({extended:false}) );

如果表单是get 提交,可使用 req.query 获取表单数据

如果表单是post提交,可适用 req.body 获取表单数据

如果请求的类型是  /dologin/jack/3232;  那么可使用冒号引导变量的路由,使用 req.params 获取数据

router.get("/dologin/:username/:password",(req,res)=>{
 let {username,password} = req.params;
 console.log( username,password );
})
3)express.json()  解析content-type为 appliction/json 格式的请求体数据。(例如ajax的data属性为一个json)
a) 入口文件中添加配置:
// 内置中间件express.json() 用来解析 post方法提交的 content-type 为 application/json 格式的数据(一般用在ajax提交时 )
app.use( express.json() );
b) 使用ajax发送请求并提交 json 格式的数据
###
POST http://localhost:3000/baoming HTTP/1.1
Content-Type: application/json

{"username":"李四","password":"1234343"}
c) 在路由处理函数中使用 req.body 来解析提交的数据
router.post("/baoming",(req,res)=>{
 console.log( req.body );
 let {username,password} = req.body;
 res.send(`<h2> ${username}报名成功</h2>`)
})

七、第三方中间件

7.1  server-favico

网站图标在线制作:tool.lu/favicon/

www.npmjs.com/package/ser…

安装中间件: npm i serve-favicon

var favicon = require('serve-favicon')

app.use(favicon(path.join(__dirname, 'public','ico''favi.ico')))
注意: 每个端口号下的图标有缓存,需要清理浏览器缓存后刷新才可以生效。

或者更改下端口号即可生效。

7.2  svg-captcha 验证码中间件

svg是一种新型的网络图片格式,可以任意缩放不失真。

安装:npm i svg-captcha

加载模块:

let svgCaptcha = require("svg-captcha");

配置项:

 let options = {
   size:4,// 验证码长度
   width:200//验证码图片宽度
   height:80//验证码图片的高度
   background:"#873223"//验证码图片的背景颜色
   noise:4//验证码图片中的干扰线
   fontSize:60//验证码图片中文字大小
   color:true, //验证码图片中文字颜色
   ignoreChars:"i1o0" //容易混淆的字符不要出现在验证码中
 };

使用:

html中插入 img 标签:

验证码:<img src="/yzm" alt=""><br>

实现响应:

let captcha = require("svg-captcha");
// 响应验证码的请求
router.get("/yzm",(req,res)=>{
 let options = {
   size:4,// 验证码长度
   width:200//验证码图片宽度
   height:80//验证码图片的高度
   background:"#873223"//验证码图片的背景颜色
   noise:4//验证码图片中的干扰线
   fontSize:60//验证码图片中文字大小
   color:true//验证码图片中文字颜色
   ignoreChars:"i1o0" //容易混淆的字符不要出现在验证码中
 };
 // 生成svg格式的图片
 let svg = captcha.create( options );
 console.log( svg );
 // svg对象中的text属性为生成的验证码字符串,data属性为验证码字符串转换的svg图形。
 res.type("svg");
 res.send( svg.data );
})

八、express-generator  生成器(脚手架)安装

作用:快速创建一个应用程序的骨架,包含了常用的包的安装、目录、代码等等。

全局安装: npm i express-generator  -g

注意:全局安装时,如果报错“mkdir” 没有权限,则需要以管理员身份重新运行 vs-code

安装完成后,输入  express  -h 查看相关命令。

实现步骤:

1) 进入要创建项目的目录下,执行如下命令

express --no-view  myApp02

创建常见的目录和文件,但是包并没有安装,需要手动安装

2) 进入创建的项目目录下,安装所有的包

cd myApp02
npm i
  1. 修改 package.json,并且复制 nodemon.json 文件到当前项目下。
 "scripts": {
   "dev": "nodemon ./bin/www"
 },

以后启动项目使用npm命令:  npm  run dev

一、formidable 中间件  解析表单数据

express.urlencode()只能解析普通表单控件数据。假如表单中有上传控件的,express.urlencode()无法解析,必须使用formidable 来实现上传功能。

安装: npm i formidable

 <form action="/baoming" method="POST" enctype="multipart/form-data">
   姓名: <input type="text" name="username"><br>
   性别:<input type="text" name="sex"><br>
   照片:<input type="file" name="photo"><br>
   <input type="submit" value="报名">
 </form>
// 响应报名表单提交
router.post("/baoming",(req,res)=>{
 // 使用 formidable 中间件解析上传表单数据
 // formidable 配置:
 // uploadDir 指定一个文件上传后存放的目录
 const form = formidable({ multiplestrueuploadDir:"./upload" });
 // form.parse()从请求对象中解析表单的控件数据
 form.parse(req,(err,fields,files)=>{
   res.writeHead(200, { 'content-type''application/json' });
   res.end(JSON.stringify({ fields, files }, null2));
   // fields对象是表单中普通控件的name与value组成的对象
   // files对象是表单中上传控件的属性组成的对象
 })
})

formidable将表单中的数据存放 fields 和 files 两个对象中。fields对象是表单中普通控件的name与value组成的对象;files对象是表单中上传控件的属性组成的对象

 "fields": {
   "username": "张三",
   "sex": "男"
 },
 "files": {
   "photo": {
     "size": 25046,
     "path": "upload\upload_db6fb06471ab2f2a3e17dd50ca666f15",
     "name": "3016253704_23.jpg",
     "type": "image/jpeg",
     "mtime": "2021-08-12T02:36:15.662Z"
   }
 }

后继需要完成的工作,将上传文件加上后缀名即可。

二、cookie-parser

负责设置客户端的cookie,获取浏览器端的cookie。cookie作用:基于客户端存储技术,用于存储用户的登录状态、用户的账号、头像、兴趣爱好、交易记录。

image-20210812140258607

cookie特征:

长度限制,4k左右;数量限制:50个左右; 时间限制:默认关闭网页即失效,可指定失效期。

访问限制(安全限制):只能访问本网站内的cookie。

内容限制:cookie默认只能存储字符串

安装:

npm i  cookie-parser

增加(设置cookie)语法: response.cookie(name,value)

读取语法: request.cookies.name   或  request.cookies['name']

cookie配置项:

domain:cookie在什么域名下有效,类型为String,。默认为网站域名

expires: cookie过期时间,类型为Date。如果没有设置或者设置为0,那么该cookie只在这个这个session(会话期)有效,即关闭浏览器后,这个cookie会被浏览器删除。

maxAge: 实现expires的功能,设置cookie过期的时间,类型为String,指明从现在开始,多少毫秒以后,cookie到期。

path: cookie在什么路径下有效,默认为'/'表示整个网站目录下有效,类型为String

secure:只能被HTTPS使用,类型Boolean,默认为false

httpOnly: 只能被web服务器访问,类型Boolean。即浏览器端不能访问使用cookie,可以防止XSS(跨站脚本攻击)攻击。

signed:使用签名,类型Boolean,默认为false。express会使用req.secret来完成签名

使用签名cookie的步骤:

1)在入口文件的cookie的中间件配置代码中添加一个字符串密钥

app.use(cookieParser("offcn.com")); //cookie-parser中间件配置
  1. 添加cookie代码中,增加选项:signed:true
res.cookie("username", username, {path:"/",maxAge:1000*60*60*24,signed:true});

3)读取cookie,语法为: req.signedCookies

req.signedCookies["username"]

三、express-session 中间件

session 管理中间件,session是基于服务器端的临时存储技术。session保存的位置位于服务器的缓存中,也是以键值对形式。

安装 : npm  i  express-session

session配置文件的说明:

secret 一个 String 类型的字符串密钥,作为服务器端生成 session 的签名

name 返回客户端的 key 的名称,默认为 connect.sid,也可以自己设置

resave 强制保存 session 即使它并没有变化,。默认为 true。建议设置成 false。

saveUninitialized 强制将未初始化的 session 存储。当新建了一个 session 且未设定属性或值时,它就处于未初始化状态。在设定一个 cookie 前,这对于登陆验证,减轻服务端存储压力,权限控制是有帮助的。(默 认:true)。建议手动添加

cookie 设置返回到前端 key 的属性,默认值为{ path: ‘/’, httpOnly: true, secure: false, maxAge: null }

rolling 在每次请求时强行设置 cookie,这将重置 cookie 过期时间(默认:false)

session的配置:

let options = {
 secret:"offcn.com"//密钥字符串
 resave:false,
 saveUninitialized:true
}
app.use( session(options) ); //session中间件配置

添加session语法: req.session(name,value);

读取session语法: req.session(name);

session应用场景:

 // svg对象中的text属性为生成的验证码字符串,data属性为验证码字符串转换的svg图形。
 // 思路:将svg.text 保存在session 中。然后将svg.data发送到客户端,让用户分辨填写。当用户提交表单后,获取用户填写的验证码与保存在session中的验证码进行对比。如果正确则可进行下一步。
 req.session["yzm"] = svg.text;

当用户提交表单时,先判断验证码是否正确

 let {username,password,yanzhengma} = req.query;
 // 判断用户提交的验证码和session保存的是否一致
 if( yanzhengma.toLowerCase() != (req.session['yzm']).toLowerCase() ){
   res.send("<h2>验证码不正确!</h2>");
   return;
 }

四、ejs 模板引擎

ejs.bootcss.com/

EJS 是一套简单的模板语言,帮你利用普通的 JavaScript 代码将 json 数据 生成 HTML 页面

安装:

npm  i  ejs

配置:在入口app.js中添加:

app.set("view engine","ejs"); //将ejs设置为express默认的模板引擎语言。

在程序中可以将json对象的一级属性直接使用在ejs模板中:

 <h1><%= title %></h1>
 <table>
   <tr>
     <th>姓名</th>
     <th>性别</th>
     <th>年龄</th>
   </tr>
   <tbody>
     <% for(let item of result){ %>
       <% if(item.sex=="男"){ %>
         <tr class="red">
       <% }else{ %>
         <tr class="green">
       <% } %>
       <td><%= item.name %></td>
       <td><%= item.sex %></td>
       <td><%= item.age %></td>
     </tr>
     <% } %>
   </tbody>

在路由处理函数中,使用render() 将数据渲染为html。注意要渲染的数据必须是对象

router.get("/student",(req,res)=>{
 let data = {
   title:"0531班学生列表",
   result:[{
     name:"张三",
     sex:"男",
     age:19
   },{
     name:"李丹",
     sex:"女",
     age:20
   },{
     name:"陈明",
     sex:"男",
     age:18
   }]
 }
 res.render("list.ejs",data);
})

注意:

1) 在ejs文件中, 不起作用

2)渲染数据只能是对象。

一、node操作mysql数据库

安装: npm i mysql

步骤:

1、创建一个连接对象

 const OPTIONS = {
   host:"localhost"//数据库服务器地址
   port:3306,  //端口号,数据库默认为3306
   user:"root",  //管理员账号
   password:"",  //密码
   database:"ujiuye" //要操作的数据库名
 }
 let connection = mysql.createConnection(OPTIONS);

2、尝试连接数据库

 connection.connect((err)=>{
   if(err){
     console.log("数据库连接失败");
     return;
   }
   console.log("数据库连接成功"+ connection.threadId );
 })

3、 执行对数据库的CRUD

 let sql = "insert into 班级(班号,班主任,班长,教室) values(?,?,?,?)";
 connection.query(sql,['0531B','王老师','王艳','0416'], (err,result)=>{
   // err 在执行curd过程的错误消息提示
   // result 执行curd的结果消息
   if(!err){
     console.log( err );
     console.log( result );
   }else{
     console.log( err );
   }
 })

4、关闭连接数据库,释放资源

 connection.end();

总结:

1) 执行完增加,删除、更新sql语句后,connection.query() 中的回调函数的形参err , result 的结果:

如果成功,err 为null;  result 为对象:

{
 fieldCount: 0,
 affectedRows: 1,  // 受影响的行数,大于1说明数据库中的记录有实质改变
 insertId: 14,
 serverStatus: 2,
 warningCount: 0,
 message: '',
 protocol41: true,
 changedRows: 0
}
2)  执行查询操作sql语句后,形参err , result 的结果

如果查询成功,result 永远返回数组,如果查询结果为空,则result为空数组;如果有查询结果,则result中是由查询记录组成对象为内容的数组:

[
{
   id: 1,
   '班号''1904B',
   '班主任''侯老师2',
   '班长''斛晓强',
   '教室''412'
 },
 {
   id: 2,
   '班号''1903C',
   '班主任''王老师',
   '班长''张雪媛',
   '教室''205'
 }
]

二、promise与异步操作

promise是ES6新增的用于解决异步编程的语法。使用promise可解决异步编程的层层嵌套,比传统的回调函数方法更合理。pormise的作用就是将异步语法以同步形式表现出来,另外promise还提供了一些统一的接口使得控制异步操作更加容易。

2.1 promise对象基本特征:

Promise 意为承诺。promise对象的基本特征:

  1. promise 实例对象有三种状态,pending( 等待中), fulfilled( 成功 ),rejected ( 失败 );由异步操作的结果来改变状态。
  2. 一旦状态改变后,无论何时再访问promise对象就永远是这个状态。被称为已定型。
let p1 =  new Promise((resolve,reject)=>{
  //此处写异步操作的代码
  setTimeout(function(){
   resolve("成功"); // resolve() 将promise对象的状态从pending变成了 fulfilled
   reject("失败"); // reject() 将promise对象的状态从pending 变成了 rejected
  },2000)
});

console.log( p1 );

2.2  promsie对象的方法

promise对象有 then()  与 catch() 方法, promise实例对象状态的变更会自动激发then() 或者 catch()方法分别用来处理成功以后和失败以后的后继操作。

promise

 let p1 = new Promise((resolve,reject)=>{
   // 写异步代码
   setTimeout(function(){
     resolve("还了100块");
     // reject("没钱还")
   },2000)
 })
 // promise实例对象状态的变更会自动激发then() 或者 catch()方法
 // then() 用来处理成功状态;catch()用来处理失败状态
 p1.then((data)=>{
   console.log( data );
 }).catch((err)=>{
   console.log( err );
 })

2.3 promise的链式写法

在then()处理函数的最后一行,再次return一个promise对象,就可以使用 then().then().then()... 的语法,则称为promise的链式语法

let fs = require("fs");
function wFile(url,content){
  return new Promise((resolve,reject)=>{
    // 此处写异步操作代码
    fs.appendFile(url,content,(err)=>{
      if(!err){
        resolve("ok")
      }else{
        reject(err)
      }
    })
  })
}
// 在then()处理函数的最后一行,再次return一个promise对象,就可以使用 then().then().then()... 的语法,则称为promise的链式语法。避免了层层嵌套的写法。
wFile("./ok.txt""床前明月光")
.then((data)=>{
  console.log("第一句完成");
  return wFile("./ok.txt""疑似地上霜");
})
.then((data)=>{
  console.log("第二句完成");
  return wFile("./ok.txt","举头望明月")
})
.then((data)=>{
  console.log("第三句完成");
})

2.4  Promise.all()

all()方法是Promise类的静态方法,不需要实例化即可调用。

all()应用场景,多个异步操作同时进行,等所有的异步操作都成功以后再执行下一步。(等最慢的)

let fs = require("fs");
function readFile(url){
  return new Promise((resolve, reject)=>{
    fs.readFile(url,(err,result)=>{
      if(!err){
        resolve(result.toString());
      }else{
        reject(err);
      }
    })
  })
}
// all()应用场景:多个异步操作同时进行,等所有的异步操作都成功以后再执行下一步。(等最慢的)
Promise.all([
  readFile("./01.txt"),
  readFile("./02.txt"),
  readFile("./03.txt")
]).then((data)=>{
  console.log( data );
})

三、async函数

ES2017标准新引入的,也是用做异步编程的解决方案,是对promise的一种扩展,让异步编程更加方便,彻底解决了回调嵌套的问题。

3.1 async 函数基本特征

  // 声明async函数: 在函数前面添加 asycn 修饰符。
  // async函数默认返回一个promise对象,并且状态默认为fulfilled(成功状态),除非程序错误
  async function fn(){
    let a = "ok";
  }

  fn()

3.2 await 标识符

await 意为等待,只等待promise实例对象成功状态后的结果(即resolve(data)发送的数据 ),然后将结果赋值给变量。   await只处理成功,不负责失败。

let fs = require("fs");
function readFile(url){
  return new Promise((resolve, reject)=>{
    fs.readFile(url,(err,result)=>{
      if(!err){
        resolve(result.toString());
      }else{
        reject(err);
      }
    })
  })
}
async function fn(){
  // await 意为等待,只等待promise实例对象成功状态后的结果(即resolve(data)发送的数据 ),然后将结果赋值给变量。
  // await只处理成功,不负责失败。
  let a =  await readFile("./01.txt");
  console.log( a );
  let b = await readFile("./02a.txt");
  console.log( b );
  let c = await readFile("./03.txt");
  console.log( c);
}
// 暂时可使用 catch()来处理异步操作过程中的失败消息
fn().catch((err)=>{
  // console.log( err );
  if(err){
    console.log("读取文件失败,请检查");
  }
})

四、数据库的promise的封装函数

1)  创建 config.js, 保存数据库连接配置参数

const OPTIONS = {
  host:"localhost"//数据库服务器地址
  port:3306,  //端口号,数据库默认为3306
  user:"root",  //管理员账号
  password:"",  //密码
  database:"ujiuye" //要操作的数据库名
}

module.exports = {
  OPTIONS
};

2) 创建 DB.js

  let mysql = require("mysql");
  let {OPTIONS} = require("./config");

  // 1、创建一个连接对象
  let connection = mysql.createConnection(OPTIONS);
  // 2、尝试连接数据库
  connection.connect((err)=>{
    if(err){
      console.log("数据库连接失败");
      return;
    }
    console.log("数据库连接成功"+ connection.threadId );
  })

  // 3、 执行对数据库的CRUD
  function query(sql,arr){
    return new Promise((resolve,reject)=>{
      connection.query(sql,arr, (err,result)=>{
        if(!err){
          resolve(result);
        }else{
          reject(err);
        }
      })
    })
  }

  // 4、关闭连接数据库,释放资源
  function close(){
    connection.end();
  }
  module.exports = {
    query,
    close
  }

应用场景:

let db = require("../tools/DB");
//删除班级
router.get("/delbanji",async (req,res)=>{
  let sql = "delete from 班级 where id=?";
  let {id} = req.query;
  let a = await db.query(sql,[id]);
  if( a.affectedRows>0){
    res.send("删除成功")
  }else{
    res.send("删除失败,请检查参数");
  }
})
// 添加班级
router.get("/addbanji",async (req,res)=>{
  let sql = "insert into 班级(班号,班长) values(?,?)";
  let {num,name} = req.query;
  let a = await db.query(sql,[num,name]);
  if( a.affectedRows>0){
    res.send("添加成功")
  }else{
    res.send("添加失败,请检查参数");
  }
})

一、登录密码验证

程序员的基本操守:永远不能把明文密码直接写在数据库中。

在线加解密:tool.oschina.net/encrypt

暴露破解网站:www.cmd5.com/

安装: npm  i  md5

使用:

let md5 = require("md5");
//普通的加密
md5("123445")
//高强度加密
let secret = "offcn.com";  //密钥字符串
md5("123456"+secret)

md5理论是不可逆的,但是可以通过反向查询破解 www.cmd5.com/

例:注册代码:

// 响应登录
router.post("/dologin",async (req,res)=>{
 let {username,password} = req.body;
 // 将用户提交的明文密码,再次加密
 let pwd = md5(password);
 // 查询数据库中有没有和用户提交的账号密码相同的记录
 let sql = "select * from users where username=? and password = ?";
 let a = await db.query(sql,[username,pwd]);
 if(a.length>0){
   res.send("登录成功");
 }else{
   res.send("账号或密码错误");
 }
})

二、RESTful API规范

API(Applcation  Progame Interface)接口。

restful其实本身并不是一个新鲜的东西,最早是在2000年由Roy Thomas Fielding博士在他的博士论文中提出。说起这位老兄,来头可不小,他是http 1.0和1.1版本协议的主要设计者,apache基金会的第一任主席,可以说是现代互联网体系的奠基者。Fielding将他对互联网软件的架构原则,定名为REST,即表述层状态转移(Representational State Transfer)

当时的互联网其实还是处于刚萌芽的状态,这个设计思想过于超前,所以早些年一直处于不温不火的状态。直到近年来,互联网业务高速发展,系统架构越来越复杂,移动互联网的兴起,前后端分离架构的流行,人们发现原来这套用于超文本传输的协议是如此适合用于设计基于互联网的api接口,基于http动词以及标准的http status返回信息,能够非常好的描述api的特性,并且可读性非常好。

传统的API接口设计:

没有规则,任意发挥容易产生歧义

http://localhost:3000/student?id=10002 查询学号为10002号的学生信息

http://localhost:3000/addstudent?id=10002  添加一个学号为10002号的学生

http://localhost:3000/updatestudent?id=10002

http://localhost:3000/editstudent?id=10002

http://localhost:3000/modifystudent?id=10002

http://localhost:3000/xiugaistudent?id=10002

REST

GET http://localhost:3000/teacher/10002 查询学号为10002号的学生信息

POST http://localhost:3000/student/10002 添加一个学号为10002号的学生

PUT   http://localhost:3000/student/10002 修改

DELETE  http://localhost:3000/student/10002 删除

总结: 干什么由请求方法动词决定,要操作的资源由URL上的名词决定。

除以以外,REST 还规定了json格式、状态码、状态码的描述等等。

RESTful API课外阅读:www.jianshu.com/p/a6d9cf64e…

三、AJAX 介绍

3.1 ajax 介绍:

AJAX(async JavaScript AND XML);本质上是在网页中利用 XMLHttpRequest 对象和服务器进行数据交互的方式。ajax技术可以使网页实现异步更新,达到不刷新页面即可对网页的内容进行更新,解决了传统方法的缺陷。

ajax技术目前广泛应用在前后端分离的开发模式中,可以实现实时更新网页的交易信息、注册验证、分页等等。

05-1传统请求

05-2ajax请求方式

3.2  原生ajax 实现(了解)

GET 发送ajax请求:

 // 1、实例一个XMLhttpRequest 对象
 let xhr = new XMLHttpRequest();
 // 2、调用xhr实例对象的open方法实现一个异步请求
 xhr.open("get","http://cj.shenzhou888.com.cn/hb_vote/api.php?action=area&id=0","true");
 // 3、调用xhr实例对象的send()实际发送一个ajax请求
 xhr.send(null);
 // 4、为xhr对象实现一个回调函数
 xhr.onreadystatechange = function(){
 // xhr.readyState 属性一共有5个值,代表5种状态
 //0 服务器已就绪,但什么都还没开始(还没调用open())
 //1 服务器已连接 ( 还没有调用send() )
 //2 请求已接收,但尚未响应(通常现在可以从响应中获取内容头)
 //3 请求处理中,还没有完成(通常响应中已有部分数据可用了,但服务器还没有完全完成响应)
 // 4 响应已经完成,可以获取并使用服务器返回的数据
   console.log( xhr.readyState );
   if(xhr.readyState==3){
     console.log("正在处理中,请稍候...");
   }
   if(xhr.readyState==4 && xhr.status==200){
     // xhr.status 状态码:
/*  1xx:信息响应类,表示接收到请求并继续处理
   2xx:处理成功响应类,表示动作被成功接收,理解和接受
   3xx:重定向响应类,为了完成指定的动作,必须接受进一步处理
   4xx:客户端错误,客户请求包含语法错误或者是不能正确执行
   5xx:服务端错误,服务器不能正确执行一个正确的请求  
*/
     // 服务器返回的数据不是直接返回客户端,而是填充在xhr.responseText 中。
     console.logJSON.parse(xhr.responseText ) );
   }
 }

3.3 post发送ajax请求

// 如果是post请求发送ajax,需要在send()之前设置请求头的MIME类型
xhr.setRequestHeader("Conent-Type","application/x-www-form-urlencode");
xhr.send("action=area&id=0");

四、jquery之ajax

原生 Ajax 的问题:虽然 js 提供了XMLHttpRequest,但每次使用相对麻烦。Jquery 对 ajax 操作进行了封装,在 Jquery 中,.ajax()方法属于最底层的方法,第二层的.ajax()方法属于最底层的方法,第二层的.get()和.post()方法,它们都建立在.post()方法,它们都建立在.ajax()方法的基础上。

4.1 $.ajax() 语法

语法:

$.ajax({

url: '', // 请求的地址

type: '', // 请求的方式(get/post),默认为get

data: '', // 发送到后端的数据

dataType:"json", //期望服务器返回的数据类型

success: function(data) {}, // 成功的回调

error: function(err) {}, // 失败的回调

})

描述:可以发送get和post请求

<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script>
$.ajax({
   url:"http://cj.shenzhou888.com.cn/hb_vote/api.php?action=area&id=0"//请求的接口地址
   type:"GET"//请求方法,可以是post,put,delete等
   data:null//客户端发送给服务器端的数据,一般是表单内控件的name与value组成的对象或字符串
   dataType:"json"//期望服务器返回给客户端的数据类型,一般是json, 也可是text,xml,html,josnp等
   // 在请求发送之前的处理函数
   beforeSend:function(){
     $(".box").show();
   },
   // 服务器成功响应之后的处理函数
   success:function(data){
     $(".box").hide(1000);
     console.log( data );
   },
   // ajax请求过程中失败后的处理函数
   error:function(err){
     console.log( err.statusText );
     alert("报名失败,请重试一次")
   }
 });
</script>

jquery在.ajax()的基础上又封装了.ajax()的基础上又封装了.get()和 $.post()方法。语法上有所简化。

五、跨域问题

在浏览器中的网页如果使用 ajax 请求另一个网页,当出现不同源时就会产生跨域。所 谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端 口号(port)。言外之意:当使用 Ajax 请求 url 时,如果协议、域名、端口三者之间任意 一个与当前页面 url 不同即为跨域:

当前页面 url被请求页面 url是否跨域原因
www.ujiuye.comwww.ujiuye.com/a同源
www.ujiuye.comwww.ujiuye.com/a跨域协议不同(http/https)
www.ujiuye.comwww.offcn.com跨域主域名不同(ujiuye/offcn)
www.ujiuye.comxue.ujiuye.com跨域子域名不同(www/xue)
www.ujiuye.com:88www.ujiuye.com:77跨域端口不同(88/77)

image-20210817160929726

5.1 跨域阻止请求解决方法:

在后台设置允许 Access-Control-Allow-Origin 请求

在所有路由之前添加一个中间件:

// 设置允许跨域
app.use((req,res,next)=>{
 res.header("Access-Control-Allow-Origin","*");
 next();
})

5.2 使用  cors 包实现允许跨域请求

安装: npm i cors

let cors = require("cors");

app.use(cors()); //允许跨域中间件

5.3 JSONP 解决方案(了解)

利用 <script src=> 或 <img src=> 浏览器没有跨域限制的特点,将 json隐藏在js 中,让浏览器认为跨域请求的是js 而不是json。从而绕过跨域请求限制。

客户端请求时,请求的接口写在 src属性中。

<script src="http://localhost:3000/student?callback=fn"></script>

后端代码,将 json数据作为函数的实参。类似:

fn({code: 200, data: [{ name: "jack", sex: "男" })

浏览器接收到上述代码后,就误认为是一段js而不是json。就直接调用函数

 function fn(data){
   console.log( data ); // data即隐藏的json数据
 }

六、Promise与Ajax

Ajax是典型的异步回调形式,而业务中往往很复杂,也就造成了使用Ajax的业务很容易出现回调地狱。作为开发者更习惯使用同步方式进行开发,所以我们使用ES6提供的Promise来解决Ajax的回调问题。

6.1 回调地狱问题

6.2  Promise、Ajax与async综合解决回调地狱