nodejs基础

1 简介

1.1 Node.js 是什么

简单的说 Node.js 就是运行在服务端的 JavaScript。

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境

Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台。

Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好

Nodejs官方文档

1.2 浏览器和Node中的 javascript

1.2.1 浏览器中的 javascript

1 浏览器中的js组成

JS核心语法:

变量、数据类型、循环、分支、判断、函数、作用域、this 等

WebAPI核心语法

BOM操作、DOM操作、基于XMLHttpRequest的Ajax操作等

2 为什么 JS 可以操作 BOM和DOM

每个浏览器都内置了 DOM、BOM 这样的 API 函数,因此,浏览器中的 JavaScript 才可以调用它们
image

3 浏览器中的JavaScript运行环境

运行环境是指代码正常运行所需的必要环境

V8 引擎负责解析和执行 JavaScript 代码

内置 API 是由运行环境提供的特殊接口,只能在所属的运行环境中被调用
image

1.2.2 Node.js中 javascript

1 Nodejs中的js组成

没有DOM和BOM、JS核心语法

Node中一些内置API:

  • 文件的读写功能
  • 网络服务的构建(web服务器后台)
  • 网络通信
  • http服务器

2 Node.js中的JavaScript运行环境

浏览器是 JavaScript 的前端运行环境

Node.js 是 JavaScript 的后端运行环境

Node.js 中无法调用 DOM 和 BOM 等浏览器内置 API
image

3、 在 Node 中为 JavaScript 提供了一些服务器级别的 API

1.3 准备环境

1.3.1 安装node

直接去 官网 下载安装即可

1、 查看node是否安装完成,安装完成会返回对应的版本号

node -v

2、 配置node全局环境变量

此电脑、高级系统设置、高级、环境变量、path里面配置 nodejs的安装目录 D:\software\nodejs\

3、 检测path环境变量是否配置了Node.js

打开cmd,输入 path,可以看到环境变量中已经包含了 D:\software\nodejs\

1.3.2 npm 包管理工具

npm是随同Nodejs一起安装的包管理工具,能解决Nodejs代码部署上的很多问题,查看npm的版本

# 提示版本号,表示安装成功
npm -v

2 npm包管理工具

2.1 介绍

npm是随同Nodejs一起安装的包管理工具,能解决Nodejs代码部署上的很多问题,常见的使用场景有以下几种:

  • 允许用户从NPM服务器下载别人编写的第三方包到本地使用。
  • 允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。
  • 允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用。
  • https://www.npmjs.com/ 各种第三方资源包下载网站

2.2 npm 版本

# 提示版本号,表示安装成功
npm -v  或者 npm --version

# 如果是旧版本,也可以通过npm命令升级 
npm install npm -g 


# 国内直接使用 npm 的官方镜像是非常慢的,所以推荐使用淘宝 NPM 镜像
# 安装淘宝镜像
npm install -g cnpm --registry=https://registry.npm.taobao.org;
# 使用
cnpm install [模块包名称name]

# 如果你不想要安装 cnpm ,而又想要通过淘宝的服务器下载包文件,可以通过下面的命令配置,这样你每次 npm install 报名 的时候,就是从淘宝服务器下载下来的文件
npm config set registry https://registry.npm.taobao.org
# 如果你想要切换会原来的源,可以设置如下:
npm config set registry https://registry.npmjs.org

# 查看 npm 配置信息
npm config list
# 或者
npm config ger registry

2.3 npm 安装包命令

# 1.  (全局安装)下载安装依赖包 
npm install 包名 -g

# 2.  (局部安装)下载安装依赖包,同时保存依赖项到 生产dependencies依赖中
npm install 包名 --save  
npm i -S 包名 -- 简写

# 3.  (局部安装)下载安装依赖包,同时保存依赖项到 开发devDependencies依赖中
npm install 包名 --save-dev  
npm i -D 包名 -- 简写

# 4. 卸载模块       
#    卸载后,你可以到 /node_modules/ 目录下查看包是否还存在,或者使用npm ls命令查看:
#    只会删除包,不会删除 package.json中的依赖项
npm uninstall 包名 

# 5.  删除卸载包,同时删除 package.json 中的依赖项
npm uninstall 包名 -g
npm uninstall 包名 --save  或者 npm un -S 包名
npm uninstall 包名 --save-dev  或者 npm un -D 包名

# 6. 更新模块,可以把当前目录下node_modules子目录里边的对应模块更新至最新版本
npm update 包名  
# 可以把全局安装的对应命令行程序更新至最新版。
npm update 包名 -g

# 7. 搜索模块
npm search 包名 

# 8.  可以清空NPM本地缓存,用于对付使用相同版本号发布新版本代码的人
npm cache clear

# 9.  查看所有全局安装的模块
npm list -g  

# 10.  查看某个模块的版本号
npm list grunt 

# 11. 初始化项目,会自动生成  package.json  包文件
npm init   或者  npm init -y

2.3.1 全局安装和本地安装

npm 的包安装分为本地安装(local)、全局安装(global)两种

# 本地安装
npm install express --save-dev
npm install express --save

# 全局安装
npm install express -g       

