公告
淡泊明志,宁静致远
网站资讯
本站文章字数合计
243.7k
本站Hexo版本
6.1.0
本站Node版本
16.14.2
本站已运行时间
最后更新时间
本文目录
  1. node基本概念
    1. node是什么
    2. nodejs与浏览器的区别
    3. nodejs可以干什么?
  2. node安装
  3. 运行node程序
    1. repl方式
    2. node执行js文件方式(掌握)
  4. node常用模块
    1. global模块(全局变量)
    2. fs模块(操作文件)
      1. 读文件
      2. 写文件
      3. 追加文件
      4. 文件的同步与异步
      5. 其他api(了解)
    3. http模块
      1. 创建服务器基本步骤
      2. request对象详解
      3. response对象详解
      4. 根据不同请求输出不同响应数据
      5. 服务器响应文件
      6. MIME类型
        1. mime模块
    4. url模块
    5. path模块
      1. 路径拼接
      2. 获取文件名
      3. 获取扩展名
  5. 包管理器-npm
    1. npm的基本概念
    2. npm基本使用
    3. 本地安装和全局安装
    4. package.json文件
    5. npm下载加速-nrm
  6. nodemon 自动重启
  7. art-template 模板引擎
  8. 服务端重定向
  9. post请求参数处理
  10. 模块化
    1. node模块分类
    2. 模块的导入导出
      1. 模块导入
      2. 模块导出
      3. module.exports与exports
      4. 第三方模块代码执行流程(了解)
    3. CommonJS 规范参考文档
  11. node操作mysql数据库
    1. 配置 mysql 模块
    2. node操作mysql增删改查
      1. 添加数据
      2. 查询数据
      3. 修改数据
      4. 删除数据
  12. node操作mongodb数据库
    1. 数据增删改查
  13. Express框架
    1. 基本使用
    2. 托管静态资源
    3. request和response
      1. request常用方法
      2. response常用方法
    4. expres中使用art-template
    5. 路由
    6. 中间件
      1. 全局中间件
      2. 局部中间件
      3. 中间件分类
        1. 应用级别的中间件
        2. 路由级别的中间件
        3. 错误级别的中间件
        4. 内置中间件
        5. 第三方中间件
    7. CORS 跨域资源共享
      1. CORS 常见响应头
      2. CORS 请求分类
        1. 简单请求
        2. 预检请求
      3. express中使用cors
  14. 身份认证
    1. jwt认证
      1. Session 的局限性
      2. JWT 工作原理图
      3. JWT 组成部分
      4. JWT的使用方式
      5. Express 使用 JWT认证
    2. session认证
      1. session认证原理
      2. Express中使用session认证
已阅读:%

分类: web前端 | 标签: node

node笔记

发表于: 2022-10-30 19:19:28 | 字数统计: 10.6k | 阅读时长预计: 52分钟

node基本概念

node是什么

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

  1. node.js官方网站
  2. node.js中文网
  3. node.js 中文社区
  4. 菜鸟教程-node

Node.js是一个Javascript运行环境(runtime environment),发布于2009年5月,由Ryan Dahl开发,实质是对Chrome V8引擎进行了封装。Node.js对一些特殊用例进行优化,提供替代的API,使得V8在非浏览器环境下运行得更好。

  • Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。
1. nodejs是在服务端运行javascript的运行环境
2. javascript并不只是能运行在浏览器端,浏览器端能够运行js是因为浏览器有js解析器,因此只需要有js解析器,任何软件都可以运行js。
3. nodejs可以在服务端运行js,因为nodejs是基于chrome v8的js引擎。
  • Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。
  • Node.js 的包管理器 npm,是全球最大的开源库生态系统。

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

nodejs与浏览器的区别

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

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

image-20221024215658030

nodejs可以干什么?

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

node安装

下载地址

官网术语解释

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

下载之后一直点下一步即可完成安装,安装之后查看node版本

node -v

运行node程序

repl方式

  1. REPL 全称: Read-Eval-Print-Loop(交互式解释器)
    • R 读取 - 读取用户输入,解析输入了Javascript 数据结构并存储在内存中。
    • E 执行 - 执行输入的数据结构
    • P 打印 - 输出结果
    • L 循环 - 循环操作以上步骤直到用户两次按下 ctrl-c 按钮退出。
  2. 在REPL中编写程序 (类似于浏览器开发人员工具中的控制台功能)
    • 直接在控制台输入 node 命令进入 REPL 环境
  3. 按两次 Control + C 退出REPL界面 或者 输入 .exit 退出 REPL 界面
    • 按住 control 键不要放开, 然后按两下 c 键

