Node.js

441 阅读4分钟

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定义变量

  1. 先定义后使用,不存在预解析,不可重复定义

    console.log(a); //a is not defined
    let a = 1;
    //如果有预解析,会打印undefined
    
  2. 块级{}作用域

    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定义常量

  1. 先定义后使用,不存在预解析,不可重复定义

  2. 定义常量,定义后不可更改

    const a = 1;
    a = 2;
    console.log(a); //Assignment to constant variable 报错
    
  3. 定义必须赋值

    const a;
    a = 1;
    console.log(a); //Missing initializer in const declaration 报错
    
  4. 一般用来定义对象,数组, 函数

    const a = {};
    a.name = 'ls';
    console.log(a); //{ name: 'ls' }
    

3.对象和数组的解构赋值,数组去重,扩展运算符,...rest,模板字符串

  1. 对象的解构赋值

    let obj = {
      username: 'ls',
      age: 20
    }
    //解构赋值
    let {username, age} = obj;
    console.log(username, age); //'ls' 20  把对象里的属性当作变量来使用,使用前记得先定义
    
  2. 对象解构赋值重命名

    let obj = {
      username: 'ls',
      age: 20
    }
    //解构赋值重命名
    let {username: name, age: a} = obj;
    console.log(name, a); //'ls' 20  username,age已经无法使用
    
  3. 数组的解构赋值

    const arr = [1, 2, 3];
    let [a, b, c] = arr;
    console.log(a, b, c); //1 2 3
    
  4. 交换两个变量值

    let a = 1;
    let b = 2;
    [b, a] = [1, 2]; //等号右边是数组  左边是解构
    console.log(a, b); // 2 1
    
  5. 数组去重

    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 ]
    
  6. 扩展运算符 三个点... 用来解压数组或者对象

    //合并数组
    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'}
    
  7. 函数形参...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); 
    
  8. 模板字符串 `` 数字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.箭头函数

箭头函数是匿名函数

  1. 完整体

    (形参...) => {代码块}  === function () {}
    
  2. 箭头函数里的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();
    
  3. 箭头函数的书写

    • 完整体

      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规范

  1. 一个js文件就是一个模块
  2. require() 导入其它模块,它是同步的
  3. module.exports 暴露一个模块
  4. 同步加载,不适用于浏览器端

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的区别

  1. exports和module.exports都是向外暴露对象
  2. 永远是以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/

  1. 是一个网站 www.npmjs.com/
  2. 也是一个管理工具,在我们安装node的时候就自动帮我们安装上了。 可以在控制台输入 npm -v查看版本号

3.安装和卸载全局包

全局安装,安装在整个电脑里的,任何地方都可以使用

npm install 包名字 -g   //g是全局的意思    简写: npm i 包名字 -g

全局卸载,必须是全局安装的包才能卸载

npm uninstall 包名字 -g  //g是全局的意思

4.安装和卸载本地包

  1. 本地包,就是在某个项目中才使用的包
  2. 包会安装在node_modules文件夹下
  3. 想在某个项目中使用包
  • 项目名字是英文
  • 初始化项目
//在项目目录下打开终端
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.数据库操作

  1. 打开phpStudy
    • 点击其他选项菜单 -> 服务管理器 -> MySql -> 启动
  2. 打开Navicat, 连接数据库,新建数据库mysql__001
  3. 右键点击mysql_001,新建表
  4. 设置表内容,保存为users
  5. p1-jj.byteimg.com/tos-cn-i-t2…
  6. 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. 后面会从缓存中加载
  3. 提高了加载速度

1.核心模块加载机制

先从缓存找,没有就加载,有就从缓存拿

2.用户模块加载机制

  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

原理:

  1. 创建一个函数test,形参接收服务器返回的值
  2. 创建script标签,src属性发送携带?callback=test 参数给服务端,服务端监听到请求后返回函数调用
  3. 'test(服务器返回的数据)'
<script>
  function test(res) {
    console.log(res);
  }
</script>
<!--发送请求给服务器,服务器会返回 test(数据) 函数的调用,形参res就会拿到后台返回的数据-->
<script src="主机名/xxx?callback=test"></script>

