node.js 史上最详细 (博主持续更新)

1. node.js介绍

诞生于2009年,它不是一门语言也不是一门框架,它是基于Google V8引擎的JavaScript运行时环境,同时结合Libuv拓展了JavaScript功能,使之支持IO,fs等语言才有的特性,使得JavaScript能够同时具有DOM操作(浏览器)和I/O,文件读写、操作数据库等能力。

应用:淘宝双十一、去哪儿网PC端核心业务;前端工具 VScode、webpack等具有Node.js开发

node的包管理工具npm成为世界中开源包管理中最大的生态、功能强大。

2. 特点

  • 事件驱动
  • 非阻塞IO模型(异步)
  • 轻量和高效

3. 在一线企业中的应用

①作为中间层

②做一些小型网站的后端 

  • 减少客户端内存,不会像MVVM模式的项目把页面渲染和数据请求都压在客户端,而是在服务端完成
  • SEO性好,不像mvvm模式页面由js生成,而是在服务端渲染好html字符,有利于网页被搜索到
  • 前端可以操控的范围增多,甚至可以操作服务器,数据层面的优化,比如中间层使用nginx、redis来优化项目,应对高并发。

优点:中间层模式是一种开发模式上的进步,但这种好的模式成本过高,如果没有一定量级的项目没必要使用 

4. node.js中的module.exports和exports

  •  Node中每个模块都有一个module对象,module对象中有一个exports属性作为借口对象,我们需要把模块之间公共方法或属性挂载到这个接口对象中,方便其他的模块使用 这些公共的方法或属性。
  • Node中每个模块的最后,都会return module.exports, 默认module.exports = {}
  • Node中每个模块都会把module.exports指向的对象赋值给一个变量exports,即module.exports == exports
  • module.exports == XX,表示当前模块导出一个单一成员,require导入的就是一个{ ... }
  • 如果需要导出多个成员,必须使用module.exports.xxx = xxx。

5. node、js中require加载第三方包的规则

  • 找写require文件同级目录中的node_module下面的同名文件,同级目录中没有node_module,就找父级(父级、祖父级等)的node_module

node.js 史上最详细 (博主持续更新)_第1张图片

  • 找该文件夹下面package.json中的main,这个main属性值就是你引入的文件路径

node.js 史上最详细 (博主持续更新)_第2张图片

6. npm 常见命令

npm 全名为node package manager,我们平时开发项目都需要使用npm命令安装依赖

npm -v :查看npm版本。
npm init :初始化后会出现一个package. json配置文件。可以在后面加上-y , 快速跳过问答式界面。npm 
install :会根据项目中的package. json文件自动下载项目所需的全部依赖。
npm install 包名--save -dev(npm install 包名-D): 安装的包只用于开发环境,不用于生产环境,会出现在
package. json文件中的devDependencies属性中。
npm install 包名--save(npm install 包名-S): 安装的包需要发布到生产环境的,会出现在
package.json文件中的dependencies属性中。
npm list :查看当前目录下已安装的node包。
npm list -g :查看全局已经安装过的node包。
npm --he1p :查看npm帮助命令。
npm update包名: 更新指定包。
npm uninsta1l 包名:卸载指定包。
npm config list :查看配置信息。
npm指定命令--he1p :查看指定命令的帮助。
npm info 指定包名:查看远程npm上指定包的所有版本信息。
npm config set registry https ://registry. npm. taobao. org :修改包下载源,此例修改为了淘宝镜像。
npm root : 查看当前包的安装路径。
npm root -g :查看全局的包的安装路径。
npm 1s包名:查看本地安装的指定包及版本信息,没有显示empty。

7. 文件读取(同步读取)

  • 使用fs.readFileSync()读取文件,第一个参数为文件名或文件描述符,第二个参数是一个对象,该对象有两个属性,一个是encoding,一个是flag(读取文件--“r”)。
  • 读取的文件为“utf-8”,直接输出读取到的content:
const fs = require("fs");
const content = fs.readFileSync("txt.txt");
console.log(content);

node.js 史上最详细 (博主持续更新)_第3张图片

  • 输出content.toString():
const fs = require("fs");
const content = fs.readFileSync("txt.txt");
console.log(content.toString());


node.js 史上最详细 (博主持续更新)_第4张图片

 总结:首先要确保读取的文件类型为"utf-8",文件中除了英文之外的语言,读取的结果默认是16进制的Buffer缓冲器,如果想要正常输出需要加toString()方法。

8. 文件读取(异步读取)

  • 使用fs.readFile()读取文件,第一个参数为文件名或文件描述符(必传);第二个参数是一个对象(可传可不传)该对象有两个属性,一个是encoding,一个是flag(是读取文件--“r”),如果不传,默认输出buffer对象;第三个参数callback(必传)
  • 该方法读取如下:
const fs = require("fs");
fs.readFile('txt.txt', {encoding: "utf-8", flag: "r"}, (err, data) => {
    if(err) {
        console.log(err);
    } else {
        console.log(data);
    }
    console.log("我证明javascript是单行执行!")
});
console.log("我证明他是异步!");
  • 输出结果为
PS C:\Users\tcsc6\Desktop\node_1> node .\index.js
我证明他是异步!

我证明javascript是单行执行!

总结:如果不指定编码方式默认输出为Buffer缓冲器,readFile是异步函数,javascript是单行执行。

  • 要输出utf-8格式的文本需要指定encoding为utf-8
const fs = require("fs");
fs.readFile('txt.txt',(err, data) => {
    if(err) {
        console.log(err);
    } else {
        console.log(data);
    }
    console.log("我证明javascript是单行执行!")
});
console.log("我证明他是异步!");
  
  • 输出结果为正常的字符串
PS C:\Users\tcsc6\Desktop\node_1> node .\index.js
我证明他是异步!
这是一个txt.txt  
我证明javascript是单行执行!
  • 封装异步读取文件Promise
function readFileAsync(url) {
    return new Promise( (resolve, rejects) => {
        fs.readFile(url, (err, data) => {
            if(!err){
                resolve(data);
            } else {
                rejects(err)
            }
        })
    })
}
readFileAsync("txt.txt").then( res => {
    console.log(res);
})

9. 文件写入(跟文件读取一样,分异步和同步方法,因同步一般不会使用,下面只介绍异步方法)

  • writeFile文件写入,第一个参数是文件名称(必填),第二个参数为写入的数据(必填),第三个参数是编码方式和操作类型(flag :append --> a 追加 ;write --> w 文件写入),该参数是可选填,第四个参数是错误回调函数

  • 实例操作:封装一个写入文件(追加方式)的promise函数(使用async 和 await)
const fs = require("fs");
const { resolve } = require("path");
const { rejects } = require("assert");
function writefs(url, data, flag = "a") {
    return new Promise((resolve, rejects) => {
        fs.writeFile(url, data, err => {
            if(!err){
                resolve("写入成功!");
            } else {
                rejects(err);
            }
        })
    })
}
async function writeFileAsync() {
    let connect = await writefs("txt.txt", "吃饺子\n");
    console.log(connect);
    
    let connect1 = await writefs("txt.txt", "红烧狮子头\n");
    console.log(connect1);
    
    let connect2 = await writefs("txt.txt", "水饺\n");
    console.log(connect2);
    
}
writeFileAsync()
  • 输出
PS C:\Users\tcsc6\Desktop\node_1> node .\index.js
写入成功!
写入成功!
写入成功!

10. 删除文件,同样只介绍异步删除文件 fs.unlink(path, callback)

  • 使用fs.unlink(path,callback),第一个参数是文件路径,第二个参数是回调函数(只有err)
  • 代码实例
const fs = require("fs");
fs.unlink("txt222.txt", () => {
    console.log("删除成功!");
})

node.js 史上最详细 (博主持续更新)_第5张图片

  • 执行结果:

node.js 史上最详细 (博主持续更新)_第6张图片

11. Buffer缓冲器(安全的alloc)

  • 解决的问题
  1. 数组不能进行二进制数据的操作
  2. 因为js数组一般不指定长度,数组地址存放在heap中,存放的地方也不连续,引用的时候效率就会降低,导致js数组不想Java、python等语言效率高
  3. buffer内存空间开辟出固定大小的内存,存放的是连续的引用的时候很容易找到,效率高
  • 实例及输出

const buf = Buffer.alloc(10);
buf[0] = "123";
buf[1] = "255";
console.log(buf);
console.log(buf.toString());
console.log(buf[0]);
console.log(buf[0].toString());

node.js 史上最详细 (博主持续更新)_第7张图片总结:Buffer.alloc(10)开辟10个字节的数组,数组的每一项都可以进行赋值,之后我们可以通过获取数组项的方法获取某一项;

12. 同样的还有allocUnsafe,不安全Buffer缓冲器

  • 实例

node.js 史上最详细 (博主持续更新)_第8张图片

const unsafebuf = Buffer.allocUnsafe(10);
unsafebuf[0] = "123";
console.log(unsafebuf);
console.log(unsafebuf[0].toString() + "\n");

总结:Buffer.allocUnsafe()同样也可以通过传入数字进行给Buffer设置长度,获取的时候使用[0、、]方式获取。不安全的原因是Buffer.allocUnsafe()会保留信息,某些非法分子会利用这些信息做非法操作,比如篡改游戏人物等级等。。

13. fs.readdir() 读取文件目录这里只介绍异步

  • fs.readdir() 第一个参数是文件路径,第二个参数可选(用来指定编码方式),第三个参数是回调函数(err及file--读取的文件目录名)

14. fs.createReadStream()-文件读取流

let fs = require("fs");
let data = "";
let rs = fs.createReadStream("./hellworld.txt", {encoding:"utf-8", flags: "r"});
rs.on("open", function() {
    console.log("打开文件");
})
rs.on("data", function(chunk) {
    console.log(chunk.length);
    data += chunk;
})
rs.on("error", function(err) {
    if(err) {
        console.log(err);
    } else {
        console.log("没有发生错误");
    }
})
// 监听程序完成事件
rs.on("end", _ => {
    console.log("程序执行完毕");
    console.log(data);
})

15.  fs.createWriteStream()-文件写入流

let fs = require("fs");
let ws = fs.createWriteStream("hellworld.txt", {flags: "w", encoding: "utf-8"});
console.log(ws);
// 监听文件打开事件
console.log(122);
ws.write("我要买个手机了", err => {
    if(err) {
        console.log(err);
    } else {
        console.log("我要买个手机了");
    }
})
ws.write("不会吧真的不会吧", err => {
    if(err) {
        console.log(err);
    } else {
        console.log("不会吧真的不会吧");
    }
})
ws.write("手机现在好贵呢", err => {
    if(err) {
        console.log(err);
    } else {
        console.log("手机现在好贵呢");
    }
})
console.log(1333);
ws.on("open", _ => {
    console.log("文件打开");
})
// 监听文件关闭事件
ws.on("close", _ => {
    console.log("文件关闭");
})
// 监听结束
ws.end(function() {
    console.log("文件写入关闭");
})
// 输出
122
1333
文件打开
我要买个手机了
不会吧真的不会吧
手机现在好贵呢
文件写入关闭
文件关闭

