nodejs基本介绍
为什么要学习nodejs
为什么要学习服务端的开发?
- 通过学习Node.js开发理解服务器开发、Web请求和响应过程、 了解服务器端如何与客户端配合
- 作为前端开发工程师(FE)需要具备一定的服务端开发能力
- 全栈工程师的必经之路
服务器端开发语言有很多,为什么要选择nodejs
- 降低编程语言切换的成本(nodejs实质上用的还是javascript)
- NodeJS是前端项目的基础设施,前端项目中用到的大量工具 (大前端)
- nodejs在处理高并发上有得天独厚的优势
- 对于前端工程师,面试时对于nodejs有一定的要求
node.js 是什么?
node.js,也叫作node,或者nodejs,指的都是一个东西。
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中的文件操作等功能
思考:
- 在浏览器端,可以使用javascript操作文件么?
- 在nodejs端,可以使用BOM和DOM的方法么?
- 我们学习nodejs,学习什么内容?
nodejs可以干什么?
- 开发服务端程序
- 开发命令行工具(CLI),比如npm,webpack,gulp,less,sass等 vue-cli
- 开发桌面应用程序(借助 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中执行
- shift加右键打开命令窗口,执行
- 执行命令:
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模块的方法非常多,用到了哪个查哪个即可。
在nodejs中,提供了fs模块,这是node的核心模块
注意:
- 除了global模块中的内容可以直接使用,其他模块都是需要加载的。
- 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){
//捕获错误代码
//可以发送邮件或者其它智能设备给工程师
}
注意:
- 写文件的时候,会把原来的内容给覆盖掉
追加文件
语法: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("我接收到请求了");
});
详细说明
- 给服务器注册request事件,只要服务器接收到了客户端的请求,就会触发request事件
- request事件有两个参数,request表示请求对象,可以获取所有与请求相关的信息,response是响应对象,可以获取所有与响应相关的信息。
- 服务器监听的端口范围为: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' }
服务端重定向
- HTTP 状态码说明
- 301 和 302
- 说明:服务端可以通过HTTP状态码让浏览器中的页面重定向
//常规版
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模块规范。
- 一个js文件就是一个模块
- 每个模块都是一个独立的作用域,在这个而文件中定义的变量、函数、对象都是私有的,对其他文件不可见。
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
- 一个模块的导出,就看module.exports的值
- 默认exports和module.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
Express
Express 框架
- 基于 Node.js 平台,快速、开放、极简的 web 开发框架
- 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应用,并返回,即:appapp.get()
:注册一个GET类型的路由- 注意:只要注册了路由,所有的请求都会被处理(未配置的请求路径,响应404)
res.send()
:发送数据给客户端,并自动设置Content-Typeres.sendFile()
:发送文件给客户端,并自动设置Content-Type- 参数可以是:字符串、数组、对象、Buffer
- 注意:只能使用一次
req
和res
:与http模块中的作用相同,是扩展后的请求和响应对象
注册路由
- 1
app.METHOD
:比如:app.get / app.post / app.delete / app.patch - 3
app.use(path, callback)
更重要的作用是处理中间件- 注意:只要是以path开头的请求地址,都可以被use处理
- 注意:可以处理任意的请求类型
- 注意:path参数可省略,默认值为:
/
express中间件
中间件:中间处理的部件,这个部件处理完成后,会交给下一个部件处理
- express中间件是一个函数 (req,res) =>{......}
- 中间件可以获取上一个中间件的内容,req res
- 中间件,可以对内容进行进一步处理,可以对req和res进行处理
- 处理完成后,可以给下一次匹配中间件去使用
//不写"/",也是匹配所有
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
- 示例:URL为'example.com/users?sort=desc',path表示:
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
}) */
})
}
后端实现 - 登录拦截
目标: 将未登录的用户, 拦截到登录页
登录功能基本实现(辅助用的)
创建用户表
登录请求
findUser(username, password, callback)
后端实现 - 状态保持 (登录保持)
http 无状态
cookie 和 session 实现状态保持的原理
喝咖啡的例子
咖啡厅有个活动: 喝 5 杯 送 1 杯
问题1: 不可能一次喝 6 杯, 需要累积
问题2: 服务员是无状态的, 不可能记住所有的客户, 每个客户喝了多少杯
依赖 express-session库实现状态保持 - 完成登录拦截 (了解)
特征:
- 自动开启session
- 自动将 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')
})