本地安装

1、 将安装包放在 ./node_modules 下(运行 npm 命令时所在的目录),如果没有 node_modules 目录,会在当前执行 npm 命令的目录下生成 node_modules 目录。

2、 可以通过 require() 来引入本地安装的包。

  const fs=require('fs')

全局安装

1、 将安装包放在 /usr/local 下或者你 node 的安装目录。使用命令 npm list -g可以查看全局安装的模块

# 全局安装的目录
C:\Users\username\AppData\Roaming\npm\node_modules

2、 可以直接在命令行里使用

2.3.2 package.json 包文件

每一个项目都要有一个package.json文件(包描述文件,就像产品的说明书一样)

这个文件可以通过npm init自动初始化出来

package.json 位于模块的目录下,用于定义包的属性

name - 包名
version - 包的版本号
description - 包的描述。
homepage - 包的官网 url 
author - 包的作者姓名
contributors - 包的其他贡献者姓名
dependencies - 依赖包列表。如果依赖包没有安装,npm 会自动将依赖包安装在 node_module 目录下
repository - 包代码存放的地方的类型,可以是 git 或 svn,git 可在 Github 上
main - main 字段指定了程序的主入口文件,require('moduleName') 就会加载这个文件。这个字段的默认值是模块根目录下面的 index.js。
keywords - 关键字

对于目前来讲,最有用的是dependencies选项,可以用来帮助我们保存第三方包的依赖信息。

如果node_modules删除了也不用担心,只需要在控制面板中npm install就会自动把package.json中的dependencies中所有的依赖项全部都下载回来。

  • 建议每个项目的根目录下都有一个package.json文件
  • 建议执行npm install 包名的时候都加上--save选项,目的是用来保存依赖信息

2.3.3 package.json和package-lock.json

package-lock.json这个文件是npm5以后才有的文件

当你安装包的时候,npm都会生成或者更新package-lock.json这个文件

package-lock.json文件的主要作用:

1、 npm5以后的版本安装都不要加--save参数,它会自动保存依赖信息

2、 package-lock.json文件会包含node_modules中所有包的信息,这样的话重新npm install的时候速度就可以提升

3、 文件主要用来锁定版本的,防止其项目自动升级

2.4 node 怎么解析执行 JavaScript

1、 创建编写JavaScript脚本文件

2、 打开终端,定位脚本文件的所属目录

3、 输入node 文件名 执行对应的文件

使用 tab 键,能够快速补全路径 node + 文件tab

3 模块系统

3.1 简介

在node中,只有文件(模块)作用域,是没有全局作用域的

模块作用域:和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域

模块之间是相互独立的,要想在不同的文件模块中使用指定的模块,就必须 使用require 引入该模块

3.2 node中使用的主要功能

使用Node编写应用程序其实主要就是在使用它的以下功能

1、 EcmaScript语言:注意此处是没有DOM和BOM操作的

2、 核心模块

  • 文件操作的fs
  • http服务操作的http
  • url路径操作模块
  • path路径处理模块
  • os操作系统信息

3、 第三方模块:比如 art-template 模板引擎

4、 自定义模块:自己创建的一些js模块

3.3 NodeJS模块规范

Node.js 遵循了 CommonJS 模块化规范,CommonJS 规定了模块的特性和各模块之间如何相互依赖

CommonJS 规定:

①每个模块内部,module 变量代表当前模块。

②module 变量是一个对象,它的 exports 属性(即 module.exports)是对外的接口。

③加载某个模块,其实是加载该模块的 module.exports 属性。require() 方法用于加载模块。

3.3.1 加载 require

可以执行被加载模块中的代码,同时得到被加载模块中的exports导出的接口对象

var 自定义变量名 = require('模块')

3.3.2 导出exports

由于Node中是模块作用域,默认文件中所有的成员只在当前模块有效,所以对于希望可以被其他模块访问到的成员,我们需要把这些公开的成员都挂载到exports接口对象中就可以了

使用 require() 方法导入模块时,导入的结果,永远以module.exports指向的对象为准

分别导出多个成员

exports.a = 123;
exports.b = function(){
    console.log('bbb')
};
exports.c = {
    foo:"bar"
};
exports.d = 'hello';

统一导出多个成员

module.exports = {
    foo = 'hello',
    add:function(){
        return x+y;
    }
};

默认导出一个成员

如果一个模块需要直接导出某个成员,而非挂载的方式,可以使用下面的方式

module.exports = 'hello';

如果要对外暴露属性或方法,建议使用 exports

如果要暴露对象(类似class,包含了很多属性和方法),建议使用 module.exports

3.4 模块导出原理

在 Node 中,每个模块内部都有一个自己的 module 对象,在该 module 对象中,有一个成员叫:exports 也是一个对象,也就是说如果你需要对外导出成员,只需要把导出的成员挂载到 module.exports 中