16.node事件(events模块)

  • node事件循环:node是单进程单线程的application,但是因为V8引擎提供的异步执行回调函数接口,通过这些接口可以处理大量并发,所以性能会很高。Node几乎每个API都是支持回调函数的,node基本上所有的事件机制都是用设计模式中观察者模式实现的;node.js单线程类似进入一个while(true)的循环事件,直到没有事件观察者退出,每个异步事件都会形成一个事件观察者,如果有事件发生就调用该函数。
开启进程
开启线程
初始化数据 window/document/location...
while(true) {
    初始化事件列表
    根据事件修改数据
    根据事件渲染页面
    if(count = 0) {
        运行js代码
        btn.onclick = function() {
            document.body.style.background = "skyblue";
            console.log(123);    
        }
        console.log(456);     
        count++;
    }
}
上面代码首先会输出456,把click事件放到事件列表中,当点击事件触发时会改变body的颜色以及输出123
  • 事件驱动程序

定义:node.js使用事件驱动模型,但web server收到请求,就把它关闭然后进行处理,然后去服务下一个web请求,当这个请求完成,他被放回处理队列,当到达队列开头这个结果被返回给客户。这个模型非常高效而且可扩展性非常强,因为webserver一直接受请求而不等待任何读写操作(这个称之为非阻塞式IO或者事件驱动I/O)。在事件驱动程式事件中,会生成一个主循环来监听事件,当检测到事件时触发回调函数。

使用:

const events = require("events"),
    fs = require("fs");
// 引入event事件之后 进行初始化一个实例 event.EventEmitter
let ee = new events.EventEmitter();
// 事件监听
ee.on("helloSuccess", _ => {
    console.log("睡醒了学习");
    console.log(_);
})
ee.on("helloSuccess", _ => {
    console.log("学习使我快乐");
    console.log(_);
})
ee.on("helloSuccess", _ => {
    console.log("晚一点出去玩");
    console.log(_);
})
// 当我们读取完文件再使用emit触发上面注册的事件
ee.emit("")
fs.readFile("./hello.txt", {encoding: "utf-8", flag: "r"}, (err, data) => {
    if(err) {
        console.log(err);
    } else {
        console.log(data);
        ee.emit("helloSuccess", data);
    }
})

上面代码封装成Promise函数

function dlReadFile(path) {
    return new Promise( (resolve, rejects) => {
        fs.readFile(path, {encoding: "utf-8", flag: "r"}, (err, data) => {
            if(err) {
                rejects(err);
            } else {
                console.log(data);
                resolve(data);
                ee.emit("helloSuccess", data);
            }
        })
    })
}
async function test() {
    await dlReadFile("./hello.txt");
    console.log("执行完毕");
}
test();

原理:

// 创建一个写入"我是一个被读的数据"的hello.txt文件
// 创建一个写入一下内容的read.js的文件
const fs = require("fs");
fs.readFile("./hello.txt", {encoding: "utf-8", flag: "r"}, (err, data) => {
    if(err) {
        console.log(err);
    } else {
        console.log(data);
        dl.emit("helloSuccess", data);
        // 1.数据库查看所有学生的详细信息
        // 2.统计年龄比例
        // 3.查看所有用户学校的详细信息
    }
})
let dl = {
    event: {
        // 这里存放的是不同事件名称的数组
        // helloSuccess [fn, fn, fn]
    },
    on: function(fnName, fn) {
        if(this.event.hasOwnProperty(fnName)) {
            this.event[fnName].push(fn);
        } else {
            this.event[fnName] = [];
            this.event[fnName].push(fn);
        }
    },
    emit:function(fnName, data) {
        if(this.event.hasOwnProperty(fnName)) {
            this.event[fnName].forEach(itemFn => {
                itemFn(data);
            });
        } else {
            console.log("该事件没有被注册");
        }
    }
}
dl.on("helloSuccess", function(data) {
    console.log("数据库查看所有学生的详细信息");
    console.log(data);
})
dl.on("helloSuccess", function(data) {
    console.log("统计年龄比例");
    console.log(data);
})
dl.on("helloSuccess", function(data) {
    console.log("查看所有用户学校的详细信息");
    console.log(data);
})
// 执行node read.js 输出
我是一个被读的数据 这个是helloText的文件
数据库查看所有学生的详细信息
我是一个被读的数据
统计年龄比例
我是一个被读的数据
查看所有用户学校的详细信息  
我是一个被读的数据

17.路径模块和系统模块

node.js中的path模块提供了一些路径操作的API,os模块提供了一些操作系统相关信息的API。

  •  path相关知识点介绍
let path = require("path");
// extname 读取路径后缀名 extension + name
let strPath = "http://127.0.0.1/index.name";
console.log(path.extname(strPath)); 
// resolve 把一个路径或路径片段的序列解析为一个绝对路径 
console.log(path.resolve("foo",'/bar',"bar"));  // 输出C:\bar\bar
// 原理:给定给定路径的序列是""从左到右"被处理的,后面的path一次解析,知道构造成一个绝对路径
// join 使用平台特定的分隔符把全部给定的path连接到一起,并规范化的路径
console.log(path.join(__dirname, "./template.js")); // 输出为C:\Users\19129\Desktop\node\nodePath\template.js
console.log(path.join("/foo", "bar", "./baz")); // 输出\foo\bar\baz
console.log(path.join("/foo", "bar", "/baz", "..")); // 输出 \foo\bar
console.log(__dirname);
console.log(__filename); // 输出C:\Users\19129\Desktop\node\nodePath\nodePath.js
console.log(path.isAbsolute("./name")); // 输出为false
console.log(path.parse(__filename));
// parse 解析结果如下 解析出路径,目录,拓展名,文件名
// {
//   root: 'C:\\',
//   dir: 'C:\\Users\\19129\\Desktop\\node\\nodePath',
//   base: 'nodePath.js',
//   ext: '.js',
//   name: 'nodePath'
// }
// __dirname 获取当前执行目录的完整路径 --> 这个是文件夹目录
// __filename 获取当前文件的完整绝对路径 --> 这个是文件当前执行文件的目录
// path.isAbsolute() 判断是不是绝对路径
  • os 获取操作系统的cpu信息及内存信息
// os 获取操作系统的cpu信息
let os = require("os");
console.log(os.cpus());
输出几盒CPU信息,下面展示的是一个盒的信息
{
model: 'Intel(R) Core(TM) i7-7500U CPU @ 2.70GHz',
speed: 2904,
times: {
    user: 37025718,
    nice: 0,
    sys: 8268843,
    idle: 208883046,
    irq: 186218
    }
}
user  CPU 在用户模式下花费的毫秒数。
nice  CPU 在良好模式下花费的毫秒数。
sys  CPU 在系统模式下花费的毫秒数。
idle  CPU 在空闲模式下花费的毫秒数。
irq  CPU 在中断请求模式下花费的毫秒数。
// os 获取操作系统的内存信息
console.log(os.totalmem()); // 我的输出17073397760 大约是16G
// 获取操作系统的CPU架构
coonsole.log(os.arch()); // 我的输出X64 如果是手机的话一般是arm 
// 获取闲置的cpu内存
console.log(os.freemem()); // 10787917824 大约闲置10个G
// 查看主机名称
console.log(os.hostname()); // DESKTOP-D3TURCO
// 查看本机系统
console.log(os.platform()); // win32

18.爬取数据(使用axios进行url请求)

  • node中的url模块
const url = require("url");
console.log(url);
// url方法中最常用的是parse 将url地址转为地址对象 方便取值
console.log(url.parse("https://www.baidu.com/s?ie=UTF-8&wd=node"));
// Url {
//     protocol: 'https:', 协议
//     slashes: true, 斜杠语法
//     auth: null, 认证方式
//     host: 'www.baidu.com', 主机地址
//     port: null, 端口
//     hostname: 'www.baidu.com', 主机地址名称
//     hash: null, url路径 #后面的值
//     search: '?ie=UTF-8&wd=node', 携带的参数包括?
//     query: 'ie=UTF-8&wd=node', 携带的参数包括
//     pathname: '/s', 路由的地址
//     path: '/s?ie=UTF-8&wd=node', 协议域名和断后之后的东西
//     href: 'https://www.baidu.com/s?ie=UTF-8&wd=node' 全部路径
//   }
// url.resolve(targetUrl, httpUrl) 自动拼接路径
let targetUrl = "http://www.taobao.com/",
    httpUrl = "./index.html";
url.resolve(targetUrl, httpUrl); // 输出为http://www.taobao.com/index.html
  • 使用axios进行网络请求,将请求的资源进行正则表达式截取
const axios = require("axios"),
    httpUrl = "https://v.sogou.com/film/list/style-+zone-+year-+starring-.html";
// console.log(axios);
axios({
    method:'get',
    url: httpUrl
  })
    .then( res => {
        // console.log(res);
        let reg = /xx(.*?)xxx/gis;
        console.log(reg.exec(res));
        // reg.exec(res) 这个就是你想获取的请求地址,接着可以使用请求地址请求,将获取到的内容再进
        // 行拆解,获取到自己想要的内容写入相应的文件中即可
});

注意:使用reg.exec()每次只能获取到一个匹配值,需要使用while(reg.exec(res))来循环获取值,在外面需要定义一个数组进行接收。

19.爬取表情包(cheerio模块)

cheerio是jquer核心功能的一个快速灵活而又简洁的实现,主要是为了用在服务端需要对DOM进行操作的地方。它是 node的抓取页面模块,为服务器特别定制的,快速、灵活、实施的jQuery的核心实现,适合各种爬虫程序。

const { rejects } = require("assert");
const cheerio = require("cheerio"),
  axios = require("axios"),
  fs = require("fs"),
  path = require("path");
const { resolve } = require("path");
let parentLink = [],
  indexHTMLUrl = "https://www.doutula.com/article/list/?page=",
  allPageNum = 0;
