koa
- 一个node框架,是express原班人马打造
生成项目
- 新建文件夹 koa-learn
- 初始化项目 npm init -y
- 安装koa npm i koa@2.13.4
安装 nodemon
- 避免每次修改服务器都要重启
- npm i nodemon -g
- nodemon -v 查看版本
创建服务器
const Koa = require('koa');
// 创建app实例
const app = new Koa();
// ctx上下文
app.use(async ctx => {
// 中间件
ctx.body = "hello koa";
})
// 监听服务 端口 3333
app.listen(3333, () => {
console.log('port 3333 is run');
})
中间件机制
const Koa = require('koa');
// 创建app实例
const app = new Koa();
// ctx上下文
app.use(async (ctx, next) => {
// 中间件1
console.log(1)
await next()
console.log(5)
})
app.use(async (ctx, next) => {
console.log(2)
await next()
console.log(4)
})
app.use(async (ctx, next) => {
console.log(3);
ctx.body = "hello koa";
})
// 监听服务
app.listen(3000, () => {
console.log('port 3000 is run');
})
// => 1 2 3 4 5
// koa的中间件是 洋葱模型
// next() 把下个中间件走完
//
错误监听
const Koa = require('koa');
// 创建app实例
const app = new Koa();
app.use(async (ctx, next) => {
try {
await next();
}catch(error) {
console.log('error', error);
ctx.status = 500;//服务器发生错误
ctx.type = "json";// 告诉客户端响应的数据是json数据
ctx.body = {// 会自动把对象转为json
code: 0,
message: error.message//未知错误
}
// 手动触发 on监听error事件,不然try/catch包裹后无法触发
ctx.app.emit('error');
}
})
// ctx上下文
app.use(async (ctx, next) => {
//抛出一个错误
throw new Error("未知错误")
})
// 全局错误处理,后台打印
app.on('error', err => {
console.log('全局处理', err);
})
// 监听服务
app.listen(3000, () => {
console.log('port 3000 is run');
})
控制台日志处理
npm i koa-logger@3.2.1
const Koa = require('koa');
const logger = require('koa-logger');
// 创建app实例
const app = new Koa();
app.use(logger());
app.use(async (ctx, next) => {
ctx.body = "hello koa";
})
// 监听服务
app.listen(3000, () => {
console.log('port 3000 is run');
});
/*
port 3000 is run
<-- GET /
--> GET / 200 8ms 9b
<-- GET /favicon.ico
--> GET /favicon.ico 200 2ms 9b
*/
错误处理
npm i koa-onerror@4.2.0
const Koa = require('koa');
const logger = require('koa-logger');
const onerror = require('koa-onerror');
// 创建app实例
const app = new Koa();
app.use(logger());// 日志处理
onerror(app);//错误处理
app.use(async (ctx, next) => {
ctx.body = "hello koa";
})
// 监听服务
app.listen(3000, () => {
console.log('port 3000 is run');
})
日志文件持久化
npm i koa-log4@2.3.2
-
在项目根目录下创建一个logger目录
-
在logger目录下创建 logs目录,用来存放日志文件
-
在logger目录下新建index.js文件
-
去npm 上拷贝
-
访问级别日志: 记录用户的所有请求,作为koa的中间件,直接使用即可
-
应用级别的日志: 可记录全局状态的error,修改app.js全局捕捉异常
// => index.js 用就完事了
const path = require('path');
const log4js = require('koa-log4');
log4js.configure({
appenders: {
// 访问级别
access: {
type: 'dateFile',
pattern: '-yyyy-MM-dd.log',//生成文件规则
alwaysIncludePattern: true,//文件名始终以日期区分
encoding: 'utf-8',
filename: path.join(__dirname, 'logs', 'access')//生成文件路劲和文件名
},
application: {
type: 'dateFile',
pattern: '-yyyy-MM-dd.log',
alwaysIncludePattern: true,
encoding: 'utf-8',
filename: path.join(__dirname, 'logs', 'application')
},
out: {
type: 'console'
}
},
categories: {
default: {
appenders: ['out'],
level: 'info'
},
access: {
appenders: ['access'],
level: 'info'
},
application: {
appenders: ['application'],
level: 'WARN'
}
}
})
module.exports = {
// 记录所有访问级别的日志
accessLogger: () => log4js.koaLogger(log4js.getLogger('access')),
logger: log4js.getLogger('application')//记录所有应用级别的日志
}
// index.js入口文件
const Koa = require('koa');
const onerror = require('koa-onerror');
const {
accessLogger,
logger
} = require('./logger')
// 创建app实例
const app = new Koa();
onerror(app);//错误处理
app.use(accessLogger());//持久化日志
app.use(async (ctx, next) => {
ctx.throw(401, '未授权', {
data: '哈哈'
})
})
app.on('error', err => {
// 把错误信息保存到本地
logger.error(err);
})
// 监听服务
app.listen(3000, () => {
console.log('port 3000 is run');
})
路由的使用
npm i koa-router@10.1.1 -S
- 创建router目录
- 创建 index.js/user.js
// => index.js
const Router = require('koa-router')
//创建一个router 路由器对象 route路由
const router = new Router();
router.get('/', (ctx, next) => {
ctx.body = "欢迎进入首页";
})
// 导出路由器对象
module.exports = router;
// user.js
const Router = require('koa-router')
//创建一个router 路由器对象 route路由
const router = new Router();
//增加一个前缀
router.prefix('/user');
router.get('/list', (ctx, next) => {
ctx.body = "欢迎user页面";
})
router.post('/add', (ctx, next) => {
ctx.body = "添加用户";
})
// 导出路由器对象
module.exports = router;
const Koa = require('koa');
const IndexRouter = require('./router');
const userRouter = require('./router/user');
const app = new Koa();
// => 注册首页路由
app.use(IndexRouter.routes());
IndexRouter.allowedMethods();
app.use(userRouter.routes());
userRouter.allowedMethods();
app.listen(3000, () => {
console.log('3000 is running')
})
get传参的2中形式
- params传参 /user/:id
- query传参 /user?name=jhw
router.get('/user/:id', (ctx, next) => {
console.log(ctx.params.id);//获取动态路由的参数
console.log(ctx.query);// {name: 'jhw'}
})
post传参安装一个包
- npm i koa-bodyparser@4.3.0
router.post('/user/add', (ctx, next) => {
console.log(ctx.body, 'or', ctx.request.body);//获取请求体里面的参数
})
// => 入口文件
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const IndexRouter = require('./router');
const userRouter = require('./router/user');
const app = new Koa();
app.use(bodyParser());
// => 注册首页路由
app.use(IndexRouter.routes());
IndexRouter.allowedMethods();
app.use(userRouter.routes());
userRouter.allowedMethods();
app.listen(3000, () => {
console.log('3000 is running')
})
路由的重定向
router.get('/', (ctx, next) => {
ctx.redirect('/list');
ctx.status = 301;
})
托管静态资源
npm i koa-static@5.0.0
const Koa = require('koa');
const static = require('koa-static')
// 创建app实例
const app = new Koa();
app.use(static(__dirname + '/public'));//静态资源托管
// 监听服务
app.listen(3000, () => {
console.log('port 3000 is run');
})
- 注意:
- 这个跟router是由顺序问题的,谁app.use在前面就展示谁
- 一般写在 router注册之后
鉴权
- cookie/session都是用来记录数据的
- cookie数据存储在浏览器,在客户端容易被篡改,基本用的不多了
- session数据存储在服务器端
router.get('/list', ctx => {
// 给客户端种植cookie
// 后面浏览器每次请求,都需要带上cookie
// 当绘画结束,cookie就死了,(如关闭浏览器)
// ctx.cookie.set('username', 'jhw');
// 设置cookie时长
ctx.cookie.set('username', 'jhw', {
maxAge: 1000 * 60 * 24 * 7//七天免登录
});
// 别的地方获取cookie
// ctx.cookie.get('username')
})
- 小案例,上一次访问的时间
router.get('/', (ctx, next) => {
let last = ctx.cookie.get('last');
// 种植cookie
ctx.cookie.set('last', new Date().toLocaleString(), {
maxAge: 1000 * 60 * 24 * 7
});
if(last) {
ctx.body = `你上一次访问本网站的时间${last}`
} else {
// 第一次访问
ctx.body = "这是你第一次访问本网站"
}
})
jwt鉴权(token 鉴权)
- jwt 鉴权流程
浏览器 服务器
| ------------(发起登录请求post)-------->------| (获取用户名和密码) -> 生成一个token(令牌)
| <------------(响应一个jwt的token)----------- |
| 浏览器把token存储在localStroage里面 |
| ---(一般情况,token放在请求头中Authorization)->| 检测token是否合法,是否有效,是否被篡改
| <--------(如果token合法,响应对应数据)---------|
| <--------(如果token不合法,告诉用户重新登录)----|
- 依赖包
- npm i koa-jwt@4.0.3 : jwt中间件
- npm i jsonwebtoken@8.5.1
- 用于生成token下发给服务器,在koa2以后的版本jsonwebtoken的方法需要另外安装
- 配置如下
const Router = require('koa-router')
const jwt = require('jsonwebtoken');
const jwtAuth = require('koa-jwt');//解密
const secret = "safddasfadsfdsa1";//密钥
//创建一个router 路由器对象 route路由
const router = new Router();
//增加一个前缀
router.prefix('/user');
router.get('/list', (ctx, next) => {
ctx.body = "欢迎user页面";
})
// 登录接口
router.post('login', (ctx, next) => {
// 1)获取用户信息
const userInfo = ctx.request.body.user;
// 2)生成token令牌
const token = jwt.sign({
data: userInfo,//不要存放敏感数据
exp: Math.floor(Date.now() / 1000) + 60 * 60,//有效时间一个小时
}, secret);//密钥
// 给客户端响应数据
ctx.body = {
ok: 1,
message: "登录成功",
user: userInfo,
token
}
});
// 退出登录
router.post('logout', (ctx, next) => {
jwt.sign({
data: '',
exp: Math.floor(Date.now() / 1000) - 60 * 60,//token过期
}, secret);
ctx.body = {
ok: 0,
message: '退出登录成功'
}
});
// 获取用户信息
router.post('getUser', jwtAuth({
secret
}), async (ctx, next) => {
ctx.body = {
message: "获取用户令牌",
userinfo: ctx.state.user.data
}
});
// 1.第三方登录
// 2.短信登录(要公司资质)
// 3.扫码登录
// 导出路由器对象
module.exports = router;
跨域处理
npm i koa-cors
- 包可能很久了,可以换包使用
- post -> form-data -> 选file -> 选图片
const Koa = require('koa');
const cors = reuire('koa-cors')
// 创建app实例
const app = new Koa();
app.use(cors());//默认公开访问,内部也可以设置
// ... 省略n行代码
// 监听服务
app.listen(3000, () => {
console.log('port 3000 is run');
})
上传文件
- npm i koa-multer@1.0.2
const Router = require('koa-router')
const multer = require('koa-multer');
const { join } = require('path')
//文件上传
const storage = multer.diskStorage({
// 存储的位置
destination: join(process.cwd(), "public/upload"),
// 文件名
filename(req, file, cb){
const filename = file.originalname.split(".")
cb(null, `${Date.now()}.${filename[filename.length - 1]}`)
}
})
const upload = multer({storage})
//创建一个router 路由器对象 route路由
const router = new Router();
// 上传文件
router.post('/upload', upload.single('touxiang'), (ctx, next) => {
ctx.body = {
ok: 1,
message: '上传成功'
}
})
// 导出路由器对象
module.exports = router;
表单验证
npm i koa-bouncer@6.0.0
// => router/index.js
const Router = require('koa-router');
const bouncer = require('koa-bouncer');
//创建一个router 路由器对象 route路由
const router = new Router();
router.post('/form', (ctx, next) => {
// 校验用户名
try {
// 当使用 koa-bouncer中间件的时候,ctx上带有validateBody方法可以对属性进行校验了
// 用try/catch包裹
ctx.validateBody('username')
.required('用户名是必传参数')
.isString()//是字符串
.trim()//去掉空格
.isLength(4, 8, "用户名必须4-8位")
ctx.validateBody('email')
.optional()// 选传
.isString()//是字符串
.trim()
.isEmail('非法邮箱格式')//邮箱格式校验
ctx.validateBody('pwd1')
.required('密码是必传参数')
.isString()//是字符串
.trim()
.match(/^[a-z0-9]{6,15}$/i, "密码0-9/字母6到15位")//可以写正则表达式
ctx.validateBody('pwd2')
.required('确认密码是必传参数')
.isString()//是字符串
.trim()
.eq(ctx.vals.pwd1, '两次密码不一致')//比较参数
ctx.body = {
code: 1,
message: '校验通过'
};
}catch(err) {
if (err instanceof bouncer.ValidationError) {
ctx.status = 400;
ctx.body = {
code: 0,
message: `校验失败:${err.message}`
}
return;
}
throw err;
}
})
// 导出路由器对象
module.exports = router;
// => 入口文件
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const bouncer = require('koa-bouncer');
const IndexRouter = require('./router');
// 创建app实例
const app = new Koa();
app.use(bodyParser());
app.use(bouncer.middleware());//使用校验中间件
app.use(IndexRouter.routes());
IndexRouter.allowedMethods();
// 监听服务
app.listen(3000, () => {
console.log('port 3000 is run');
})
koa生成图形验证码
npm i trek-captcha@0.4.0
// => router/index.js
const Router = require('koa-router');
const captcha = require('trek-captcha');//生成验证码
//创建一个router 路由器对象 route路由
const router = new Router();
// 生成图形验证码
router.get('/captcha', async (ctx, next) => {
// buffer就是图片 token:前端传过来的验证码数据需要和token进行对比(生成图形字符串)
let { buffer, token } = await captcha({
size: 4,//4位数
});
// 存下来session.token
// ctx.session.token = token;//存到上下文,在别的接口中就可以进行对比了
// 生成验证码图片
ctx.body = buffer;
})
// 导出路由器对象
module.exports = router;
// 前端如果想换其他随机验证码可以让请求数据的src加上时间Date now()
Restful API风格接口
// => router/index.js
const Router = require('koa-router');
const router = new Router();
router.prefix('/user');
// 模拟数据库
let users = [
{id: 1, name: 'wc', age: 16},
{id: 2, name: 'xq', age: 18},
]
// 根据name找对应的用户
router.get('/', (ctx, next) => {
let { name } = ctx.query;
let user = users;
// 过滤用户信息
if (name) {
user = users.filter(u => u.name === name);
}
ctx.body = {
code: 1,
data: user
}
})
// 根据id找对应user
router.get('/:id', ctx => {
let { id } = ctx.params;
let user = users;
if(id) {
user = users.filter(u => u.id == id);
}
ctx.body = {
code: 1,
data: user
}
})
// 添加用户
router.post('/', ctx => {
let { name, age } = ctx.request.body;
if(name && age) {
users.push({
id: users.length + 1,
name,
age
});
return ctx.body = {
code: 1,
message: '添加用户成功'
}
}
ctx.body = {
code: 0,
message: '添加用户失败'
}
})
// 根据id修改用户
router.put('/', ctx => {
let { name, age, id } = ctx.request.body;
if(id) {
users.forEach(u => {
if(u.id == id) {
u.name = name;
u.age = age
}
})
return ctx.body = {
code: 1,
message: '修改用户成功'
}
}
ctx.body = {
code: 0,
message: '修改用户失败'
}
})
// 根据id删除用户
router.delete('/', ctx => {
let { id } = ctx.request.body;
if(id) {
users = users.filter(u => u.id != id);
console.log(users);
return ctx.body = {
code: 1,
message: '删除用户成功'
}
}
return ctx.body = {
code: 0,
message: '删除用户失败'
}
})
module.exports = router;
// => 入口文件
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const IndexRouter = require('./router');
const app = new Koa();
app.use(bodyParser());
// 注册路由
app.use(IndexRouter.routes())
IndexRouter.allowedMethods();
app.listen(3000, () => {
console.log('3000 is running');
})
restfulapi概念 [动词 + 宾语] 个人理解用一整套的方式去定义接口,让开发者一看就知道是干嘛的
- get: 读取资源
- post: 新建资源
- put: 更新资源
- patch: 资源部分数据更新
- delete: 删除资源