nodejs基本介绍

为什么要学习nodejs

为什么要学习服务端的开发?

  1. 通过学习Node.js开发理解服务器开发Web请求和响应过程了解服务器端如何与客户端配合
  2. 作为前端开发工程师(FE)需要具备一定的服务端开发能力
  3. 全栈工程师的必经之路

服务器端开发语言有很多,为什么要选择nodejs

  1. 降低编程语言切换的成本(nodejs实质上用的还是javascript)
  2. NodeJS是前端项目的基础设施,前端项目中用到的大量工具 (大前端)
  3. nodejs在处理高并发上有得天独厚的优势
  4. 对于前端工程师,面试时对于nodejs有一定的要求

node.js 是什么?

node.js,也叫作node,或者nodejs,指的都是一个东西。

  1. node.js官方网站
  2. node.js中文网
  3. node.js 中文社区

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,nodejs允许javascript代码运行在服务端

1. nodejs不是一门新的编程语言,nodejs是在服务端运行javascript的运行环境
2. 运行环境:写得程序想要运行必须要有对应的运行环境
    php代码必须要有apache服务器
    在web端,浏览器就是javascript的运行环境
    在node端,nodejs就是javascript的运行环境
2. javascript并不只是能运行在浏览器端,浏览器端能够运行js是因为浏览器有js解析器,因此只需要有js解析器,任何软件都可以运行js。
3. nodejs可以在服务端运行js,因为nodejs是基于chrome v8的js引擎。

nodejs的本质:不是一门新的编程语言,nodejs是javascript运行在服务端的运行环境,编程语言还是javascript

nodejs与浏览器的区别

相同点:nodejs与浏览器都是浏览器的运行环境,都能够解析js程序。对于ECMAScript语法来说,在nodejs和浏览器中都能运行。

不同点:nodejs无法使用DOM和BOM的操作,浏览器无法执行nodejs中的文件操作等功能

思考:

  1. 在浏览器端,可以使用javascript操作文件么?
  2. 在nodejs端,可以使用BOM和DOM的方法么?
  3. 我们学习nodejs,学习什么内容?

nodejs可以干什么?

  1. 开发服务端程序
  2. 开发命令行工具(CLI),比如npm,webpack,gulp,less,sass等 vue-cli
  3. 开发桌面应用程序(借助 node-webkit、electron 等框架实现)

安装nodejs

nodejs版本

下载地址

官网术语解释

  • LTS 版本:Long-term Support 版本,长期支持版,即稳定版。
  • Current 版本:Latest Features 版本,最新版本,新特性会在该版本中最先加入。

查看node版本

node -v

运行nodejs程序

  • 创建js文件 helloworld.js
  • 写nodejs的内容:console.log('hello nodejs')
  • 打开命令窗口 cmd
    • shift加右键打开命令窗口,执行 node 文件名.js即可
    • 给vscode安装terminal插件,直接在vscode中执行
  • 执行命令:node helloworld.js

注意:在nodejs中是无法使用DOM和BOM的内容的,因此document, window等内容是无法使用的。

global模块-全局变量

Node.js 中的全局对象是 global, 类似于浏览器中的window

常用的global属性

//Ctrl+C 停止终端代码运行
//cls或者clear 清空终端代码
console: 用于打印日志
setTimeout/clearTimeout: 设置清除延时器
setInterval/clearInterval: 设置清除定时器

//获取的都是决定路径
__dirname: 当前文件的路径,不包括文件名(目录路径)
__filename: 获取当前文件的路径,包括文件名(文件路径)

//与模块化相关的,模块化的时候会用到
require
exports
module

fs文件系统模块

fs模块是nodejs中最常用的一个模块,因此掌握fs模块非常的有必要,fs模块的方法非常多,用到了哪个查哪个即可。

文档地址:http://nodejs.cn/api/fs.html

在nodejs中,提供了fs模块,这是node的核心模块

注意:

  1. 除了global模块中的内容可以直接使用,其他模块都是需要加载的。
  2. fs模块不是全局的,不能直接使用。因此需要导入才能使用。
var fs = require("fs");

读取文件

语法:fs.readFile(path[, options], callback)

方式一:不传编码参数(可以读取图片、视频、音频)

//参数1: 文件的路径
//参数2: 读取文件的回调函数
  //参数1:错误对象,如果读取失败,err会包含错误信息,如果读取成功,err是null
  //参数2:读取成功后的数据(是一个Buffer对象,内部二进制的,需要使用toString转成字符串)

fs.readFile("data.txt", function(err, data){
  console.log(err);
  console.log(data);
});

方式二:传编码参数(读取文本内容)

//参数1: 文件的路径
//参数2: 编码,如果设置了,返回一个字符串,如果没有设置,会返回一个buffer对象
//参数3: 回调函数
fs.readFile("data.txt", "utf8",function(err, data){
  console.log(err);
  console.log(data);
});

关于Buffer对象