getAllPageNUm();
async function getAllPageNUm() {
  let pageInfo = await axios.get(`${indexHTMLUrl}1`);
  let $ = cheerio.load(pageInfo.data),
    btnPagiinationLength = $(".pagination li").length;
  console.log(btnPagiinationLength);
  console.log(
    $(".pagination li")
      .eq(btnPagiinationLength - 2)
      .text()
  );
  allPageNum = $(".pagination li")
    .eq(btnPagiinationLength - 2)
    .text();
  // 初始化
  for (let i = 1; i <= allPageNum; i++) {
    console.log(123);
    console.log(dlWait);
    await dlWait(3000 * i);
    getPageInfo(`${indexHTMLUrl}${i}`);
  }
}
// cheerio用来解析html或者xml文档
function getPageInfo(url) {
  axios.get(url).then((res) => {
    // 将获取到的数据放到cheerio中进行解析
    let $ = cheerio.load(res.data),
      reg = /(.*?)\d/gis;
    $("#home .col-sm-9>a").each((i, item) => {
      // 获取到所有的标签头url连接
      let parentUrl = $(item).attr("href"),
        parentTitle = $(item).find(".random_title").text();
      // 全部分类
      parentLink.push({ url: parentUrl, title: parentTitle });
    });
    // console.log(parentLink);
    getImgInfo(parentLink);
  });
}
// 获取链接页面详细信息
function getImgInfo(parentLink) {
  // console.log(parentLink);
  parentLink.forEach((element) => {
    let res = axios.get(element.url);
    console.log(res);
    $ = cheerio.load(res);
    $(".pic-content img").each(async (i, item) => {
      console.log($(item).attr("src"));
      let imgUrl = $(item).attr("src");
      let extname = path.extname(imgUrl);
      let ws = fs.createWriteStream(
        `./img/${item.title}/${item.title}-${i}${extname}`
      );
      await dlWait(50 * i);
      // 获取到详细信息之后写入图片
      sxios.get(
        imgUrl,
        { responseType: "stream" }.then((res) => {
          res.data.pipe(ws);
          res.data.on("close", (_) => {
            ws.close();
          });
        })
      );
    });
  });
}
// 将延迟函数封装成promise对象
function dlWait(timeout) {
  return new Promise((resolve, rejects) => {
    setTimeout(() => {
      resolve(`成功执行延迟函数${timeout}`);
      console.log(111);
    }, timeout);
  });
}

20.反爬虫策略

当网站监测到是爬虫的时候,会封掉爬虫的ip,这时候我们还想爬,则需要进行ip代理进行爬取数据,代码如下

// 使用axios代理发送请求
let options = {
    proxy: {
        host: '123.149.136.233', //
        port: 9999,
        auth: {
          username: 'mikeymike',
          password: 'rapunz3l'
        }
        // auth是验证信息 你只有登录的时候才可以爬
    },
}
axios.get(url, options).then( res => { 
    console.log(res.data);
})

21.爬取音乐(直接请求接口分析返回的数据即可)

上面爬取的是后端直接返回的html,我们用cherrio直接解析即可,但是现在都是前端来写页面,页面中基本上不会直接返回要爬取的信息,现在都是通过调用的服务返回数据,前端进行解析。

const axios = require("axios"),
  fs = require("fs"),
  path = require("path");
let url = "http://www.app-echo.com/api/recommend/sound-day?page=";
function makeDir() {
  fs.mkdir(path.resolve(__dirname, "mp3"), (err) => {
    if (err) {
      console.log(err);
    } else {
      console.log("创建目录成功");
    }
  });
}
async function getPage(num) {
  let res = await axios.get(url + num);
  console.log(res.data);
  let _data = (res && res.data && res.data.list) || [],
    musicList = [];
  _data.forEach((item) => {
    if (item.sound && item.sound.source) {
      let title = item.sound.name,
        filename = path.parse(item.sound.source).name,
        content = `${title}, ${item.sound.source}, ${filename}\n`;
      fs.writeFile(
        path.resolve(__dirname, "music.txt"),
        content,
        { encoding: "utf-8", flag: "a" },
        (err) => {
          if (err) {
            console.log(err);
          } else {
            // console.log("音乐名称写入成功");
          }
        }
      );
      musicList.push({ url: item.sound.source, name: filename });
    }
  });
  console.log(musicList);
  downMusic(musicList);
}
function downMusic(musicList) {
  musicList.forEach(async (item) => {
    let ws = fs.createWriteStream(
      path.resolve(__dirname, "mp3/", item.name + ".mp3")
    );
    console.log(path.resolve(__dirname, "mp3/", item.name + ".mp3"));
    let res = await axios.get(item.url, { responseType: "stream" });
    // return;
    res.data.pipe(ws);
    // 监听读取流关闭的时候写入流关闭
    res.data.on("close", function () {
      ws.close();
    });
  });
}
function init() {
  makeDir();
  getPage(1);
}
init();

22.puppeteer(无头浏览器模块-操作Google浏览器)

出现的背景:chrome59(linux,macos)、chrome(windows)之后,Chrome自带headless(无界面)模式很方便做自动化测试或者爬虫,但是如何和puppeteer模式的chrome交互是一个问题,通过启动chrome时的命令行参数仅能实现建议的启动时初始化操作。Selenium、Webdriver等是一种解决方案,但是往往依赖众多,不够扁平。

puppeteer是谷歌官方出品的一个通过DevTools协议控制的headless Chrome的Node库,可以通过puppeteer的提供的api直接控制Chrome模拟大部分用户操作来进行UI Test或者作为爬虫访问页面来收集数据。

作用:通过代码实现打开浏览器等操作,通过代码控制整个chrome浏览器。

生成页面PDF
抓取SPA(单页应用)并生成预渲染内容(即"SSR"(服务器端渲染))   
自动提交表单,进行UI测试,键盘输入等 

使用:

// 首先 npm i pupeteer -S
let puppeteer = require("puppeteer");
async function test(params) {
  // puppeteer.launch实例开启浏览器
  // 可以传入一个options对象,可以配置为有界面浏览器,同样可以配置为无界面浏览器
  // 无界面浏览器性能更高更快、有界面的一般用于前期调试,发布之后使用无界面的高效快速
  let options = {
    // 设置浏览器打开视图的宽高
    defaultViewport: {
      width: 1400,
      height: 800,
    },
    // 打开有界面浏览器
    headless: false,
  };
  // 连接浏览器
  let browser = await puppeteer.launch(options);
  let page = await browser.newPage();
  // 打开浏览器
  await page.goto("http://www.baidu.com");
  // 生成图片 路径是当前路径
  await page.screenshot({ path: "baidu.png" });
  // 获取页面内容 获取到querySelectAll所有的元素
  page.$$eval(".imgitem", (elements) => {
    elements.forEach((item) => {
      console.log(item.innerHTML);
    });
  });
  page.on("console", (...args) => {
    console.log(args);
  });
}
test(); // 执行之后 会打开Google浏览器打开百度页面

23.puppeteer获取和操作页面数据

let puppeteer = require("puppeteer");
let fs = require("fs");
let axios = require("axios");
const { rejects } = require("assert");
async function test(params) {
  // puppeteer.launch实例开启浏览器
  // 可以传入一个options对象,可以配置为有界面浏览器,同样可以配置为无界面浏览器
  // 无界面浏览器性能更高更快、有界面的一般用于前期调试,发布之后使用无界面的高效快速
  let options = {
    // 设置浏览器打开视图的宽高
    defaultViewport: {
      width: 1920,
      height: 1080,
    },
    // 打开有界面浏览器
    headless: false,
  };
  // 连接浏览器
  let browser = await puppeteer.launch(options);
  let page = await browser.newPage(); // 备注 page.xx返回的都是promise
  // 打开浏览器
  await page.goto(
    "https://image.baidu.com/search/index?tn=baiduimage&ct=201326592&lm=-1&cl=2&ie=gb18030&word=%B4%F3%D0%D8%D0%D4%B8%D0%C3%C3%D7%D
3&fr=ala&ala=1&alatpl=adress&pos=0&hs=2&xthttps=111111"
  );
  // 截图 生成图片 路径是当前路径的同级路径
  await page.screenshot({ path: "baidu.png" });
  // 通过点击页面跳转的方式
  let elementHandles = await page.$$(".imgitem");
  console.log(elementHandles);
  // 这里的2指的是第三个元素 元素下标是从0开始的
  elementHandles[2].click();
  // 如果是电影网站我们需要代码去操作输入框并写入搜索内容去点击搜索
  let inputSearch = page.$(".search .formhue");
  (await inputSearch).focus();
  // 往输入框输入内容
  page.keyboard.type("孙悟空");
  // 点击按钮回车
  let btnElem = page.$(".search .btn");
  (await btnElem).click();
  // 因为这种电影网站一般最外层包裹着一个a标签会有默认事件 阻止默认事件
  await page.$(".searcher", (item) => {
    item.addEvenetListener("click", (event) => {
      event.stopProperation();
    });
  });
  return;
  // 获取页面内容 获取到querySelectAll所有的元素
  // page.$$eval获取到的是真是的元素信息
  let myElements = await page.$$eval(".imgitem", (elements) => {
    let eles = [];
    elements.forEach(async (item, index) => {
      console.log(item.getAttribute("data-objurl"));
      let eleObj = {
        href: item.getAttribute("data-objurl"),
        name: item.getAttribute("data-title"),
        index: index,
      };
      eles.push(eleObj);
    });
    return eles;
  });
  console.log(myElements);
  // 重新打开一个新页面
  let pageOn = await browser.newPage();
  pageOn.goto(myElements[2].href);
  page.on("console", (eventMsg) => {
    console.log(eventMsg);
  });
}
test(); // 执行之后 会打开Google浏览器打开百度页面

24.获取所有电子书的详细信息

const { rejects } = require("assert");
const { resolve } = require("path");
// 目标获取https://sobooks.cc/,所有书名和电子书链接
// 进入网站,获取整个网站所有页数
// 获取列表页的所有链接
// 进入每个点子书的详情页获取下载电子书的网盘地址
// 将获取的数据保存到book.txt文档中
const puppeteer = require("puppeteer"),
  fs = require('fs');