但是我们发现,每次导出接口成员的时候都通过 module.exports.xxx = xxx 的方式很麻烦,所以,Node 为了简化你的操作,专门提供了一个变量:exports 等于 module.exports (console.log(exports === module.exports)

当一个模块需要导出单个成员的时候必须使用module.exports = xxx的方式,不能使用exports = xxx,因为每个模块最终return的是module.exports,而exports只是module.exports的一个引用,所以exports即使重新赋值,也不会影响module.exports

每个模块最终都会默认return一个module.exports对象 return module.exports

3.5 node中的模块分类

3.5.1 核心模块(内置模块)

node本身提供的一些服务API,例如http模块、fs模块、path模块、os模块等,可以直接引用并使用

3.5.2 第三方模块

一般是通过 npm 下载的第三方包,例如expree、art-templaye、jquery等,使用的时候也是直接引用即可

3.5.3 自定义模块

用户自己定义的一些js模块

注意:

  • 相对路径必须加 ./ 或者 ../
  • 可以省略后缀名
// 如果是相对路径,一定不可以省略 ./
var a = require('./a.js')

// 推荐 省略后缀名
var a = require('./a')

3.6 模块加载机制

3.6.1 优先从缓存加载

模块在第一次加载后会被缓存。 这也意味着多次调用 require() 不会导致模块的代码被执行多次。

不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率

3.6.2 内置模块

内置模块是由 Node.js 官方提供的模块,内置模块的加载优先级最高

例如,require('fs') 始终返回内置的 fs 模块,即使在 node_modules 目录下有名字相同的包也叫做 fs

3.6.3 自定义模块

使用 require() 加载自定义模块时,必须指定以 ./../ 开头的路径标识符

文件的后缀名 .js 是可以省略的

在加载自定义模块时,如果没有指定 ./../ 这样的路径标识符,则 node 会把它当作内置模块或第三方模块进行加载

同时,在使用 require() 导入自定义模块时,如果省略了文件的扩展名,则 Node.js 会按顺序分别尝试加载以下的文件:

①按照确切的文件名进行加载

②补全 .js 扩展名进行加载

③补全 .json 扩展名进行加载

④补全 .node 扩展名进行加载

⑤加载失败,终端报错

3.6.4 第三方模块

如果传递给 require() 的模块标识符不是一个内置模块,也没有以 ./../ 开头,则 Node.js 会从当前模块的父目录开始,尝试从 /node_modules 文件夹中加载第三方模块

第三方模块一般是通过 npm 下载下来的模块, 使用的时候就可以通过 require('包名') 的方式来进行加载使用

1、 先找到当前文件所处目录中的 node_modules 目录

2、 查找 node_modules 目录中的 包: node_modules/art-template

3、 查看包文件中的 package.jsonnode_modules/art-template/package.json

4、 查找 包文件中的 main属性,这个属性就记录了 该包的入口模块,然后就会加载使用这个第三方包

5、 如果 package.json 文件不存在或者 main 指定的入口模块是也没有,则 node 会自动找该目录下的 index.js,也就是说 index.js 会作为一个默认备选项

6、 如果以上所有任何一个条件都不成立,则会进入上一级目录中的 node_modules 目录查找,如果上一级还没有,则继续往上上一级查找,依次往上查找,如果直到当前磁盘根目录还找不到,最后报错: can not find module xxx

一般情况下,一个项目有且只有一个 node_modules,放在项目根目录中,这样项目中所有的子目录中的代码都可以加载到第三方包

3.6.5 目录作为模块

当把目录作为模块标识符,传递给 require() 进行加载的时候,有三种加载方式:

1、 在被加载的目录下查找一个叫做 package.json 的文件,并寻找 main 属性,作为 require() 加载的入口

2、 如果目录里没有 package.json 文件,或者 main 入口不存在或无法解析,则 Node.js 将会试图加载目录下的 index.js 文件

3、 如果以上两步都失败了,则 Node.js 会在终端打印错误消息,报告模块的缺失:Error: Cannot find module 'xxx'

3.7 标识

3.7.1 模块中路径标识

模块中的路径标识就是相对于当前文件模块,不受node命令所处路径的影响

在模块加载中,相对路径不可以省略

3.7.2 文件操作中的路径标识

在文件操作中,一般 / 相当于当前模块所处磁盘根目录

// 文件操作中 ./ 
// ./index.txt    相对于当前目录
// /index.txt    相对于当前目录
// /index.txt   绝对路径,当前文件模块所处根目录
// d:express/index.txt   绝对路径
fs.readFile('./index.txt',function(err,data){
    if(err){
       return  console.log('读取失败');
    }
    console.log(data.toString());
})

3.8 __dirname__filename

在node的每个模块中,除了require,exports等模块相关的API之外,还有两个特殊的成员:

1、 __dirname,可以用来动态获取当前文件模块所属目录的绝对路径

console.log(__dirname);  // 返回: F:\XXX\Nodejs\demo_nodejs

2、 __filename,可以用来动态获取当前文件的绝对路径(包含文件名)

console.log(__filename); // 返回: F:\XXX\Nodejs\demo_nodejs\app.js

__dirnamefilename是不受执行node命令所属路径影响的

在文件操作中,使用相对路径是不可靠的,因为node中的文件操作的路径被设计为相对于执行node命令所处的路径

所以为了解决这个问题,只需要把相对路径变为绝对路径(绝对路径不受任何影响)就可以了。

就可以使用__dirname或者__filename来帮助我们解决这个问题

在拼接路径的过程中,为了避免手动拼接带来的一些低级错误,推荐使用path.join()来辅助拼接

var path = require('path');
console.log(path.join(__dirname + '/a.txt')) // F:\XXX\Nodejs\demo_nodejs\a.txt

在文件操作中,文件操作的路径是相对于执行node命令所属的目录路径,不是相对于当前文件模块的路径

4 Web开发模式和身份验证

目前主流的 Web 开发模式有两种,分别是:基于服务端渲染的传统 Web 开发模式;基于前后端分离的新型 Web 开发模式

4.1 服务器端渲染

服务器端渲染:服务器发送给客户端的 HTML 页面,是在服务器通过字符串的拼接,动态生成的。因此,客户端不 需要使用 Ajax 这样的技术额外请求页面的数据。

app.get('/index.html', (req, res) => {
  // 1. 要渲染的数据对象  
  const user = { username: 'zs', age: 16 }
  
  // 2. 服务器端通过字符串拼接的方式,动态生成
  const html = `<h1>姓名:${user.username},年龄:${user.age}</h1>`

  // 3. 将动态生成好的页面内容,直接响应给客户端用户。因此客户端拿到的数据是已经渲染好的动态数据
  res.send(html)
})

4.1.1 优缺点

优点

1、 前端耗时少

因为服务器端负责动态生成 HTML 内容,浏览器只需要直接渲染页面即可。尤其是移动端,更省电

2、 有利于SEO

因为服务器端响应的是完整的 HTML 页面内容,所以爬虫更容易爬取获得信息,更有利于 SEO

缺点

1、 占用服务器端资源

即服务器端完成 HTML 页面内容的拼接,如果请求较多,会对服务器造成一定的访问压力

2、 不利于前后端分离,开发效率低

使用服务器端渲染,则无法进行分工合作,尤其对于前端复杂度高的项目,不利于 项目高效开发

4.2 前后端分离的开发模式

前后端分离的概念:前后端分离的开发模式,依赖于 Ajax 技术的广泛应用。简而言之,前后端分离的 Web 开发模式, 就是后端只负责提供 API 接口,前端使用 Ajax 调用接口的开发模式

4.2.1 优缺点

优点

1、 开发体验好

前端专注于 UI 页面的开发,后端专注于api 的开发,且前端有更多的选择性

2、 用户体验好

Ajax 技术的广泛应用,极大的提高了用户的体验,可以轻松实现页面的局部刷新

3、 减轻了服务器端的渲染压力

因为页面最终是在每个用户的浏览器中生成的。

缺点

不利于 SEO

因为完整的 HTML 页面需要在客户端动态拼接完成,所以爬虫对无法爬取页面的有效信息

利用 Vue、React 等前端框架的 SSR (server side render)技术能够很好的解决 SEO 问题

4.3 身份认证

身份认证(Authentication)又称“身份验证”、“鉴权”,是指通过一定的手段,完成对用户身份的确认

不同开发模式下的身份认证:

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

前后端分离推荐使用 JWT 认证机制

4.3.1 Session 认证机制

1 HTTP 协议的无状态性

HTTP 协议的无状态性,指的是客户端的每次 HTTP 请求都是独立的,连续多个请求之间没有直接的关系,服务器不会 主动保留每次 HTTP 请求的状态

Cookie 是存储在用户浏览器中的一段不超过 4 KB 的字符串。它由一个名称(Name)、一个值(Value)和其它几个用 于控制 Cookie 有效期、安全性、使用范围的可选属性组成。

不同域名下的 Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的 Cookie 一同发送到服务器

Cookie几个特性:自动发送、域名独立、过期时限、4k限制

由于 Cookie 是存储在浏览器中的,而且浏览器也提供了读写 Cookie 的 API,因此 Cookie 很容易被伪造,不具有安全 性。因此不建议服务器将重要的隐私数据,通过 Cookie 的形式发送给浏览器

客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的 Cookie,客户端会自动 将 Cookie 保存在浏览器中

随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的 Cookie,通过请求头的形式发送给 服务器,服务器即可验明客户端的身份
image

4 Session的工作原理

image

5 在Express中使用session认证

// 1.  导入 express 模块
const express = require('express')
// 2.  创建 express 的服务器实例
const app = express()

// 3.  TODO_01:配置 Session 中间件
const session = require('express-session')
app.use(
  session({
    secret: 'test', // 这个是加密字符串,任意字符串
    resave: false,
    saveUninitialized: true,
  })
)

// 托管静态页面
app.use(express.static('./pages'))
// 解析 POST 提交过来的表单数据
app.use(express.urlencoded({ extended: false }))

// 登录的 API 接口
app.post('/api/login', (req, res) => {
  // 判断用户提交的登录信息是否正确
  if (req.body.username !== 'admin' || req.body.password !== '000000') {
    return res.send({ status: 1, msg: '登录失败' })
  }

  // TODO_02:请将登录成功后的用户信息,保存到 Session 中
  // 注意:只有成功配置了 express-session 这个中间件之后,才能够通过 req 点出来 session 这个属性
  req.session.user = req.body // 用户的信息
  req.session.islogin = true // 用户的登录状态

  res.send({ status: 0, msg: '登录成功' })
})

// 获取用户姓名的接口
app.get('/api/username', (req, res) => {
  // TODO_03:请从 Session 中获取用户的名称,响应给客户端
  if (!req.session.islogin) {
    return res.send({ status: 1, msg: 'fail' })
  }
  res.send({
    status: 0,
    msg: 'success',
    username: req.session.user.username,
  })
})

// 退出登录的接口
app.post('/api/logout', (req, res) => {
  // TODO_04:清空 Session 信息
  req.session.destroy()
  res.send({
    status: 0,
    msg: '退出登录成功',
  })
})

// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(80, function () {
  console.log('Express server running at http://127.0.0.1:80')
})

