一、搭建基本服务器
- 引入两个模块:http、fs
- 创建http对象,监听端口
- 引入url、querystring模块
- 处理静态文件请求、接收动态数据
二、模块化开发
-
模块化的意义:形成局部作用域,不会污染全局变量
- commonJS:node、webpack是其规范的实现
- node不支持ES6的模块化,但支持所有的ES6+语法
- 可以通过typescript转化,在node中使用ES6模块化
-
批量导出可输出多次
exports.属性1 = 值1
exports.属性2 = 值2
- 导出的都是属性,可导出任何类型的值
- 但导入的只是对象,通过对象的属性执行
-
默认导出只输出一次
- 默认导出只输出第一个值
module.exports = [a,b]
module.exports = {a,b}
- 当批量导出和默认导出同时存在,只输出默认导出
- 且下面的默认输出会覆盖上面的默认输出语句
- 可以导出任何类型,导出什么类型,引入的就是什么类型
-
引入的类型跟输出形式有关
- 批量导出,引入的都是对象
- 引入对象:
const module = require("路径")
- 按需使用,引入对象身上的属性
const module = require("路径").属性
- 引入对象:
- 默认导出,与引入类型相同
const module = require("路径")[i]
const module = require("路径").属性
- 没有导出,引入的就是空对象
- 引入路径:支持任何类型
- 不指定路径:先找系统模块 -> 再从项目环境找node_modules|bower_components(依赖模块) -> not found
- 指定路径:找指定路径 -> not found
- 批量导出,引入的都是对象
-
模块化代码执行
- 模块里的代码从引入的那一行开始执行
- 导出的值从引入后调用的那一行开始执行
三、express
包管理工具:npm、yarn、bower
-
接口响应
- 支持各种请求方式:get、post、put、delete...
app.请求姿势API(接口名称,处理函数) app.get(url,(req,res,next)=>{}) app.post(url,(req,res,next)=>{}) ...
-
app.use():传入中间件到app实例
- 安装中间件、路由,接受一个函数
- use响应所有的请求姿势(get,post,...)
// app.use([地址],中间件|路由|函数体) // 地址 "/" 可省略 app.listen("3000","主机",()=>{}); app.use(express.static("./www")); app.use(bodyParser());
-
app.all():处理子管道的共同业务
app.all("/admin/*",(req,res,next)=>{ next() // 管道流,流入下一管道 }) // all匹配全路径 处理所有HTTP // 需要next()延续后续
-
动态接口:admin/:ab/:abc
- 响应动态url接口地址
~/admin/abc/dadc
~/admin/s12/acs33
-
请求体/request
req.query // 获取地址栏的数据 req.body // 获取非地址栏的数据 依赖中间件 // req.body依赖中间件:body-parser req.params // 获取动态接口名 req.method // 获取前端提交方式
-
响应体/response
res.send(any) // 对等 res.write + end res.end(string|buffer) res.json(json) // 返回json res.status(404).send() // 返回状态和信息 res.jsonp(响应数据) // 调用请求时的回调函数并传递响应数据 res.sendFile(path.resolve('public/error.html')) // 渲染纯 HTML 文件 // 上部引入const path = require("path");
-
jsonp响应
app.set('jsonp callback name','回调函数名') // 默认callback app.get('/jsonp接口',(req,res,next)=>res.jsonp(数据))
-
中间件
- middleware,处理自定义业务,只处理请求到结束响应的中间部分
// npm i body-parser -S // 安装包 let bodyParser=require('body-parser') // 引入中间件 app.use(bodyParser()) // 安装中间件
-
后端跳转
app.get("/api/old", (req, res, next) => { res.redirect("/api/new"); }) // res.redirect(url) // 指向一个接口 app.get("/api/new", (req, res) => { console.log("这是新业务"); })
四、身份验证
(一)session
- 客户端用户名跟密码请求登录
- 服务端收到请求,去库验证用户名与密码
- 验证成功后,服务端种一个cookie或发一个字符到客户端,同时服务器保留一份session
- 客户端收到 响应 以后可以把收到的字符存到cookie
- 客户端每次向服务端请求资源的cookie会自动携带
- 服务端收到请求,然后去验证cookie和session,如果验证成功就向客户端返回请求的库数据
Session存储位置:服务器内存,磁盘,或者数据库里
Session存储内容:id,存储时间,用户名等说明一下登录的用户是谁
客户端携带:cookie自动带,localStorage手动带
如何保存信息给浏览器
- 前端种:
- cookie/localstorage
- 后端种:
- 服务器给浏览器种cookie: cookie-parser,只种cookie,不留session
- 服务器给浏览器种cookie的同时在服务器上生成seesion: cookie-session
cookie-session
// 安装并引入cookie-session
const cookieSession = require('cookie-session');
// 配置中间件
app.use(cookieSession({
name: "test_session", // 保存到服务器的session的名字
keys: ["a", "b", "c"], // [必传参数,代表加密层级]
maxAge:1000 //保留cookie的时间,ms
}));
// 种cookie,备份session
req.session.key=value;
// 删除cokkie、session
delete req.session.key;
req.session.key = undefined;
(二)token
- 在服务端不需要存储用户的登录记录,全部发给客户端有客户端自己存(cookie,local)
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端会签发一个 Token(加了密的字符串),再把这个 Token 发送给客户端
- 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
- 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
- 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
token的实现
// 安装并引入jsonwebtoken
const jwt = require('jsonwebtoken');
// 生成token,返回给客户端 --- 异步回调函数
// jwt.sign({username,id:"db_id"},"test_token",(err,token)=>{
// if(!err) res.send({err:0,msg:"登录成功",data:[],token});
// });
// 生成token,同步获取
let token = jwt.sign({username,id:"db_id"},"test_token");
res.send({err:0,msg:"登录成功",data:[],token});
// 获取客户端发送的token
let token = req.headers.token || req.query.token || req.body.token;
console.log(token);
// 校验token
jwt.verify(token,"test_token",(err,decode)=>{
if(err){
res.send({err:2,msg:"token校验失败",data:"no data"});
}else{
res.send({err:1,msg:"token校验成功",data:[]});
}
});
// 注销,删除token前端完成
(三)两者区别
- session需要在用户端保存信息;
- token能够避免CSRF攻击;
- token的安全性更高;
- session存在多服务器粘性问题。
五、文件上传
-
思想:前端表单->后端接收到文件本身->保存到服务器上->给数据库记录文件一些信息->库返回给nodejs相关信息->nodejs返回给前端
-
实现:multer->文件名会随机->fs模块改名->path系统模块解析磁盘路径
- 后端:multer 接受 form-data编码数据
(一)path模块
操作系统磁盘路径
-
编码
- windows:
c:\\user\\admin\\a.jpg
- mac:
~/desktop/1901
- windows:
-
UI呈现
- windows:
c:\user\admin
- mac:
~/desktop/1901
- windows:
-
API
-
磁盘路径解析 parse
// string -> object // path.parse('c:\\wamp\\xx.png') path.parse('./wamp/xx.png') //返回 { root: 'c:\\', 盘符 dir: 'c:\\wamp', 目录 base: 'xx.png', 文件名 ext: '.png', 扩展名 name: 'xx' 文件,不含扩展名 }
-
片段合并 join
path.join('磁盘路径1','磁盘路径2','磁盘路径n')
__dirname 全局|魔术变量 返回当前文件所在的磁盘路径
-
片段合并 resolve
path.resolve('磁盘路径1','磁盘路径n')
- 合并磁盘片段,从右到左找根,找到从当前向右拼接,没有找到根,以当前文件路径为根
-
(二)multer中间件
-
multer 接受 form-data编码数据,所有要求前端携带时应注意
- 如:
- 如:
-
使用
//1 引入 let multer = require('multer'); //2 实例化 let objMulter = multer({ dest: './upload' }); //dest: 指定 保存位置(存到服务器) //安装中间件 app.use(objMulter.any()); //允许上传什么类型文件,any 代表任何类型
-
中间件扩展了req请求体
req.files
app.get('/reg',(req,res)=>{ req.files // 多个文件 // req.file // 单个文件 })
fieldname: 表单name名 originalname: 上传的文件名 encoding: 编码方式 mimetype: 文件类型 buffer: 文件本身 size:尺寸 destination: 保存路径 filename: 保存后的文件名 不含后缀 path: 保存磁盘路径+保存后的文件名 不含后缀
六、后端渲染
通常根据后端返回的json数据,然后来生成html被称为前端渲染,而后端渲染是后端把json与html结合渲染好后返回到浏览器,没前端什么事了
-
模板引擎
- 无论前后谁来渲染页面,都会用到模板引擎,前端渲染页面实际上是操作dom,后端渲染页面是把数据和html字符拼接后丢给浏览器
(一)jade
-
使用
let jade = require('jade') let html = jade.renderFile('jade模板文件',{数据},{pretty:true}); //返回字符
-
jade语法
- 父子要缩进
- 属性:标签(key=value,key2=value)
- 内容: 标签 内容
其他扩展
(二)ejs
-
使用
let ejs = require('ejs') ejs.renderFile('ejs模板文件',{要合并到html数据},回调(err,data)) // err:错误,null代表没有错误 // data:渲染后的字符|流 // ejs模板:后缀名为ejs的html文件
-
ejs语法
- ejs 结构就是html
- 输出: <%= 数据名|属性名|变量名 + 表达式 %>
- 语句: <% 语句 %> 需要被<% %> 包裹
- 非转义输出: <%- 数据名|变量名 + 表达式 %>
- 载入公共:<%- include('./hd.ejs',{数据}) %>
其他扩展
七、路由
告诉你去哪,对于前端,主要是导向,告诉浏览器应该去哪,对于后端,可以理解为一个子服务,一个路由就是一个小的服务(server/app)模块,处理一个接口
-
配置和使用
- 创建模块文件:
/router/xx.js
// 1.创建路由 let router = express.Router(); // 2.路由处理响应 router.响应API(地址, 处理函数); // 3.导出路由 module.exports = router;
- 主服务:
/app.js
//安装路由 app.use('地址',router);
- 子路由/子服务/子模块:
/router/xx.js
//子路由里安装路由 嵌套 router.use('地址',子router) //截获当前路由下的部分公共业务 router.all('*',当前router路由下的验证工作) //需要next 延续
- 主路由的地址对应子路由的根
- 如:app.js:
/api/user
~~ user.js:/
- 如:app.js:
/api/user/add
~~ user.js:/add
- 如:app.js:
- 创建模块文件: