node.js
1.node.js的组成
ECMAScript核心(var for if ....) + 全局成员(setTimeout, console...) + 核心API模块(node提供的一些方法,对象)
什么是node.js?
一种服务器端运行js的环境,让我们能够通过js做后台服务器开发。(JS写服务端,抢JAVA饭碗的)
2.ES6语法
Es5里,全局变量 === 顶级作用域window
Es6里,const,let全局变量不是顶级作用域window
Es6新增数据类型:symbol
1.let定义变量
-
先定义后使用,不存在预解析,不可重复定义
console.log(a); //a is not defined let a = 1; //如果有预解析,会打印undefined -
块级{}作用域
for (let i = 0; i < 10; i++) { //i只作用于{}内 function a() { console.log(i); //打印0 ~ 9,作用域链的关系,可以访问外部let定义的i } a(); } console.log(i);//i is not defined
2.const定义常量
-
先定义后使用,不存在预解析,不可重复定义
-
定义常量,定义后不可更改
const a = 1; a = 2; console.log(a); //Assignment to constant variable 报错 -
定义必须赋值
const a; a = 1; console.log(a); //Missing initializer in const declaration 报错 -
一般用来定义对象,数组, 函数
const a = {}; a.name = 'ls'; console.log(a); //{ name: 'ls' }
3.对象和数组的解构赋值,数组去重,扩展运算符,...rest,模板字符串
-
对象的解构赋值
let obj = { username: 'ls', age: 20 } //解构赋值 let {username, age} = obj; console.log(username, age); //'ls' 20 把对象里的属性当作变量来使用,使用前记得先定义 -
对象解构赋值重命名
let obj = { username: 'ls', age: 20 } //解构赋值重命名 let {username: name, age: a} = obj; console.log(name, a); //'ls' 20 username,age已经无法使用 -
数组的解构赋值
const arr = [1, 2, 3]; let [a, b, c] = arr; console.log(a, b, c); //1 2 3 -
交换两个变量值
let a = 1; let b = 2; [b, a] = [1, 2]; //等号右边是数组 左边是解构 console.log(a, b); // 2 1 -
数组去重
const arr = [1, 1, 2, 3, 4, 3]; const newArr = Array.from(new Set(arr)); //新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值,Array.from()把伪数组转成数组 console.log(newArr); //[ 1, 2, 3, 4 ] -
扩展运算符 三个点... 用来解压数组或者对象
//合并数组 const a1 = [1, 2]; const a2 = [3,...a1];//a2 = [3, 1, 2] //合并对象 const obj1 = {name: 'ls'}; const obj2 = {age: 18, ...obj1}; //{age: 18, name: 'ls'} -
函数形参...rest 接收的是一个数组,是实参的值,放进rest数组里。而函数里的arguments是伪数组
function foo(a,b,...rest){ console.log(a); //1 console.log(b); //2 console.log(rest); //[3, 4, 5] } foo(1,2,3,4,5); -
模板字符串 `` 数字1左边的反引号 变量用 ${变量名} 表示
var name = "ls"; var age = 22; //es5 console.log("Hello I'm " + name + ",my age is " + age + "") //es6 console.log(`Hello I'm ${name},my age is ${age}`)
4.箭头函数
箭头函数是匿名函数
-
完整体
(形参...) => {代码块} === function () {} -
箭头函数里的this指向它外部函数里的this
const _name = '王五' const obj = { _name: '张三', fn: function () { //我是定时器回调函数的外部函数,我的this谁调用指向谁 console.log(this._name); //obj调用的我,所以我打印 '张三' //改造前 setTimeout(function () { console.log('我是普通函数打印的' + this._name); //obj调用的我,我是普通函数 '王五' }, 200) //改造成箭头函数 箭头函数里的this指向它外部函数里的this setTimeout(() => { console.log('我是箭头函数打印的' + this._name); //obj调用的我,我是箭头函数 '张三' }, 200) } } obj.fn(); -
箭头函数的书写
-
完整体
const getNum = (x, y) => { return x + y }; console.log(getNum(1, 2)); //3 -
只有一个形参,()可以省略
const getNum = x => { return x + 2; } console.log(getNum(1, 2)); //3 -
没有形参,()不能省略
const getNum = () => { return 1 + 2; } console.log(getNum()); //3 -
{}内只有一行代码,{}可以省略
const getNum = x => x + 2; console.log(getNum(1)); //3
-
5.定义对象中属性和方法的新方式
1.对象属性名和函数名一致,只写一次就行
const fn = () => {console.log('打印一下')};
//es5
const obj1 = {
fn: fn
}
//es6
const obj2 = {
fn
}
obj1.show(); //打印一下
obj2.show(); //打印一下
2.对象中的方法简写
//es5
const obj1 = {
show: function () {
console.log('打印一下')
}
//es6 这么写this指向就变了哦
show: () => {
console.log('打印一下')
}
}
//es6
const obj2 = {
//es5普通写法的简写, 如果是箭头函数没有简写
show() {
console.log('打印一下')
}
}
3.模块
require是同步,读取写入是异步
1.文件读取
//导入fs模块
const fs = require('fs');
//读取文件
fs.readFile('文件路径/文件名', 'utf8', (err, data) => {
if(err) return console.log('文件读取失败!');
console.log(data); //读取的文件内容
})
2.写入文件 ,如果之前文件已经存在则覆盖
//导入fs模块
const fs = require('fs');
//写入文件
fs.writeFile('文件路径/文件名', '写入的内容', err => {
if(err) return console.log('文件写入失败');
console.log('文件写入成功');
})
3.向指定文件追加内容,如果文件不存在就创建一个新的文件并追加内容
//导入fs模块
const fs = require('fs');
//写入文件
fs.appendFile('文件路径/文件名', '追加的内容', err => {
if(err) console.log('文件追加失败');
console.log('文件追加成功');
})
4.fs模块中的路径操作问题
1.node命令运行时,如果fs模块调用的方法的路径是相对路径(./ ../ ) ,那么就会把node命令运行时所在的磁盘目录去拼接这个相对路径,容易造成路径拼接错误
p1-jj.byteimg.com/tos-cn-i-t2…
2.错误示范
p1-jj.byteimg.com/tos-cn-i-t2…
3.__dirname 保存的是当前文件所在的绝对路径
p1-jj.byteimg.com/tos-cn-i-t2…
4.记住,在nodejs中,请使用__dirname + 相对路径名解决路径问题,注意不要 ./路径/文件名,直接 /路径/文件名
const fs = require('fs');
fs.readFile(__dirname + '/files/1.txt', 'utf8', (err, data) => {
if(err) console.log('文件读取失败');
console.log(data);
})
5.读取文件信息
const fs = require('fs');
fs.stat(__dirname + '/files', (err, stats) => { //此时路径是目录
if(err) console.log('读取失败');
console.log(stats.size); //文件的字节, 如果是目录则为0
console.log(stats.birthtime); //文件或者目录创建时间
console.log(stats.isFile()); //是否为文件
console.log(stats.isDirectory()); //是否为目录
})
6.拷贝文件信息,目标文件存在则覆盖
const fs = require('fs');
fs.copyFile(__dirname + '/files/2.txt', __dirname + '/files/copy.txt', (err, data) => {
if(err) console.log('文件拷贝失败');
console.log('文件拷贝成功');
})
7.path.join解决的问题
- 用来拼接路径
//引入path模块
const path = require('path');
//拼接路径
path.join('路径', '路径', '路径'...);
注
//path.join() 路径拼接时,路径名 ./路径 和 /路径 效果是一样的
path.join('c:/', './b') === path.join('c:/', '/b')
- 用于配合__dirname使用
const path = require('path');
const fs = require('fs');
fs.readFile(path.join(__dirname, './files/1.txt'), 'utf8', (err, data) => {
//路径也可以写成 /files/1.txt
if(err) return console.log('文件读取失败');
console.log(data);
})
8.js的单线程和异步
1.js一直都是单线程语言,异步操作交给浏览器或node,js先执行执行栈中的代码,完成后从任务队列把异步任务放进执行栈执行
p1-jj.byteimg.com/tos-cn-i-t2…
2.node环境和浏览器环境可以开启多线程
9.模块化
1.就是一种约定,一种规范,一种开发思想
2.让程序员编写的代码可以按照这个规范方便其它js调用
10.CommonJS规范
- 一个js文件就是一个模块
- require() 导入其它模块,它是同步的
- module.exports 暴露一个模块
- 同步加载,不适用于浏览器端
11.node中的作用域
1.global作用域,类似于浏览器中的window,属于node环境中的全局对象,挂载在node环境里
//模块中定义全局变量
global.a = 1;
//以下这种定义方式是属于模块私有的变量,模块作用域有用
let a = 1;
2.模块是独立的作用域,其它模块无法直接访问
12.把模块中的变量和方法导出
1.第一种方式,这种方式基本不用,存在全局变量污染
//模块
let a = 1;
const fn = function () {};
//通过global全局对象导出
global.a = a;
global.fn = fn;
2.第二种方式, 通过module.exports导出
//模块
let a = 1;
const fn = function () {};
//通过module.exports
module.exports = {
a,
fn
}
13.如何引入一个模块
//模块1 暴露出去
let a = 1;
const fn = function () {};
//通过module.exports
module.exports = {
a,
fn
}
//模块2 引入模块1暴露出来的成员
const obj = require('模块1的路径'); //不需要通过path.join(__dirname + '模块1路径')方法拼接路径
console.log(obj); //{a: 1, fn: 函数}
14.exports与module.exports的区别
- exports和module.exports都是向外暴露对象
- 永远是以module.exports为准
15.模块的分类
1.node核心API模块
- 导入方式
//导入核心模块
const 变量 = require('模块名')
2.第三方模块,就是包
- 导入方式
//导入第三方模块 先通过npm安装第三方模块,存储在node_modules文件夹里
const 变量 = require('模块名');
3.自定义模块,我们在本地自己写的js文件
- 导入方式,记得module.exports导出
//导入自定义模块
const 变量 = require('模块路径名/文件名.js');
4.npm
1.包的定义和使用,我们一般只需要通过npm下载下来用就可以了
1.可以理解为插件,这个插件可能也引用了其他的第三方模块
2.遵循commonJS规范,入口文件必有module.exports导出成员供我们使用
3.包的规范结构,了解即可
-
必须是一个单独的文件夹
-
包一定要存放在node_modules文件下,这样在导入第三方模块时,不需要写路径
-
必须要有package.json这个文件
1.符合json格式
2.必须有三个属性
- name 包的名字
- version 包的版本号
- main 包的入口文件
//package.json文件,此文件必须在一个项目根目录下
{
"name": "插件的名称",
"version": 1.0.0,
"main": "入口文件路径"
}
p1-jj.byteimg.com/tos-cn-i-t2…
2.npm是什么
npm config set registry https://registry.npm.taobao.org/
- 是一个网站 www.npmjs.com/
- 也是一个管理工具,在我们安装node的时候就自动帮我们安装上了。 可以在控制台输入 npm -v查看版本号
3.安装和卸载全局包
全局安装,安装在整个电脑里的,任何地方都可以使用
npm install 包名字 -g //g是全局的意思 简写: npm i 包名字 -g
全局卸载,必须是全局安装的包才能卸载
npm uninstall 包名字 -g //g是全局的意思
4.安装和卸载本地包
- 本地包,就是在某个项目中才使用的包
- 包会安装在node_modules文件夹下
- 想在某个项目中使用包
- 项目名字是英文
- 初始化项目
//在项目目录下打开终端
npm init -y //初始化项目,此时项目根目录会多一个package.json的文件
- 安装包
//在项目目录下打开终端
npm i 包名字 -S
//S大写 代表的意思: 这个项目打包上线运行时所依赖的插件,这个包必须打包进js代码里
npm i 包名字 -D
//代表的意思:这个项目在开发时要用的插件,项目打包上线时不需要 提高开发效率的插件 比如less包
- 卸载本地包
//在项目目录下打开终端
npm uninstall 包名字 -S/-D //S还是D,看你这个包安装时候是S还是D安装的
5.删除node_modules文件夹后如何安装之前所有包
一个项目需要依赖的第三方包有很多,为了减小项目文件传递时间,node_modules文件夹可以删除
node_modules删除后项目就不能运行了,我们如何安装之前装过的包呢?
//在当前根目录下,开发时用这个命令
npm i //此时node会根据package.json中所记录的包名和版本号开始自动安装所有的包
//在当前根目录下,项目拷贝过来后,要上线运行,用这个命令
npm i --production //此时node会根据package.json中所记录的 自动安装所有production属性下的包
6.安装国内的cnpm包管理器,下载包速度快
npm install -g cnpm --registry=https://registry.npm.taobao.org
//解决 npm 安装 node-sass 速度慢的问题
//在 ~/.npmrc 加入下面内容
sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
//.npmrc 文件位于
win:C:\Users\[你的账户名称]\.npmrc
//完整配置如图
registry=https://registry.npm.taobao.org
sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
7.node创建一个最基本的web静态资源服务器
1.静态资源服务器
- 没有数据库参与
//1.导入http模块
const http = require('http');
//2.创建服务器对象
const server = http.createServer();
//3.监听客户端请求
server.on('request', (req, res) => {
//req 客户端对象
//res 服务器对象
//防止中文乱码
res.writeHeader(200, {
'Content-Type': 'text/plain; charset=utf-8'
})
//res.end只接受字符串和二进制
res.end('服务器启动成功'); //向客户端发送数据
})
//4.启动服务器
server.listen(3000, '127.0.0.1', () => {
console.log('http://127.0.0.1:3000')
})
2.动态资源服务器
- 有数据库参与
8.根据不同url返回不同页面,包括js,css
//1.导入http, fs, path模块
const http = require('http');
const fs = require('fs');
const path = require('path');
//2.创建服务器对象
const server = http.createServer();
//3.监听客户端请求
server.on('request', (req, res) => {
//req 客户端对象
//防止中文乱码
res.writeHeader(200, {
//plain表示普通的文本,html表示以html标签形式去解析服务器返回的内容
'Content-Type': 'text/html; charset=utf-8'
})
const url = req.url; //客户端请求的url路径
if(url == 路径字符串) { //路径字符串根据自己需求来写
//读取文件,发送给客户端
fs.readFile(path.join(__dirname, html或js或css文件路径), (err, data) -> {
if(err) return res.end('404 Not Found');
//发送给客户端
res.end(data)
})
} else {
res.end('404');
}
})
//4.启动服务器
server.listen(3000, '127.0.0.1', () => {
console.log('http://127.0.0.1:3000')
})
9.通过使用art-template模板返回动态页面
//1. npm init -y 初始化项目,文件夹必须是英文名称,生成package.json文件
//2. 安装art-Template
//cnpm i art-Template -S
//3.写代码
const http = require('http');
const path = require('path');
const temp = require('art-template');
const server = http.createServer();
server.on('request', (req, res) => {
const url = req.url;
if(url == '/') {
//最好path.join()拼接路径
const html = temp(path.join(__dirname, '/xx/art-template.html'), {
//动态数据,后期可以从数据库获取
name: 'ls',
hobby: ['唱歌', '跳舞']
});
res.end(html);
} else {
res.end('404');
})
server.listen(3000, '127.0.0.1', () => {
console.log('http://127.0.0.1:3000');
});
<!--art-template.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<p>这是artTemplate模板渲染的页面</p>
<p>{{name}}</p>
{{each hobby}}
<p>{{$value}}</p>
{{/each}}
</body>
</html>
5.express框架
1.nodemon工具的使用
可以动态刷新我们node开启的服务器
//全局安装
cnpm i nodemon -g
//在项目中通过nodemon运行项目
nodemon xxxx.js
2.express框架
node平台开发web的框架,基于node中的http模块进行了进一步封装
//项目中安装
cnpm i express -S
3.通过express框架创建一个服务器
//1.初始化一个项目
cnpm init -y
//2.项目中安装express框架
cnpm i express -S
//3.在项目文件中创建服务器的代码如下
//导入express框架
const express = require('express');
//创建服务器对象
const app = express();
//监听客户端请求
app.get('请求路径', (req, res) => {
//req是客户端对象
//res是服务端对象
res.send();//向客户端发送数据
//或者
res.sendFile()//向客户端发送数据
});
//开启服务器
app.listen(3000, '127.0.0.1', () => {
console.log('http://127.0.0.1:3000');
})
4.在express框架中res.send和res.senFile的作用
res.send('ok');//支持发送字符串给客户端
res.send({name: 'ls'});//支持发送对象,转成JSON格式的字符串发送给客户端
res.send([1, 2, 3]); //支持发送数组,转成JSON格式的字符串发送给客户端
res.send(new Buffer(123)); //支持发送二进制数据,在客户端会有下载效果
//在http模块中的res.end()
//不支持直接发送中文(需要设置res.writeHeader(200,{...})),对象, 数组...
//res.sendFile()可以向客户端发送html文件
//等同于原生语法的 fs.readFile()读取文件,然后通过res.end(读到的文件)
res.sendFile(path.join(__dirname, '文件路径'));
//或者
res.sendFile('文件路径', {root: __dirname});
5.express.static()快速托管静态资源文件
- 在原生语法中 需要先 fs.readFile() 读取静态资源文件,然后res.end(data)发送给浏览器访问
- 在express框架中 需要 res.sendFile(path.join(__dirname, '静态资源文件路径')) 让浏览器访问
//app.use() 专门注册中间件
//托管静态资源文件,浏览器直接访问,无需调用其他方法
app.use(express.static(path.join(__dirname, '静态资源目录路径')));
//如果想让被托管的资源前缀加上虚拟目录 列如: 127.0.0.1:3000/abc/home.html
app.use('/abc', express.static(path.join(__dirname, '/views'))); //views文件夹下有home.html
//通过url地址访问被托管的资源 和托管静态资源文件下的路径一致
//举列子:
app.use(express.static(path.join(__dirname, '/node_modules')));//托管根目录下node_modules所有文件
//node_modules文件下有一个jquery文件夹
//在url地址中输入 http://127.0.0.1:3000/jquery/dist/jquery.js 就可以访问托管资源目录下的文件了
6.配置ejs渲染动态页面,前后端不分离渲染
//安装ejs包 cnpm i ejs -S
const path = require('path');
//引入express模块,创建app对象
const express = require('express')
const app = express();
//配置express默认模板引擎 app.set('view engine', 模板后缀名)
app.set('view engine', 'ejs');
//设置模板页面保存路径 app.set('views', 模板路径)
app.set('views', path.join(__dirname, './views'))
//监听客户端请求
app.get('/', (req, res) => {
res.render('index.ejs', {name: 'ls', hobby: ['打球', '洗澡']})
})
//开启服务器
app.listen('3000', () => {
console.log('http://127.0.0.1:3000');
})
<!--html中的ejs语法-->
<p>
<%=name%>
</p>
<!--循环渲染-->
<% hobby.forEach(item => {</%>
<p><%= item %></p>
<% }) %>
7.配置art-Template渲染动态页面,前后端不分离渲染
//先安装两个模块 cnpm i art-template express-art-template -S
//引入express模块
const express = require('express');
//引入path模块
const path = require('path');
//创建server对象
const app = express();
//自定义模板引擎 app.engine(模板后缀名, 模板函数 require('express-art-template'));
app.engine('html', require('express-art-template'));
//将自定义的模板,配置成express默认的模板引擎 app.set('view engine', 模板后缀名)
app.set('view engine', 'html');
//设置模板页面的存放路径
app.set('views', path.join(__dirname,'./views'));
//监听客户端请求
app.get('/', (req, res) => {
//渲染模板,传输给客户端res.render(要渲染的页面名称, 数据)
res.render('index.html', {name: 'ls', hobby: ['打球', '洗澡']})
})
//开启服务器
app.listen('3000', () => {
console.log('http://127.0.0.1:3000');
})
<!--html中的art-Template语法-->
{{name}}
{{each hobby}}
<p>{{$value}}</p>
{{/each}}
8.把路由相关代码提取到单独路由模块当中
后端路由
- 前端请求的url地址,后端监听到,然后用一个回调函数去处理就叫路由
- express中的路由使用,提取路由模块
const express = require('express')
const path = require('path')
//创建路由对象
const router = express.Router()
//设置路由监听
//get请求
router.get('/', (req, res) => {
res.sendFile(path.join(__dirname, './views/about.html'))
})
router.get('/home', (req, res) => {
res.sendFile(path.join(__dirname, './views/home.html'))
})
router.get('/movie', (req, res) => {
res.sendFile(path.join(__dirname, './views/movie.html'))
})
//post请求
router.post('/postData', (req, res) => {
//req 客户端对象
//res 服务器对象
res.send();
})
//暴露路由对象
module.express = router
//引入express模块
const express = require('express');
//创建express服务器对象
const app = express();
//导入路由对象
const router = require('./7.router.js');
//write code...
//注册路由对象
app.use(router);
//开启服务器,监听端口
app.listen(3000, () => {
console.log('http://127.0.0.1:3000')
})
6.数据库
1.数据库操作
- 打开phpStudy
- 点击其他选项菜单 -> 服务管理器 -> MySql -> 启动
- 打开Navicat, 连接数据库,新建数据库mysql__001
- 右键点击mysql_001,新建表
- 设置表内容,保存为users
- p1-jj.byteimg.com/tos-cn-i-t2…
- p1-jj.byteimg.com/tos-cn-i-t2…
2.数据库语法
//引入express模块
const express = require('express');
//创建express服务器对象
const app = express();
//导入mysql模块
const mysql = require('mysql');
//创建mysql的连接对象
const conn = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'root',
database: 'mysql_001'
})
//直接调用 conn.query('要执行的sql语句', (err, result) => {}) 方法执行Sql语句就行
const sqlStr1 = 'select * from users'
conn.query(sqlStr1, (err, res) => {
if (err) return console.log('获取数据失败' + err.message)
console.log(res)
})
//新增
// const user = { name: '古天乐', age: 40, gender: '男' }
// const sqlStr2 = 'insert into users set ?'
// conn.query(sqlStr2, user, (err, res) => {
// if (err) return console.log('新增数据失败' + err.message)
// console.log(res)
// })
//修改
// const user = { id: 4, name: '张家辉', age: 45, gender: '男' }
// const sqlStr3 = 'update users set ? where id=?'
// conn.query(sqlStr3, [user, user.id], (err, res) => {
// if (err) return console.log('修改数据失败' + err.message)
// console.log(res)
// })
//删除
const sqlStr4 = 'delete from users where id=?'
conn.query(sqlStr4, 7, (err, res) => {
if (err) return console.log('删除数据失败' + err.message)
console.log(res)
})
//开启服务器,监听端口
app.listen(3000, () => {
console.log('http://127.0.0.1:3000')
})
7.require的查找规则
- 第一次会执行模块内代码
- 后面会从缓存中加载
- 提高了加载速度
1.核心模块加载机制
先从缓存找,没有就加载,有就从缓存拿
2.用户模块加载机制
- 先从缓存找,没有就加载,有就从缓存拿
- 如果用户省略了后缀名
require(./index)
查找规则: index -> index.js -> index.json -> index.node
3.第三方模块加载机制
1.node_modules文件夹 -> 对应文件名 -> package.json文件 -> main属性对应的目录文件
2.以上如果任何一个没查到
3.再去项目上一层目录中再去找node_modules文件夹按顺序查
4.如果一致查到磁盘根目录都没,报错
8.express服务器获取参数的三种形式
//引入express模块
const express = require('express');
const bodyParser = require('body-parser');
//创建express服务器对象
const app = express();
// 注册 body-parser 中间件,来解析Post提交过来的表单数据
app.use(bodyParser.json()); //ajax发送
app.use(bodyParser.urlencoded({ extended: false }));//表单post提交数据
//write code...
//监听客户端的get请求
//1.获取 http:127.0.0.1:3000/user?id=10&name=zs 中?后的参数
app.get('/user', (req, res) => {
// 服务器,可以直接通过 req.query 属性,获取到客户端提交到服务器的 查询参数
console.log(req.query);
res.send('ok')
});
//2.获取路径参数 http:127.0.0.1:3000/user/10/zs
//url中的:表示参数项
app.get('/user/:id/:name', (req, res) => {
console.log(req.params);
res.send('okk')
});
//3.获取post请求参数,http:127.0.0.1:3000/user 使用body-parser中间件
//监听客户端post请求
app.post('/user', (req, res) => {
// 注意:如果在项目中,想要通过 req.body 获取客户端提交的表单数据,
// 必须 先注册 body-parser 中间件才可以!
console.log(req.body);
res.send('okkk')
});
//开启服务器,监听端口
app.listen(3000, () => {
console.log('http://127.0.0.1:3000')
});
9.跨域解决方案(面试题)
1.JSONP(需要前后端配合),不是ajax请求,不支持post
原理:
- 创建一个函数test,形参接收服务器返回的值
- 创建script标签,src属性发送携带?callback=test 参数给服务端,服务端监听到请求后返回函数调用
- 'test(服务器返回的数据)'
<script>
function test(res) {
console.log(res);
}
</script>
<!--发送请求给服务器,服务器会返回 test(数据) 函数的调用,形参res就会拿到后台返回的数据-->
<script src="主机名/xxx?callback=test"></script>
2.CORS跨域资源共享,支持Ajax请求,支持get, post
条件:
- 官方的浏览器跨域解决方案
- 浏览器和服务器都需要支持CORS
- 关键在于服务器设置好CORS后,浏览器就可以跨域访问了
3.node中使用CORS
//1.安装模块 cnpm i cors -S
//2.导入模块
const cors = require('cors');
//3.注册模块
app.use(cors())
10.MVC的三层架构
MVC 后端开发思想
p1-jj.byteimg.com/tos-cn-i-t2…
M层:model数据层,就是数据库操作
V层:view视图层,res.sendFile( )或者 res.render()渲染的页面,配合静态资源托管express.static()
C层:controller业务处理层,路由抽离,路由对应的函数抽离,入口文件
11.cookie的用途
1.定义
- cookie就是存储在客户端的一小段文本字符串
- 客户端同服务器建立链接之后,由服务端发送给客户端
- 客户端每一次发送请求,都会把cookie携带,一并发送给服务器
2.作用,用途
- 由于http协议是无状态的,即协议只负责传输数据,并不能保存数据
- cookie可以用来存储客户信息
3.缺陷
cookie保存的信息在服务器与客户端之间来回传送,不安全,不能用于保存敏感信息
传输的数据只有4kb
4.特点
默认每一次关闭页面,cookie都会失效
可以指定过期时间
//expires属性可以设置过期时间,UTC格式
const expiresTime = new Date(Date.now() + 10000).toUTCString()
res.writeHeader(200, {
'Content-Type': 'text/html;chartset=utf-8';
//让isvisit一定时间后过期
'Set-Cookie': ['isvisit=yes;expires=' + expiresTime, 'test=ook']
})
12.session的用途
1.定义
- session存储在服务端
- 在服务端开辟空间,用来保存每个客户端的私有数据
- 服务端向客户端发送一个cookie,cookie用来保存sessionID
- 客户端通过向服务端发送cookie,服务端解析出sessionID, 用来匹配客户端的私有数据
2.原理
p1-jj.byteimg.com/tos-cn-i-t2…
13.在express中使用session
1.安装session模块
cnpm i express-session -S
2.导入session
const session = require('express-session')
3.注册中间件
//注册成功后在任何req里,都可以访问到req.session这个对象
app.use(session({
secret: 'asdgqwe', //加密秘钥,任意字符串
resave: false, //强制session保存到session store中
saveUninitialized: false //强制没有初始化的session保存到storage中
}))
4.将私有数据保存到当前请求的session会话中,一般用在监听到登录请求时的回调函数里
app.post('/login', (req, res) => {
//通过数据库操作,判定用户已经登录
//将用户信息保存到session中
req.session.user = 数据;
//用布尔值保存用户是否登录
req.session.isLogin = true;
})
5.清空session
app.get('/logout', (req, res) => {
//清空session
req.session.destory(() => {
//服务端操作重定向到首页
res.redirect('/')
})
})
14.cookie, session, token特点
1.为什么会出现这种技术: 因为http协议是无状态的,它无法让服务器识别到底是谁在发送请求
1.cookie 第一代技术 不需要前端操作
-
存储在客户端
-
容量小4kb, 不安全
-
客户端首次同服务器通讯时,是没有cookie的,是从服务端发送过来
-
cookie可以设置有效时间
-
客户端在和服务端通讯时,客户端会自动在请求头中把cookie发送给服务端
2. session 第二代技术 不需要前端操作
- 存储在服务端
- 它存储大小就是服务器的容量大小
- 它会生成一个seesionID放进cookie里发送到客户端,然后客户端会把含有sessionID的cookie在通讯时发送给服务端
- 如果用户太多的话,服务端容量会造成泄漏
3.token 第三代技术
-
它是随着每一次通讯在客户端和服务端来回发送
-
它是一段加密后的无序字符串,保存的是用户的信息包括过期时间吗,服务端需要解密后才能拿到客户信息
-
发送token可以有两种方式
①跟随cookie走, 不需要前端操作
②服务器如果把token放在响应体里,那么就需要我们前端去主动存储,我们一般存储在localStorage里或者sessionStorage里, 需要前端操作
③如果是前端去操作token,我们需要每一次发送请求时候,手动把token放在请求头里发送给服务端
15.加密
1.1 MD5加密(后端不推荐)
-
MD5 是一种加密算法**,在调用这个算法的时候,提供一个密码的明文, 调用的结果,得到一个 32 位长度的密文;
-
**MD5 算法的特性:**相同的字符串,如果多次调用 md5 算法,得到的结果,完全一样;
-
MD5 算法,无法被逆向解密;
-
但是,基于 md5 算法的第二个特性,我们可以进行碰撞暴力破解;(MD5 存在被暴力破解的安全性问题)
-
为了解决 简单的明文密码,被 md5 加密后,通过 暴力破解的安全性问题, 然后就出现了加盐的MD5加密;
-
目前,md5的暴力破解,又升级了,升级到了
彩虹表; -
由于彩虹表出现,我们推荐大家在存储网站密码的时候,使用
bcrypt加密算法,得到加密之后的密文进行存储; -
前端推荐使用MD5加密算法,加密后的数据发送给后端,后端再使用其他加密方式进行二次加密,安全性更强
1.2 bcrypt 加密算法(后端推荐)
- 在调用加密算法的时候,需要手动提供一个
幂次; - 调用加密算法,得到的加密结果格式:
$版本号$循环的幂次$22位的随机盐 31位的密文- 加密的
随机盐和加密的幂次,和加密算法的版本号已经被存储到了真正的密文中;
- 加密的
1.3 项目中使用 bcrypt 的步骤
-
运行
npm i node-pre-gyp -g -
在项目根目录中,打开终端,运行
cnpm install bcrypt -S -
导入
bcrypt// 导入加密的模块 const bcrypt = require('bcrypt') -
定义幂次
// 定义一个 幂次 const saltRounds = 10 // 2^10 -
调用
bcrypt.hash()加密:// 加密的方法 bcrypt.hash('123', saltRounds, (err, pwdCryped) => { console.log(pwdCryped) }) -
调用
bcrypt.compare()对比密码是否正确:// 对比 密码的方法 bcrypt.compare('123', '$2b$10$i1ufUKnC9fXTsF9oqqvLMeDnpNfYIvhyqKRG03adiebNFPkjW3HPW', function(err, res) { console.log(res) // 内部对比的过程: // 1. 先获取 输入的明文 // 2. 获取输入的密文 // 2.1 从密文中,解析出来 bcrypt 算法的 版本号 // 2.2 从密文中,解析出来 幂次 // 2.3 从密文中,解析出来前 22 位 这个随机盐 // 3. compare 方法内部,调用 类似于 hash 方法 把 明文,幂次,随机盐 都传递进去 最终得到正向加密后的密文 // 4. 根据最新得到的密文,和 compare 提供的密文进行对比,如果相等,则 返回 true ,否则返回 false; })