4.3.2 JWT 认证机制-推荐

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

当前端请求后端接口不存在跨域问题的时候,推荐使用 Session 身份认证机制

当前端需要跨域请求后端接口的时候,不推荐使用 Session 身份认证机制,推荐使用 JWT 认证机制。

1 JWT

JWT(英文全称:JSON Web Token)是目前最流行的跨域认证解决方案

JWT 通常由三部分组成,分别是 Header(头部)、Payload(有效荷载)、Signature(签名)。 三者之间使用英文的“.”分隔,格式如下

Header.Payload.Signnature

Payload 部分是真正的用户信息,它是用户信息经过加密之后生成的字符串

Header 和 Signature 是安全性相关的部分,只是为了保证 Token 的安全性

2 JWT的使用

客户端收到服务器返回的 JWT 之后,通常会将它储存在 localStorage 或 sessionStorage 中。

此后,客户端每次与服务器通信,都要带上这个 JWT 的字符串,从而进行身份认证

推荐的做法是把 JWT 放在 HTTP 请求头的 Authorization 字段中,格式如下

Authorization:Bearer <token>

3 JWT的工作原理

image

4 在Express中使用 JWT 认证

安装项目依赖 jsonwebtoekn express-jwt

npm i jsonwebtoekn express-jwt@5.3.3