1. Buffer对象是Nodejs用于处理二进制数据的。
2. 其实任意的数据在计算机底层都是二进制数据,因为计算机只认识二进制。
3. 所以读取任意的文件,返回的结果都是二进制数据,即Buffer对象
4. Buffer对象可以调用toString()方法转换成字符串。

写文件

语法:fs.writeFile(file, data[, options], callback)

//参数1:写入的文件名(如果文件不存在,会自动创建)
//参数2:写入的文件内容(注意:写入的内容会覆盖以前的内容)
//参数3:写文件后的回调函数
fs.writeFile("2.txt", "hello world, 我是一个中国人", function(err){
  if(err) {
    return console.log("写入文件失败", err);
  }
  console.log("写入文件成功");
});

拓展:throw 抛出错误,中断代码不再向下执行,工作中配合try与catch使用

try{
    //错误代码
}catch(e){
    //捕获错误代码
    //可以发送邮件或者其它智能设备给工程师
}

注意:

  1. 写文件的时候,会把原来的内容给覆盖掉

追加文件

语法:fs.appendFile(path, data[, options], callback)

//参数1:追加的文件名(如果文件不存在,会自动创建)
//参数2:追加的文件内容
//参数3:追加文件后的回调函数
fs.appendFile("2.txt", "我是追加的内容", function(err){
  if(err) {
    return console.log("追加文件内容失败");
  }
  console.log("追加文件内容成功");
})

思考:如果没有appendFile,通过readFile与writeFile应该怎么实现?

文件同步与异步的说明

fs中所有的文件操作,都提供了异步和同步两种方式

异步方式:不会阻塞代码的执行

//异步方式
var fs = require("fs");

console.log(111);
fs.readFile("2.txt", "utf8", function(err, data){
  if(err) {
    return console.log("读取文件失败", err);
  }
  console.log(data);
});
console.log("222");

同步方式:会阻塞代码的执行

//同步方式
console.log(111);
var result = fs.readFileSync("2.txt", "utf-8");
console.log(result);
console.log(222);

总结:同步操作使用虽然简单,但是会影响性能,因此尽量使用异步方法,尤其是在工作过程中。

其他api(了解)

方法有很多,但是用起来都非常的简单,学会查文档

文档:http://nodejs.cn/api/fs.html

方法名 描述
fs.readFile(path, callback) 读取文件内容(异步)
fs.readFileSync(path) 读取文件内容(同步)
fs.writeFile(path, data, callback) 写入文件内容(异步)
fs.writeFileSync(path, data) 写入文件内容(同步)
fs.appendFile(path, data, callback) 追加文件内容(异步)
fs.appendFileSync(path, data) 追加文件内容(同步)
fs.rename(oldPath, newPath, callback) 重命名文件(异步)
fs.renameSync(oldPath, newPath) 重命名文件(同步)
fs.unlink(path, callback) 删除文件(异步)
fs.unlinkSync(path) 删除文件(同步)
fs.mkdir(path, mode, callback) 创建文件夹(异步)
fs.mkdirSync(path, mode) 创建文件夹(同步)
fs.rmdir(path, callback) 删除文件夹(异步)
fs.rmdirSync(path) 删除文件夹(同步)
fs.readdir(path, option, callback) 读取文件夹内容(异步)
fs.readdirSync(path, option) 读取文件夹内容(同步)
fs.stat(path, callback) 查看文件状态(异步)
fs.statSync(path) 查看文件状态(同步)

path路径模块

路径操作的问题

在读写文件的时候,文件路径可以写相对路径或者绝对路径

//data.txt是相对路径,读取当前目录下的data.txt, 相对路径相对的是指向node命令的路径
//如果node命令不是在当前目录下执行就会报错, 在当前执行node命令的目录下查找data.txt,找不到
fs.readFile("data.txt", "utf8", function(err, data) {
  if(err) {
    console.log("读取文件失败", err);
  }

  console.log(data);
});

相对路径:相对于执行node命令的路径

绝对路径:__dirname: 当前文件的目录,__filename: 当前文件的目录,包含文件名

path模块的常用方法

关于路径,在linux系统中,路径分隔符使用的是/,但是在windows系统中,路径使用的\

在我们拼写路径的时候会带来很多的麻烦,经常会出现windows下写的代码,在linux操作系统下执行不了,path模块就是为了解决这个问题而存在的。

常用方法:

path.join();//拼接路径

//windows系统下
> path.join("abc","def","gg", "index.html")
"abc\def\gg\a.html"

//linux系统下
> path.join("abc","def","gg", "index.html")
'abc/def/gg/index.html'

path.basename(path[, ext])	返回文件的名称
path.dirname(path)	返回路径的目录名
path.extname(path)	获取路径的扩展名

var path = require("path");
var temp = "abc\\def\\gg\\a.html";
console.log(path.basename(temp));//a.html
console.log(path.dirname(temp));//abc\def\gg
console.log(path.extname(temp));//.html

【优化读写文件的代码】

path模块其他api(了解)

