一、初识
Node.js 定义
Node.js 是一个基于 V8 引擎的 JavaScript 运行时(运行环境)
Node.js 中的 JS 运行环境
- V8 引擎
- 内置 API
- fs
- path
- http
- js 内置对象
- querystring
- 等等
** 注意:**
浏览器是 JavaScript 的前端运行环境
Node.js 是 JavaScript 的后端运行环境
Node.js 中无法调用 DOM、BOM 等浏览器的 API
Node.js 作用
基于 Node.js 有很多强大的工具和框架
① Express—Web 应用
② Electron—跨平台的桌面应用
③ restify—快速构建 API 接口
④ 读写操作数据库、创建实用的命令行工具辅助前端开发
⑤ 等等
学习路径
- 基础语法
- 内置 API
- express、数据库
Node.js 执行 JavaScript 代码
- 在终端进入 js 文件目录
- node 文件名
二、文件系统模块
- readFile
- writeFile
** 注意:**
- 动态路径拼接
- __dirname:表示当前文件所处的目录
三、path 路径模块
- 拼接路径:path.join([…path])
- 查看文件名:path.basename(path[,ext])
- 查看文件后缀:path.extname(path)
四、http 模块
- 创建基本的 web 服务器
// 1.引入http模块
const http = require("http");
// 2.创建服务器
const server = http.createServer();
// 3.监听request事件
server.on("request", (req, res) => {
// 3.1 req是请求对象,包含与客户端相关的数据与属性
// 如:req.url:客户端请求的地址;req.method:请求类型
const url = req.url;
const method = req.method;
// 3.2 res是相应对象,包含与服务端相关的数据与属性
// 如:res.end(str):向客户端发送指定的内容并结束这次请求的处理过程
res.setHeader('Content-Type','text/html; charset=utf-8')
res.end('hello')
});
// 4.启动服务器
server.listen(8080, () => {
console.log("http server running at http://127.0.0.1:8080");
});
五、模块化
(一)、模块化基础
模块化定义
解决一个复杂问题时,自顶向下把系统分成若干模块的过程。
对于整个系统来说,模块是课组合、分解和更换的单元。
模块化好处
- 提高了代码的复用性
- 提高了代码的可维护性
- 可实现按需加载
模块的使用
- 加载模块:
- require()
模块作用域
和函数作用域类似,只能再当前模块内被访问。
防止全局变量的污染。
(二)、Node.js 模块的三大分类
内置模块
内置模块是 Node.js 官方提供的。
例如:fs,path,http 等等。
自定义模块
用户自己创建的.js 文件。
第三方模块
由第三方开发出来的模块,使用前需要先下载。
第三方模块也叫做包。
(三)、向外共享模块作用域中的成员
module 对象
在每个.js 自定义模块中都有一个 module 对象,它存储了和当前模块有关的信息.
打印 module 的一个例子
Module {
id: '.',
path: 'E:\\工作\\node学习',
exports: {},
filename: 'E:\\工作\\node学习\\1.js',
loaded: false,
children: [],
paths: [
'E:\\工作\\node学习\\node_modules',
'E:\\工作\\node_modules',
'E:\\node_modules'
]
}
module.exports 对象
将模块内的成员共享出去,供外界使用。默认情况为{}
外界用 require()方法导入自定义模块时,得到的就是 module.exports 这个对象。
exports 对象
exports 指向的就是 module.exports 这个对象。
** 注意:**
将模块导出的对象始终为 module.exports 这个对象。
(四)、Node.js 模块化规范
Node.js 遵循了 CommonJS 模块化规范。
规定:
- 每个模块内部,module 变量代表当前模块。
- module 变量是一个对象,他的 exports 属性(即 module.exports)是对外的接口。
- 加载某个模块,其实就是加载该模块的 module.exports 属性。require()方法用于加载模块。
(五)、npm 管理包
包:
即第三方模块。包是基于内置模块封装出来的,提供了更高级、更方便的 API,极大的提高了开发效率。
https://www.npmjs.com/ 查看包 https://registry.npmjs.org/ 下载包
npm
随着 Node.js 安装包一起安装。
- 下载
npm install 包名称
简写:
npm i 包名称
- 卸载
npm uninstall 包名称
初次安装包出现的文件:
- node_modules:
存放所有已安装到项目中的包 - package-lock.json:
记录 node_modules 目录下每个包的下载信息
这两个文件不要修改操作。npm 会自动管理。
包管理配置文件
npm 规定,在项目根目录中,必须提供一个叫做 package.json 的包管理配置文件。
package.json:记录与项目有关的一些配置信息。
例如:
- 项目的名称、版本号、描述等等
- 项目中的包
- 哪些包只在开发期间用到
- 哪些包在开发和部署时都要用到
如何快速创建 package.json:
npm init -y
- dependencies
只在项目开发阶段用到的包 - devDependencies
在开发和上线都需要用到的包
npm i 包名 -D
等同于
npm install 包名 --save-dev
解决包下载速度慢
淘宝 NPM 镜像服务器
- 检查当前的下载包镜像源
npm config get registry
- 将下载包的镜像源切换为淘宝镜像
npm config set registry=https://registry.npm.taobao.org/
- 检查镜像源是否下载成功
npm config get registry
nrm
nrm 是用来切换镜像源的
- 安装
npm i nrm -g
(-g 安装到全局) - 查看所有的镜像源
nrm ls
- 将下包的镜像源切换到 taobao 镜像
nrm use taobao
包的分类
- 项目包
- 开发依赖包:
npm i 包名
在 package.json 的 dependencies 的节点下 - 核心依赖包:
npm i 包名 -D
在 package.json 的 devDependencies 的节点下
- 全局包:
npm i 包名 -g
(一般在 C 盘\用户\用户名\AppData\Roaming\npm\node_modules)
规范的包结构
- 包必须以单独的目录而存在
- 包的顶级目录下必须包含 package.json 这个包管理配置文件
- package.json 中必须包含 name(包的名字)、version(版本号)、main(包的入口)
(六)、开发属于自己的包
一个例子——包含有的功能:
- 格式化日期
// 1.导入
const tools = require("./sunny-tools");
// 2.使用
const dt = tools.dateFormat(new Date());
console.log(dt);
- 转义 HTML 中的特殊字符
// 1.导入
const tools = require("./sunny-tools");
// 2.使用
const htmlStr = '<h1 style="color:red;">你好<span>小黄 </span></h1>';
const str = tools.htmlEscape(htmlStr);
console.log(str);
- 还原 HTML 中的特殊字符
// 1.导入
const tools = require("./sunny-tools");
// 2.使用
const rawHTML = tools.htmlUnEscape(str);
console.log(rawHTML);
步骤
-
初始化包的结构
① 新建 sunny-tools 文件
② 在 sunny-tools 文件中新建:- package.json(包管理配置文件)
- index.js(包的入口文件)
- README.md(包的说明文档)
③ 在 sunny-tools 文件中新建 src 文件夹,再在 src 中新建:
- dateFormat(转换时间的模块)
- htmlEscape(有关 HTML 字符转义模块)
-
初始化 package.json 文件
{
"name":"sunny-tools",
"version":"1.0.0",
"main":"index.js",
"description": "提供了格式化时间,HTMLEscape的功能",
"keywords": ["sunny","dataFomat","escape"],
"license": "ISC"
}
- 初始化 index.js
const date = require("./src/dateFormat");
const escape = require("./src/htmlEscape");
module.exports = {
...date,
...escape,
};
- 编写 dateFormat
function dateFormat(dataStr) {
const dt = new Date(dataStr);
const y = dt.getFullYear();
const m = padZero(dt.getMonth() + 1);
const d = padZero(dt.getDay());
const hh = padZero(dt.getHours());
const mm = padZero(dt.getMinutes());
const ss = padZero(dt.getSeconds());
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`;
}
// 补零函数
function padZero(n) {
return n > 9 ? n : "0" + n;
}
module.exports = {
dateFormat,
};
- 编写 htmlEscape
// 转义HTML字符
function htmlEscape(htmlstr) {
return htmlstr.replace(/<|>|"|&/g, (match) => {
switch (match) {
case "<":
return "<";
case ">":
return ">";
case '"':
return """;
case "&":
return "&";
}
});
}
// 还原HTML字符
function htmlUnEscape(str) {
return str.replace(/<|>|"|&/g, (match) => {
switch (match) {
case "<":
return "<";
case ">":
return ">";
case """:
return '"';
case "&":
return "&";
}
});
}
module.exports = {
htmlEscape,
htmlUnEscape,
};
- 编写包的说明文档
- 安装方式
- 导入方式
- 格式化时间
- 转义 HTML 特殊字符
- 还原 HTML 字符
- 开源协议
- 发布
- 注册 npm 帐号
① 访问https://www.npmjs.com/
② 注册
③ 一定要点击发给邮箱的验证链接 - 在终端登录 npm 帐号
** 注意: **
登录前一定要将镜像切换到官方即 npm
//查看所有的镜像源
nrm ls
//将下包的镜像源切换到 npm 镜像
nrm use npm
npm login
//根据提示输入用户名、密码
-
发布包
切换到发布包的根目录下(包名要唯一)
npm publish
-
删除已发布的包
npm unpublish 包名 --force
(七)、模块的加载机制
- 优先从缓存中加载
模块在第一次加载后会被缓存
多次调用相同的 require(),不会被执行多次 - 内置模块的加载机制
优先级最高 - 自定义模块加载机制
- 必须指定已
./
或../
的路径表示符。如果没有则 node 把它当作内置模块或者第三方模块加载。 - 如果省略了文件扩展名,node 按以下顺序尝试加载:
① 补全.js
② 补全.json
③ 补全.node
④ 加载失败,报错
- 第三方模块加载机制
从当前模块的父目录开始,从/node_modules 加载
如果没有找到,在上一层父级的/node_modules 中加载
直到文件系统的根目录 - 目录作为模块
① 在加载的目录下找 package.json 中找 main 属性,作为 require()加载入口
② 没有 ①,尝试加载 index.js
③ 没有 ①②,终端报错
六、Express
(一)、Express 基础
定义
基于 Node.js 平台,快速、开放、极简的 Web 开发框架。
通俗:Express 和 Node.js 内置的 http 模块类似,专门用来创建 Web 服务器
下载 express
npm install express
基本使用——创建 web 服务器
- 创建基本的 web 服务器
// 1. 导入express
const express = require("express");
// 2. 创建 web 服务器
const app = express();
// 3. 调用app.listen(端口号,启动成功的回调函数),启动服务器
app.listen(80, () => {
console.log("express server running at http://172.0.0.1");
});
- 监听 GET 请求
app.get():
- 参数 1:URL 地址
- 参数 2:对应的处理函数
- req:请求对象
- res:响应对象
- 监听 POST 请求
app.post():
- 参数 1:URL 地址
- 参数 2:对应的处理函数
- req:请求对象
- res:响应对象
- 把内容响应给客户端
res.send()
参数可为 json 对象,也可以为文本
托管静态资源
- express.static()
express.static()创建一个静态资源服务器
app.use(express.static('public'))
现在可以访问 public 目录下所有文件(假设 public 有 css.js,index.js,index.html)
http://localhost:3000/css.js
http://localhost:3000/index.js
http://localhost:3000/index.html
例子:
const express = require("express");
const app = express();
app.use(express.static("./clock"));
app.listen(80, () => {
console.log("express server running at http://127.0.0.1");
});
- 托管多个静态资源目录
多次调用 express.static()就可以
注意:
如果多个静态资源目录下有相同的文件时,则按顺序加载(即访问的是第一个)
例子:
const express = require("express");
const app = express();
app.use(express.static("./files"));
app.use(express.static("./clock"));
app.listen(80, () => {
console.log("express server running at http://127.0.0.1");
});
//files和clock文件下都有clock.html
//访问http://127.0.0.1/clock.html 出现的是files下的clock.html
- 挂载路径前缀
app.use('/public',express.static('public'))
现在可以访问 public 目录下所有文件(假设 public 有 css.js,index.js,index.html)
http://localhost:3000/public/css.js
http://localhost:3000/public/index.js
http://localhost:3000/public/index.html
nodemon
下载
npm i -g nodemon
使用
原来是node app.js
,下载改为
nodemon app.js
(二)、Express 路由
基础知识
- 路由的定义
路由:广义上,路由就是映射关系 - express 中的路由
- 路由:客户端的请求与服务器处理函数之间的映射关系
- 组成:请求的类型、请求的 URL 地址、处理函数
app.METHOD(PATH,HANDLER)
- 例子:
app.get("/user", (req, res) => { res.send({ name: "zs", age: 20, gender: "男" }); }); app.post("/user", (req, res) => { res.send("请求成功"); });
- 路由的匹配过程:
- 每当一个请求到达服务器之后,需要经过路由的匹配,只有匹配成功之后才会调用对应的处理函数
- 在匹配时,会按照路由的顺序进行匹配,如果请求的类型和请求的 URL 同时匹配成功,则 Express 会将这次请求转发交给对应得函数进行处理
路由的使用
- 最简单的路由使用
不常用
在 express 中使用路由最简单的方式就是把路由挂载到 app 上,示例:
// 1. 导入express
const express = require("express");
// 2. 创建 web 服务器
const app = express();
//挂载路由
app.get("/", (req, res) => {
res.send('hello');
});
app.post("/", (req, res) => {
res.send("请求成功");
});
// 3. 调用app.listen(端口号,启动成功的回调函数),启动服务器
app.listen(80, () => {
console.log("express server running at http://127.0.0.1");
});
- 模块化路由
将路由抽离为单独的模块,步骤:
- 创建路由模块对应的.js 文件
- 调用
express.Router()
函数创建路由对象 - 向路由对象上挂载具体的路由
- 使用
module.exports
向外共享路由对象 app.use()
函数注册路由模块
① 创建路由模块
// 1.导入express
const express = require("express");
// 2.创建router对象
const router = express.Router();
// 3. 挂载具体的路由对象
router.get("/user/list", (req, res) => {
res.send("Get user list");
});
router.post("/user/add", (req, res) => {
res.send("Add new user");
});
// 4. 向外导出路由对象
module.exports = router;
② 注册路由模块
//1.到哦如路由模块
const userRouter = require(./ruoter/user.js)
2.使用app.use()注册路由模块
app.use(userRouter)
- 为路由模块添加前缀
类似于托管静态资源,为静态资源统一挂载访问前缀一样,路由模块添加前缀的方式也非常简单://1.到哦如路由模块 const userRouter = require(./ruoter/user.js) 2.使用app.use()注册路由模块 app.use('/api',userRouter)
(三)、中间件
基础知识
- 定义:
中间件就是流程中的中间处理环节。 - Express 中间件的调用流程
当一个请求到达 Express 的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理 - Express 中间件的格式
中间件本质上就是一个 function 处理函数
const express = require("express");
const app = express();
app.get("/", function(req,res,next){
next()
});
app.listen(3000);
注意:
中间件函数的形参列表中必须包含next
参数,而路由处理函数中只包含 req 和 res 4. next()函数作用
next函数
是实现多个中间件连续调用的关键,他表示把流转关系转交给下一个中间件或路由
- 创建最简单的中间件
const express = require("express");
const app = express;
// 定义一个最简单的中间件函数
const mw = function (req, res, next) {
console.log("这是最简单的中间件");
next();
};
app.listen(80, () => {
console.log("http://127.0.0.1");
});
- 中间件的作用
多个中间件之间,共享同一份 req 和 res,基于这样的特性,
我们可以在上游的中间件中,统一为 req 或 res 对象添加自定义的属性或方法,供下游的中间件进行使用。
const express = require("express");
const app = express();
// 定义一个最简单的中间件函数
const mw = function (req, res, next) {
const time = Date.now();
// 为req挂载自定义属性,从而把time共享给后面的路由
req.startTime = time;
next();
};
// 将mw注册为全局生效的中间件
app.use(mw);
app.get("/", (req, res) => {
res.send("Home page." + req.startTime);
});
app.get("/user", (req, res) => {
res.send("User page." + req.startTime);
});
app.listen(80, () => {
console.log("http://127.0.0.1");
});
全局生效的中间件
- 全局中间件
- 客户端发起的任何请求,到达服务器之后,都会触发的中间件,叫做全局生效的中间件
- 通过调用
app.use(中间件函数)
即可定义一个全局生效的中间件
// 定义一个最简单的中间件函数
const mw = function (req, res, next) {
console.log("这是最简单的中间件");
next();
};
// 将mw注册为全局生效的中间件
app.use(mw);
例子:
const express = require("express");
const app = express();
// 定义一个最简单的中间件函数
const mw = function (req, res, next) {
console.log("这是最简单的中间件");
next();
};
// 将mw注册为全局生效的中间件
app.use(mw);
app.get("/", (req, res) => {
console.log("调用了'/'这个路由");
res.send("Home page.");
});
app.get("/user", (req, res) => {
console.log("调用了'/user'这个路由");
res.send("User page.");
});
app.listen(80, () => {
console.log("http://127.0.0.1");
});
- 多个全局中间件
多次调用app.use()
。客户端请求到达服务器之后,会按照中间件的先后顺序
一次执行调用
例子:
const express = require("express");
const app = express();
app.use(function (req, res, next) {
console.log("中间件1");
next();
});
app.use(function (req, res, next) {
console.log("中间件2");
next();
});
app.get("/", (req, res) => {
res.send("Home page.");
});
app.get("/user", (req, res) => {
res.send("User page.");
});
app.listen(80, () => {
console.log("http://127.0.0.1");
});
局部生效的中间件
- 不使用
app,use
定义的中间叫做局部生效的中间件,示例:
const express = require("express");
const app = express();
//定义中间件1
const mw1 = function (req, res, next) {
console.log("这是中间件函数1");
next();
};
// mw1在“/”局部生效
app.get("/", mw1, (req, res) => {
res.send("Home page");
});
// mw1对“/user”无影响
app.get("/user", (req, res) => {
console.log("mw1未生效");
res.send("User page");
});
app.listen(80, () => {
console.log("http://127.0.0.1");
});
- 使用多个局部生效的中间件
示例:
const express = require("express");
const app = express();
//定义中间件1
const mw1 = function (req, res, next) {
console.log("这是中间件函数1");
next();
};
// 定义中间件2
const mw2 = function (req, res, next) {
console.log("这是中间件函数2");
next();
};
// 使用多个局部中间件,方式一
app.get("/my1", mw1, mw2, (req, res) => {
res.send("My1 page");
});
// 使用多个局部中间件,方式二
app.get("/my2", [mw1, mw2], (req, res) => {
res.send("My2 page");
});
app.listen(80, () => {
console.log("http://127.0.0.1");
});
中间件的使用注意事项
- 一定要在路由之前注册中间件
- 客户端发送过来的请求,可以连续调用多个中间件进行处理
- 执行完中间件的业务代码之后,不要忘记调用
next()
函数 - 为了防止代码逻辑混乱,调用 next()函数之后不要在后面再写代码
- 连续调用多个中间件时,多个中间件之间,共享 req 和 res 对象
中间件的分类
① 应用级别的中间件
② 路由级别的中间件
③ 错误级别的中间件
④ Express 内置的中间件
⑤ 第三方的中间件
应用级别的中间件
通过app.use()
或app.get()
或app.post()
,绑定到 app 实例上的中间件,叫做应用级别的中间件。
示例:
//1.
app.use(function (req, res, next) {
console.log("中间件1");
next();
});
//2.
const mw1 = function (req, res, next) {
console.log("这是中间件函数1");
next();
};
app.get("/", mw1, (req, res) => {
res.send("Home page");
});
路由级别的中间件
绑定到express.Router()
实例上的中间件,叫做路由级别的中间件。
用法和应用级别的中间件没有区别。
但,应用级别中间件绑定到 app 实例上,路由级别中间件绑定到 router 实例上。
示例:
const app = express()
const router = express.Router()
// 路由级别中间件
router.use(function(req,res,next){
console.log("Time:",Date.now());
next();
})
app.use('/',router)
错误级别的中间件
- 错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题
- 格式:function 处理函数中,必须有 4 个形参,顺序为
(err,req,res,next)
示例:
app.get("/", (req, res) => {
throw new Error("服务器内部发生了错误!");
res.send("Home page");
});
app.use(function (err, req, res, next) {
console.log("发生了错误:" + err.message);
res.send("Error!" + err.message);
});
注意:错误级别的中间件必须注册在所有路由之后!
Express 内置的中间件
express.static
:快速托管静态资源的内置中间件,例如:HTML 文件、图片、CSS 样式等express.json
:解析 JSON 格式的请求体数据(4.16+)express.urlencoded
:解析 URL-encoded 格式的请求体数据(4.16+)
示例:
//配置解析application/json格式数据的内置中间件
app.use(express.json())
//配置解析application/x-www-form-urlencoded 格式数据的内置中间件
app.use(express.urlencoded({extended:false}))
示例:
const express = require("express");
const app = express();
// 设置解析表单JSON数据的中间件
app.use(express.json());
// 设置解析application/x-www-form-urlencoded 格式的中间件
app.use(express.urlencoded({ extended: false }));
app.post("/user", (req, res) => {
// 在服务器使用 req.body 接收客户端发送过来的请求体
// 默认情况下,若不配置解析表单数据的中间件,req.body 默认等于undefined
console.log(req.body);
res.send("ok");
});
app.post("/book", (req, res) => {
// 在服务器使用 req.body 接收客户端发送过来的请求体
console.log(req.body);
res.send("ok");
});
app.listen(80, () => {
console.log("Express server running at http://172.0.0.1");
});
第三方的中间件
由第三方开发出来的中间件,按需下载并配置第三方中间件,从而提高开发效率
npm install 中间件名
- 使用
require
导入中间件 - 调用
app.use
注册并使用中间件
自定义中间件
例子:模拟一个类似与express.urlencoded
这样的中间件,来解析 POST 提交到服务器的表单数据。
① 定义中间件
② 监听 req 的 data 事件
③ 监听 req 的 end 事件
④ 使用 qurestring 模块解析请求体数据
⑤ 将解析出来的数据对象挂载为 req.body
⑥ 将自定义中间件封装为模块
注意:
- 当数据量较大时,客户端会把数据切割后,分批发送到服务器,data 事件可能会触发多次,每触发一次 data 事件时,获取到的数据只是完整数据的一部分,需要手动对接收到的数据进行拼接
Node.js
内置了一个qureystring
模块,专门用来处理查询字符串。通过这个模块提供的 parse()函数,把查询字符串解析成对象格式
代码:
- 自定义中间件模块:my-body-parser.js
// 引入qureysrting模块
const qs = require("qs");
const bodyParser = (req, res, next) => {
// 定义str,存储客户端发送过来的数据
let str = "";
// 监听req的data事件
req.on("data", (chunk) => {
str += chunk;
});
// 监听req的end事件
req.on("end", () => {
// 这时在str为完整数据
console.log(str);
// TODO:把字符串格式的请求体数据,解析成对象格式
const body = qs.parse(str);
console.log(body);
// 挂载为req.body
req.body = body;
next();
});
};
module.exports = bodyParser;
- 使用自定义中间件
const express = require("express");
const app = express();
// 导入自己封装的my-body-parser
const myBodyParser = require("./10my-body-parser");
app.use(myBodyParser);
app.post("/user", (req, res) => {
res.send(req.body);
});
app.listen(80, () => {
console.log("Express server running at http://172.0.0.1");
});
(四)、使用 Express 编写接口
① 创建 express 服务器
② 创建 API 路由模块(在此编写接口)
编写 GET 接口
router.get("/get", (req, res) => {
// 通过 req.body 获取客户端通过查询字符串,发送到服务器的数据
const query = req.query;
// 调用 res.send() 方法,向客户端响应处理的结果
res.send({
status: 0, //0成功,1失败
msg: "GET请求成功", //状态描述
data: query, //需要响应给客户端的数据
});
});
编写 POST 接口
注意:一般都要配置解析表单数据的中间件
router.post("/post", (req, res) => {
const body = req.body;
res.send({
status: 0,
msg: "POST请求成功",
data: body,
});
});
CORS 跨域资源共享
- 接口的跨域问题解决
CORS
跨域资源共享JSONP
:只支持GET
- 使用
cors中间件
解决跨域
cors 是 Express 的一个第三方中间件。
步骤:
npm install cors
- 导入
const cors = require('cors')
- 在路由之前调用
app.use(cors())
配置中间件
- CORS 定义
CORS:跨域资源共享,由一系列 HTTP 响应头组成,这些 HTTP 响应头决定浏览器是否组织前端 JS 代码与跨获取资源
浏览器的同源安全策略默认阻止网页跨域获取资源,造成跨域问题。 - CORS 注意事项
- CORS 主要在服务器端进行配置,客户端浏览器无须做任何额外的配置,即可开启了 CORS 接口
- CORS 在浏览器有兼容性。只有支持 XMLHttpRequest Level2 的浏览器,才能正常访问开启了 CORS 的服务器端接口(如:IE10+、Chrome4+、FireFox3.5+)
- CORS 响应头
- Access-Control-Allow-Origin
语法:Access-Control-Allow-Origin:<origin|*>
origin 参数指定了允许访问资源的 url - Access-Control-Allow-Headers
语法:res.setHeader('Access-Control-Allow-Headers',参数)
- 默认情况下,CORS 只支持客户端发送如下 9 个请求头:
Accept、Accept-Language、Content-Language、DRP、Downlink、Save-Data、Viewport-Width、Width、Content-Type(值仅限于 text/plain、multipart/form-data、application/x-www-form-urluncoded 三者之一) - 如果客户端向服务器发送了额外的请求头信息,则需在服务器通过
Access-Control-Allow-Headers
对额外的请求头进行声明,否则这次请求会失败
- 默认情况下,CORS 只支持客户端发送如下 9 个请求头:
- Access-Control-Allow-Methods
默认情况下,CORS 仅支持客户端发起GET
、POST
、HEAD
请求。
- CORS 请求的分类
客户端在请求 CORS 接口时,根据请求方式和请求投的不同,可以将 CORS 的请求分为两大类:
① 简单请求
② 预检请求 - 简单请求
满足条件:
① 请求方式:GET、POST、HEAD 三者之一
② HTTP 头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DRP、Downlink、Save-Data、Viewport-Width、Width、Content-Type(值仅限于 text/plain、multipart/form-data、application/x-www-form-urluncoded 三者之一) - 预检请求
- 定义:在浏览器与服务器正式通信之前,浏览器会先发送
OPTION
请求进行预检,以获取服务器是否允许该实际请求,所以这一次的 OPTION 请求成为“预检请求”。服务器响应预检请求后,才会发送真正的请求,并且携带真实数据。 - 满足条件:
① 请求方式为 GET、POST、HEAD 之外的 Method 类型
② 请求头中包含自定义头部字段
③ 向服务器发送了 application/json 格式的数据
- 简单请求与预检请求的区别
简单请求的特点:客户端与服务器只会发生一次请求
预检请求的特点:客户端与服务器会发生两次请求,OPTION 预检请求成功之后,才会发起真正的请求。
JSONP 接口
- 概念:
浏览器通过<script>
标签的src
属性,请求服务器上的数据,同时,服务器返回一个函数的调用。这种请求数据的方式叫做 JSONP - 特点:
- JSONP 不属于真正的 Ajax 请求,因为它没有使用 XMLHttpRequest 对象
- 仅支持 GET 请求
- 创建 JSONP 接口的注意事项
如果项目中已经配置了 CORS 跨域资源共享,为了防止冲突,必须在配置 CORS 中间件之前声明 JSONP 的接口。否则 JSONP 接口会被处理成开启了 CORS 的接口 - 实现 JSONP 接口的步骤
① 获取客户端发送过来的回调函数的名字
② 得到要通过 JSONP 形式发送给客户端的数据
③ 根据前两步得到的数据,拼接出一个函数调用的字符串
④ 把上一步拼接得到的字符串响应给客户端的<script>
标签进行解析执行
例子:
app.get("/api/jsonp", (req, res) => {
// TODO:定义具体的JSONP接口
const funcName = req.query.callback;
const data = { name: "zs", age: 22 };
const scriptStr = `${funcName}(${JSON.stringify(data)})`;
res.send(scriptStr);
});
七、数据库与身份认证
(一)、数据库的基本概念
数据库:组织、存储和管理数据的仓库
常见的数据库
- 常见的数据库
- MySQL 数据库(开源免费;Community + Enterprise)
- Oracle 数据库(收费)
- SQL Server 数据库(收费)
- Mongodb 数据库(Community + Enterprise)
- 分类
- 传统型数据库=关系型数据库= SQL 数据库:MySQL、Oracle、SQL Server
特性:
① 关系型数据库,是指采用了关系模型来组织数据的数据库;
② 关系型数据库的最大特点就是事务的一致性;
③ 简单来说,关系模型指的就是二维表格模型,而一个关系型数据库就是由二维表及其之间的联系所组成的一个数据组织。
优点:
① 容易理解:二维表结构是非常贴近逻辑世界一个概念,关系模型相对网状、层次等其他模型来说更容易理解;
② 使用方便:通用的 SQL 语言使得操作关系型数据库非常方便;
③ 易于维护:丰富的完整性(实体完整性、参照完整性和用户定义的完整性)大大减低了数据冗余和数据不一致的概率;
缺点:
① 为了维护一致性所付出的巨大代价就是其读写性能比较差;
② 固定的表结构;
③ 高并发读写需求;
④ 海量数据的高效率读写
⑤ 支持 SQL,可用于复杂的查询。 - 新型数据库= 非关系型数据库=NoSQL 数据库:Mongodb
特性:
① 使用键值对存储数据;
② 分布式;
③ 一般不支持 ACID 特性;
④ 非关系型数据库严格上不是一种数据库,应该是一种数据结构化存储方法的集合。
优点:
① 无需经过 sql 层的解析,读写性能很高;
② 基于键值对,数据没有耦合性,容易扩展;
③ 存储数据的格式:nosql 的存储格式是 key,value 形式、文档形式、图片形式等等,文档形式、图片形式等等,而关系型数据库则只支持基础类型。
缺点:
① 不提供 sql 支持,学习和使用成本较高;
② 无事务处理,附加功能 bi 和报表等支持也不好;
关系型数据库的数据组织结构
数据结构分为:数据库(database)
、数据表(table)
、数据行(row)
、字段(field)
实际开发库、表、行、字段关系:
① 一般每个项目对应一个数据库
② 不同数据存储到不同表
③ 每个表存储数据由字段决定
④ 表中的行代表每一条具体的数据
(二)、安装并配置 MySQL
安装的软件
MySQL Sever:专门用来提供数据存储和服务的软件
MySQL Workbentch:可视化的 MySQL 管理工具
Navicat:可视化的 MySQL 管理工具
(三)、MySQL 的基本使用
- 连接数据库
- 创建数据库
- 创建表
- 向表中写入数据
一些 sql 语句:
-- 1.筛选
select * from users;
select username,password from users;
-- 2.插入
insert into users (username,password) values ('sunny','789123');
-- 3.更新
update users set password='888888' where id=3;
update users set password='888111',status=1 where id=3;
-- 4.删除
-- delete from users where id=4;
-- 5.where
select * from users where status=1;
select * from users where id>=2;
-- <> !=不等于
select * from users where username<>'ls';
select * from users where username!='ls';
-- 6.and \ or
select * from users where status=0 and id<2;
select * from users where status=0 or id<2;
-- 7.order by 排序
-- 升序ASC或者省略
select * from users order by status;
select * from users order by status asc;
-- 降序
select * from users order by status desc;
-- 多重排序
select * from users order by status desc,username asc;
-- 8.count(*)函数
-- 统计状态为0的条目数
select count(*) from users where status=0;
-- 9.as 设置别名
select count(*) as total from users where status=0;
select username as uname,password as upwd from users;
(四)、在 Expres 中操作 MySQL
步骤
- 安装第三方 MySQL 模块(mysql):
npm i mysql
- 通过 mysql 模块连接到 MySQL 数据库:
// 1.导入mysql模块
const mysql = require("mysql");
// 2.建立与MySQL数据库的连接
const db = mysql.createPool({
host: "127.0.0.1", //数据库的IP地址
user: "root",
password: "root",
database: "mydb_01", //操作数据库的名字
});
// 测试mysql模块能否正常工作
// db.query()函数,指定要执行的SQL语句,通过回调函数拿到结果
db.query("select 1", (err, res) => {
if (err) return console.log(err.message);
console.log(res);
});
- 通过 mysql 模块执行 SQL 语句
通过 mysql 模块执行 SQL 语句
- 查询数据
// 查询数据
// select得到的结果为数组
const sql1 = "select * from users";
db.query(sql1, (err, res) => {
if (err) return console.log(err.message);
console.log(res);
});
- 插入数据
// 插入数据
// insert into 得到的结果为对象
const newUser = { username: "小红花", password: "456123" };
const sql2 = "insert into users (username,password) values (?,?) ";
db.query(sql2, [newUser.username, newUser.password], (err, res) => {
if (err) return console.log(err.message);
// affectedRows 影响的行数
if (res.affectedRows === 1) {
console.log("插入数据成功");
}
});
(五)、前后端的身份认证
分类
- Session:一般应用于依赖服务端加载资源的项目
- JWT:一般应用于前后端分离的项目
Session
cookie
- 定义:
cookie 是存储在用户浏览器中的一般不超过 4KB 的字符串 - 组成:
一个名称、一个值和其他几个用于控制 cookie 有效期、安全性、使用范围的可选属性组成 - 注意:
不同域名下的 cookie 各自独立,每当客户端发起请求时,会自动吧当前域名下所有未过期的 cookie 一同发送到服务器 - 特性:
① 自动发送
② 域名独立
③ 过期时限
④4KB 限制
cookie 在身份认证中的作用
- 客户端第一次请求服务器时,服务器通过响应头的形式,向客户端发送一个身份认证的 cookie
- 客户端自动将 cookie 保存在浏览器中
- 当客户端再次请求服务器时,浏览器会自动将 cookie 以请求头发送给服务器
- 服务器根据请求头中的 cookie 验明用户身份
cookie 不具有安全性
由于 cookie 存储在浏览器中,而且浏览器也提供了读写 cookie 的 API,因此 cookie 很容易被伪造,不具有安全性
Session 工作原理
- 客户端登陆:提供帐号密码
- 服务器验证帐号和密码,将登陆成功的用户信息存储在服务器的内存中,同时生成对应的 cookie 字符串
- 服务器响应:将生成的 cookie 响应给客户端
- 浏览器
在 Express 中使用 Session
- 安装express-session中间件
npm i express-session
- 配置express-session中间件:通过 **app.use()**来注册 session 中间件
示例:
// 1. 导入session中间件
var session = require("express-session");
// 2. 配置 session 中间件
app.use(
session({
secret: "sunny learn", //secret可为任意字符串
resave: false, //固定写法
saveUninitialized: true, //固定写法
})
);
- 向 session 中存数据:通过**req.session()**来访问和使用 session 对象
示例:
app.post("/api/login", (req, res) => {
// 判断用户提交的登陆信息是否正确
if (req.body.username !== "admin" || req.body.password !== "000000") {
return res.send({
status: 1,
msg: "登录失败",
});
}
req.session.user = req.body;
req.session.islogin = true;
res.send({
status: 0,
msg: "登录成功",
});
});
- 从 session 中取数据:直接从req.session对象上获取之前存储的数据
示例:
app.get("/api/username", (req, res) => {
if (!req.session.islogin) {
return res.send({ status: 1, msg: "fail" });
}
res.send({ status: 0, msg: "success", username: req.session.username });
});