image-20221024220716342

node执行js文件方式(掌握)

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

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

node常用模块

global模块(全局变量)

JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。

在浏览器 JavaScript 中,通常 window 是全局对象, 而 Node.js 中的全局对象是 global,所有全局变量(除了 global 本身以外)都是 global 对象的属性。

在 Node.js 我们可以直接访问到 global 的属性,而不需要在应用中包含它。

常用的global属性

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模块不是全局的,不能直接使用。因此需要导入才能使用。
let fs = require("fs");

读文件

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

path: 带文件名称的文件路径

options: 文件编码(可选) 若不传则返回buffer对象

callback(err,data): 文件读完的回调

方式1:不传编码参数,回调函数中可以得到buffer对象,需要使用toString转化成字符串

let fs = require('fs')
fs.readFile('1.txt',(err,data) => {
    console.log(data) //打印buffer对象
    console.log(data.toString()) //正常打印字符串
})

方式2: 传编码参数,回调函数中可以得到读取到的字符串

let fs = require('fs')
fs.readFile('1.txt','utf-8',(err,data) => {
    console.log('data :>> ', data);
})

关于Buffer对象

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

写文件

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

filepath:带文件名称的文件路径

data: 要写入的文件内容

callback(err): 写入成功之后的回调

注意:此方式会把之前文件的内容覆盖

let fs = require('fs')
fs.writeFile('2.txt','node写文件',err => {
    console.log('写入成功!')
})

追加文件

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

filepath:带文件名称的文件路径

data: 要追加的文件内容

callback(err): 追加成功之后的回调

let fs = require('fs')
fs.appendFile('2.txt','我是追加的',err => {
    console.log('追加成功!')
})

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

先把之前的内容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)查看文件状态(同步)

http模块

创建服务器基本步骤

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

//2. 创建服务器-createServer方法
let server = http.createServer();

//3. 服务器处理请求-on监听request事件
server.on("request", function() {
  console.log("我接收到请求了");
});

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

详细说明

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

request对象详解

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

常见属性:

method: 请求的方式
url: 请求的地址
headers: 所有的请求头信息
rawHeaders: 所有的请求头信息(数组的方式)

例如

let http = require('http')

let server = http.createServer()

server.on('request',(request,response) => {
    let {method,url} = request
    console.log('method :>> ', method);
    console.log('url :>> ', url);
})

server.listen(8000,() => console.log('server start success at 8000'))

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

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

response对象详解

文档地址: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 404 500
res.statusMessage: 响应的状态信息, OK Not Found ,会根据statusCode自动设置。
res.setHeader(name, value); 设置响应头信息, 比如content-type
res.writeHead(statusCode, statusMessage, options); 设置响应头,同时可以设置状态码和状态信息。

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

案例

let http = require('http')

let server = http.createServer()

server.on('request',(req,res) => {
    // res.writeHead(200,{'content-type': 'application/json'})
    // res.write(JSON.stringify({name: 'tom' , age: 18}))
    // res.end()

    //res.setHeader('content-type', 'application/json')
    res.end(JSON.stringify({name: 'jerry' , age: 18}))
})

server.listen(8000,() => console.log('server start success at 8000'))

根据不同请求输出不同响应数据

  • request.url
  • req.url:获取请求路径
    • 例如:请求http://127.0.0.1:3000/index 获取到的是:/index
    • 例如:请求http://127.0.0.1:3000/ 获取到的是:/
    • 例如:请求http://127.0.0.1:3000 获取到的是:/
let http = require('http')

let server = http.createServer()

server.on('request',(req,res) => {
    let {url} = req
    console.log('url :>> ', url);
    let pageName = '未知页面'
    if('/' === url){
        pageName = '首页'
    }else if('/login' === url){   
        pageName = '登录页'
    }
    res.setHeader('content-type', 'application/json')
    res.end(pageName)
})

server.listen(8000,() => console.log('server start success at 8000'))

服务器响应文件

浏览器中输入的URL地址,仅仅是一个标识,不与服务器中的目录一致。

也就是说:返回什么内容是由服务端的逻辑决定

let http = require('http')
let fs = require('fs')

let server = http.createServer()