jsonwebtoken:用于生成 JWT 字符串

express-jwt:用于将 JWT 字符串解析还原成 JSON 对象

// 导入 express 模块
const express = require('express')
// 创建 express 的服务器实例
const app = express()

// TODO_01:安装并导入 JWT 相关的两个包,分别是 jsonwebtoken 和 express-jwt
const jwt = require('jsonwebtoken') // 用于生成 JWT 字符串
const expressJWT = require('express-jwt') // 用于将 JWT 字符串解析还原成 JSON 对象

// 允许跨域资源共享
const cors = require('cors')
app.use(cors())

// 解析 post 表单数据的中间件
const bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({ extended: false }))

// TODO_02:定义 secret 密钥,建议将密钥命名为 secretKey
const secretKey = 'itheima No1 ^_^'

// TODO_04:注册将 JWT 字符串解析还原成 JSON 对象的中间件
// 注意:只要配置成功了 express-jwt 这个中间件,就可以把解析出来的用户信息,挂载到 req.user 属性上
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }))

// 登录接口
app.post('/api/login', function (req, res) {
  // 将 req.body 请求体中的数据,转存为 userinfo 常量
  const userinfo = req.body
  // 登录失败
  if (userinfo.username !== 'admin' || userinfo.password !== '000000') {
    return res.send({
      status: 400,
      message: '登录失败!',
    })
  }
  // 登录成功
  // TODO_03:在登录成功之后,调用 jwt.sign() 方法生成 JWT 字符串。并通过 token 属性发送给客户端
  // 参数1:用户的信息对象
  // 参数2:加密的秘钥
  // 参数3:配置对象,可以配置当前 token 的有效期
  // 记住:千万不要把密码加密到 token 字符中
  const tokenStr = jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '30s' })
  res.send({
    status: 200,
    message: '登录成功!',
    token: tokenStr, // 要发送给客户端的 token 字符串
  })
})

// 这是一个有权限的 API 接口
app.get('/admin/getinfo', function (req, res) {
  // TODO_05:使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
  console.log(req.user)
  res.send({
    status: 200,
    message: '获取用户信息成功!',
    data: req.user, // 要发送给客户端的用户信息
  })
})

// TODO_06:使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err, req, res, next) => {
  // 这次错误是由 token 解析失败导致的
  if (err.name === 'UnauthorizedError') {
    return res.send({
      status: 401,
      message: '无效的token',
    })
  }
  res.send({
    status: 500,
    message: '未知的错误',
  })
})

// 调用 app.listen 方法,指定端口号并启动web服务器
app.listen(8888, function () {
  console.log('Express server running at http://127.0.0.1:8888')
})

11. nodejs操作mongoose

mongoose 官网

更多MongoDB基础知识参考

11.1 简介

mongoose 是node中提供操作MongoDB的模块,它是基于 MongoDB 官方的 mongodb 包进一步做了封装,可以提高开发效率,让你操作 MongoDB 数据库更方便

能够通过node语法实现MongoDB数据库增删改查,从而实现用node写程序来管理MongoDB数据库

schema: 约束字段/列数据

主要作用:用来约束MongoDB文档数据(哪些字段必须,哪些字段可选的)

model:模型

一个模型 对应 一个集合,主要用它来实现数据的增删改查

通过模型来管理集合中的数据

11.2 mongoose使用

1 项目创建和下载安装依赖

# 1.  创建项目目录
mkdir mongodb-demo

# 2. 进入项目目录
cd mongodb-demo

# 3. 初始化项目
npm init -y

# 4. 下载安装mongoose第三方模块
#    --save 可以省略,npm 5.0版本以后
npm install mongoose --save  # 或者 npm i mongoose  

2 mongoose 构建

// 0. 引入第三方模块 mongoose
const mongoose = require('mongoose');

// 1. 连接本机数据库 school ,如果指定连接的数据库不存在,当你插入第一条数据之后就会自动被创建出来
mongoose.connect('mongodb://localhost/school')

// 2. 设置集合(表)的数据架构   Schema
var Schema = mongoose.Schema({
	name: {
		type: String,
		required: true, // 必填     
	},
	age: Number, // 年龄
	// 性别
	gender: {
		type: Number,
		enum: [0, 1], // 枚举数据  
		default: 0, // 默认值     
	},
	job: String, // 职位
});

/*
    3. 将文档结构发布 一个 名为 student 的模型
    mongoose.model 方法就是用来将一个架构发布为 model
	     第一个参数:传入一个大写名词单数字符串用来表示你的数据库名称,mongoose 会自动将大写名词的字符串生成 小写复数 的集合名称:例如这里的 Student 最终会变为 students 集合名称      
	    
	    第二个参数:架构 Schema   
        
		返回值:模型构造函数    
*/
var model_student = mongoose.model('Student', Schema);

3 增删改查

3.1 保存/新增 数据 save

//  创建实例对象
var stuObj = new model_student({
	name: '李玉霞',
	age: 23,
	gender: 0,
	job: '医生'
});

// 将数据对象 保存 到集合    
stuObj.save(function(err, result) {
	if (err) return console.error(err);
	console.log('保存成功');
});

3.2 查询数据

// 1.  查询 所有 数据    
model_student.find(function(err, result) {
	if (err) return console.error(err);
	console.log(result);
})

// 2.  查询 name = 'zhangsan' 的所有数据   
model_student.find({
	name: 'zhangsan'
}, function(err, result) {
		if (err) return console.error(err);
	console.log(result);
})

// 3. 查询 name = 'zhangsan' 一条的数据       
model_student.findOne({
	name: 'zhangsan'
}, function(err, result) {
	if (err) return console.error(err);
	console.log(result);
})

3.3 删除数据

// 1.  删除 name = 'zhangsan' 的所有数据       
model_student.remove({
	name: 'zhangsan'
}, function(err, result) {
	if (err) return console.error(err);
	console.log('删除成功');
})

// 根据 id 删除一个
model_student.findByIdAndRemove(id,[options],callback)

// 根据 条件 删除一个
model_student.findOneAndRemove(id,[options],callback)

3.4 更新数据

// 根据id 更新数据         
model_student.findByIdAndUpdate('6073c730a1cf34434845a151', {
	name: 'lisi'
}, function(err, result) {
	if (err) return console.error(err);
	console.log('更新成功');
})

// 根据条件更新所有
model_student.update(conditions,doc,[,options],callback)

// 根据指定的条件更新一个
model_student.findOneAndUpdate(conditions,doc,[,options],callback)

// 根据id更新一个
model_student.findByIdAndUpdate(id,doc,[,options],callback)

3.5 分页查询,每页显示2条数据

model_student.find({}).skip(1).limit(2).then(res => {
	console.log(res) 
}).catch(err => {
	console.log(err) 
})

6 Node操作MySQL数据库

npm文档

6.1 步骤

6.1.1 安装项目依赖

npm i mysql

6.1.2 使用

// 0. 引入项目依赖 mysql
var mysql = require('mysql');

// 1. 创建数据库连接
var connection = mysql.createConnection({
	host: 'localhost',  // localhost==本机
	port: '3356',            // 端口号
	user: 'root',             // 用户名
	password: 'root',        // 密码
	database: 'dbname'         // 数据库名
});

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

// 3. 执行数据操作:增删改查

    // 3.1 查询
    const sql = 'SELECT * from member WHERE id=15;';

    // 3.2 增加一条数据
    const sql = `INSERT INTO member VALUES(NULL, "admin", "123456")`;

    // 3.3 修改
    const sql = `UPDATE member SET name='lisi' where id=1`;

    // 3.4 删除
    const sql = `DELETE FROM member WHERE id=2`;

// 执行操作
connection.query(sql, function(error, results, fields) {
	if (error) throw error;
	console.log('The solution is: ', results);
});

// 4. 关闭连接
connection.end();

7 Node常用模块

编程狮参考文档

7.1 Nodejs值 qs 模块(序列化)

7.1.1 安装和引用

1 安装模块

npm install --save qs