let httpUrl = "https://sobooks.cc/page/";
async function openBrowser() {
  let browser = await puppeteer.launch({
    headless: false,
    defaultViewport: { height: 1080, width: 1920 },
  });
  let page = await browser.newPage();
  // 截取谷歌请求
  await page.setRequestInterception(true);
  page.on("request", interception => {
    let urlObj = url.parse(interception.url());
    if (urlObj.hostname == "googleads.g.doubleclick.net") {
      // 如果是谷歌的广告请求,放弃当次请求,谷歌广告请求慢
      interception.abort();
    } else {
      interception.continue();
    }
  })
  page.goto(`${httpUrl}1`);
  // 获取总页数
  let allPageNum = await page.$eval(
    ".pagination li:last-child span",
    (element) => {
      console.log(element);
      let reg = /\d+/gi;
      // 返回总页数
      return reg.match(element.innerText);
    }
  );
  // 当page.操作结束后返回关闭close
  page.close();
  for (let i = 1; i < array.length; i++) {
    await delay(4000 * i);
    getPageInfo(i);
  }
}
// 获取每页所有电子书的链接和名称 hrefList
async function getPageInfo(num) {
  let page = await browser.newPage();
  await page.setRequestInterception(true);
  page.on("request", interception => {
    let urlObj = url.parse(interception.url());
    if (urlObj.hostname == "googleads.g.doubleclick.net") {
      // 如果是谷歌的广告请求,放弃当次请求,谷歌广告请求慢
      interception.abort();
    } else {
      interception.continue();
    }
  })
  await page.goto(`${httpUrl}${num}`);
  let hrefList = await page.$$eval(
    ".card .card-item .thumn-img>a",
    (elements) => {
      let arr = [];
      elements.forEach((item, i) => {
        let elemObj = {
          href: item.getAttribute("href"),
          title: item.getAttribute("title"),
        };
        arr.push(elemObj);
      });
      return arr;
    }
  );
  page.close();
  console.log(hrefList);
  getBookInfo(hrefList)
}
async function getBookInfo (bookList) {
  // 定义book地址列表
  let bookAdressList = [];
  bookList.forEach(item => {
    await delay(4000 * i);
    let page = await browser.newPage();
    await page.setRequestInterception(true);
    page.on("request", interception => {
    let urlObj = url.parse(interception.url());
    if (urlObj.hostname.indexOf("google") != -1) {
      // 如果是谷歌的广告请求,放弃当次请求,谷歌广告请求慢
      interception.abort();
    } else {
      interception.continue();
    }
  })
    await page.goto(item.href);
    page.$eval(".e-secret a:last-child", elem => {
      bookAdressList.push({
        bookName: item.title,
        href: `${elem.getAttribute("href")}\n`
      })
    })
  })
  page.close();
  // 持久化书名
  bookAdressList.length && bookAdressList.forEach(item => {
    fs.writeFile("./book.txt", `${item.name} ===> ${item.href}`, { encoding: "utf-8", flag: "a" }, err => {
      if (err) {
        console.log(err);
      } else {
        console.log("写入成功");
      }
    })
  })
}
function delay (timeout) {
  return new Promise((resolve, rejects) => {
    setTimeout(() => {
      resolve(`延迟时间为${timeout}`);
    }, timeout);
  })
}
openBrowser();

25.爬虫总结

  • 爬虫介绍

通过模拟浏览器的请求,服务器就会根据我们的请求返回我们想要的数据,将数据解析出来,并且进行保存。

  • 爬虫流程     
目标∶确定你想要获取的数据
1.确定想要的数据在什么页面上〔—般详细的数据会在详情页)
⒉确定在哪些页面可以链接到这些页面(一般分类列表页面会有详情页的链接数据)
3.寻找页面之间和数据之间的规律
分析页面
1.获取数据的方式(正则,cherrio )
2.分析数据是通过ajax请球的数据,还是html里自带的数据
3.如果是通过AJAX请求的数据,那么需要获取ajax请求的链接,一般请求到的数据都为]SON格式数据,那么就
会比较容易解析。
4.如何数据在HTML里面,那么就用cherrio通过选挥器将内容选中
编写单个数据获取的案例
1.解析出分类页的链接地址
⒉解析出列表页的链接地址
3.解析出详情页的链接地址
4.解析详情页里面想要获取的数据
5.将数据进行保存到本地或者是数据库
如果遇到阻碍进行反爬虫对抗
1.User-Agent是否是正常浏览器的信息
2将请求头设置成跟浏览器一样的内容
3.因为爬虫的爬取速度过快,会导致封号。1那么可以降低速度进行解决,2可以使用代理进行解决
4.如果设置需要凭证,那么可以采用无界浏览器真实模拟。
  • 爬虫请求需要的库