server.on('request',(req,res) => {
    let {url} = req
    console.log('url :>> ', url);
    let pageName = 'error.html'
    if('/' === url){
        pageName = 'index.html'
    }else if('/login' === url){   
        pageName = 'login.html'
    }
    //读取不同的文件,返回
    fs.readFile(`./pages/${pageName}`,'utf-8',(err,data) => {
        res.setHeader('content-type', 'text/html')
        res.end(data)
    })
})

server.listen(8000,() => console.log('server start success at 8000'))

MIME类型

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

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

url模块

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

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

path模块

路径拼接

语法:path.join(path1,path2…)

作用:用来拼接字符串

const path = require('path')
//1 基本用法(掌握)
const res1 = path.join(__dirname,'1.txt')
console.log('res1:',res1) // res1: D:\webcode\node-test\6.path模块\1.txt
//2 一个../ 会抵消一个上一级目录(了解)
const res2 = path.join(__dirname,'../','1.txt')
console.log('res2:',res2) // res2: D:\webcode\node-test\1.txt
//3 ./ 会被忽略(了解)
const res3 = path.join(__dirname,'./','1.txt')
console.log('res3:',res3) // res3: D:\webcode\node-test\6.path模块\1.txt

获取文件名

使用 path.basename() 方法,可以获取路径中的最后一部分,常通过该方法获取路径中的文件名

语法:path.basename(path[, ext])

  • path: 文件路径
  • ext: 文件扩展名(可选)
const path = require('path')
const filepath = 'D:/webcode/node-test/1.txt'
//获取文件名带后缀
console.log(path.basename(filepath))//1.txt 
//获取文件名不带后缀
console.log(path.basename(filepath,'.txt'))//1 

获取扩展名

语法:path.extname(filepath)

const filepath = 'D:/webcode/node-test/1.txt'
//获取扩展名
console.log(path.extname(filepath)) //.txt

包管理器-npm

npm的基本概念

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

npm 由三个独立的部分组成:
    网站
    注册表(registry)
    命令行工具 (CLI)
  • 作用:通过npm来快速安装开发中使用的包
  • npm不需要安装,只要安装了node,就自带了npm

npm基本使用

  • 初始化包
npm init;    //这个命令用于初始化一个包,创建一个package.json文件,我们的项目都应该先执行npm init
npm init -y;  //快速的初始化一个包, 不能是一个中文名
  • 安装包
npm install 包名;  //安装指定的包名的最新版本到项目中
npm install 包名@版本号;  //安装指定包的指定版本

npm i 包名; //简写
  • 卸载包
npm uninstall 包名;  //卸载已经安装的包

npm uni 包名;//简写

本地安装和全局安装

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

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

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

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缓存:

npm cache clean -f  // -f强制清除

npm下载加速-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程序

art-template 模板引擎

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

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

服务端重定向

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

post请求参数处理

说明:POST请求可以发送大量数据,没有大小限制

// 接受POST参数
var postData = []

// 给req注册一个data事件, 只要浏览器给服务器发送post请求,data事件就会触发
// post请求发送的数据量可以很大, 这个data事件会触发多次,一块一块的传输
// 要把所有的chunk都拼接起来
// data事件:用来接受客户端发送过来的POST请求数据
var result = "";
req.on('data', function (chunk) {
  result += chunk;
})

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

模块化

模块化规范:

  • AMD: requirejs
  • CMD: seajs 玉伯 浏览器端的模块
  • commonJS: nodejs 服务端的模块

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

  1. 一个js文件就是一个模块
  2. 每个模块都是一个独立的作用域,在这个而文件中定义的变量、函数、对象都是私有的,对其他文件不可见。

node模块分类

  • 1 核心模块

    • 由 node 本身提供,不需要单独安装(npm),可直接引入使用。例如:fs模块、path模块等
  • 2 第三方模块

    • 由社区或个人提供,需要通过npm安装后使用。
  • 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

  • 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

CommonJS 规范参考文档

node操作mysql数据库

数据库准备:

1.安装mysql5数据库

2.在mysql中创建nodedb这个数据库(可利用Navicat图形工具)

3.在nodedb数据库中添加users表 (可利用Navicat图形工具)

建表sql语句如下:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) ,
  `password` varchar(255) ,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

配置 mysql 模块

  1. 安装 mysql 模块
npm install mysql
  1. 建立连接
const mysql = require('mysql')

const db = mysql.createPool({
  host: '127.0.0.1',
  user: 'root', //mysql账号
  password: 'root',//mysql密码
  database: 'nodedb', //自己创建的mysql库 
})
  1. 测试是否正常工作
db.query('select 1', (err, results) => {
  if (err) console.log(err.message)
  console.log(results)
})

node操作mysql增删改查

添加数据

第一种添加方式:

//1.准备数据
const {name,username,password} = {name: '汤姆', username: 'tom', password: '123'}
//2.准备sql ?标识占位符
const sqlStr = 'insert into users (name,username, password) values(?,?,?)'
//3.执行sql 参数1:sql字符串  参数2:占位符对应的数据 参数3:插入之后的回调
db.query(sqlStr,[name,username,password],(err,res) => {
  if(err) console.log('err :>> ', err);
  console.log('res :>> ', res);
  if(res.affectedRows == 1) console.log('插入成功!')
})

第二种添加方式:向表中新增数据时,如果数据对象的每个属性和数据表的字段一一对应,则可以通过如下方式快速插入数据:

const obj = {name: '汤姆', username: 'tom', password: '123'}
db.query('insert into  users set ?',obj,(err,res) => {
   if(err) console.log('err :>> ', err)
   console.log('res :>> ', res);
   if(res.affectedRows == 1) console.log('插入成功11!')
})

查询数据

//查询数据
db.query('select * from users',(err,res) => {
  console.log('res :>> ', res);
})

修改数据

const {username,password,name,id} = {id: 3, name: '杰瑞', username: 'jerry', password: '456'}
const sqlStr = 'update users set username=?,password=?,name=? where id=?'
db.query(sqlStr,[username,password,name,id],(err,res) => {//注意第二个参数的顺序
   if(err) console.log('err :>> ', err)
   if(res.affectedRows == 1) console.log('修改成功!')
})

快捷修改方式

const obj = {id: 3, name: '杰瑞1', username: 'jerry1', password: '4516'}
const sqlStr = 'update users set ? where id=?'
db.query(sqlStr,[obj,obj.id],(err,res) => {//注意第二个参数的顺序
  if(err) console.log('err :>> ', err)
  if(res.affectedRows == 1) console.log('修改成功!')
})

删除数据

db.query('delete from users where id=?',[1],(err,res) => {
  if(res.affectedRows == 1) console.log('删除成功!')
})

node操作mongodb数据库

安装:npm i mongodb

// 导入 mongodb,并获取到客户端对象
const MongoClient = require('mongodb').MongoClient

// 连接数据库服务地址
const url = 'mongodb://localhost:27017'

// 连接数据库
MongoClient.connect(url, function (err, client) {
  if (err) {
    return console.log('链接数据库失败', err)
  }

  console.log('数据库链接成功');

  // 获取集合对象
  const db = client.db('nodedb')

  // 关闭数据库链接
  client.close()
})

数据增删改查

  • 添加数据
const db = client.db('nodedb')

// 添加
db.collection('users')
  // 添加一条数据
  .insert({name: 'rose', age: 19}, function (err, data) {
    console.log(data);
  })
  // 添加多条数据
  .insertMany([{ name: 'tom', age: 20 }, { name: 'jerry', age: 21 }], function (err, data) {
    console.log(data);
  })
  • 查询数据
const db = client.db('nodedb')

// 查询
db.collection('users').find().toArray(function (err, data) {
  console.log(data)
})
  • 修改数据
const db = client.db('nodedb')

db.collection('users').update({ name: 'tom' }, { $set: { age: 22 } }, function (err, result) {
    console.log(result);
  })
  • 删除数据
const db = client.db('nodedb')

db.collection('users')
  // 删除一条数据:
  .deleteOne({name: 'rose'}, function (err, result) {
    console.log(result);
  })
  // 删除多条数据:
  .deleteMany({age: {$lt: 20}}, function (err, result) {
    console.log(result);
  })

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('服务器已启动')
})

参数说明

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

托管静态资源

  • 通过 express.static() 方法可创建静态资源服务器,向外开放访问静态资源。
  • Express 在指定的静态目录中查找文件,并对外提供资源的访问路径,存放静态文件的目录名不会出现在 URL 中
  • 访问静态资源时,会根据托管顺序查找文件
  • 可为静态资源访问路径添加前缀
app.use(express.static('static'))
app.use('/web', express.static('web'))

//可直接访问 static 目录下的静态资源
http://localhost:3000/img/2.jpg

