restful API
为什么叫REST
6个限制
客户-服务(Client-Server)
统一接口的限制
什么是restful API
符合REST架构风格的API
具体:
现实举例:
请求设计规范
响应设计规范
安全
HTTPS 鉴权 限流
开发者友好
文档 超媒体
koa
一些命令行:
cd /d/code 切换到文件夹
mkdir xxx 创建文件夹
code xxx vsCode打开文件夹
搭建koa程序
npm init: 生成package.json文件, 管理依赖
npm i koa --save 安装koa
编写index.js --> 执行node index.js启动程序
const Koa = require('koa') // 1.引入koa
const app = new Koa() //1.创建一个应用程序
app.use(ctx => { //2.中间件
ctx.body = 'hello world 111'
})
app.listen(3000, () => console.log('程序已启动')) // 1.启动应用程序 监听端口
npm i nodemon --save-dev // 修改文件后程序自动启动 只在开发阶段使用
由于nodemon 不是全局安装的所以使用时需要修改脚本如下:
"scripts": {
"start": "nodemon index.js"
},
执行npm start 启动程序
编写koa中间件及洋葱模型
中间件:
const Koa = require('koa')
const app = new Koa()
app.use(async (ctx, next) => {
await next() //第一个中间件只有调用了next,下面的中间件才能执行,否则下面的不执行
ctx.body = 'hello world 111'
})
app.use(ctx => console.log(2)) //第二个中间件
app.listen(3000, () => console.log('程序已启动'))
洋葱模型
const Koa = require('koa')
const app = new Koa()
//第一个中间件
app.use(async (ctx, next) => {
console.log(1)
await next()
console.log(2)
ctx.body = 'hello world 111'
})
//第二个中间件
app.use(async (ctx, next) => {
console.log(3)
await next()
console.log(4)
})
//第三个中间件
app.use(async (ctx) => {
console.log(5)
})
app.listen(3000, () => console.log('程序已启动'))
// 1 3 5 4 2
路由
路由的本质是中间件
const Koa = require('koa')
const app = new Koa()
//仿路由中间件
app.use( async ctx => {
if (ctx.url ==='/') { //处理不同的url
ctx.body = '主页'
} else if (ctx.url === '/users') {
if(ctx.method === 'GET') { //处理不同的http方法
ctx.body = '用户列表'
} else if (ctx.method === 'POST') {
ctx.body = '创建用户'
} else {
ctx.status = 405
}
} else if (ctx.url.match(/\/users\/\w+/)) { //解析路由URL上的参数
const userId = ctx.url.match(/\/users\/(\w+)/)[1]
ctx.body = `这是用户${userId}`
} else {
ctx.status = 404
}
})
app.listen(3000, () => console.log('程序已启动'))
\w: 代表一个字母或者数字
\w+: 代表多个字母或者数字
正则表达式会返回一个数组, 数组的第一项是整个的URL,如果给其中的某一项加上括号,则会返回匹配里面的正则表达式作为数组的第二项
koa-router实现路由
npm i koa-router --save 安装koa-router
基本实现:
const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
const router = new Router() //实例化router
router.get('/', ctx => {
ctx.body = '主页'
})
router.get('/users', ctx => {
ctx.body = '用户列表'
})
router.post('/users', ctx => {
ctx.body = '创建用户'
})
router.get('/users/:id', ctx=>{
ctx.body = `这是用户${ctx.params.id}` //获取路由中的参数
})
app.use(router.routes()) //注册中间件
app.listen(3000, () => console.log('程序已启动'))
使用前缀:
const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
const router = new Router() //实例化router
const userRouter = new Router({prefix: '/users'}) //实例化router 前缀
router.get('/', ctx => {
ctx.body = '主页'
})
userRouter.get('/', ctx => {
ctx.body = '用户列表'
})
userRouter.post('/', ctx => {
ctx.body = '创建用户'
})
userRouter.get('/:id', ctx=>{
ctx.body = `这是用户${ctx.params.id}`
})
app.use(router.routes()) //注册中间件
app.use(userRouter.routes()) //注册中间件
app.listen(3000, () => console.log('程序已启动'))
多中间件(仿用户鉴权):
const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
const router = new Router() //实例化router
const userRouter = new Router({prefix: '/users'}) //实例化router 前缀
const auth = async (ctx, next) => { //仿用户鉴权
if (ctx.url !== '/users') {
ctx.throw(401)
}
await next()
}
router.get('/', ctx => {
ctx.body = '主页'
})
userRouter.get('/', auth, ctx => {
ctx.body = '用户列表'
})
userRouter.post('/', auth, ctx => {
ctx.body = '创建用户'
})
userRouter.get('/:id', auth, ctx=>{
ctx.body = `这是用户${ctx.params.id}`
})
app.use(router.routes()) //注册中间件
app.use(userRouter.routes()) //注册中间件
app.listen(3000, () => console.log('程序已启动'))
http options的作用
面试常问,考察对HTTP及跨域理解的深度
帮助理解koa-router的allowedMethods作用
作用:
检测服务器所支持的请求方法 (截图)
cors中的预检请求 (某个网站只有某些请求可以跨域, 可以通过options请求来预检)
allowedMethods作用:
响应options方法,告诉它所支持的请求方法(响应头的Heads中看)
响应的返回405(不允许, 比如: 如果/users这个url没有delete这个请求方法, 如果去请求就会报这个错)和501(没实现, 比如用link方法去请求会返回501, 因为koa不支持)
const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
const router = new Router() //实例化router
const userRouter = new Router({prefix: '/users'}) //实例化router 前缀
const auth = async (ctx, next) => { //仿用户鉴权
if (ctx.url !== '/users') {
ctx.throw(401)
}
await next()
}
router.get('/', ctx => {
ctx.body = '主页'
})
userRouter.get('/', auth, ctx => {
ctx.body = '用户列表'
})
userRouter.post('/', auth, ctx => {
ctx.body = '创建用户'
})
userRouter.get('/:id', auth, ctx=>{
ctx.body = `这是用户${ctx.params.id}`
})
app.use(router.routes()) //注册中间件
app.use(userRouter.routes()) //注册中间件
app.use(userRouter.allowedMethods()) //注册中间件 主要作用是所有的接口都可以用options发送请求
app.listen(3000, () => console.log('程序已启动'))
获取HTTP请求参数
query string, 如: ?q=keyword
router parama, 如: /users/:id
body, 如: {name: "李磊"}
Head, 如: accept, cookie
发送HTTP响应:
发送status, 如: 200/400等
发送body, 如: {name: "李磊"}
发送Head, 如: allow content-type
异常状况有哪些
运行时错误, 都返回500
逻辑错误:
404: 找不到
412: 先决条件失败
422: 无法处理的实体, 参数格式不对
获取http请求参数
?q=keyword --> ctx.query
/users/:id --> ctx.params
userRouter.get(’/:id’, auth, ctx=>{
ctx.body = db[ctx.params.id * 1]
})
获取body需要安装插件koa-bodyparser (npm i koa-bodyparser --save)
const bodyparser = require(‘koa-bodyparser’) //作用是解析请求体
app.use(bodyparser()) //注册
ctx.request.body
如:
userRouter.put('/:id', ctx=>{
db[ctx.params.id * 1] = ctx.request.body
ctx.body = ctx.request.body
})
ctx.header.xxx
发送HTTP响应
合理的目录结构
新建app文件夹, 将index.js文件放进去, 修改pacage.json
"scripts": {
"start": "nodemon app"
},
koa自带的错误处理
412错误:
findById(ctx) {
if (ctx.params.id * 1 >= db.length ) {
ctx.throw(412, '先决条件失败: id大于等于数组长度')
}
ctx.body = `这是用户${ctx.params.id}`
}
500错误:
find(ctx) {
a * b
ctx.body = [{name: 'aa'}, {name: 'bb'}]
}
自己编写错误中间件
const Koa = require('koa')
const bodyparser = require('koa-bodyparser') //作用是解析请求体
const routing = require('./routes')
const app = new Koa()
// 404错误不走中间件,会提前抛出
app.use(async (ctx, next) => { //自定义错误中间件
try {
await next ()
} catch (err) {
// 6.err信息截图
// 运行时报错时, err中没有status和statusCode
ctx.status = err.status || err.statusCode || 500
ctx.body = {
// restful api 希望返回格式为json
message: err.message
}
}
})
app.use(bodyparser()) //注册
routing(app) //批量注册路由
app.listen(3000, () => console.log('程序已启动'))
使用koa-json-error进行错误处理
npm i koa-json-error --save
index.js引入并注册:
const error = require('koa-json-error')
app.use(error({
// 生产环境不返回堆栈信息
postFormat: (e, { stack, ...rest }) => process.env.NODE_ENV === 'production' ? rest : { stack, ...rest }
}));
此处需要设置环境变量:
"scripts": {
"start": "cross-env NODE_ENV=production node app", //启动 npm start
"dev": "nodemon app" //npm run dev
},
由于Windows不能直接设置环境变量, 需要安装依赖cross-env实现跨平台设置环境变量
npm i cross-env --save-dev
使用koa-parameter校验参数
npm i koa-parameter --save
index.js中引入注册:
const parameter = require('koa-parameter')
app.use(error({
postFormat:(e, {stack, ...rest}) => process.env.NODE_ENV === 'production' ? rest : {stack, ...rest}
}))
app.use(bodyparser()) //注册
app.use(parameter(app)) //由于是用来校验请求体的所以放这里
routing(app) //批量注册路由
app.listen(3000, () => console.log('程序已启动'))
controller users.js文件中使用:
create(ctx) {
ctx.verifyParams({ //如果不满足条件返回422
name: {type: 'string', required: true},
age: {type: 'number', required: false}
})
ctx.body = {name: 'ccc'}
}
//controller users.文件
class UsersCtl {
find(ctx) {
ctx.body = [{name: 'aa'}, {name: 'bb'}]
}
findById(ctx) {
if (ctx.params.id * 1 >= db.length ) {
ctx.throw(412, '先决条件失败: id大于等于数组长度')
} //手动添加校验
ctx.body = `这是用户${ctx.params.id}`
}
create(ctx) {
ctx.verifyParams({
name: {type: 'string', required: true}
}) // 利用中间件校验
ctx.body = {name: 'ccc'}
}
update(ctx) {
if (ctx.params.id * 1 >= db.length ) {
ctx.throw(412, '先决条件失败: id大于等于数组长度')
}
ctx.verifyParams({
name: {type: 'string', required: true}
})
ctx.body = `这是用户${ctx.params.id}`
}
delete(ctx) {
if (ctx.params.id * 1 >= db.length ) {
ctx.throw(412, '先决条件失败: id大于等于数组长度')
}
ctx.status = 204
}
}
module.exports = new UsersCtl()
NoSQL
分类
列存储(Hbase) 图存储(FlockDB)
文档存储(MongoDB): 按文档存储实际上存储格式是json, 一段json就是一个文档,也可以把一段文档当做是表格中的一行
对象存储(db4o) Key-value存储(Redis): 用于缓存, 如消息存储
XML存储(BsaeX)
优点:
MongoDB
优点
使用
云存储: MongoDB Atlas --> 注册
安装mongoose依赖 npm i mongoose --save
引入注册使用:
const mongoose = require('mongoose') //引入
const { connectionStr } = require('./config') //存放配置文件
mongoose.connect( connectionStr, () => console.log('mongoose 已连接') ) //连接mongoose数据库
mongoose.connection.on('error', console.error ) //打印错误信息
设计用户模块Schema
models users.js
const mongoose = require('mongoose')
const { Schema, model } = mongoose
const userSchema = new Schema({
name: {type: String, required: true}
})
module.exports = model('User', userSchema) // User表示一个集合
session
用户认证与授权
认证: 你是谁
授权: 什么能做什么不能做
劣势(见截图13):
cookie具有不可跨域性, 设置cookie时,不仅要设置sessionId 还要设置一个domain的变量(表示cookie生效的域名, 默认情况下只有这些域名下才可以使用cookie)
JWT
构成:
三者都是经过base64编码过的
签名是对头部和有效载荷部分进行签名
编写koa中间件实现用户认证与授权
认证: 验证token,并获取用户信息
授权: 使用中间件保护接口
用koa-jwt实现用户认证与授权
npm i koa-jwt --save 安装
用中间件保护接口
使用中间件获取用户信息
使用koa-body中间间获取上传的文件
安装koa-body替换koa-bodyparser, 因为后者只支持json和form,不支持文件这种格式
// const bodyparser = require('koa-bodyparser') //作用是解析请求体, 只能解析json和form,不能解析文件
const koaBody = require('koa-body') //作用是解析请求体 能解析json form 文件
// app.use(bodyparser()) //注册 解析请求体
app.use(koaBody({
multipart: true, //代表启用文件格式
formidable: {
uploadDir: path.join(__dirname, '/public/uploads'), //文件上传路径
keepExtensions: true //保存文件拓展名
}
})) //注册 解析请求体
cotroller:
// 上传文件
upload (ctx) {
console.log(ctx.request);
console.log(ctx.request.files);
const file = ctx.request.files.file
ctx.body = {
path: file.path
}
}
}
router:
const { upload } = require('../controllers/home')
router.post('/upload', upload)
使用koa-static中间件生成图片链接
npm i koa-static --save
const koaStatic = require('koa-static')
// 生成图片链接
app.use(koaStatic(path.join(__dirname, 'public')))
修改上传文件的controller:
// 上传文件
upload (ctx) {
const file = ctx.request.files.file
const basename = path.basename(file.path)
ctx.body = {
url: `${ctx.origin}/uploads/${basename}` //ctx.origin 获取到的是localhost
}
}
}
public下面的任何文件都可以通过它生成http链接