一 RESTful API 理论
1.1 REST 是什么?
-
- web软件架构风格
- 用来创建网络服务
- web软件架构风格
-
- Representational State Transfer
- Representational: 数据的表现形式( 最佳实践JSON )
- State: 当前状态或数据
- Transfer: 数据传输
- Representational State Transfer
- 6个限制
- 客户-服务器(Client - Server)
- 关注点分离
- 服务端专注数据存储,提升了简单性
- 前端专注用户界面,提升了可移植性
- 无状态 (Stateless)
- 所有用户会话信息都保存在客户端
- 每次请求必须包括所有信息,不能依赖上下文信息
- 服务端不保存会话信息:提升了简单性、可靠性、可见性
- 缓存(Cache)
- 所有服务端响应都要被标为可缓存或不可缓存
- 缓存减少前后端交互,提升了性能
- 统一接口 (Uniform Interface)
- 统一指接口设计尽可能统一通用,提升了简单性、可见性
- 接口与实现解耦,使前后端可独立开发迭代
- 统一接口的子限制
- 资源的标识
- 资源是任何可命名的事物,如用户、评论等
- 每个资源可被URI唯一标识
- 通过表述来操作资源
- 表述就是Representation 如JSON XML等
- 客户端不能直接操作SQL服务端资源
- 客户端应通过表述(如JSON)来操作资源
- 自描述消息
- 每个消息(请求或响应)必须提供足够的信息让接受者理解
- 通过媒体类型(application/json , application/xml)
- HTTP方法:GET 、 POST 、 DELETE
- 是否缓存: Cache-control
- 超媒体作为应用状态引擎
- 超媒体:带文字的链接
- 应用状态:一个网页
- 引擎:驱动、跳转
- 合起来:点击链接跳转到另一个网页
- 资源的标识
- 分层系统(Layered System)
- 每层只知道相邻的一层,后面隐藏的就不知道了
- 客户端不知道是和代理还是真实服务器通信
- 其他层:安全层、负载均衡、缓存层等
- 按需代码(Code-On-Demand) 可选
- 客户端可下载运行服务端传来的代码(如JS)
- 通过减少一些功能,简化了客户端
- 客户-服务器(Client - Server)
1.2 RESTful API
- 即符合REST架构风格的API
- 基本的URI
- api.gethub.com/users
- 标准HTTP方法
- GET / POST / PUT / PATCH / DELETE
- 传输的数据媒体类型
- JSON / XML
- 实例
- GET /users ---- 获取user列表
- GET /users/12 ---- 查看某个具体的 user
- POST /users ---- 新建一个 user
- PUT /users/12 ---- 整体替换更新 user 12
- PATCH /users/:name ---- 部分更新,只更新name
- DELETE /users/12 ---- 删除 user12
1.3 RESTful API 最佳实践
- 请求设计规范
- URI使用名词,尽量用复数,如/users
- URI使用嵌套表示关联关系,如/users/12/repos/5
- 使用正确的HTTP方法,如GET / POST / PUT / DELETE
- 不符合CRUD情况:POST/action/子资源
- 响应设计规范
- 查询
https://api.github.com/users?since=100
- 分页
https://api.github.com/user/repos?page=2&per_page=100
- 字段过滤
https://api.github.com/users/username?fileds=name
- 状态码
- Status
- 错误处理
-
HTTP/1.1 422 { “message”: “Validation Failed”, “errors”: [ { “resource”: ‘Issue’, “field”: “title”, “code”: “missing_field” } ] }
-
- 查询
- 安全
- HTTPS
- 鉴权
- 限流
- 增删改查应返回什么响应?
- 增改:应返回修改的对象
- 删:应返回 204 (没有内容但成功了)
二 Koa2
2.1 简介
- 基于NodeJS下一代web框架
- Web应用和API开发领域
- 更小、更富有表现力、更健壮
- 利用async函数,丢弃了回调函数
- 增强错误处理:try catch
- 没有捆绑任何中间件
2.2 Hello world
-
npm init
-
npm i koa --save
-
npm i nodemon --save-dev
-
package.json
"scripts": { "start": "nodemon index.js" },
-
index.js
const Koa = require("koa"); const app = new Koa(); app.use((ctx) => { ctx.body = "hello world"; }); app.listen(3000);
-
npm start
2.3 koa中间件与洋葱模型
-
app.use(async (ctx, next) => { console.log(1); await next(); console.log(3); }); app.use((ctx) => { console.log(2); });
2.4 路由
- 处理不同的URL
- 处理不同的HTTP方法
- 解析URL上的参数
- 在koa中,是一个中间件
-
const Koa = require("koa"); const app = new Koa(); app.use(async ctx => { if (ctx.url === "/") { ctx.body = "this is home page"; } else if (ctx.url === "/users") { if (ctx.method === "GET") { ctx.body = "this is users list page"; } else if (ctx.method === "POST") { ctx.body = "this is create new user page"; } else { ctx.status = 405; // 方法不允许 Method Not Allowed } } else if (ctx.url.match(/\/users\/\w+/)) { const userId = ctx.url.match(/\/users\/(\w+)/)[1]; ctx.body = `this is user id ${userId}`; } else { ctx.status = 404; // 未找到 Not Found } }); app.listen(3000);
-
- 使用koa-router实现路由
-
const Koa = require("koa"); const Router = require("koa-router"); const app = new Koa(); const router = new Router(); const usersRouter = new Router({ prefix: "/users" }); app.use(router.routes()); app.use(usersRouter.routes()); const auth = async (ctx, next) => { if (ctx.url !== "/users") { ctx.throw(401); // 未受权 Unauthorized } await next(); }; router.get("/", (ctx) => { ctx.body = "this is home page"; }); usersRouter.get("/", auth, (ctx) => { ctx.body = "this is users list page"; }); usersRouter.post("/", auth, (ctx) => { ctx.body = "this is create new user"; }); usersRouter.get("/:id", auth, (ctx) => { ctx.body = `this is user ${ctx.params.id}`; }); app.listen(3000);
-
2.5 HTTP options
- 检测服务器所支持的请求方法
- 响应中 headers => allow 中会列举支持的HTTP方法
- CORS中的预检请求
- koa-router中的allowedMethods
app.use(usersRouter.allowedMethods());
- 响应options方法,告诉它所支持的请求方法
- 自动返回405(不允许)
- 比如users未实现delete,但发送了DELETE请求,将返回405
- 自动返回501(没实现)
- koa不支持的方法会返回501
2.6 增删改查响应
-
usersRouter.get("/", (ctx) => { // this is users list page ctx.body = [{ name: "jon" }, { name: "lily" }]; }); usersRouter.post("/", (ctx) => { // this is create new user ctx.body = { name: "jon" }; }); usersRouter.get("/:id", (ctx) => { // this is user jon ctx.body = { name: "jon" }; }); usersRouter.put("/:id", (ctx) => { // this is after change ctx.body = { name: "jon2" }; }); usersRouter.delete("/:id", (ctx) => { // this is after del ctx.status = 204; });
2.7 控制器
- 拿到路由分配的任务,并执行
- 在koa中,控制器也是中间件
- 获取HTTP请求参数
- Query String
- 如?q=keyword
- ctx.query:{q:li}
- 如?q=keyword
- Router Params
- 如/users/jon
- ctx.params:{id:’jon’}
- 如/users/jon
- Body
- 如{name: ‘jon’}
npm i koa-bodyparser -S
-
- Postman设置body -> row => JSON
- postman Headers添加数据 {"name": “jon”}
- ctx.request.body
- 如{name: ‘jon’}
- Header
- 如Accept, Cookie
- ctx.header
- 如Accept, Cookie
- VSCode 调试API
-
- 停止服务
- 点击Run -> Run Script: start
- 打断点
- Postman中发送请求
- 调试
- 可右键 add to watch
-
- Query String
- 处理业务逻辑
- 发数HTTP响应
- 发送Status
- 如200 / 400 等
ctx.status = 204
- 如200 / 400 等
- 发送Body
- 如{name:’jon’}
ctx.body = { name: "jon" };
- 如{name:’jon’}
- 发送Header
- 如Allow, Content-Type
ctx.set(‘Allow’, ‘GET, POST’)
- 如Allow, Content-Type
- 发送Status
- 最佳实践
- 每个资源可控制器放在不同的文件里
- 批量挂载路由
-
src/routes/index.js 批量挂载路由
const fs = require("fs"); module.exports = (app) => { fs.readdirSync(__dirname).forEach((file) => { if (file === "index.js") { return; } const route = require(`./${file}`); app.use(route.routes()).use(route.allowedMethods()); }); };
src/index.js
const routing = require("./routes"); routing(app);
-
- 将路由单独放在一个目录
-
src --routes ----index.js ----home.js ----users.js
-
routes/users.js
const Router = require("koa-router"); const router = new Router({ prefix: "/users" }); const {find, delete: del } = require("../controllers/users"); router.get("/", find); router.delete("/:id", del); //... module.exports = router;
-
- 将控制器单独放在一个目录
-
src --controlles ----home.js ----users.js
-
- 批量挂载路由
- 尽量使用类+类方法的形式编写控制器
- controlles/users.js
class UsersCtl { find(ctx) { ctx.body = db; } delete(ctx) { db.splice(ctx.params.id * 1); ctx.status = 204; } //... } module.exports = new UsersCtl();
- controlles/users.js
- 严谨的错误处理
- 运行时错误,都返回 500
- 逻辑错误
- 找不到 404
- 先决条件失败 412
ctx.throw(412)
- 无法处理的实体(参数格式不对)422
npm i koa-parameter -S
- src/index.js
controlles/users.jsconst parameter = require("koa-parameter"); app.use(parameter(app));
create(ctx){ // 检验请求 (name 为string 必选) // 如不满足自动返回 422 ctx.verifyParams({ name: {type: ‘string’, required: true} }) }
- 手写错误处理中间件
-
src/index.js 确保挂载在第一个中间件
app.use(async (ctx, next) => { try { await next(); } catch (err) { ctx.status = err.status || err.statusCode || 500; ctx.body = { message: err.message }; } });
-
- 能够捕获
ctx.throw()
- 对于500, 需增加短路条件
- 对于404, 中间件无法捕获
- 能够捕获
-
-
- koa-json-error
npm i koa-json-error -S
- src/index.js
const error = require("koa-json-error"); app.use( error({ postFormat: (err, { stack, ...rest }) => process.env.NODE_ENV === "production" ? rest : { stack, ...rest }, }) );
- 每个资源可控制器放在不同的文件里
三 登录认证
3.1 session
- 1 客户端发送用户名密码给服务端
2 服务端登录,生成sessionID保存到redis中,并返回给客户端存入cookie,设置httpOnly 和 expires
3 因客户端每次请求都会带上cookie,所以之后的每次请求sessionID 也会跟到服务端
4 服务端验证 sessionID,以此判断是否登录
3.2 JWT
- JSON Web Token
- 定义了一种紧凑且独立的方式,可将各方之间的信息作为 JSON对象进行安全传输
- 该信息可验证和信任,因为是经过数字签名的
- 构成
- 头部 Header
- typ: token的类型,这里固定为JWT
- alg: 使用的hash算法
- 有效载荷 Payload
- 存储需要传递的信息,如用户ID、用户名
- 还包含元数据,如过期时间,发布人等
- 与Header不同,Payload可加密
- 签名 Signature
- 对Header和Payload部分进行签名
- 保证Tonken在传输工程中没有被篡改或损坏
- 头部 Header
- 工作原理
- 1 客户端发送用户名密码给服务端
2 服务端登录,生成Token,并返回给客户端存入 localStorage 或 sessionStorage
3 之后的每次将Token加入请求头中的Authorization:Bearer
4 服务端验证 Token,以此判断是否登录
- 1 客户端发送用户名密码给服务端
3.3 session 对比 JWT
- 可拓展性
- JWT多服务器更好,无需redis
- 安全性
- 都会受的攻击,不要把敏感数据方式cookie后JWT中
- RESTful API
- REST要求程序无状态,session违反了此要求
- 性能
- JWT每次传输量更大
- session需要查询数据库
- 时效性
- JWT无法实时更新,需等过期
3.4 在node中使用JWT
npm i jsonwebtoken -S
启动node
node
设置环境变量
jwt = require(‘jsonwebtoken’);
然后签名
token = jwt.sign({name:’jon’}, ‘secret’);
解码
jwt.decode(token);
验证是否被窜改
jwt.verify(token, 'secret');
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!