//通过带有 /web 前缀的地址访问 bruce 目录下的文件
http://localhost:8080/web/img/1.jpg

request和response

request常用方法

  • query属性:获取get请求参数,是一个对象
//传参:http://localhost:8088/user?name=jerry&age=18

//处理请求
app.get('/user',(req,resp) => {
    resp.send(req.query)//将get参数直接返回
})
  • body:获取POST请求参数,需要配置body-parser模块, POST请求参数
//导入bodyParser模块
const bodyParser = require('body-parser')
//将POST请求参数转化为对象,存储到req.body中 (application/x-www-form-urlencoded方式)
app.use(bodyParser.urlencoded({ extended: true }))
app.post('/user',(req,resp) => {
    resp.send(req.body)//将post参数直接返回
})
  • params:获取restful风格的参数
//传参:http://localhost:8088/user/jerry/19

app.put('/user/:name/:age',(req,resp) => {
    resp.send(req.params)//将restful参数直接返回
})

response常用方法

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

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

// 设置HTTP响应码
res.status(200);

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

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

expres中使用art-template

安装

npm install art-template 
npm install express-art-template

给express绑定一个模版引擎

//给express设置模版引擎
//参数1: 模版引擎的后缀名,  以后的模版文件都应该是 html结尾
//参数2: 使用什么模版引擎
app.engine("html", require('express-art-template'))

通过res.render()渲染模版引擎

//参数1; 模版文件的路径,相对路径,回去views目录下查找
//参数2: 数据
res.render(path.join(__dirname, "index.html"), {name:"zs"})

关于模版引擎的配置(了解)

//模版文件默认去aa目录下查找  默认值:  views
app.set("views", "aa");

//设置模板引擎的默认后缀
app.set("view engine", "html");

路由

创建路由模块:

// router.js

const express = require('express')
// 创建路由对象
const router = express.Router()

// 挂载具体路由
router.get('/user/list', (req, res) => {
  res.send('Get user list.')
})
router.post('/user/add', (req, res) => {
  res.send('Add new user.')
})

// 向外导出路由对象
module.exports = router

注册路由模块:

const express = require('express')
const router = require('./router')

const app = express()

// 注册路由模块,添加访问前缀
app.use('/api', router) //测试时记得代码 /api 前缀

app.listen(8088, () => {
  console.log('http://127.0.0.1')
})

中间件

  • 中间件是指流程的中间处理环节
  • 服务器收到请求后,可先调用中间件进行预处理(比如登录拦截、错误拦截等)
  • 中间件是一个函数,包含 req, res, next 三个参数,next() 参数把流转关系交给下一个中间件或路由

中间件注意事项;

  • 在注册路由之前注册中间件(错误级别中间件除外)
  • 中间件可连续调用多个
  • 别忘记调用 next() 函数
  • next() 函数后别写代码
  • 多个中间件共享 reqres对象

全局中间件

  • 通过 app.use() 定义的中间件为全局中间件
const express = require('express')
const app = express()

// 定义第一个全局中间件
app.use((req, res, next) => {
  console.log('调用了第1个全局中间件')
  next()
})
// 定义第二个全局中间件
app.use((req, res, next) => {
  console.log('调用了第2个全局中间件')
  next()
})

app.get('/user', (req, res) => {
  res.send('User page.')
})

app.listen(80, () => {
  console.log('http://127.0.0.1')
})

局部中间件

const express = require('express')
const app = express()

// 定义中间件函数
const mw1 = (req, res, next) => {
  console.log('调用了第一个局部生效的中间件')
  next()
}

const mw2 = (req, res, next) => {
  console.log('调用了第二个局部生效的中间件')
  next()
}

// 两种定义局部中间件的方式
app.get('/hello', mw2, mw1, (req, res) => res.send('hello page.'))
app.get('/about', [mw1, mw2], (req, res) => res.send('about page.'))

app.get('/user', (req, res) => res.send('User page.'))

app.listen(80, function () {
  console.log('Express server running at http://127.0.0.1')
})

中间件分类

应用级别的中间件

通过 app.use()app.get()app.post() ,绑定到 app 实例上的中间件

路由级别的中间件

绑定到 express.Router() 实例上的中间件,叫做路由级别的中间件。用法和应用级别中间件没有区别。应用级别中间件是绑定到 app 实例上,路由级别中间件绑定到 router 实例上。

const app = express()
const router = express.Router()

router.use(function (req, res, next) {
  console.log(1)
  next()
})

