node基本概念
node是什么
node.js,也叫作node,或者nodejs,指的都是一个东西。
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中的文件操作等功能
nodejs可以干什么?
- 开发服务端程序
- 开发命令行工具(CLI),比如npm,webpack,gulp,less,sass等
- 开发桌面应用程序(借助 node-webkit、
electron
等框架实现)
node安装
下载地址
官网术语解释
- LTS 版本:Long-term Support 版本,长期支持版,即稳定版。
- Current 版本:Latest Features 版本,最新版本,新特性会在该版本中最先加入。
下载之后一直点下一步即可完成安装,安装之后查看node版本
node -v
运行node程序
repl方式
- REPL 全称: Read-Eval-Print-Loop(交互式解释器)
- R 读取 - 读取用户输入,解析输入了Javascript 数据结构并存储在内存中。
- E 执行 - 执行输入的数据结构
- P 打印 - 输出结果
- L 循环 - 循环操作以上步骤直到用户两次按下 ctrl-c 按钮退出。
- 在REPL中编写程序 (类似于浏览器开发人员工具中的控制台功能)
- 直接在控制台输入
node
命令进入 REPL 环境
- 直接在控制台输入
- 按两次 Control + C 退出REPL界面 或者 输入
.exit
退出 REPL 界面- 按住 control 键不要放开, 然后按两下 c 键
node执行js文件方式(掌握)
- 创建js文件
helloworld.js
- 写nodejs的内容:
console.log('hello nodejs')
- 打开命令窗口
cmd
- shift加右键打开命令窗口,执行
node 文件名.js
即可 - 给vscode安装
terminal
插件,直接在vscode中执行
- shift加右键打开命令窗口,执行
- 执行命令:
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模块的方法非常多,用到了哪个查哪个即可。
在nodejs中,提供了fs模块,这是node的核心模块
注意:
- 除了global模块中的内容可以直接使用,其他模块都是需要加载的。
- 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");
});
详细说明
- 给服务器注册request事件,只要服务器接收到了客户端的请求,就会触发request事件
- request事件有两个参数,request表示请求对象,可以获取所有与请求相关的信息,response是响应对象,可以获取所有与响应相关的信息。
- 服务器监听的端口范围为: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对象中,常用的就是method
和url
两个参数
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:"大吉大利,今晚吃鸡"});
注意点:文件的路径必须是绝对路径
服务端重定向
- HTTP 状态码说明
- 301 和 302
- 说明:服务端可以通过HTTP状态码让浏览器中的页面重定向
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模块规范。
- 一个js文件就是一个模块
- 每个模块都是一个独立的作用域,在这个而文件中定义的变量、函数、对象都是私有的,对其他文件不可见。
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
exports
是module.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 模块
- 安装 mysql 模块
npm install mysql
- 建立连接
const mysql = require('mysql')
const db = mysql.createPool({
host: '127.0.0.1',
user: 'root', //mysql账号
password: 'root',//mysql密码
database: 'nodedb', //自己创建的mysql库
})
- 测试是否正常工作
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应用,并返回,即:appapp.get()
:注册一个GET类型的路由- 注意:只要注册了路由,所有的请求都会被处理(未配置的请求路径,响应404)
res.send()
:发送数据给客户端,并自动设置Content-Type- 参数可以是:字符串、数组、对象、Buffer
- 注意:只能使用一次
req
和res
:与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()
函数后别写代码- 多个中间件共享
req
、res
对象
全局中间件
- 通过
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 三者之一)
- 如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过 A
ccess-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 字符串的形式来认证用户的身份。
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认证
- 安装:
jsonwebtoken
用于生成 JWT 字符串;express-jwt
用于将 JWT 字符串解析还原成 JSON 对象
npm install jsonwebtoken express-jwt
- 定义 secret 密钥
- 为保证 JWT 字符串的安全性,防止其在网络传输过程中被破解,需定义用于加密和解密的 secret 密钥
- 生成 JWT 字符串时,使用密钥加密信息,得到加密好的 JWT 字符串
- 把 JWT 字符串解析还原成 JSON 对象时,使用密钥解密
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
// 密钥为任意字符串
const secretKey = 'hlf'
- 生成 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'})
})
})
- JWT 字符串还原为 JSON 对象
- 客户端访问有权限的接口时,需通过请求头的
Authorization
字段,将 Token 字符串发送到服务器进行身份认证 - 服务器可以通过 express-jwt 中间件将客户端发送过来的 Token 解析还原成 JSON 对象
// unless({ path: [/^\/api\//] }) 指定哪些接口无需jwt认证
app.use(expressJWT({ secret: secretKey }).unless({ path: [/^\/api\//] }))
- 获取用户信息
- 当 express-jwt 中间件配置成功后,即可在那些有权限的接口中,使用
req.user
对象,来访问从 JWT 字符串中解析出来的用户信息
app.get('/admin/getinfo', (req, res) => {
console.log(req.user)
res.send({
status: 200,
message: '获取信息成功',
data: req.user,
})
})
- 捕获解析 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认证原理
Express中使用session认证
- 安装 express-session 中间件
npm install express-session
- 配置中间件
const session = require('express-session')
app.use(
session({
secret: 'hlf', // secret 的值为任意字符串
resave: false,
saveUninitalized: true,
})
)
- 向 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' })
})
- 从 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 })
})
- 清空 session
app.post('/api/logout', (req, res) => {
// 清空当前客户端的session信息
req.session.destroy()
res.send({ status: 0, msg: 'logout done' })
})