1.Node.JS简介
- Node.JS是基于浏览器引擎的JavaScript后端运行环境
- Node.JS无法调用DOM和BOM等浏览器内置API
2.fs文件系统模块
- 使用fs.readFile(path[,options],callback)和fs.writeFile(path,data[,options],callback)来读写文件内容
- 导入fs模块
const fs = require('fs');
//fs以utf-8格式读取文件内容
fs.readFile('./filepath/read.txt','utf-8',function(err,datastr){
if(err){
return console.log('读取失败'+err.message);
}
console.log('读取成功'+datastr);
})
//fs向文件写入内容,data为写入内容
fs.readFile('./filepath/write.txt','abckdfff','utf-8',function(err){
//写入成功为null,写入失败为错误对象
return console.log(err);
})
3.path模块
- 是用来处理路径问题的模块
- path.join() 将多个路径片段拼接成一个完整的路径字符串
- path.basename() 从路径字符串中将文件名解析出来
- path.extname() 获取路径中拓展名部分
const path = require('path');
//注意../会抵消前面的路径
path.join('/a','/b/c','../','./d'.'e');//结果\a\b\d\e
const fpath = '/a/b/c/d/index.txt';
console.log(path.basename(fpath));//输出index.txt
console.log(path.extname(fpath));//输出.txt
4.http模块
(1) 创建web服务器
//导入模块
const http = require('http');
//创建实例
const server = http.creatServer();
//绑定request事件,监听客户端请求
server.on('request',(req,res)=>{
//req可以访问与客户端相关的数据或属性,url是请求的url地址,method是请求的方法
const str = `your request url is ${req.url},request method is ${req.method}`;
//解决中文乱码问题,设置响应头
res.setHeader('Content-Type','etxt/html:cahrset=utf-8');
//调用res.end()向客户端响应内容
res.end(str);
});
//启动web服务器
server.listen(80,()=>{
console.log('http server running');
});
(2)根据不同的url来响应不同的内容
server.on('request',(req,res)=>{
const url = req.url;
let content = `<h1>404 not found</h1>`;
if(url === '/' || url === `/index.html`){
content = `<h1>首页</h1>`;
}
else if(url === `/about.html`){
content = `<h1>关于页面</h1>`;
}
res.setHeader('Content-Type','etxt/html:cahrset=utf-8');
res.end(content);
});
5.模块化
(1)加载模块:使用require方法,可以加载内置模块、用户自定义模块、第三方模块进行使用
(2)module.exports(),以当前指向的对象为准
module.exports.username = 'zs';//挂载属性
module.exports.sayhello = function(){console.log('Hello');};//挂载方法
(3)exports对象,是module.exports的简化,默认情况下两者指向同一个空对象,最终require导入的结果以module.exports指向的对象为准
6. npm与包
- 导入包
const moment = require('moment');
const dt = moment().format('YYYY-MM-DD HH-mm-ss');
console.log(dt);
- 项目中安装包,打开项目对应终端安装
npm install包完整名称
//简写
npm i 完整名称
//安装指定版本包
npm i 完整名称@版本号
//卸载包
npm uninstall 包完整名称
- 通过package.json记录安装的包信息
//快速创建package.json文件
npm init -y
7.模块的加载机制
(1)优先从缓存中加载,即多次调用require()不会重复加载
(2)内置模块的加载优先级最高
(3)加载自定义模块需要在路径前加上’./‘或者’…/’
(4)第三方模块的加载机制:先尝试从/node_modules加载,若没有则移动到上级父目录,直到根目录
8.Express
(1)通过express.static()创建静态资源服务器
const express = require('express');
const app = express();
//可以访问clock文件夹内所有文件,存放静态文件的目录不会出现在URL中
app.use(express.static('./clock'));
//托管多个静态资源,多次引用express.static()即可,调用资源时按顺序查找
app.use(express.static('./files'));
//可以在前面加上前缀
app.use('/public',express.static('files'));
app.listen(80,()=>{
console.log('running');
})
(2)express路由
app.GET('./sever',function(req,res){
res.send('hello')
});
app.POST('./sever',function(req,res){
res.send('hello')
});
app.all('./sever',function(req,res){
res.send('hello')
});
(3)模块化路由具体步骤:
- 创建路由模块对应js文件
- 调用express.Router函数创建路由对象
- 向路由对象上挂载具体路由
- 使用module.exports向外共享路由对象
- 使用app.use()函数注册路由模块,同时可以像静态资源那样为路由模块添加前缀
//模块化js文件
const express = require('express');
const router = express.Router();//创建路由对象
router.get('/user/list',function(req,res){
res.send('hello');
});
router.post('/user/add',function(req,res){
res.send('hello');
});
module.exports = router;
//引用路由模块
const express = require('express');
const app = express();
const router = require('./router');
//注册路由模块
app.use(router);//注册全局中间件
app.listen(8000,()=>{
console.log('服务已启动,8000端口监听中...');
});
9.Express中间件
(1)express中间件本质上是一个函数,它的形参中必须包含next参数,区别于路由处理函数
- next函数是多个中间件连续调用的关键,它把流转关系转交给下一个中间件或者路由
(2)全局生效的中间件
- 全局生效的中间件:客户端发送的请求到服务器后都会触发的中间件
- 多个中间件之间共享同一份req和res,基于这样的特性可以在上游的中间件统一为req或res添加自定义属性或方法,供下游中间件或路由使用
- 可以用app.use()连续定义多个中间件,在客户端请求到达服务器后,会按照中间件定义的先后顺序进行调用
const mw = function(req,res,next){
console.log('这是最简单的中间件函数');
next();//把流转关系转交给下一个中间件或者路由
}
//将mw注册为全局生效的中间件
app.use(mw);
//全局生效的中间件简化形式
app.use(req,res,next)=>{
console.log('这是最简单的中间件函数');
next();
};
(3)局部生效的中间件
- 局部生效的中间件:不使用app.use()定义的中间件
const mw1 = function(req,res,next){
console.log('这是最简单的中间件函数');
next();
}
//mw1只在当前路由中生效,mw1属于局部生效的中间件
app.get('/home',mw1,function(req,res){
res.send('Home Page');
});
//mw1不会影响下面的路由
app.get('/user',function(req,res){
res.send('User Page');
});
- 如何使用多个局部生效的中间件
app.get('/user',mw1,mw2,function(req,res){res.send('User Page');});
app.get('/user',[mw1,mw2],function(req,res){res.send('User Page');});
(4)使用中间件的五个注意事项
- 一定要在路由之前注册中间件
- 客户端发送的请求可以连续调用多个中间件处理
- 执行完中间件的业务代码不要忘记next()函数
- 为了防止代码逻辑混乱,不要在next()函数后写额外的代码
- 连续调用多个中间件,它们之间共享req和res对象
(5)中间件的分类
- Express官方将常见的中间件分为了五类:应用级别的中间件、路由级别的中间件、错误级别的中间件、Express内置的中间件、第三方的中间件;
//应用级别的中间件:通过app.use()或app.get()或app.post()绑定到app实例上的中间件
app.use(req,res,next)=>{next();}
app.get('./server',mw,(req,res)=>{res.send('hello');});
//路由级别的中间件:绑定到express.Router()实例上的中间件
var express = require('express');
var router = express.Router();
router.use(function(req,res){
console.log('time'+Data.now());
next();
});
app.use('/',router);
//错误级别的中间件:专门用来捕捉项目中发生的异常错误,从而防止项目异常崩溃的问题,并且不许有四个参数(err,req,res,next)
app.get('/',function(req,res){
throw new Error('服务器内发生了错误!');//抛出一个自定义错误
res.send('Home Page');
})
app.use(function(err,req,res,next){
console.log('发生了错误:'+err.message);//服务端打印错误消息
res.send('Error!'+err.message);//向客户端发送消息
})
//express内置的中间件
//express.static 快速托管静态资源的内置中间件(无兼容性)
//express.json 解析json格式的内置中间件(有兼容性,在4.16.0+版本可用)
app.use(express.json())
//express.urlencoded 解析URL-encoded格式的请求体数据(有兼容性,在4.16.0+版本可用)
app.use(express.urlencoded({ extend:false }))
//第三方中间件
//一般使用npm install xx 安装中间件,require导入,app.use()注册
(6)自定义中间件
手动模拟一个express.urlencoded中间件模块,来解析POST请求提交到服务器的表单数据
- 定义中间件
- 监听req的data事件
- 监听req的end事件
- 使用querystring模块解析请求体数据
- 将解析出来的对象解析为req.body
- 将自定义中间件封装为模块
const express = require('express');
const qs = require('querystring');
const app = express();
//解析表单的中间件
app.use(req,res,next)=>{
//定义中间件具体业务逻辑
let str = '';
//监听req的data事件
req.on('data',(chunk)=>{
str += chunk;
});
//监听req的end事件
req.on('end',()=>{
const body = qs.parse(str);
req.body = body;
next();
});
});
//到这里将中间件提取出去封装成一个模块,用module.exports导出即可使用
app.post('/server'.(req,res)=>{
res.send('ok');
})
app.listen(80,function(){
console.log('running at 127.0.0.1');
})
(7)解决接口跨域问题两个方案(CORS与JSONP)
- CORS(主流解决方案):cors是第三方中间件
//使用 npm install cors 安装中间件
//使用 const cors = require('cors');导入中间件
//在路由router之前调用app.use(cors());配置中间件
- CORS的三个响应头:
(a)Access-Control-Allow-Origin:设置跨域
//后一项可以允许特定网址跨域请求或设置为‘*’允许任意网址跨域
res.setHeader('Access-Control-Allow-Origin','http://www.baidu.com');
res.setHeader('Access-Control-Allow-Origin','*');
(b)Access-Control-Allow-Headers:客户端向服务器发送了额外的请求头信息,则需要在服务器对额外的请求头信息进行声明
res.setHeader('Access-Control-Allow-Header','Content-Type,X-Custom-Header');
(c)Access-Control-Allow-Methods:跨域资源共享,客户端希望通过特定请求方式请求资源时需要在服务器先声明确定允许使用的请求方式
res.setHeader('Access-Control-Allow-Methods','POST,GET,DELETE,HEAD');
res.setHeader('Access-Control-Allow-Methods','*');
- CORS的简单请求与预检请求
(a)简单请求
(b)预检请求
(c)简单请求与预检请求的区别
- JSONP(只适用于GET请求方法)
(a)jsonp的概念:浏览器端通过<script>标签的src属性,请求服务器上的数据,同时服务器返回一个函数的调用,这种请求数据的方式叫jsonp
(b)创建jsonp接口的注意事项:如果已经配置了CORS跨域请求,需要在CORS中间件之前先声明jsonp接口
app.get('/api/jsonp',(req,res)=>{});
app.use(cros());
app.get('/api/get',(req,res)=>{});
10.如何在项目中操作mysql
(1)安装mysql模块:
npm i mysql
(2)配置mysql模块:
//导入mysql模块
const mysql = require('mysql');
//建立与mysql数据库的连接
const db = mysql.creatPool({
host:'127.0.0.1', //数据库的ip
user:'root', //登入账号
password:'system_01110', //登入密码
database:'user' //指定操作的数据库
});
(3)测试mysql模块能否正常工作
db.query('select1',(err,results)=>{
if(err) return console.log(err.message);
console.log(results);
})
11.前后端的身份认证
(1)WEB开发模式
目前主流的开发模式有两种:
基于服务端渲染的传统开发模式:服务器发送给客户端的界面是在服务器拼接动态生成后发送回客户端的,所以不需要Ajax这样的技术去额外请求页面的数据
app.get('/server',(req,res)=>{
const user = {name:'刘洋',age:25};
const html = `<h1>姓名:${user.name};年龄:${user.age}</h1>`;
res.send(html);
});
前后端分离的新型web开发模式:后端只提供API接口,前端使用Ajax调用接口的开发模式
(2)身份认证:
- 像二维码、邮箱密码等验证用户身份的方法
- 服务器渲染一般采用Session认证机制
- 前后端分离一般使用JWT认证机制
(3)Cookie
- Cookie是存储在用户浏览器里的一段不超过4kB的字符串。它由一个名称(name)和一个值(value)和其他几个用于控制Cookie有效性、安全性、使用范围的可选属性组成。
- 不同域名下的Cookie是不同的,客户端发送请求时会自动把当前域名下的所有未过期Cookie一同发送到服务器。
- Cookie的特点:自动发送、有时效性、域名限制、4kB大小
- 客户端第一次请求服务器时,服务器会通过响应头向客户端发送一个身份认证的Cookie,客户端会自动将Cookie保存在浏览器;随后每次客户端浏览器请求服务器时会自动以请求头的方式向服务器发送身份认证Cookie以验证身份
- Cookie是不安全的,很容易被伪造
(3)Session的工作原理
12.在Express中使用Session身份认证
(1)安装express中间件
npm install express-session
(2)配置express中间件
var session = require('express-sessoin');
app.use(session{
secret : 'keybord cat',
resave : false,
saveUninitialized : true;
});
(3)向session中存数据
app.post('/api/login',(req,res)=>{
if(req.body.username !== 'admin' || req.body.password !== '000000'){
return res.send({status:1.msg:'登陆失败'});
}
});
req,session.user = req.body;//用户信息存储到session
req.session.islogin = true;//登录状态存储到session
res.send({status:0,msg:'登陆成功'});
(4)从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.username});
});
(5)清空session
app.post('/api/logout',(req,res)=>{
res.session.destroy();
res.send({status:0,msg:'退出登录'});
});
13.JWT(JSON WEB TOKEN)认证
(1)session认证的局限性:需要配合cookie才能使用,而且cookie不能跨域使用,所以需要额外配置才能实现跨域。所以在跨域请求的时候,最好采用JWT认证。
(2)JWT的工作原理
(3)JWT字符串的组成
- 一般由Header(头部)、Payload(有效荷载)、Signature(签名)组成
- Payload是真正的用户信息部分,是用户信息加密后的字符串。Header和Signature是安全性相关的部分,只是为了保障Token的安全性。
- 格式:Header.Payload.Signature
(4)在express中使用JWT(以下步骤按顺序进行)
- 安装JWT相关包:
//jsonwebtoken生成JWT字符串。express-jwt将JWT字符串还原成json对象
npm i jsonwebtoken express-jwt
- 定义secret密钥,生成JWT字符串的时候进行加密,在解析JWT的时候需要密钥进行解析,密钥本质是一个字符串。
const secretkey = 'itconstkey';
- 在登陆成功后生成JWT字符串
//调用jsonwebtoken包提供的sign()方法,将用户信息加密成JWT字符串响应客户端
app.post('/api/login',(req,res)=>{
//省略登陆失败的代码
res.send({
status:200,
message:'登陆成功',
token:jwt.sign({username:userinfo.username},secretkey,{expiresIn:'30s'});
});
});
- 将JWT字符串还原成json对象
//.unless({path:[/^\/api\//]})用来指定哪些接口不需要访问权限
//配置成功express—jwt中间件后,就可以把解析出来的用户信息挂载到req.user上
app.use(expressJWT({secret:secretKey}).unless({path:[/^\/api\//]}))
- 使用req.user对象获取用户信息
app.get('/api/login',(req,res)=>{
console,log(req.user);
res.send({
status:200,
message:'success',
data:req.user //发送给客户端的信息
});
});
- 捕获解析JWT失败后的产生的错误
app.use((err,req,res,next)=>{
//token解析失败导致的错误
if(err.name === 'UnauthorizedError'){
return res.send({status:401,message:'无效的token'});
}
//其他原因导致的错误
res.send({status:200,message:'未知的错误'});
});