app.use('/', router)
错误级别的中间件
  • 用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题
  • 错误级别中间件的处理函数中,必须有 4 个形参,形参顺序从前到后分别是 (err, req, res, next)
  • 错误级别的中间件必须注册在所有路由之后
const express = require('express')
const app = express()

app.get('/', (req, res) => {
  throw new Error('服务器内部发生了错误!')
  res.send('Home page.')
})

// 定义错误级别的中间件,捕获整个项目的异常错误,从而防止程序的崩溃
app.use((err, req, res, next) => {
  console.log('发生了错误!' + err.message)
  res.send('Error:' + err.message)
})

app.listen(80, function () {
  console.log('Express server running at http://127.0.0.1')
})
内置中间件

自 Express 4.16.0 版本开始,Express 内置了 3 个常用的中间件,极大的提高了 Express 项目的开发效率和体验:

  • express.static 快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼容性)
  • express.json 解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
  • express.urlencoded 解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
第三方中间件

CORS 跨域资源共享

  • CORS(Cross-Origin Resource Sharing,跨域资源共享)解决跨域,是通过 HTTP 响应头决定浏览器是否阻止前端 JS 代码跨域获取资源
  • 浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了 CORS 相关的 HTTP 响应头,就可解除浏览器端的跨域访问限制
  • CORS 主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了 CORS 的接口。
  • CORS 在浏览器中有兼容性。只有支持 XMLHttpRequest Level2 的浏览器,才能正常访问开启了 CORS 的服务端接口(例如:IE10+、Chrome4+、FireFox3.5+)。

CORS 常见响应头

  • Access-Control-Allow-Origin:制定了允许访问资源的外域 URL
res.setHeader('Access-Control-Allow-Origin', 'http://bruceblog.io')
res.setHeader('Access-Control-Allow-Origin', '*')
  • Access-Control-Allow-Headers
  • 默认情况下,CORS 仅支持客户端向服务器发送如下的 9 个请求头:Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type (值仅限于 text/plain、multipart/form-data、application/x-www-form-urlencoded 三者之一)
  • 如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过 Access-Control-Allow-Headers 对额外的请求头进行声明,否则这次请求会失败!
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Custom-Header')
  • Access-Control-Allow-Methods
  • 默认情况下,CORS 仅支持客户端发起 GET、POST、HEAD 请求。如果客户端希望通过 PUT、DELETE 等方式请求服务器的资源,则需要在服务器端,通过 Access-Control-Alow-Methods 来指明实际请求所允许使用的 HTTP 方法
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE, HEAD')
res.setHEader('Access-Control-Allow-Methods', '*')

CORS 请求分类

简单请求
  • 请求方式:GET、POST、HEAD 三者之一
  • HTTP 头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type(只有三个值 application/x-www-formurlencoded、multipart/form-data、text/plain)
预检请求
  • 请求方式为 GET、POST、HEAD 之外的请求 Method 类型
  • 请求头中包含自定义头部字段
  • 向服务器发送了 application/json 格式的数据

在浏览器与服务器正式通信之前,浏览器会先发送 OPTION 请求进行预检,以获知服务器是否允许该实际请求,所以这一次的 OPTION 请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据

express中使用cors

安装中间件:npm install cors
导入中间件:const cors = require('cors')
配置中间件:app.use(cors())

身份认证

jwt认证

前后端分离推荐使用 JWT(JSON Web Token)认证机制,是目前最流行的跨域认证解决方案

Session 的局限性

  • Session 认证机制需要配合 Cookie 才能实现。由于 Cookie 默认不支持跨域访问,所以,当涉及到前端跨域请求后端接口的时候,需要做很多额外的配置,才能实现跨域 Session 认证。
  • 当前端请求后端接口不存在跨域问题的时候,推荐使用 Session 身份认证机制。
  • 当前端需要跨域请求后端接口的时候,不推荐使用 Session 身份认证机制,推荐使用 JWT 认证机制

JWT 工作原理图

用户的信息通过 Token 字符串的形式,保存在客户端浏览器中。服务器通过还原 Token 字符串的形式来认证用户的身份。

image-20221030192657149

JWT 组成部分

  • Header、Payload、Signature
  • Payload 是真正的用户信息,加密后的字符串
  • Header 和 Signature 是安全性相关部分,保证 Token 安全性
  • 三者使用 . 分隔