2.CORS跨域资源共享,支持Ajax请求,支持get, post

条件:

  1. 官方的浏览器跨域解决方案
  2. 浏览器和服务器都需要支持CORS
  3. 关键在于服务器设置好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.定义

  1. cookie就是存储在客户端的一小段文本字符串
  2. 客户端同服务器建立链接之后,由服务端发送给客户端
  3. 客户端每一次发送请求,都会把cookie携带,一并发送给服务器

2.作用,用途

  1. 由于http协议是无状态的,即协议只负责传输数据,并不能保存数据
  2. 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.定义

  1. session存储在服务端
  2. 在服务端开辟空间,用来保存每个客户端的私有数据
  3. 服务端向客户端发送一个cookie,cookie用来保存sessionID
  4. 客户端通过向服务端发送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 第一代技术 不需要前端操作

  1. 存储在客户端

  2. 容量小4kb, 不安全

  3. 客户端首次同服务器通讯时,是没有cookie的,是从服务端发送过来

  4. cookie可以设置有效时间

  5. 客户端在和服务端通讯时,客户端会自动在请求头中把cookie发送给服务端

2. session 第二代技术 不需要前端操作

  1. 存储在服务端
  2. 它存储大小就是服务器的容量大小
  3. 它会生成一个seesionID放进cookie里发送到客户端,然后客户端会把含有sessionID的cookie在通讯时发送给服务端
  4. 如果用户太多的话,服务端容量会造成泄漏

3.token 第三代技术

  1. 它是随着每一次通讯在客户端和服务端来回发送

  2. 它是一段加密后的无序字符串,保存的是用户的信息包括过期时间吗,服务端需要解密后才能拿到客户信息

  3. 发送token可以有两种方式

    ①跟随cookie走, 不需要前端操作

    ②服务器如果把token放在响应体里,那么就需要我们前端去主动存储,我们一般存储在localStorage里或者sessionStorage里, 需要前端操作

    ③如果是前端去操作token,我们需要每一次发送请求时候,手动把token放在请求头里发送给服务端

15.加密

1.1 MD5加密(后端不推荐)

  1. MD5 是一种加密算法**,在调用这个算法的时候,提供一个密码的明文, 调用的结果,得到一个 32 位长度的密文;

  2. **MD5 算法的特性:**相同的字符串,如果多次调用 md5 算法,得到的结果,完全一样;

  3. MD5 算法,无法被逆向解密

  4. 但是,基于 md5 算法的第二个特性,我们可以进行碰撞暴力破解;(MD5 存在被暴力破解的安全性问题)

  5. 为了解决 简单的明文密码,被 md5 加密后,通过 暴力破解的安全性问题, 然后就出现了加盐的MD5加密;

  6. 目前,md5的暴力破解,又升级了,升级到了 彩虹表

  7. 由于彩虹表出现,我们推荐大家在存储网站密码的时候,使用 bcrypt 加密算法,得到加密之后的密文进行存储;

  8. 前端推荐使用MD5加密算法,加密后的数据发送给后端,后端再使用其他加密方式进行二次加密,安全性更强

1.2 bcrypt 加密算法(后端推荐)

  1. 在调用加密算法的时候,需要手动提供一个 幂次;
  2. 调用加密算法,得到的加密结果格式:$版本号$循环的幂次$22位的随机盐 31位的密文
    • 加密的随机盐加密的幂次,和加密算法的版本号已经被存储到了真正的密文中;

1.3 项目中使用 bcrypt 的步骤

  1. 运行 npm i node-pre-gyp -g

  2. 在项目根目录中,打开终端,运行 cnpm install bcrypt -S

  3. 导入 bcrypt

    // 导入加密的模块
    const bcrypt = require('bcrypt')
    
  4. 定义幂次

    // 定义一个 幂次
    const saltRounds = 10    // 2^10
    
  5. 调用 bcrypt.hash() 加密:

    // 加密的方法
    bcrypt.hash('123', saltRounds, (err, pwdCryped) => {
       console.log(pwdCryped)
    })
    
  6. 调用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;
    })