方法名 描述
path.basename(path[, ext]) 返回文件的最后一部分
path.dirname(path) 返回路径的目录名
path.extname(path) 获取路径的扩展名
path.isAbsolute(path) 判断目录是否是绝对路径
path.join([...paths]) 将所有的path片段拼接成一个规范的路径
path.normalize(path) 规范化路径
path.parse(path) 将一个路径解析成一个path对象
path.format(pathObj) 讲一个path对象解析成一个规范的路径

http模块

创建服务器基本步骤

//1. 导入http模块,http模块是node的核心模块,作用是用来创建http服务器的。
var http = require("http");

//2. 创建服务器
var server = http.createServer();

 //3. 启动服务器,监听某个端口
server.listen(9999, function(){
  console.log("服务器启动成功了, 请访问: http://localhost:9999");
});

//4.服务器处理请求
server.on("request", function(req,res) {
    //只要给请求一律给OK
    res.end("OK")
  console.log("我接收到请求了");
});

详细说明

  1. 给服务器注册request事件,只要服务器接收到了客户端的请求,就会触发request事件
  2. request事件有两个参数,request表示请求对象,可以获取所有与请求相关的信息,response是响应对象,可以获取所有与响应相关的信息。
  3. 服务器监听的端口范围为:1-65535之间,推荐使用3000以上的端口,因为3000以下的端口一般留给系统使用(6666端口号不可用)

创建服务器简写形式

const http = require("http")
http.creatServer((req,res)=>{
    res.end("OK")
}).listen(8888,()=>{
    console.log("服务器启动成功了")
})

request请求对象详解(req)

文档地址:http://nodejs.cn/api/http.html#http_message_headers

常见属性:

headers: 所有的请求头信息
method: 请求的方式
url: 请求的地址

注意:在发送请求的时候,可能会出现两次请求的情况,这是因为谷歌浏览器会自动增加一个favicon.ico的请求。

小结:request对象中,常用的就是method和url两个参数

response响应对象详解(res)

文档地址:http://nodejs.cn/api/http.html#http_class_http_serverresponse

常见的属性和方法:

res.write(data): 设置响应体片段(给浏览器发送请求体,可以调用多次,从而提供连续的请求体
res.end(); 响应结束只能调用一次(通知服务器,所有响应头和响应主体都已被发送,即服务器将其视为已完成。
res.end(data); 结束请求,并且响应一段内容,相当于res.write(data) + res.end()
res.statusCode: 响应的的状态码 200成功 302重定向 304缓存 404找不到 5xx服务器报错
res.statusMessage: 响应的状态信息, OK Not Found ,会根据statusCode自动设置。
res.setHeader(name, value); 设置响应头信息, 比如res.setHeader('content-type','text/html; chartset=utf-8')
res.writeHead(statusCode, statusMessage, options); 设置响应头,同时可以设置状态码和状态信息。

注意:必须先设置响应头,才能设置响应。

实现静态WEB服务器

服务器响应首页

  • 注意:浏览器中输入的URL地址,仅仅是一个标识,不与服务器中的目录一致。也就是说:返回什么内容是由服务端的逻辑决定
server.on('request', function(req, res) {
  var url = req.url
  if(url === '/') {
    fs.readFile('./index.html', function(err, data) {
      if(err) {
        return res.end('您访问的资源不存在~')
      }

      res.end(data)
    })
  }
})

根据根据不同url,响应不同文件

content-type设置-MIME类型

  • MIME(Multipurpose Internet Mail Extensions)多用途Internet邮件扩展类型 是一种表示文档性质和格式的标准化方式
  • 浏览器通常使用MIME类型(而不是文件扩展名)来确定如何处理文档;因此服务器将正确的MIME类型附加到响应对象的头部是非常重要的
  • MIME 类型

静态资源的通用处理

MIME类型的通用处理-mime模块

  • 作用:获取文件的MIME类型
  • 安装:npm i mime
var mime = require('mime')

// 自动根据文件名/文件路径 获取对应的MIME类型
mime.getType(url)                    // ⇨ 'text/plain'
// 根据MIME获取到文件后缀名
mime.getExtension('text/plain')        // ⇨ url

npm - Node包管理工具

npm的基本概念

1. npm 是node的包管理工具,
2. 它是世界上最大的软件注册表,每星期大约有 30 亿次的下载量,包含超过 600000 个 包(package) (即,代码模块)。
3. 来自各大洲的开源软件开发者使用 npm 互相分享和借鉴。包的结构使您能够轻松跟踪依赖项和版本。
  • 作用:通过npm来快速安装开发中使用的包
  • npm不需要安装,只要安装了node,就自带了npm

npm基本使用

初始化包

npm init;    //这个命令用于初始化一个包,创建一个package.json文件,我们的项目都应该先执行npm init
npm init -y;  //快速的初始化一个包, 不能是一个中文名

安装包

npm install 包名;  //安装指定的包名的最新版本到项目中
npm install 包名@版本号;  //安装指定包的指定版本
//jQuery@1.12

npm i 包名; //简写

注意:如果修改使用依赖包的版本,规范:先卸载旧版,再安装新版本

如果只有package.json文件,没有node_modules文件夹

可以使用 npm i 默认安装整个依赖

卸载包

npm uninstall 包名;  //卸载已经安装的包

清除缓存

npm cache clean -f // 如果npm安装失败了,可以用这个命令来清除缓存

package.json文件

package.json文件,包(项目)描述文件,用来管理组织一个包(项目),它是一个纯JSON格式的。

  • 作用:描述当前项目(包)的信息,描述当前包(项目)的依赖项
  • 如何生成:npm init或者npm init -y
  • 作用
    • 作为一个标准的包,必须要有package.json文件进行描述
    • 一个项目的node_modules目录通常都会很大,不用拷贝node_modules目录,可以通过package.json文件配合npm install直接安装项目所有的依赖项
  • 描述内容
{
  "name": "03-npm",  //描述了包的名字,不能有中文
  "version": "1.0.0",  //描述了包的的版本信息, x.y.z  如果只是修复bug,需要更新Z位。如果是新增了功能,但是向下兼容,需要更新Y位。如果有大变动,向下不兼容,需要更新X位。
  "description": "", //包的描述信息
  "main": "index.js", //入口文件(模块化加载规则的时候详细的讲)
  "scripts": {  //配置一些脚本,在vue的时候会用到,现在体会不到
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],  //关键字(方便搜索)
  "author": "",  //作者的信息
  "license": "ISC",  //许可证,开源协议
  "dependencies": {   //重要,项目的依赖, 方便代码的共享  通过 npm install可以直接安装所有的依赖项
    "bootstrap": "^3.3.7",
    "jquery": "^3.3.1"
  }
}

注意:一个合法的package.json,必须要有name和version两个属性

本地安装和全局安装

有两种方式用来安装 npm 包:本地安装和全局安装。选用哪种方式来安装,取决于你如何使用这个包。

  • 全局安装:如果你想将其作为一个命令行工具,那么你应该将其安装到全局。这种安装方式后可以让你在任何目录下使用这个命令。比如less命令,webpack命令,hcc-md命令 。
  • 本地安装:如果你自己的模块依赖于某个包,并通过 Node.js 的 require 加载,那么你应该选择本地安装,这种方式也是 npm install 命令的默认行为。
// 全局安装,会把npm包安装到C:\Users\HUCC\AppData\Roaming\npm目录下,作为命令行工具使用
npm install -g 包名;

//全局卸载 
npm uninstall -g 包名;

//本地安装,会把npm包安装到当前项目的node_modules文件中,作为项目的依赖
npm install 包名;

更新全局安装的包

# 查看全局已经安装的包
npm list -g --dept 0
#	更新全局指定的包,jshint是包名
npm update -g jshint
#	要找出需要更新的软件包
npm outdated -g --depth=0.
#	要更新所有全局软件包
npm update -g.

常见的命令行工具

设置淘宝镜像

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

设置官方镜像

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

nrm(镜像管理器)

  • nrm:npm registry manager(npm仓库地址管理工具)
  • 安装:npm i -g nrm
# 带*表示当前正在使用的地址

# 查看仓库地址列表
nrm ls
# 切换仓库地址
nrm use taobao

nodemon 自动重启

  • 作用:监视到js文件修改后,自动重启node程序
  • 安装:npm i -g nodemon
  • 使用:nodemon app.js 运行node程序

实现动态WEB服务器

在node中使用art-template

npm install art-template
  • 核心方法
// 基于模板路径渲染模板
//参数1:文件的决定路径
//参数2:数据
//返回值:返回渲染后的内容
// template(filename, data)
let html = template(path.join(__dirname, "pages", "index.html"), {name:"大吉大利,今晚吃鸡"});

注意点:文件的路径必须是绝对路径

get请求处理-url模块

  • 说明:用于 URL 处理与解析
  • 注意:通过url拿到的查询参数都是字符串格式
// 导入url模块
var url = require('url')

// 解析 URL 字符串并返回一个 URL 对象
// 第一个参数:表示要解析的URL字符串
// 第二个参数:是否将query属性(查询参数)解析为一个对象,如果为:true,则query是一个对象
var ret = url.parse('http://localhost:3000/details?id=1&name=jack', true)
console.log(ret.query) // { id: '1', name: 'jack' }

服务端重定向

//常规版
res.setHeader("location","/")
res.statusCode = 302
res.end()

//简化版
res.writeHead(302, {
  'Location': '/'
})
res.end()

moment日期格式化插件

moment日期格式化插件,可以在普通js中使用,用法参考官网

moment().format();//国标

POST请求参数的处理

  • 说明:POST请求可以发送大量数据,没有大小限制
// 接受POST参数
var postData = []
// data事件:用来接受客户端发送过来的POST请求数据
var result = "";
req.on('data', function (chunk) {
  result += chunk;
})

// end事件:当POST数据接收完毕时,触发
req.on('end', function () {
  cosnole.log(result); 
})

请求体处理-querystring模块

  • 用于解析与格式化 URL 查询字符串
  • 注意:只在专门处理查询字符串时使用
// foo=bar&abc=xyz&abc=123
var querystring = require('querystring')

// 将查询参数转化为对象
// 第一个参数: 要解析的 URL 查询字符串
querystring.parse('foo=bar&abc=xyz') // { foo: 'bar', abc: 'xyz' }

模块化

基本概念

在nodejs中,应用由模块组成,nodejs中采用commonJS模块规范。

  1. 一个js文件就是一个模块
  2. 每个模块都是一个独立的作用域,在这个而文件中定义的变量、函数、对象都是私有的,对其他文件不可见。
  • 01-模块化的概念用于演示每一个模块都有自己单独的作用域

node中模块分类

  • 1 核心模块
    • 由 node 本身提供,不需要单独安装(npm),可直接引入使用
  • 2 第三方模块
    • 由社区或个人提供,需要通过npm安装后使用
  • 3 自定义模块
    • 由我们自己创建,比如:tool.js 、 user.js

核心模块

  • fs:文件操作模块
  • http:网络操作模块
  • path:路径操作模块
  • url: 解析地址的模块
  • querystring: 解析参数字符串的模块
  • 基本使用:1 先引入 2 再使用
// 引入模块
var fs = require('fs');

第三方模块

  • 第三方模块是由 社区或个人 提供的
  • 比如:mime模块/art-template/jquery...
  • 基本使用:1 先通过npm下载 2 再引入 3 最后使用

用户自定义模块

  • 由开发人员创建的模块(JS文件)
  • 基本使用:1 创建模块 2 引入模块
  • 注意:自定义模块的路径必须以./获取../开头
// 加载模块
require('./a')     // 推荐使用,省略.js后缀!

require('./a.js')

模块的导入与导出

模块导入

  • 通过require("fs")来加载模块
  • 如果是第三方模块,需要先使用npm进行下载
  • 如果是自定义模块,需要加上相对路径./或者../,可以省略.js后缀,如果文件名是index.js那么index.js也可以省略。
  • 模块可以被多次加载,但是只会在第一次加载

模块导出

  • 在模块的内部,module变量代表的就是当前模块,它的exports属性就是对外的接口,加载某个模块,加载的就是module.exports属性,这个属性指向一个空的对象。
//module.exports指向的是一个对象,我们给对象增加属性即可。
//module.exports.num = 123;
//module.exports.age = 18;

//通过module.exports也可以导出一个值,但是多次导出会覆盖
module.exports = '123';
module.exports = "abc";

module.exports与exports

  1. 一个模块的导出,就看module.exports的值
  2. 默认exports和module.exports指向了同一个对象
  • exportsmodule.exports 的引用
  • 注意:给 module.exports 赋值会切断与 exports 之间的联系
    • 1 直接添加属性两者皆可
    • 2 赋值操作时,只能使用 module.exports,复杂数据类型都是存地址,地址修改后对象就变了
console.log( module.exports === exports ) // ==> true

// 等价操作
module.exports.num = 123
exports.num = 123

// 赋值操作:不要使用 exports = {}
module.exports = {}

第三方模块(以mime包为例)

  • 先基于当前文件模块所属目录找 node_modules 目录
  • 如果找到,则去该目录中找 mime 目录
  • 如果找到 mime 目录,则找该目录中的 package.json 文件
  • 如果找到 package.json 文件,则找该文件中的 main 属性
  • 如果找到 main 属性,则拿到该属性对应的文件路径
  • 如果找到 mime 目录之后
    • 发现没有 package.json
    • 或者 有 package.json 没有 main 属性
    • 或者 有 main 属性,但是指向的路径不存在
    • 则 node 会默认去看一下 mime 目录中有没有 index.js index.node index.json 文件
  • 如果找不到 index 或者找不到 mime 或者找不到 node_modules
  • 则进入上一级目录找 node_moudles 查找规则同上
  • 如果上一级还找不到,继续向上,一直到当前文件所属磁盘根目录
  • 如果最后到磁盘根目录还找不到,最后报错:can not find module xxx

Express

Express 框架

起步

  • 安装:npm i express
// 导入 express
var express = require('express')
// 创建 express实例,也就是创建 express服务器
var app = express()

// 路由
app.get('/', function (req, res) {
  res.send('Hello World!')
})

// 启动服务器
app.listen(3000, function () {
  console.log('服务器已启动')
})

API说明

  • express():创建一个Express应用,并返回,即:app
  • app.get():注册一个GET类型的路由
    • 注意:只要注册了路由,所有的请求都会被处理(未配置的请求路径,响应404)
  • res.send():发送数据给客户端,并自动设置Content-Type
  • res.sendFile():发送文件给客户端,并自动设置Content-Type
    • 参数可以是:字符串、数组、对象、Buffer
    • 注意:只能使用一次
  • reqres:与http模块中的作用相同,是扩展后的请求和响应对象

注册路由

  • 1 app.METHOD:比如:app.get / app.post / app.delete / app.patch
  • 3 app.use(path, callback) 更重要的作用是处理中间件
    • 注意:只要是以path开头的请求地址,都可以被use处理
    • 注意:可以处理任意的请求类型
    • 注意:path参数可省略,默认值为:/

express中间件

中间件:中间处理的部件,这个部件处理完成后,会交给下一个部件处理

  1. express中间件是一个函数 (req,res) =>{......}
  2. 中间件可以获取上一个中间件的内容,req res
  3. 中间件,可以对内容进行进一步处理,可以对req和res进行处理
  4. 处理完成后,可以给下一次匹配中间件去使用
//不写"/",也是匹配所有
app.get("/",(req,res,next)=>{
    //第三参数:将处理过的req和res交给下一个匹配的中间件处理
    next()
    //next()调用将请求交给下一个中间件的使用
    req.id //获取请求电脑的ip地址
})
app.get("/",(req,res)=>{
    //匹配的中间件
})

利用中间件处理post请求参数原理

//不写"/",也是匹配所有
app.use("/",(req,res,next)=>{
    const results = ""
    req.on("data",chunk=>{
        results += chunk
    })
    
    req.on("end",()=>{
        //将post数据处理成键值对形式
        const query = querystring.parse(results)
        //将数据处理后存储到请求体中
        req.body = query
        next()
    })
})

实现静态服务器

  • req.path:请求路径
    • 示例:URL为'example.com/users?sort=desc',path表示:/users
  • res.sendFile()

处理静态资源

  • 静态资源:图片、CSS、JavaScript 文件 等
  • 如何处理?使用 express.static() 方法来托管静态资源
  • 注意:express.static()可以调用多次
  • 思考:app.use('/web', express.static('web')) 的含义?
    • 访问:http://localhost:3000/web/anoceanofsky.jpg
// 托管web目录下的静态资源
app.use(express.static('web'))
// 相当于:app.use('/', express.static('web'))
app.use('/', express.static(path.join(__driname,"web")))
// 访问上述静态资源
// http://localhost:3000/anoceanofsky.jpg

// 当请求达到服务器后,express就会到web目录下,查找anoceanofsky.jpg
// 并读取该文件,返回给浏览器

express对于req新增内容

request常用属性和方法

// 获取请求路基中的参数,是一个对象 ---> Get请求参数
req.query

// 获取POST请求参数,需要配置`body-parser`模块, POST请求参数
req.body
  • 获取POST请求参数(配置body-parser模块)
//安装下载中间件 body-parser 和 multer
//Multer 是一个 node.js 中间件,用于处理 multipart/form-data 类型的表单数据,它主要用于上传文件。它是写在 busboy 之上非常高效。

// 将POST请求参数转化为对象,存储到req.body中

// 导入body-parser模块
var bodyParser = require('body-parser');

//以下两条是配置中间件的
app.use(bodyParser.json());// for parsing application/json
app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded

// 此时,就可以获取到POST请求参数了
console.log(req.body)

express对于res新增内容

response常用属性和方法

// send() 发送数据给客户端,并自动设置Content-Type
res.send()

// 发送文件给浏览器,并根据文件后缀名自动设置Content-Type
// 注意:文件路径必须是绝对路径
res.sendFile(path.join(__dirname, 'index.html'))

// 设置HTTP响应码
res.sendStatus(200) // equivalent to res.status(200).send('OK')
res.sendStatus(404)
res.send("404 not found")//equivalent to res.sendStatus(404).send("404 not found")

// 设置响应头
res.set('Content-Type', 'text/plain')
res.set({
  'Content-Type': 'text/plain',
  'cute': 'fangfang'
})

// 重定向
res.redirect('/index')

Express使用模板引擎

// 为后缀为html的模板设置模板引擎
app.engine('html', require('express-art-template'))

// 设置模板文件所在的目录(使用相对路径需要设置)
app.set('views', './')
// 设置模板文件的后缀为 html
app.set('view engine', 'html')

// 渲染 index.html 模板文件,并发送给浏览器
res.render('index', { list: [] })

Express 中外置路由使用

  • 目的:将路由封装到一个独立的路由模块中,有利于代码的封装和模块化
/*
  router.js 文件代码如下:
*/

// 1 加载 express 模块
var express = require('express')

// 2 调用 express.Router() 方法,得到一个路由容器实例
var router = express.Router()

// 3 为 router 添加不同的路由
router.get('/', function (req, res) {
  res.send('hello express')
})
router.get('/add', function (req, res) {

})

// 4. 将 router 路由容器导出
module.exports = router
/*
  在 app.js 文件中:
*/
var express = require('express')

// 1 加载上面自定义的路由模块
var router = require('./router')

var app = express()

// 2. 将自定义路由模块 router 通过 app.use() 方法挂载到 app 实例上
//    这样 app 实例程序就拥有了 router 的路由
app.use( router )

app.listen(3000, function () {
  console.log('running...')
})

数据库基本概念

为什么要有数据库

没有数据库,我们的数据都是存储在文件当中的,那么文件存储数据的缺点有:

  • 文件的安全性问题。
  • 文件不利于查询和对数据的管理。
  • 文件不利于存放海量数据
  • 文件在程序中控制不方便

什么是数据库

数据库,简而言之可视为电子化的文件柜——存储电子文件的处所,用户可以对文件中的数据运行增加、删除、修改、查询等操作。

前端程序员只需要对数据库有一定了解即可。

浏览器---->服务器---->数据库

数据库的分类

关系型数据库:

  • MySQL
  • Oracle、
  • SQL Server
  • SQLite(安卓)

非关系型数据库

  • mongodb
  • redis
  • BigTable

DBA

数据库中基本术语

  • 数据库database:存放数据的仓库,一般一个项目中的数据会存储到一个数据库中
  • table: 一个表对应一类数据,比如学生表,老师表
  • columns:一张表由多列组成,也叫一个字段,比如学生的姓名,成绩,年龄等
  • rows: 一个学生信息对应一行,一行也叫一条记录。

数据库的可视化操作

创建数据库

创建表

存储以下学生信息

{id: 1, name: '张三', age: 18, gender: '男', content: '这是描述信息‘}

数据类型

int: 整数类型

varchar: 字符类型

datetime: 日期类型

数据库的常见命令

SQL: 结构化查询语言(Structured Query Language)简称 SQL 。用于数据库的增删改查以及管理等功能。

数据库相关

  • --SQL 中的注释
  • SHOW DATABASES; 查看所有的数据
  • CREATE DATABASE mydb; 创建数据库
  • DROP DATABASE mydb;删除数据库
  • USE mydb; 使用数据库

表相关

  • SHOW TABLES;查看当前数据库中所有的表
  • 创建表
CREATE TABLE user(
    id INT auto_increment PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    age INT ,
    gender VARCHAR(4),
    content VARCHAR(255)
    createtime timestamp not null default current_timestamp,--设置默认自动生成时间
    updatetime timestamp default current_timestamp on update current_timestamp --设置自动更改时间
)

--TIMESTAMP和DATETIME的不同点:对于TIMESTAMP,它把客户端插入的时间从当前时区转化为UTC(世界标准时间)进行存储。查询时,将其又转化为客户端当前时区进行返回。而对于DATETIME,不做任何改变,基本上是原样输入和输出。

  • DROP TABLE user;删除表

插入数据

--字段名(列名),键与值需要一一对应
INSERT INTO user (name, age, gender, content) VALUES ('鹏鹏', 18, '男', '哈哈哈,哈哈哈')

-- 如果省略列名,那么必须要和所有字段(列名)一一对应,没有值填写NUll占位置
INSERT INTO user VALUES (null, '鹏鹏', 18, '男', '哈哈哈,哈哈哈')

--可以只填写必填项
INSERT INTO user SET name='pp', age=18, gender='男', content='嘻嘻嘻'

修改数据

// 修改所有的数据
UPDATE USER SET name='大鹏子'
// 根据条件修改
UPDATE USER set name='鹏鹏', content="这是内容" WHERE id = 2

删除数据

// 删除所有的数据
DELETE FROM USER

// 删除id为5的数据
DELETE FROM USER WHERE id = 5

查询数据

-- 查询所有数据
SELECT * FROM user

-- 查询指定列
SELECT id, name,age from user

条件查询

--- 并且
SELECT * from user where name='鹏鹏' AND age=21

--- 或者
SELECT * from user where name='鹏鹏' or age=21

-- 范围查询

-- 模糊查询  %表示通配  _表示单个字符
SELECT * from user where name LIKE '%鹏' --后面含有鹏
SELECT * from user where name LIKE '鹏%' --前面含有鹏
SELECT * from user where name LIKE '%鹏%' --包含

-- in语句
SELECT * from user where name in ('鹏鹏', 'pp')

-- order by
-- 排序需要写在最后面,,默认asc升序  desc:降序
SELECT * from user ORDER BY id desc

-- limit分页,limit 参数:一跳过几条,参数二:选取几条
--只写一个参数默认为第二个参数
SELECT * from user ORDER BY id desc limit 3
--跳过三条取三条
SELECT * from user ORDER BY id desc limit 3,3
--案例,每页三条,共两页
--第一页
SELECT * from user ORDER BY id desc limit 0,3;
--第二页
SELECT * from user ORDER BY id desc limit 3,6;

-- 获取总条数
SELECT count(*) as total FROM user

导入和导出数据库脚本

node 操作 mysql

基本使用

  • 安装
npm install mysql
  • 基本使用
// 导入第三方包
const mysql = require('mysql')
// 创建连接
var connection = mysql.createConnection({
  // 本地
  host: 'localhost',
  //用户名
  user: 'root',
  //密码
  password: 'root',
  // 数据库名称
  database: 'mydb',
  // 端口号
  port: 3306
})

// 连接数据库
connection.connect()

// 执行sql语句
//connection.query(sql语句,参数列表,回调函数)
connection.query('select * from user where id = 8', (err, result) => {
  if (err) return console.log('查询失败', err)
  // result返回的是数组, 数组中是一个对象
  console.log(result)
})

// 关闭连接
connection.end()

查询语句

var name = '鹏鹏'
//connection.query(sql语句,参数列表,回调函数)
// 使用?表示占位,可以防止sql注入
//方式一
connect.query('select * from user where name="name"',(err, result) => {
  if (err) return console.log('错误了', err)
  console.log(result)
})
//方式二,es6的方法
connect.query(`select * from user where name="${name}"`,(err, result) => {
  if (err) return console.log('错误了', err)
  console.log(result)
})
//方式三,系统自带的方法
connect.query(`select * from user where name=?`, name, (err, result) => {
  if (err) return console.log('错误了', err)
  console.log(result)
})

插入语句

connect.query(
  'insert into user (name, age, gender, content) values (?, ?, ?, ?)',
  ['鹏哥', 18, '男', '哈哈哈哈'],
  err => {
    if (err) return console.log('错误', err)
    console.log('添加成功了')
  }
)

// 方式2
connect.query(
  'insert into user set ?',
  {
    name: '鹏鹏123',
    age: 30,
    gender: '男',
    content: '哈哈哈'
  },
  (err, result) => {
    if (err) return console.log('错误', err)
    console.log('添加成功了', result)
  }
)

修改语句

connect.query(
  'update user set ? where id = ?',
  [
    {
      name: '鹏456',
      age: 30,
      gender: '男',
      content: '哈哈哈'
    },
    10
  ],
  (err, result) => {
    if (err) return console.log('错误', err)
    console.log('添加成功了', result)
  }
)

删除语句

//删除单条
connect.query('delete from user where id = ?', [10], (err, result) => {
  if (err) return console.log('失败', err)
  console.log(result)
})
//删除多条
const ids = [1,2,3]
const str = ids.join(",")
connect.query(`delete from user where id in (${str})`, (err, result) => {
  if (err) return console.log('失败', err)
  console.log(result)
})

批量调用db 连接池模块封装

const mysql = require('mysql')
const pool = mysql.createPool({
  connectionLimit: 100,
  // 地址
  host: '101.43.203.220',
  // 用户名
  user: 'admin',
  // 密码
  password: 'admin',
  // 数据库名称
  database: 'wechat',
  // 端口号
  port: 3306
})


module.exports = (callback) =>{
  pool.getConnection(function(err, connection) {
    if (err) throw err // 连接失败
    callback&&callback(connection) // 使用连接

    /*
      范例:
      connection.query('SELECT something FROM sometable', function (error, results, fields) {
      // 完成任务后释放连接
      connection.release()
      // 捕获报错信息
      if (error) throw error
    }) */
  })
}

后端实现 - 登录拦截

目标: 将未登录的用户, 拦截到登录页

登录功能基本实现(辅助用的)

  1. 创建用户表

  2. 登录请求

    findUser(username, password, callback)

后端实现 - 状态保持 (登录保持)

http 无状态

喝咖啡的例子
咖啡厅有个活动:  喝 5 杯 送 1 杯
问题1: 不可能一次喝 6 杯, 需要累积
问题2: 服务员是无状态的, 不可能记住所有的客户, 每个客户喝了多少杯

依赖 express-session库实现状态保持 - 完成登录拦截 (了解)

特征:

  1. 自动开启session
  2. 自动将 sessionid 存到浏览器

req.session

1、安装express-session npm install express-session

2、配置express-session

//依赖express组件需要安装
var app = express()
// 配置session, 会在用户第一次请求时, 自动开启session 和 cookie 的空间
// cookie中会自动存储 sessionId
app.use(session({
  secret: 'keyboard cat',  // 额外秘钥, 根据秘钥进行sessionid加密
  resave: false,
  saveUninitialized: true,  // 是否需要在第一次请求时, 自动存储session 和 cookie
  cookie: { secure: true } //https 安全认证,使用http请求可以不需要这个设置
}))

3、统一处理未登录的用户

// 统一处理未登录的用户, 只要req.session中没有 user 信息, 说明没登陆
// 直接拦截到登录页 (不管请求的是哪一个页面 => 匹配所有的路径)
app.use((req, res, next) => {
  // 登录过, 需要放行, 或者如果就访问的是登陆页, 也放行
  if (req.session.user || req.url === '/login') {
    // 登录过, 放行
    next()
  } else {
    // 没登陆过, 拦截到登录页
    res.redirect('/login')
  }
})

4、对于登陆请求的处理

// 登录请求的处理 (post请求)
router.post('/login', (req, res) => {
  // console.log(req.body)
  const { username, password } = req.body
  // 根据用户名 和 密码, 去数据库查询, 根据结果判断是否跳转首页
  db.findUser(username, password, results => {
    if (results.length === 0) {
      // 空数组, 登录失败, 拦截回登录页
      res.redirect('/login')
    } else {
      // 成功了  results [{}]
      // console.log(results[0])
      // 将整个用户信息都存到 req.session 空间中
      req.session.user = results[0]
      console.log(req.session)
      res.redirect('/')
    }
  })
})

退出功能

req.session.user = null

router.get('/logout', (req, res) => {
  // 清除服务端, req.session中数据
  req.session.user = null
  res.redirect('/login')
})