Header.Payload.Signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTcsInVzZXJuYW1lIjoiQnJ1Y2UiLCJwYXNzd29yZCI6IiIsIm5pY2tuYW1lIjoiaGVsbG8iLCJlbWFpbCI6InNjdXRAcXEuY29tIiwidXNlcl9waWMiOiIiLCJpYXQiOjE2NDE4NjU3MzEsImV4cCI6MTY0MTkwMTczMX0.bmqzAkNSZgD8IZxRGGyVlVwGl7EGMtWitvjGD-a5U5c

JWT的使用方式

  • 客户端会把 JWT 存储在 localStorage 或 sessionStorage 中
  • 此后客户端与服务端通信需要携带 JWT 进行身份认证,将 JWT 存在 HTTP 请求头 Authorization 字段中
  • 加上 Bearer 前缀
Authorization: Bearer <token>

Express 使用 JWT认证

  1. 安装:jsonwebtoken 用于生成 JWT 字符串;express-jwt 用于将 JWT 字符串解析还原成 JSON 对象
npm install jsonwebtoken express-jwt
  1. 定义 secret 密钥
  • 为保证 JWT 字符串的安全性,防止其在网络传输过程中被破解,需定义用于加密和解密的 secret 密钥
  • 生成 JWT 字符串时,使用密钥加密信息,得到加密好的 JWT 字符串
  • 把 JWT 字符串解析还原成 JSON 对象时,使用密钥解密
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')

// 密钥为任意字符串
const secretKey = 'hlf'
  1. 生成 JWT 字符串
app.post('/api/login', (req, res) => {
  ...
  res.send({
    status: 200,
    message: '登录成功',
    // jwt.sign() 生成 JWT 字符串
    // 参数:用户信息对象、加密密钥、配置对象-token有效期
    // 尽量不保存敏感信息,因此只有用户名,没有密码
    token: jwt.sign({username: userInfo.username}, secretKey, {expiresIn: '10h'})
  })
})
  1. JWT 字符串还原为 JSON 对象
  • 客户端访问有权限的接口时,需通过请求头的 Authorization 字段,将 Token 字符串发送到服务器进行身份认证
  • 服务器可以通过 express-jwt 中间件将客户端发送过来的 Token 解析还原成 JSON 对象
// unless({ path: [/^\/api\//] }) 指定哪些接口无需jwt认证
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }))
  1. 获取用户信息
  • 当 express-jwt 中间件配置成功后,即可在那些有权限的接口中,使用 req.user 对象,来访问从 JWT 字符串中解析出来的用户信息
app.get('/admin/getinfo', (req, res) => {
  console.log(req.user)
  res.send({
    status: 200,
    message: '获取信息成功',
    data: req.user,
  })
})
  1. 捕获解析 JWT 失败后产生的错误
  • 当使用 express-jwt 解析 Token 字符串时,如果客户端发送过来的 Token 字符串过期或不合法,会产生一个解析失败的错误,影响项目的正常运行
  • 通过 Express 的错误中间件,捕获这个错误并进行相关的处理
app.use((err, req, res, next) => {
  if (err.name === 'UnauthorizedError') {
    return res.send({ status: 401, message: 'Invalid token' })
  }
  res.send({ status: 500, message: 'Unknown error' })
})

session认证

服务端渲染推荐使用 Session 认证机制

session认证原理

image-20221030194211947

Express中使用session认证

  1. 安装 express-session 中间件
npm install express-session
  1. 配置中间件
const session = require('express-session')
app.use(
  session({
    secret: 'hlf', // secret 的值为任意字符串
    resave: false,
    saveUninitalized: true,
  })
)
  1. 向 session 中存数据

中间件配置成功后,可通过 req.session 访问 session 对象,存储用户信息

app.post('/api/login', (req, res) => {
  req.session.user = req.body
  req.session.isLogin = true

  res.send({ status: 0, msg: 'login done' })
})
  1. 从 session 取数据
app.get('/api/username', (req, res) => {
  if (!req.session.isLogin) {
    return res.send({ status: 1, msg: 'fail' })
  }
  res.send({ status: 0, msg: 'success', username: req.session.user.username })
})
  1. 清空 session
app.post('/api/logout', (req, res) => {
  // 清空当前客户端的session信息
  req.session.destroy()
  res.send({ status: 0, msg: 'logout done' })
})
------ 本文结束,感谢您的阅读 ------
本文作者: 贺刘芳
版权声明: 本文采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。