// request,axios:通过库,帮助我们快速实现HTTP请求包的打包
request.get('请求地址', {
'请求头字段':'请求头的value值'
}, (res)=>{处理返回的内容});
// axios优势会更明显,前后端通杀,前后端调用的方式一致。
axios.get('请求地址',参数对象).then(function (response) {
  console.log (response);
}
axios获取图片
axios({
  method : 'get' ,
  url : 'http://bit.ly/2mTM3nY',
  responseType : 'stream'
})
.then(function(response) {
  response.data.pipe(fs.createwritestream( 'ada_lovelace.jpg'))
心
});
选择语言
puppeteer:完全模拟浏览器
// 打开浏览器
let options = {
  headless: true,//是否是无界面浏览器
  s1owMo: 250,//调试时可以减慢操作速度
  defaultviewport: {
    width: 1200,//设置视窗的宽高
    height: 800
  },
  timeout: 3000,
}
//默认超时3秒
let browser = await puppeteer.1aunch(options);
// 打开新标签页
let page = await browser.newPage();
// 获取所有浏览器中的页面
let pages = await browser.pages();
// 关闭浏览器
browser.close();
将页面跳转至
await page.goto(ur1)
获取页面的对象,并进行操作
let btn = await page. $(selector)
let input = await page.$(selector)
//点击按钮
btn.click()
// 聚焦到输入框
input.forcus()
// 在页面上写入内容或者键盘按键
await page.keyboard.type( 'He1lo wor1d!');
await page. keyboard.press ('ArrowLeft');
await page.keyboard.down ('shift');
// 设置鼠标的移动
await page.mouse.move(0,0);
await page.mouse.down();
await page.mouse.move(0,100);
await page.mouse.move(100,100);
await page. mouse.move(100,0);
await page.mouse.move(0,0);
await page.mouse.up();
// 截获页面请求
await page.setRequestInterception(true);
page.on("request", (request) => {
  request.ur1(); //可以获取请求的网址,request,包含了所有的请求信息
  if (你想要的条件) {
    request.continue();
  } else {
    request.abort([errorCode]);
  }
});
// 获取浏览器的信息和内容;
page.$eva1(selector, (item) => {
  return item;
});
心;
page.$eval(selectors, (items) => {
  return items;
});

26.协议及协议栈

// 什么是协议
协议是网络中计算机与设备之间进行通信的一系列规则的集合,常用协议有IP、TCP、HTTP、POP3、SMTP等
// 什么是协议栈
在网络中,为了完成通信,必须使用多层上的多种协议,这些协议按照层次顺序组合在一起,构成了协议栈
(Protocol Stack),也称为协议族(Protocol Suite)
// 协议作用
一个网络协议的作用主要有两个:建立简历对等层之间的虚拟通信;二是实现层次之间的无关性
// 层次间的无关性
所谓层次间的无关性,就是指较高层次和相邻的相低层次进行通信时,只是利用较低层次的接口和服务,而不需要
了解底层实现该功能所需的算法和协议的细节;较低层次也只仅是使用从高层系统传送过来的参数和控制信息,这
也就是层次间的无关性
// 网络协议族、栈组成
网络通信协议的作用是负责在网络上建立通信通道和控制通过通道信息流的规则,为了进行网络通信,通信双方必
须遵守通信协议。

node.js 史上最详细 (博主持续更新)_第9张图片

传输过程小栗子:

// 发送"想你"的微信消息给老婆
应用层: 我和老婆的电脑建立Http连接,之后进入传输层
传输层:将"想你"进行传输层协议封装并进行TCP方式或者UDP方式的传输,之后进入网络层
网络层:将"想你"信息进行网络位置的定位,找到老婆微信的网络地址,之后进入数据链路层
数据链路层:既然知道老婆微信网络地址之后就得选择送的方式,是小汽车还是飞机还是自行车呢,之后进入物理
层
物理层:选择送货的方式。小汽车呢还是飞机呢还是自行车呢,之后会送到老婆的手中。
经历了每层的传输每层不同的协议会进行封装"你好"这个消息。

老婆接收到消息之后这个消息是进行了这五层协议的"加密",需要分别对照物理层、数据链路层、网络层、传输
层、应用层不同的协议进行"解密",之后就可以读到"想你"的消息了

27.协议详细介绍

  • TCP/IP协议

TCP/IP协议是分层协议,从底层到应用层分别是照物理层、数据链路层、网络层、传输层、应用层,数据是层层封装,封装的方式一般都是在原有数据的前面加一个数据控制头。

IPV4 --- 255:255:255:255 

IPV6 --- :X:X:X:X:X:X:X:X,其中每个X表示地址中的16b,以十六进制表示,IPv6的地址长度为128位

  • Telnet协议(远程登录协议)

Telnet协议是TCP/IP协议中的一种应用协议,可以为终端仿真提供支持,可使用户连接到主机上,使主机响应起来就像它直接连接在终端上一样,Telnet协在发送端口和接受端使用TCP的23端口以进行专用的通信。

  • FTP协议

FTP协议使用TCP20号和21号端口, 20号端口用于数据交换, 21号端口用于建立连接,允许目录和文件访问,上传下载,不能远程执行文件。
TFTP是简单文件传输协议( Trivial File Transfer Protocol, TFTP ) , TFTP是无连接的,使用UDP的69号端口,用于当数据传输错误无关紧要而且无须安全性时的小型文件的传输。

  • SMTP协议(qq)

SMTP是简单邮件传输协议( Simple Mail Transfer Protocol , SMTP )是为网络系统间的电子邮件交换而设计的。使用25端口。SMTP只需要在接收端的一个电子邮件地址即可发送邮件。POP3 协议用来接收邮件.使用110端口。

  • DNS协议

DNS是域名解析服务( Domain Name Service, DNS ) , 作用是将域名转换为IP地址,或将IP地址转换为域名,用于解析完全合格域名( FQDN)。使用53号端口。

  • DHCP协议(动态地址获取设置)

DHCP是动态主机配置协议(DHCP) ,服务器可以提供的信息有:

1. IP地址
2、子网掩码(subnet mask)

3、域名(domain name)

4、默认网关(default gatway)

5、DNS

28.HTTP协议详解

HTTP协议是Hyper Text Transfer Protocol (超文本传输协议)的缩写,是用于从万维网( WWW:World Wide Web)服务器传输超文本到本地浏览器的传送协议。
HTTP是一个基于TCR/IP通信协议来传递数据( HTML文件,图片文件,查询结果等)。
HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。目前在WWW中使用的是HTTP/1.0的第六版
HTTP/1.1的规范化工作正在进行之中,而且HTTP-NG(Next Generation of HTTP)的建议已经提出。
HTTP协议工作于客户端服务端架构为上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。

  • HTTP协议的主要特点

1、简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST.每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
2、灵活: HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
3.、无连接:无连接的含义是限制每次连接只处理一个请求。 服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
4、无状态: HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另-方面,在服务器不需要先前信息时它的应答就较快。
5、支持B/S及C/S模式。
BS:browser浏览器- server服务器,举例浏览器看抖音
CS:client客户端server服务器,举例app看抖音

  • HTTP协议与URL的关系

HTTP使用统一资源标识符 ( Uniform Resource ldentifiers, URI )来传输数据和建立连接。URL是一种特殊类型的URI,包含了用于查找某个资源的足够的信息。
URL,全称是UniformResourceLocator,中文叫统一资源定位符,是 互联网上用来标识某一处资源的地址。 以下面这个URL为例,介绍下普通URL的各部分组成。
http://www.xxxxx.com:8080/news/index.asp?boardID=5&ID=24618&page=1#name 

组成部分详解:

1.协议部分:该URL的协议部分为"http" ,这代表网页使用的是HTTP协议。在Internet中可以使用多种协议,如HTTP , FTP等等本例中使用的是HTTP协议。在"HTTP"后面的//"为分隔符。
2.域名部分:该URL的域名部分为"www.xxxxx.com"。一个URL中,也可以使用IP地址作为域名使用。
3.端口部分:跟在域名后面的是端口,域名和端口之间使用":”作为分隔符。端口不是一个URL必须的部分,如果省略端口部分,将采用默认端口。
4.虚拟目录部分:从域名后的第一个"/"开始到最后-个"/”为止 ,是虚拟目录部分。虛拟目录也不是一个URL必须的部分。本例中的虚拟目录是"/news/"。
5.文件名部分:从域名后的最后一个”/”开始到“ ?"为止,是文件名部分,如果没有"?”,则是从域名后的最后一个"'开始到"#"为止,文件部分,如果没有" ? "和"#”,那么从域名后的最后一个"/"开始到结束 ,都是文件名部分。本例中的文件名是"index.asp"。文件名部分也不是URL必须的部分,如果省略该部分,则使用默认的文件名。
6.锚部分:从"# "开始到最后,都是锚部分。 本例中的锚部分是"name"。锚部分也不是一个URL必须的部分。
7.参数部分:从“? "开始到"#"为止之间的部分为参数部分,又称搜索部分、查询部分。本例中的参数部分为"boardID=5&lD=24618&page=1"。参数可以允许有多个参数,参数与参数之间用“&"作为分隔符。

  • URI和URL的区别

URI 是uniform resource identifier .统一资源标识符,用来唯一的标识一个资源。
Web上可用的每种资源如HTML文档.图像。视频片段。程序等都是一个来URI来定位的
URI一般由三部组成:
①访问资源的命名机制
②存放资源的主机名
③资源自身的名称.由路径表示,着重强调于资源。
URL是uniform resource locator.统一资源定位器.它是一种具体的URI .即URL可以用来标识一个资源,而且还指明了如何locate这个资源。
URL是Internet上用来描述信息资源的字符串,主要用在各种WWW客户程序和服务器程序上,特别是著名的Mosaic.
采用URL可以用一种统一的格式来描述各种信息资源, 包括文件。服务器的地址和目录等。URL一般由三部组成:

①协议(或称为服务方式)
②存有该资源的主机UP地址(有时也包括端口号)
③主机资源的具体地址。如目录和文件名等
URN  uniform resource name .统一资源命名是通过名字来标识资源.比如malilto;java-netejava.sun.com.
URI是以一种抽象的,高层次概念定义统一资源标识,而URL和URN则是具体的资源标识的方式。URL和URN都是一种URI,笼统地说每个URL都是URI,但不一定每个URI都是URL,这是因为URI还包括一个子类,即统一资源名称(URN),它命名资源但不招定如何定位资源。上面的mailto. news 和isbn URI都是URN的示例。
在Java的URI中一个URI实例可以代表绝对的,也可以是相对的,只要它符合URI的语法规则。而URL类则不仅符合语义,还包含了定位该资源的信息,因此它不能是相对的。
在Java类库中,URI类不包含任何访问资源的方法.它唯一的作用就是解析。
相反的是URL类可以打开一个到达资源的流。

  • Request请求
TCP/IP协议(下面是HTTP协议)
d目标ip及端口: 180.97.93.62:443(如果是https协议端口为443,http默认为80)
s来源谁访问的:192.168.99.190:9090
HTTP中都是用文本形式来传输,这个文本里面有两部分组成,一部分是请求头一部分是请求体
HTTP协议
请求头(放置请求相关的信息)
请求体(一般都是POST请求表单传输的数据)
格式:
请求行 ---> 请求方法 空格 URL 空格 协议版本 换行符
请求头部 ---> 请求头部字段名 : 值 回车符 换行符
空一行 ---> 回车符 换行符 下面是
请求数据
username=admin&password=123456
下面是一个请求头的例子
GET /api/usercenter/login?msg=1&_=1601900250637 HTTP/1.1
Host: baike.baidu.com
Connection: keep-alive
Accept: application/json, text/javascript, */*; q=0.01
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) 
Chrome/83.0.4103.116 Safari/537.36 (判断是谁给我发的请求)
X-Requested-With: XMLHttpRequest
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://baike.baidu.com/item/%E9%9B%B7%E5%86%9B/1968?fr=aladdin
Accept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9
Cookie: BIDUPSID=E25A008832AF7E1DE6D0D21669BD7BA6; PSTM=1594549670; 
BAIDUID=E25A008832AF7E1D2109678E213B407B:FG=1; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598;
(登录验证) H_PS_PSSID=32820_32617_1442_32735_31660_32795_32723_32231_7516_7605_32116_32719_26350; 
Hm_lvt_55b574651fcae74b0a9f1cf9c8d7c93a=1601714765,1601900243,1601900250; 
  • 相应头
第一部分:状态行,由HTTP协议版本号,状态码,状态消息三部分組成
第一行为状态行,(HTTP/1.1 )表明HTTP版本カ1.1版本,状态码为200,状志消息为(ok)
第二部分:报头消息,用来说明客户端要使用的一些附加信息
第二行和第三行为消息报头
Date为生成响应的日期和事件; Conten-Type:指定了 MIME炎型的HTML(text/html),编码类型是UTF-8
第三部分:空行,消息后面的空行是必须的
第四部分:响应正文,服务器返回姶客戸端的文本信息.
空行后面的html部分为响应正文
  • HTTP协议之状态码

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

1xx :指示信息--表示请求已接收,继续处理
2xx :成功--表示请求已被成功接收、理解、接受
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 //服务器当前不能处理客户端的请求, -段时间后可能恢复正常
  • HTTP请求方法
根据HTTP标准, HTTP请求可以使用多种请求方法。
HTTP1.0定义了三种请求方法: GET, POST和HEAD方法。
HTTP1.1新增了五种请求方法: OPTIONS, PUT, DELETE, TRACE和CONNECT方法。
GET请求指定的页面信息 ,并返回实体主体。
HEAD类似于get请求 ,只不过返回的响应中没有具体的内容,于获取报头
POST向指定 资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会
导致新的资源的建立和/或已有资源的修改。
PUT从客户端向服务 器传送的数据取代指定的文档的内容。
DELETE请求服务器删除指定的页面。
CONNECT HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。
OPTIONS允许客户端查看 服务器的性能。
TRACE回显服务 器收到的请求,主要用于测试或诊断。、
HTTP工作原理 HTTP协议定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。HTTP协议采用 了请求/响应模型。客户端向服务器发送一个请 求报文,请求报文包含请求的方法、URL、协议版本、请求头部和 请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头 部和响应数据。
  •  浏览器输入URL地址到渲染的整个过程
以下是HTTP请求/响应的步骤:
1、客户端连接到Web服务器
一个HTTP喀户端,通常是浏览器,与Web服务器的HTTP端口(默认为80 )建立一个TCP套接字连接。例如,http://www.oakcms.cn。
2、发送HTTP请求
通过TCP套接字,客户端向Web服务器发送一个文本的请 求报文, -个请求报文由请求行、请求头部、空行和请求数
据4部分组成。
3、服务器接受请求并返回HTTP响应
Web服务器解析请求,定位请求资源。分析请求链接,分析凭证,分析请求的客户端,请求数据库获取相对应的信息,
最终按照HTTP协议进行响应。服务器将资源复本写到TCP套接字,由客户端读取。一个响应由状态行、响应头部、
空行和响应数据4部分组成。
4、释放连接ICP连接
若connection模式为close ,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection模式
为keepalive ,则该连接会保持一段时间,在该时间内可以继续接收请求;
5.客户端浏览器解析HTML内容
客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每-个响应头,响应头告知以下为若千
字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML ,根据HTML的语法对其进行格式化,并在浏览器
窗口中显示。

 简化版本:

例如:在浏览器地址栏键入URL,按下回车之后会经历以下流程:1、浏览器向DNS服务器请求解析该URL中的域名所对应的IP地址;
2、解析出IP地址后,根据该IP地址和默认端口80 ,和服务器建立ICP连接;
3、浏览器发出读取文件(URL中域名后面部分对应的文件)的HTTP请求,该请求报文作为ICP三次握手的第三个报文的数据发送给服务器;
4、服务器对浏览器请求作出响应,并把对应的html文本发送给浏览器;
5、释放TCP连接;
6、浏览器将该html文本并显示内容,渲染DOM树,请求新链接;

29.搭建简单的服务器(http模块)

由于http是node内置模块所以不需要引入

const http = require("http");
let server = http.createServer();
server.on("request", (req, res) => {
  // 设置响应文本格式
  res.setHeader("Content-type", "text/html;charset=UTF-8");
  console.log(req);
  console.log(req.url);
  let _url = req.url;
  if (_url == "/") {
    res.end("

这是首页

"); } else if (_url == "/gnxw") { res.end("

国内新闻

"); } else if (_url == "/gwxw") { res.end("

国外新闻

"); } else { res.end("

404!页面找不到!

"); } // 输出请求头 console.log(req.headers); // 下面的_headers是输出的headers let _headers = { host: "127.0.0.1:9090", connection: "keep-alive", pragma: "no-cache", "cache-control": "no-cache", "user-agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36", accept: "image/webp,image/apng,image/*,*/*;q=0.8", "sec-fetch-site": "same-origin", "sec-fetch-mode": "no-cors", "sec-fetch-dest": "image", referer: "http://127.0.0.1:9090/", "accept-encoding": "gzip, deflate, br", "accept-language": "zh-CN,zh;q=0.9", }; }); server.listen(9090, (_) => { console.log("端口为9090"); });

30. 封装自己的Promise(promise原理)

class daPromise {
  constructor(fn) {
    // 将成功的事件函数集成在successList数组里
    this.successList = [];
    // 将失败的事件函数集成在failList数组里
    this.failList = [];
    // 初始化promise状态 pending fullfilled rejected
    this.state = "pending";
    // 传入的函数对象(异步操作的函数内容
    fn(this.resolveFn.bind(this), this.rejectFn.bind(this));
  }
  then(successFn, failFn) {
    if (typeof successFn == "function") {
      this.successList.push(successFn);
    }
    if (typeof failFn == "function") {
      this.successList.push(failFn);
    }
  }
  catch(failFn) {
    if (typeof failFn == "function") {
      this.successList.push(failFn);
    }
  }
  resolveFn(res) {
    this.state = "fullfilled";
    // 注册到的所有成功事件进行调用
    this.successList.forEach((item) => {
      item(res);
    });
  }
  rejectFn(res) {
    this.state = "rejected";
    // 注册到的所有失败事件进行调用
    this.failList.forEach((item) => {
      item(res);
    });
  }
}

   31.上面的服务器封装优化

  • 首先我们需要明确优化的最终目标,如下
// 封装的最终目标
let app = new dlApp();
app.on("/", (req, res) => {
  res.end("这是首页");
});
app.run(80, (_) => {
  console.log("服务启动了,端口为80,地址为127.0.0.1");
});
  • 实现,输入"/"时显示首页,输入"/gnxw"时是国内新闻,输入"/gwxw"时是国外新闻。
let http = require("http"),
  path = require("path"),
  url = require("url"),
  fs = require("fs");
class dlApp {
  constructor() {
    // 定义用来注册的接口对象
    this.reqEvent = {};
    //
    this.server = http.createServer();
    this.server.on("request", (req, res) => {
      let pathObj = path.parse(req.url);
      console.log(pathObj);
      // 判断接口地址中有没有这个url地址
      if (this.reqEvent.hasOwnProperty(pathObj.dir)) {
        res.setHeader("Content-type", "text/html;charset=UTF-8");
        this.reqEvent[pathObj.dir](req, res);
      } else if (pathObj.dir == "/static") {
        res.setHeader("Content-type", this.getContentType(pathObj.ext));
        let rs = fs.createReadStream("./static/" + pathObj.base);
        rs.pipe(res);
      } else {
        res.setHeader("Content-type", "text/html;charset=UTF-8");
        res.end("

404!页面找不到!

"); } }); } // 注册接口 on(url, fn) { this.reqEvent[url] = fn; } // 启动服务 run(port, callback) { this.server.listen(port, callback); } // 定义响应返回的类型 getContentType(extname) { switch (extname) { case ".jpg": return "image/jpeg"; case ".html": return "text/html;charset=utf-8"; case ".js": return "text/jacascript;charset=utf-8"; case ".json": return "text/json;charset=utf-8"; case ".gif": return "image/gif"; case ".css": return "text/css"; default: break; } } } // 封装的最终目标 let app = new dlApp(); app.on("/", (req, res) => { res.end("这是首页"); }); app.on("/gnxw", (req, res) => { res.end("这是国内新闻"); }); app.on("/gnxw/index.html", (req, res) => { res.end("这是国内新闻首页"); }); app.on("/gwxw", (req, res) => { res.end("这是国外新闻"); }); app.on("/gwxw/index.html", (req, res) => { res.end("这是国外新闻首页"); }); app.run(80, (_) => { console.log("服务启动了,端口为80,地址为127.0.0.1"); });

 32.实现动态渲染

33.NPM上传包

1.创建文件夹

2.npm包初始化,填写相应的信息,在文件夹中写入一个文件

npm init 

 

{
  "name": "dlfs",
  "version": "0.0.1",
  "description": "原生封装fsPromise",
  "main": "dlfs.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "promise"
  ],
  "author": "dalei",
  "license": "ISC"
}

3.注册npm官网用户,必须要验证邮箱

4.打开cmd切换到文件夹进行npm login进行登录

5.进行文件的上传npm publish 即可

  • 中途遇到的问题
npm ERR! publish Failed PUT 403
npm ERR! code E403
npm ERR! [no_perms] Private mode enable, only admin can publish this module [no_perms] Private mode enable, only admin can publish this module: your-package
使用
npm config set registry http://registry.npmjs.org

34.服务器部署到公网上

A类地址:10.0.0.0~10.255.255.255
B类地址:172.16.0.0 ~172.31.255.255
C类地址:192.168.0.0~192.168.255.255

流程:

1.注册花生壳
2.实名认证
3.免费获取域名和花生壳体验账号
4.进入花生壳设置平台
https ://b. oray. com/forward/
5.添加应用
应用名称
应用内网主机的IP地址
应用内网服务的端口号
6.下载花生壳软件
https ://hsk . oray. com/download/
7.安装并启动应用

35. Node操作数据库

  • node连接数据库
// 解决Node.js mysql客户端不支持认证协议引发的“ER_NOT_SUPPORTED_AUTH_MODE”问题
https://yq.aliyun.com/articles/705235
// 引入mysql模块
const mysql = require("mysql");
let options = {
  host: "localhost",
  // port: "3306", // 端口默认为3306 也可以设置其他的
  user: "root",
  password: "123456",
  database: "carrier", // 数据库名
};

let connection = mysql.createConnection(options);
connection.connect((err) => {
  if (err) {
    console.log(err);
    console.log("连接失败");
  } else {
    console.log("连接成功");
  }
});
connection.query("SELECT * from carrierinfo", (err, results, fields) => {
  if (err) throw err;
  console.log(results);
  console.log(fields);
});

32.数据库的范式与关系 

1.范式    

  • 第一范式-原子性: 表中的数据只能是原子性(不能再分的)的值(比如一个人有多个电话需要拆分)
  • 第二范式:必须满足第一范式以及没有部分依赖(比如名称和id依赖)
例如,员工表的一个候选键是{id , mobile , deptNo),而deptName依赖于deptNo,同样name依赖于id,因
此不是2NF的。为了满足第二范式的条件,需要将这个表拆分成employee、dept、employee dept.
employee_mobile四个表。
  • 第三范式: 必须满足第二范式及没有传递依赖(比如省份传递)                                                                                                     
例如,员工表的province、city、district依赖于zip,而zip依赖于id,换句话说,province、city、
district传递依赖于id,违反了3NF规则。为了满足第三范式的条件,可以将这个表拆分成employee和zip两个表

注意: 在关系数据库模型设计中,一般需要满足第三范式的要求。如果一个表具有良好的主外键设计,就应该是满足3NF
的表。规范化带来的好处是通过减少数据冗余提高更新数据的效率,同时保证数据完整性。然而,我们在实际应用
中也要防止过度规范化的问题。规范化程度越高,划分的表就越多,在查询数据时越有可能使用表连接操作。而如
果连接的表过多,会影响查询性能。关键的问题是要依据业务需求,仔细权衡数据查询和数据更新关系,指定最合
适的规范化程度。不要为了遵循严格的规范化规则而修改业务需求。

2.数据库关系

数据库实体间有三种对应关系:—对一、—对多、多对多
—对一关系示例:
一个学生对应一个学生档案材料每个人都有唯一的身份证号
—对多关系示例:
一个学生只属于一个班,但这个班有多名学生
多对多关系示例:
一个学生可以选择多门课,一门课也可以有多名学生
一个人可以有多个角色,一个角色可以有多个人

33.连表查询

  • 外键


 

外键的级联操作
在删除students表的数据时,如果这个id值在scores中已经存在,则会抛异常
推荐使用逻辑删除,还可以解决这个问题
可以创建表时指定级联操作,也可以在创建表后再修改外键的级联操作
语法
alter table scores add constraint stu_sco foreign key(stuid)references students(id) on
delete cascade;
级联操作的类型包括:
restrict(限制)∶默认值,抛异常
cascade(级联)︰如果主表的记录删掉,则从表中相关联的记录都将被删除
set null:将外键设置为空
no action:什么都不做
  • 连表查询(以上是一对多关系的实操,作者和书籍表,一个作者对应多个书籍)
连接查询分类如下:
表A inner join表B:表A与表B匹配的行会出现在结果中 
// a表中的每条数据都会去匹配b表中的数据然后组装起来
// 小栗子
select * from author inner join authorbook on author.id = authorbook.authorid where 
author.name = "吴承恩";



表A left join表B:表A与表B匹配的行会出现在结果中,外加表A中独有的数据,未对应的数据使用null填
充I
表Aright join表B∶表A与表B匹配的行会出现在结果中,外加表B中独有的数据,未对应的数据使用null
填充
在查询或条件中推荐使用"表名.列名"的语法
如果多个表中列名不重复可以省略“表名."部分
如果表的名称太长,可以在表名后面使用' as简写名'或'简写名',为表起个临时的简写名称
  • left join

node.js 史上最详细 (博主持续更新)_第10张图片

  • right join

node.js 史上最详细 (博主持续更新)_第11张图片                                      34.自关联查询和子查询

node.js 史上最详细 (博主持续更新)_第12张图片         

-- 查询所有学生的成绩 
-- 第一步INNER JOIN 融合score和学生表 并且筛选出学生id对应的score数据
-- 第二步INNER JOIN融合第一步查出来表并融合科目表 筛选出和科目id一致的score数据
select score.id, student.name, project.project, score.score FROM score INNER JOIN student 
on score.studentid = student.id 
INNER JOIN project on score.projectid = project.id;
  •  自关联查询(自己查询自己)

设计一张省市关联表

node.js 史上最详细 (博主持续更新)_第13张图片

-- 找出太原市里的所有省份
SELECT r1.id, r1.`name` as "省份"  from province_city as r1 
INNER JOIN province_city as r2 
on r1.id = r2.pid WHERE r1.`name` = "山西省";

1	山西省	0
2	太原市	1
3	大同市	1
4	娄烦县	2
5	古交市	1

                                                                            

注意:上下级关系包含,公司的层级关系,游戏里帮派层级关系

  • 子查询(用SELECT查出来的作为条件查询)
// 筛选出成绩中年龄小于20岁的人
SELECT * from score INNER JOIN student 
on score.stuid = student.id 
WHERE student.studentname in (select studentname from student WHERE studentage < 20 );
-- 如果有学生大于50岁才能查找出来
SELECT * FROM teacher WHERE EXISTS (SELECT studentname FROM student WHERE studentage > 50);

    不建议进行多级嵌套查询 不要给数据库太多压力 尽可能让程序多做一些数据的处理

35.mysql视图

  • 视图定义及使用
对于复杂的查询,在多次使用后,维护是一件非常麻烦的事情
解决方法∶定义视图
视图本质就是对查询的一个封装
定义视图:
create view stuscore as
select students.* , scores.score from scores
inner join students on scores.stuid=students.id;
执行完上面语句时会创建一个视图,该视图会随着其关联数据库的改变而发生改变
视图的用途就是查询
select * from stuscore;
  • UPDATE 更新数据库某条数据中的某个字段

MySQL UPDATE 更新
如果我们需要修改或更新MySQL 中的数据,我们可以使用SQL UPDATE命令来操作。
语法
以下是 UPDATE命令修改MySQL数据表数据的通用SQL语法∶
UPDATE table_name(表名) SET field1=new-value1,fie1d2=new-value2 WHERE Clause](where后面的是要操作的筛选出来的行)
选择语言
你可以同时更新一个或多个字段。
你可以在WHERE子句中指定任何条件。
你可以在一个单独表中同时更新数据。
当你需要更新数据表中指定行的数据时WHERE子句是非常有用的。
通过命令提示符更新数据
  • DELETE删除某个数据库中的某条数据
你可以使用SQL的 DELETE FROM命令来删除MySQL数据表中的记录。你可以在mysql>命令提示符或PHP脚本中执行该命令。
语法
以下是SQL DELETE语句从 MySQL数据表中删除数据的通用语法︰
DELETE FROM table_name [WHERE Clause](通过WHERE筛选出来的数据)
如果没有指定WHERE子句,MySQL表中的所有记录将被删除。(这句话特别重要)
选择语言
你可以在WHERE子句中指定任何条件
您可以在单个表中一次性删除记录。
当你想删除数据表中指定的记录时WHERE子句是非常有用的。
从命令行中删除数据
这里我们将在SQL DELETE 命令中使用WHERE子句来删除 MySQL数据表runoob_tbl所选的数据。

   我们一般都不会进行删除的操作,比如用户啊啥的,这是资源,我们可以使用逻辑删除,比如在数据库表中加入一个is_delete的字段来看改数据是否删除了

36. 事务锁

  • 概念
当一个业务逻辑需要多个sql完成时,如果其中某条sql语句出错,则希望整个操作都退回,·使用事务可以完成退
回的功能,保证业务逻辑的正确性
事务四大特性(简称ACID)
1 原子性(Atomicity):事务中的全部操作在数据库中是不可分割的,要么全部完成,要么均不执行。
2 一致性(Consistency):几个并行执行的事务,其执行结果必须与按某一顺序串行执行的结果相一致。
3 隔离性(Isolation):事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须是透明的
4 持久性(Durability):对于任意已提交事务,系统必须保证该事务对数据库的改变不被丢失,即使数据库出现故障
要求:表的类型必须是innodb或bdb类型,才可以对此表使用事务·查看表的创建语句
show create table students;
修改表的类型
alter table '表名' engine=innodb;
事务语句
开启begin; 开始事务 只写这个数据库表中的数据不会发生改变
提交commit; begin之后如果多条数据sql没错,数据库表才会发生变化,否则不会提交到数据库表中
回滚rollback; begin之后回滚的数据不讲存在

 

  • 示例

node.js 史上最详细 (博主持续更新)_第14张图片

  37.Express项目

  • 前言

ndoe.js,一个基于javsscript运行环境的服务器语言,它的出现使得javascript有能力去实现服务器操作。在
gitHub上ndoe.js的star数已接近6万,可见其受欢迎程度;而基于node.,js的Express则把原先的许多操作变的
简单灵活,一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。使用Express可以快速地搭建一个完整
功能的网站。
express官方网址: www.expressjs.com.cn
Express的安装方式
Express的安装可直接使用npm包管理器上的项目,在安装npm之前可先安装淘宝镜像∶
npm install -g cnpm --registry=https : / /registry.npm.taobao.org
这样我们使用cnpm的来代替npm,这使得下载速度提高很多;其次你需要在你项目目录下运行以下指令来初始化
npm,期间所有提示按enter键即可,这会生成package.json,它是用于描述项目文件的。
cnpm init
再输入
cnpm insta11
这下项目目录中又会多出一个叫node_modules文件夹,里面是node.js为我们提供的模块,当然现在没有。接下
来便是真正的安装express了,执行:
cnpm instal7 express --save
这时,我们看到node_modules文件夹多了许多不同版本的应用文件夹,接下来执行
express --version
  •   快速创建项目的三种方法

//服务器框架 express(多用) 升级版--> koa
// 安装
npm i express --save
npm i express -g --save
// 安装express脚手架 用来快速生成express服务器app
npm i express-generator  -g --save

// 查看命令行的指令含义
express -h
options :
--version // 输出版本号
-e,--ejs   // 添加对ejs模板引擎的支持
--pug     // 添加对pug模板引擎的支持
--hbs   // 添加对handlebars模板引擎的支持
-H,--hogan  // 添加对hogan.js模板引擎的支持
-v,--view // 添加对视图引擎(view) 的支持(ejs |hbs|hjsljadelpugItwig/vash)
(默认是 jade模板引擎)工
--no-view // 创建不带视图引擎的项目
-C,--css // 添加样式表引擎的支持(less |stylus |compass|sass)((默认是普通的css文件)
--git // 添加.gitignore
-f, --force // 强制在非空目录下创建
-h,--help //输出使用方法

之后
express --version 查看当前版本
报错: express: 无法加载文件XXX 说明是需要使用管理员身份去执行该命令

// 重点
// 创建一个myapp的Express应用,并使用ejs模板引擎 
// 第一种方法
express --view=ejs app
// 进入app 并安装依赖
cd myapp
npm install
在packjson中 配置 "local": "SET DEBUG=app:* & npm start"

// 第二种 创建一个easyexpress 之后npm i express
// 创建一个index.js文件 文件写入以下内容
let express = require("express");
let app = express();
app.get("/", function (req, res) {
  res.send("

hello world

"); }); app.listen(8080, (_) => { console.log("服务器启动完成", "http://127.0.0.1:8080"); }); // 之后node index.js // 第三种 在根目录中的终端直接输入 express project_name -e 之后安装npm i 安装依赖并启动项目即可

  38.Express路由

let express = require("express");
let app = express();
// 1. 字符串的路由模式
app.get("/", (req, res) => {
  res.end("这是首页");
});
// 2. 字符串模式路径 类字符串的模式
// 匹配abcd或者abd
app.get("/ab?cd", (req, res) => {
  res.end("ab?cd");
});
// 匹配abd或者abbb..d
app.get("/ab+d", (req, res) => {
  res.end("ab+d");
});
// 匹配abd或者abbb..d
app.get("/a(bc)?d", (req, res) => {
  res.end("a(bc)?d");
});
// 匹配axxxd ad等
app.get("/a*d", (req, res) => {
  res.end("a*d");
});
// 3. 正则表达式路径
// 此路由匹配其中带有a的任何内容
// /\/a\d{10}/
// 4. 动态路由使用 ":" 路由参数的名称必须是由"文字字符"[A-Za-z0-9]组成
// 查看用户下面的分类下面的用户id
app.get("/user/:cataoryid/a:userid/", (req, res) => {
  res.setHeader("charset", "utf-8");
  res.end(
    "用户id页面:  \n" +
      req.params.userid +
      "\n用户分类id获取\n" +
      req.params.cataoryid
  );
});
// 由于连字符()和点(.)是按字面解释的,因此可以将它们与路由参数一起使用,以实现有用的目的。
// Route path: /flights/:from-:to
// Request URL: http://1ocalhost:3000/f1ights/LAX-SFOreq params : { "from": "LAX","to" : 
"SFO"I
// Route path:/plantae/:genus.:species
// Request URL: http://loca7host:3000/plantae/Prunus.persica req.params:{"genus ":"Prunus 
","species":"persica" }
// 选择语言
// 要更好地控制可以由route参数匹配的确切字符串,可以在括号(()后面附加一个正则表达式﹐
// Route path:/user/:userId(\d+)
// Request URL:http://localhost :3000/user/42req.params:{"userId":"42"}
// 由于正则表达式通常是文字字符串的一部分,因此请确保\使用其他反斜杠对所有字符进行转义,例如\\d+。

// 路由处理程序
// 您可以提供行为类似于中间件的多个回调函数来处理请求。唯一的例外是这些回调可能会调用next( ' 
route ')以绕过其余的路由回调。您可以使用此机制在路由上施加先决条件,然后在没有理由继续使用当前路
由的情况下将控制权传递给后续路由。
// 路由处理程序可以采用函数,函数数组或二者组合的形式,如以下示例所示。单个回调函数可以处理路由。
例如︰
// app.get('/example/a',function(req,res){res.send('He7lo from A!')
// })
// 多个回调函数可以处理一条路由(确保指定了next对象)。例如∶
app.get(
  "/example/b",
  function (req, res, next) {
    // 第一个函数做一件事
    console.log("the response wil1 be sent by the next function ...");
    req.hostname = "127.0.0.1";
    next();
  },
  function (req, res, next) {
    // 第二个函数做第二件事
    res.send("req.hostname: \n" + req.params.hostname);
  }
);
// 回调函数数组可以处理路由。例如:

app.listen(8080, function () {
  console.log("服务启动了");
  console.log("127.0.0.1:8080");
});
module.exports = app;

  39. 路由应用从boss数据库表中获取数据

1.安装mysql和express 

2.使用express boss -e 创建一个express服务器

3.在根目录中创建一个dlMysql.js,用来连接company数据库,js文件内容如下

const mysql = require("mysql");
let options = {
  host: "localhost",
  user: "root",
  password: "123456",
  database: "company",
};
let connection = mysql.createConnection(options);
connection.connect((err) => {
  if (err) {
    console.log(err);
  } else {
    console.log("连接成功");
  }
});

function sqlQuery(sql, arr) {
  return new Promise((resolve, reject) => {
    connection.query(sql, arr, (err, results) => {
      if (err) {
        reject(err);
      } else {
        resolve(results);
      }
    });
  });
}
module.exports = sqlQuery;

  4.改写app.js

const express = require("express");
let app = express();
let sqlQuery = require("./dlMysql");

//  首页内容是boss表中的前两天数据
app.get("/", async (req, res) => {
  let strSql = "select * from boss limit 0,2";
  let queryResult = await sqlQuery(strSql);
  console.log(Array.from(queryResult));
  res.json(Array.from(queryResult));
});
// 获取到boss数据
app.get("/boss", async (req, res) => {
  // console.log(req);
  let strSql = "select bossname, age, sex from boss limit 0,2";
  let queryResult = await sqlQuery(strSql);
  console.log(Array.from(queryResult));
  // Array.from() 方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
  res.json(Array.from(queryResult));
});
app.get("/boss/:bossid", async (req, res) => {
  let strSql = "select * from boss where id = ?";
  let bossid = req.params.bossid;
  let result = await sqlQuery(strSql, [bossid]);
  res.json(Array.from(result));
});
app.listen(8080);
module.exports = app;

   5.访问http://127.0.0.1:8080/boss/1

node.js 史上最详细 (博主持续更新)_第15张图片 6.访问http://127.0.0.1:8080/

node.js 史上最详细 (博主持续更新)_第16张图片41.ejs引擎使用模板                                                                                     

1.使用express xx -e之后在views文件夹中存在index.ejs文件

2.改造app.js

// 连接数据库 写接口返回数据
const express = require("express");
const ejs = require("ejs");
let app = express();
let sqlQuery = require("./dlMysql");

// 将模板引擎与express关联起来
app.set("views", "views"); // 设置视图的对应目录views
app.set("view engine", "ejs"); // 设置默认的模板引擎
app.engine("ejs", ejs.__express); // 定义模板引擎

//  首页内容是boss表中的前两天数据
app.get("/", async (req, res) => {
  let strSql = "select * from boss limit 0,2";
  let queryResult = await sqlQuery(strSql);
  console.log(Array.from(queryResult));
  // res.json(Array.from(queryResult));
  res.render("index", { title: "dl首页" });
});
// 获取到boss数据
app.get("/boss", async (req, res) => {
  // console.log(req);
  let strSql = "select bossname, age, sex from boss limit 0,2";
  let queryResult = await sqlQuery(strSql);
  console.log(Array.from(queryResult));
  // Array.from() 方法从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
  res.json(Array.from(queryResult));
});
app.get("/boss/:bossid", async (req, res) => {
  let strSql = "select * from boss where id = ?";
  let bossid = req.params.bossid;
  let result = await sqlQuery(strSql, [bossid]);
  res.json(Array.from(result));
});
app.listen(8080);
module.exports = app;

 3.dlMysq.js文件是40的内容

4.访问http://127.0.0.1:8080/

node.js 史上最详细 (博主持续更新)_第17张图片 42.ejs渲染页面的三种语法

<% xxx %>:里面写入的是js语法,
<%= xxx %>:里面是服务端发送给ejs模板转义后的变量,输出为原htm1
<%- xxx %>:里面也是服务端发送给ejs模板后的变量,不过他会把htm1输出来
  • app.js文件

const express = require("express");
const ejs = require("ejs");
let app = express();
// 将模板引擎与express关联起来
app.set("views", "views"); // 设置视图的对应目录views
app.set("view engine", "ejs"); // 设置默认的模板引擎
app.engine("ejs", ejs.__express); // 定义模板引擎
// 下面介绍的是ejs模板的三种常见使用规则
app.get("/", async (req, res) => {
  // 插入变量方式
  let options = {
    title: "这是首页",
    articleTitle: "

相信自己创造未来

", }; res.render("index", options); }); app.get("/condition", async (req, res) => { // 条件方式 let options = { username: "小明", gender: "男", }; res.render("condition", options); }); app.get("/xh", async (req, res) => { // 循环操作 let options = { stars: ["蔡徐坤", "郭敬明", "吴亦凡", "鹿晗"], }; res.render("xh", options); }); module.exports = app;
  •    view文件夹下的index.ejs, tj.ejs, xh.ejs文件内容

index.ejs



  
    <%= title %>
    
  
  
    

<%= title %>

Welcome to <%= title %>

横杠的插入方式

<%-articleTitle%>

等号的插入方式

<%=articleTitle%>

     condition.ejs



  
    
    
    Document
  
  
    

性别

<%if(gender == "男") {%>

编程相关的内容

<%} else {%>

追星相关的内容

<%}%>

   xh.ejs



  
    
    
    Document
  
  
    

明星循环

111
<%for(var i = 0; i < stars.length; i++) {%>

<%-stars[i]%>

<%}%>

43.express ejs建站 

  • 主要注意的一点如下

只要是ejs模板引入css的时候第一步需要在public的css文件夹中将css文件写进来
在app.js文件中写入
//设置静态目录
app.use(express.static(path.join(__dirname, 'public ')));
在ejs文件中引入css的時候
直接使用绝对路径即可
/xx/xx.css

 44.express 处理get和post请求

  • 处理get请求

// form get 请求
app.get("/search", async (req, res) => {
  console.log(req.url);
  // 接受表单发送的请求并拆解成对象的方式输出
  let keyValueArr = req.url.split("&");
  let reqParams = {};
  keyValueArr.forEach((item) => {
    let key = item.split("=")[0];
    let value = item.split("=")[1];
    reqParams[key] = value;
  });
  console.log(reqParams);
  // 这里的reqParams等于req.query
  console.log(req.query);
  // 根据req.query.key 搜索数据库中对应的数据
  // let strSql = "select id, bookname, catory from mall where name like %" + req.query.key 
+ "%";
  // let result =await sqlQuery(strSql);
  // res.json(Array.from(result));
  res.send("搜索页面"); //
});
app.get("/ajax", (req, res) => {
  res.send("AJAX");
});
  •   处理post请求
// 处理登录
// 登录页
app.get("/login", (req, res) => {
  res.render("login");
});
app.post("/login", async (req, res) => {
  let username = req.body.username;
  let password = req.body.password;
  // 查询数据库是否有此用户名和密码
  let strSql = "select * from user where username = ? and password = ?";
  let arr = [username, password];
  let searchResult = await sqlQuery(strSql);
  if (searchResult.length) {
    res.send("登录成功");
  } else {
    res.send("登录失败,请检查用户名或者密码");
  }
});

总结:get请求使用req.query获取请求的参数,post请求用req.body获取参数,其实query和body是中间件设置到req请求上的属性,

我们也可以自己封装

45.中间件

  • 从字面意思,我们可以了解到它大概就是做中间代理操作,事实也是如此;
  • 大多数情况下,中间件就是在做接收到请求和发送响应中间的一系列操作。事实上,express是一个路由和中间件的web框架,Express应用程序基本上是一系列中间件函数的调用。

流程如下:

1.浏览器发送请求
2.express接受请求中间处理的过程
3.路由函数处理渲染(app.use(function(req, res, next){}))
4.res.render渲染

中间件函数可以执行以下任务:
执行任何代码。
对请求和响应对象进行更改。
结束请求/响应循环。
调用堆栈中的下一个中间件函数。
中间件也分为应用层中间件、路由中间件、内置中间件、错误处理中间件和第三方中间件。

应用层中间件
// 其实访问/的时候 经过了app.use的每个函数
// 封装一个dlQuery中间件
app.use((req, res, next) => {
  let splitRes = req.url.split("?");
  if (splitRes.length) {
    let keyValueArr = splitRes[1].split("&");
    let dlQuery = {};
    keyValueArr.forEach((item) => {
      let key = item.split("=")[0];
      let value = item.split("=")[1];
      dlQuery[key] = value;
    });
    req.dlQuery = dlQuery;
  } else {
    req.dlQuery = {};
  }
  next();
});
// 访问 http://127.0.0.1:3000/?name=11&pass=000 输出 { name: '11', pass: '000' }
// 截获之后可以不写next 直接res.send("404")
// app.use里面可以写多个函数,想要走到下一个函数的时候必须next()
app.use(
  (req, res, next) => {
    console.log(111111);
    next();
  },
  (req, res, next) => {
    console.log(122222);
    next();
  }
);
// 路由中间件
var express = require("express");
// express.Router() 实例化路由模块 该路由模块相当于一个小型的app
// 假设现在我们新建一个商城app
let routerMall = express.Router();
// express.Router()实例也有中间件可以用来拦截
routerMall.use((req, res, next) => {
  console.log("你是我的人");
  next();
});
routerMall.get("/", (req, res) => {
  res.send("商城首页");
});
routerMall.get("/list", (req, res) => {
  res.send("商城列表页");
});
module.exports = routerMall;
// 错误类型中间件
var createError = require("http-errors");
next(createError(404));
// 

46.Cookie

一、关于Cookie
在我们关闭一个登录过的网址并重新打开它后,我们的登录信息依然没有丢失;当我们浏览了商品后历史记录里出
现了我们点击过的商品;当我们推回到首页后,推荐商品也为我们选出了相似物品;事实上当我们有过此类操作后,
浏览器会将我们的操作信息保存到cookie上面。阿进而言之,cookie就是储存在用户本地终端上的数据。Cookie
的特点
1.cookie保存在浏览器本地,只要不过期关闭浏览器也会存在。
2.正常情况下cookie不加密,用户可轻松看到
3.用户可以删除或者禁用cookie
4. cookie可以被篡改
5.cookie可用于攻击
6. cookie存储量很小,大小一般是4k7.发送请求自动带上登录信息
二、Cookie的安装及使用
1.安装
cnpm insta11 cookie-parser --save
2.引入
const cookieParser=require("cookie-parser");
3.设置中间件
app.use(cookieParser());
4.设置cookie
res.cookie("name" , ' zhangsan' ,{maxAge: 900000,httponly: true});//res. cookie(名称,值,{配置信息})
关于设置cookie的参数说明:
1.domain:域名
2. name=value:键值对,可以设置要保存的KeyValue,注意这里的name不能和其他属性项的名字一样
3.Expires :过期时间(秒),在设置的某个时间点后该Cookie就会失效,如expires=Wednesday,09-Nov-99 
23:12:40 GMT。
4. maxAge:最大失效时间(毫秒),设置在多少后失效。
5. secure:当secure值为true时,cookie在 HTTP中是无效,在HTTPS中才有效。6.Path:表示在那个路由下可
以访问到cookie。
7. httpOnly :是微软对COOKIE做的扩展。如果在COOKIE中设置了"httpOnly"属性,则通过程序(JS脚本、
applet等)将无法读取到COOKIE信息,防止XSS攻击的产生。
8. singed:表示是否签名cookie,设为true 会对这个cookie签名,这样就需要用res.signedCookies而不是
res.cookies访问它。被篡改的签名cookie会被服务器拒绝,并且cookie值会重置为它的原始值。
var express = require("express");
var router = express.Router();

/* GET home page. */
router.get("/", function (req, res, next) {
  res.render("index", { title: "Express" });
});
router.get("/setcookie", (req, res) => {
  // 基础设置cookie, 有效期认为是一个会话,浏览器关闭会失效
  // maxAge设置失效时间, domain: 设置域名
  res.cookie("isLogin", "true", {
    maxAge: 5000,
    domain: "ccc.com",
    httpOnly: true,
  });
  res.send("返回cookie成功");
});
router.get("/login", (req, res) => {
  console.log(req.cookies);
  console.log(req.cookies.isLogin);
  if (req.cookies.isLogin == "true") {
    res.send("cookie验证通过,登录成功");
  } else {
    res.send("登录失败");
  }
});

module.exports = router;

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        

 

 

 

你可能感兴趣的:(持续更新,node.js,前端,nodejs,前端,1024程序员节)