2 引入模块

// ES6方式 引入
import qs from'qs'
// 或者 Commonjs方式 引入
const qs = require("qs")

7.1.2 使用

1 qs.stringify()

将对象或数组序列化成URL的格式

// 1. 对象序列化
const obj = {
	methods: 'query_stu'
	id: 1,
	name: 'chenchen'
}
qs.stringify(obj) // 返回数据:methods=query_stu&id=1&name=chenchen


// 2. 数组序列化
const arr = [2,3]
qs.stringify({a:arr}) // 返回数据:'a[0]=2&a[1]=3'

2 qs.parse()

qs.parse()则就是反过来啦,将我们通过qs.stringify()序列化的对象或者数组转回去

const url = 'id=1&name=chenchen'
qs.parse(url) // 返回数据  {id:1,name:chenchen}

7.2 Nodejs之 path 路径模块

path模块是 Node.js 官方提供的、用来处理路径的模块。它提供了一系列的方法和属性,用来满足用户对路径的处理需求

官网文档Path模块

7.2.1 path.join()

可以把多个路径片段拼接为完整的路径字符串

path.join('/test', 'test1', 'test2/logs')  // 返回:/test/test1/test2/logs

// 注意:  ../ 会抵消前面的路径
const pathStr = path.join('/a', '/b/c', '../', './d', 'e')
console.log(pathStr)  // \a\b\d\e

7.2.2 dirname,basename和extname

path.dirname:获取路径中的目录部分

path.basename:获取路径的文件名,默认包含扩展名

path.extname:获取一个路径中的扩展名部分

const path = require('path')

const completePath = "/myData/myValue/bas.html";

// 获取路径中的目录部分
console.log(path.dirname(completePath)); // 返回:/myData/myValue


// 获取路径的文件名,默认包含扩展名
console.log(path.basename(completePath)); // 返回:bas.html
// 还可以传第二个参数,可以直接获取文件名
console.log(path.basename(completePath),'.html'); // 返回:bas


// 获取 扩展名
console.log(path.extname(completePath));  // 返回:.html

7.2.3 path.normalize(p)

用于规范化路径,注意'..''.'

发现多个斜杠时,会替换成一个斜杠。当路径末尾包含一个斜杠时,保留。

Windows系统使用反斜杠

path.normalize('/foo/bar//baz/asdf/quux/..')
// 返回:  '/foo/bar/baz/asdf'

7.2.4 path.resolve([from ...], to)

能够将to参数解析为绝对路径

如果参数to不是一个相对于参数from的绝对路径,to会添加到from右侧,直到找到一个绝对路径为止

如果使用所有from参数后,还是没有找到绝对路径,将会使用当前工作目录

返回的路径已经规范化过,并且去掉了尾部的斜杠(除非是根目录)

path.resolve('/foo/bar', './baz'); // 返回:'/foo/bar/baz'

path.resolve('/foo/bar', '/tmp/file/') // 返回:/tmp/file/

path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif')
// 如果当前项目在 /home/myself/node中
// 返回:/home/myself/node/wwwroot/static_files/gif/image.gif

7.2.5 path.isAbsolute(path)

判断参数path是否是绝对路径

path.isAbsolute('//server')  // true
path.isAbsolute('C:/foo/..') // true
path.isAbsolute('bar\\baz')   // false
path.isAbsolute('.')         // false

7.2.6 path.relative(from, to)

解决从fromto的相对路径

有时我们会有2个绝对路径,需要从中找到相对目录

path.relative('C:\\orandea\\test\\aaa', 'C:\\orandea\\impl\\bbb')
// 返回:  '..\\..\\impl\\bbb'


path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb')
// 返回:  '../../impl/bbb'

7.2.7 path.sep 文件分隔符

特定平台的文件分隔符,'\\''/'

// Windows平台

'foo\\bar\\baz'.split(path.sep)
// ['foo', 'bar', 'baz']

7.2.8 path.parse(pathString)

返回路径字符串的对象

path.parse('C:\\path\\dir\\index.html')
// 返回:
{
    root : "C:\\",
    dir : "C:\\path\\dir",
    base : "index.html",
    ext : ".html",
    name : "index"
}

7.3 Nodejs之fs模块

fs 模块是 Node.js 官方提供的、用来操作文件的模块。它提供了一系列的方法和属性,用来满足用户对文件的操作需求

7.3.1 fs.readFile

主要功能是:读取指定文件中的内容

fs.readFile(path[,option],callback)

参数path:必选,字符串,表示要读取的文件的路径

参数option:可选,表示以什么编码格式来读取文件,一般是 utf8

参数callback:必选,文件读取完成后,通过回调函数拿到读取的结果

// 1. 使用 require 方法加载 fs 核心模块
var fs = require('fs')

// 2. 读取文件:fs.readFile
/*

	第一个参数就是要读取的文件路径
	第二个参数是一个回调函数:回调函数的第一个参数是error,第二个参数是data
	如果读取文件成功,data返回是数据,error返回是null
	如果读取文件失败,data返回是null,error返回是错误对象

*/

