21-koa2知识回顾1

89 阅读6分钟

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');
})
  • 注意:
  1. 这个跟router是由顺序问题的,谁app.use在前面就展示谁
  2. 一般写在 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: 删除资源