fs.readFile('./data/name.txt','utf8', function (error, data) {
	// 文件中存储的数据是二进制数据 0 1,但是我们看到的数据是转化为16进制后的数据,所以可通过toString()转换字符串
	if (error) {
		return console.log('读取文件失败了', error.message)
	}
	console.log(data.toString());
})

7.3.2 fs.writeFile

主要功能:向指定的文件中写入内容

fs.writeFile(path,data[,option],callback)

参数path:必选,需要指定一个文件路径的字符串,表示文件的存放路径。

参数data:必选,表示要写入的内容。

参数option:可选,表示以什么格式写入文件内容,默认值是 utf8。

参数callback:必选,文件写入完成后的回调函数

var fs = require('fs')

/*

fs.writeFile写文件:向文件中写入数据
	第一个参数:文件路径
	第二个参数:要写入的文件内容
	第三个参数:回调函数,参数error
		如果写入文件成功,error返回是null
		如果写入文件失败,error返回是错误对象
		
*/

fs.writeFile('./data/hello.txt', '大家好,给大家介绍一下,我是Node.js', function (error) {
	if (error) {
		return console.log('写入失败', error.message)
	}
	console.log('写入成功了')
})

7.3.3 文件读取操作路径问题

在使用 fs 模块操作文件时,如果提供的操作路径是以 ./ 或 ../ 开头的相对路径时,很容易出现路径动态拼接错误的问题。

原因:代码在运行的时候,会以执行 node 命令时所处的目录,动态拼接出被操作文件的完整路径

解决方案:在使用 fs 模块操作文件时,直接提供完整的路径,不要提供 ./ 或 ../ 开头的相对路径,从而防止路径动态拼接的问题

path.join(__dirname, './files/1.txt')

7.4 Nodejs之http模块

7.4.1 概述

http 模块是 Node.js 官方提供的、用来创建 web 服务器的模块。

通过 http 模块提供的 http.createServer() 方法,就能方便的把一台普通的电脑,变成一台 Web 服务器,从而对外提供 Web 资源服务

7.4.2 服务器相关

服务器和普通电脑的区别在于,服务器上安装了 web 服务器软件,例如:IIS、Apache 等。通过安装这些服务器软件,就能把一台普通的电脑变成一台 web 服务器。

在 Node.js 中,我们不需要使用 IIS、Apache 等这些第三方 web 服务器软件。因为我们可以基于 Node.js 提供的 http 模块,通过几行简单的代码,就能轻松的手写一个服务器软件,从而对外提供 web 服务

1 IP地址

IP 地址就是互联网上每台计算机的唯一地址,因此 IP 地址具有唯一性,一般是用192.168.1.1方式表示

互联网中每台Web服务器,都有自己的IP地址,比如可以通过 ping www.baidu.com 命令,即可查看到百度服务器的 IP 地址

2 域名和域名服务器

IP地址和域名是一一对应的关系,这份对应关系存放在一种叫做域名服务器(DNS,Domain name server)的电脑中。使用者只需通过好记的域名访问对应的服务器即可,对应的转换工作由域名服务器实现。因此,域名服务器就是提供 IP 地址和域名之间的转换服务的服务器

3 端口号

在一台电脑中,可以运行成百上千个 web 服务每个 web 服务都对应一个唯一的端口号。客户端发送过来的网络请求,通过端口号,可以被准确地交给对应的 web 服务进行处理

每个端口号不能同时被多个 web 服务占用

7.4.5 创建web服务基本步骤

①导入 http 模块

②创建 web 服务器实例

③为服务器实例绑定 request 事件,监听客户端的请求(网络请求)

④启动服务器

// 1. 导入 http 模块
const http = require('http')

// 2. 创建 web 服务器实例
const server = http.createServer()

// 3. 为服务器实例绑定 request 事件,监听客户端的请求
server.on('request', function (req, res) {
  // 只要有客户端来请求我们的服务器,就会触发 request 事件,从而调用这个事件处理函数  
  console.log('Someone visit our web server.')
})

// 4. 启动服务器
server.listen(8080, function () {
  console.log('server running at http://127.0.0.1:8080')
})

1 req请求对象

req 是请求对象,包含了与客户端相关的数据和属性

  • req.url 是客户端请求的 URL 地址
  • req.method 是客户端请求的 method 类型

2 res响应对象

res是响应对象,包含了服务器相关的数据或属性

  • res.end('Hello'):向客户端发送指定的内容,并结束这次请求

3 中文乱码问题

调用 res.setHeader() 方法,设置 Content-Type 响应头,可以解决中文乱码的问题

const http = require('http')
const server = http.createServer()

server.on('request', (req, res) => {
  // 定义一个字符串,包含中文的内容
  const str = `您请求的 URL 地址是 ${req.url},请求的 method 类型为 ${req.method}`
  
  
  // 调用 res.setHeader() 方法,设置 Content-Type 响应头,解决中文乱码的问题
  res.setHeader('Content-Type', 'text/html; charset=utf-8')
    
    
  // res.end() 将内容响应给客户端
  res.end(str)
})

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

posted @ 2022-01-27 14:46  songxia777  阅读(194)  评论(0编辑  收藏  举报