4 Koa实战 - Restful API

课程目标

  • 编写RESTful API
  • 文件上传
  • 表单校验
  • 图形验证码
  • 发送短信
  • 案例:用户注册
  1. 掌握Koa中编写Restful风格API
  2. 掌握Koa中文件上传、表单验证、图形验证码、发送短信等常见任务

编写RESTful API

  • 方法设计 PUT和DELETE ngnix可能会有问题

    • GET:读取(Read)
    • POST:新建(Create)
    • PUT:更新(Update)
    • PATCH:更新(Update),部分更新
    • DELETE:删除(Delete)
  • 对象

    • GET /users(通常用复数避免多级)
    • GET /users/1 (一样是使用复数)
  • 状态码

    • 1xx :相关信息
    • 2xx :操作成功
    • 3xx :重定向
    • 4xx :客户端错误
    • 5xx :服务器错误
  • 返回

        //推荐
        HTTP/1.1 400 Bad Request
        Content-Type: application/json
        {
         "error": "不合法的附件",
         "detail": { "uname": "用户名为必填项" }
        }
  • 解决跨域: npm i koa2-cors

    var Koa = require('koa');
    var cors = require('koa2-cors');
    var app = new Koa();
    app.use(cors());

    参考文档:理解resful架构、 resful API最佳实践

文件上传

  • 安装koa-multer: npm i koa-multer -S
  • 配置:./routes/users.js

    const upload = require("koa-multer")({ dest: "./public/images" });
    router.post("/upload", upload.single("file"), ctx => {
     console.log(ctx.req.file); // 注意数据存储在原始请求中
     console.log(ctx.req.body); // 注意数据存储在原始请求中
     ctx.body = "上传成功";
    });
  • 调用接口,./public/upload-avatar.html

    
    
     
      
      
      
      
      
      
      
      文件上传
     
     
      

表单校验

  • 安装koa-bouncer: npm i -S koa-bouncer
  • 配置:app.js

    // 为koa上下文扩展一些校验方法
    app.use(bouncer.middleware());
  • 基本使用:user.js

    router.post("/", ctx => {
     try {
      // 校验开始
      ctx
      .validateBody("uname")
      .required("要求提供用户名")
      .isString()
      .trim()
      .isLength(6, 16, "用户名长度为6~16位");
      // ctx.validateBody('email')
      //  .optional()
      //  .isString()
      //  .trim()
      //  .isEmail('非法的邮箱格式')
      ctx
      .validateBody("pwd1")
      .required("密码为必填项")
      .isString()
      .isLength(6, 16, "密码必须为6~16位字符");
      ctx
      .validateBody("pwd2")
      .required("密码确认为必填项")
      .isString()
      .eq(ctx.vals.pwd1, "两次密码不一致");
      // 校验数据库是否存在相同值
      // ctx.validateBody('uname')
      //  .check(await db.findUserByUname(ctx.vals.uname), 'Username taken')
      ctx.validateBody("uname").check("jerry", "用户名已存在");
      // 如果走到这里校验通过
      // 校验器会用净化后的值填充 `ctx.vals` 对象
      console.log(ctx.vals);
      console.log("POST /users");
      // const { body: user } = ctx.request; // 请求body
      const user = ctx.vals;
      user.id = users.length + 1;
      users.push(user);
      ctx.body = { ok: 1 };
    } catch (error) {
      if (error instanceof bouncer.ValidationError) {
       ctx.body = '校验失败:'+error.message;
       return;
     }
      throw error
    }
    });

图形验证码(就为了拿个图片?)

  • 安装trek-captcha: npm i trek-captcha -S
  • 使用:./routes/api.js

    const captcha = require("trek-captcha");
    router.get("/captcha", async ctx => {
     const { token, buffer } = await captcha({ size: 4 });
     ctx.body = buffer;
    });
  • 图片显示,upload-avatar.html

    
    
    

    发送短信

  • 秒滴短信API
  • 安装依赖: npm i -S moment md5 axios
  • 接口编写,./routes/api.js

    router.get("/sms", async function(ctx) {
     // 生成6位随机数字验证码
     let code = ran(6);
     // 构造参数
     const to = ctx.query.to; // 目标手机号码
     const accountSid = "3324eab4c1cd456e8cc7246176def24f"; // 账号id
     const authToken = "b1c4983e2d8e45b9806aeb0a634d79b1"; // 令牌
     const templateid = "613227680"; // 短信内容模板id
     const param = `${code},1`; // 短信参数
     const timestamp = moment().format("YYYYMMDDHHmmss");
     const sig = md5(accountSid + authToken + timestamp); // 签名
     try {
      // 发送post请求
      const resp = await axios.post(
       "https://api.miaodiyun.com/20150822/industrySMS/sendSMS",
       qs.stringify({ to, accountSid, timestamp, sig, templateid, param }),
      { headers: { "Content-Type": "application/x-www-form-urlencoded" } }
     );
      if (resp.data.respCode === "00000") {
       // 短信发送成功,存储验证码到session,过期时间1分钟
       const expires = moment()
       .add(1, "minutes")
       .toDate();
       ctx.session.smsCode = { to, code, expires };
       ctx.body = {ok:1}
     } else {
        ctx.body = {ok:0, message: resp.data.respDesc}
     }
    } catch (e) {
      ctx.body = {ok:0, message: e.message}
    }
    });

案例:用户注册

  • 前端页面,register.html

    
    
     
      
      
      
      
      
      
      
      
      文件上传
     
     
      
    获取短信验证码 提交
  • 注册接口编写,./routes/students.js
const Router = require("koa-router");
const router = new Router({ prefix: "/students" });
const bouncer = require("koa-bouncer");
router.post("/", async ctx => {
 try {
  // 输入验证
  const { code, to, expires } = ctx.session.smsCode;
  ctx
  .validateBody("phone")
  .required("必须提供手机号")
  .isString()
  .trim()
  .match(/1[3-9]\d{9}/, "手机号不合法")
  .eq(to, "请填写接收短信的手机号");
  ctx
  .validateBody("code")
  .required("必须提供短信验证码")
  .isString()
  .trim()
  .isLength(6, 6, "必须是6位验证码")
  .eq(code, "验证码填写有误")
  .checkPred(() => new Date() - new Date(expires) < 0, "验证码已过期");
  ctx
  .validateBody("password")
  .required("必须提供密码")
  .isString()
  .trim()
  .match(/[a-zA-Z0-9]{6,16}/, "密码不合法");
  // 入库, 略
  ctx.body = { ok: 1 };
} catch (error) {
  if (error instanceof bouncer.ValidationError) {
   console.log(error);
   ctx.status = 401;
 } else {
   ctx.status = 500;
 }
  ctx.body = { ok: 0, message: error.message };
}
});
module.exports = router;

koa2 中间件

  • 路由相关

    • 'koa-bodyparser' 【post解析】

      app.use(bodyParser({  
        extendTypes:\['json','form','text'\]  
      }))
    • 'koa-router' 【路由】

      import Router from 'koa-router';  
      import axios from './utils/axios'  
      import Cart from '../dbs/models/cart'  
      import md5 from 'crypto-js/md5' //加密  
      
      let router \= new Router({prefix: '/cart'})  
      router.post('/getCart', async ctx \=> {  
        let {id} \= ctx.request.body  
        console.log(id);  
        try {  
          let result \= await Cart.findOne({cartNo: id})  
          ctx.body \= {  
            code: 0,  
            data: result  
              ? result.detail\[0\]  
              : {}  
          }  
        } catch (e) {  
          ctx.body \= {  
            code: -1,  
            data: {}  
          }  
        }  
      })
    • 'koa-multer' 【文件上传】

      router.post("/upload", upload.single("file"), ctx \=> {  
        console.log(ctx.req.file); // 注意数据存储在原始请求中  
        console.log(ctx.req.body); // 注意数据存储在原始请求中  
        ctx.body \= "上传成功";  
      });
    • 'koa2-cors' 【跨域】

      // 跨域  
      var cors \= require('koa2-cors');  
      app.use(cors());
    • 'koa-bouncer' 【校验】

      //配置
      const bouncer \= require("koa-bouncer");  
      app.use(bouncer.middleware());
      
      //使用
      router.post("/", ctx => {
       try {
        // 校验开始
        ctx
        .validateBody("uname")
        .required("要求提供用户名")
        .isString()
        .trim()
        .isLength(6, 16, "用户名长度为6~16位");
        // ctx.validateBody('email')
        //  .optional()
        //  .isString()
        //  .trim()
        //  .isEmail('非法的邮箱格式')
        ctx
        .validateBody("pwd1")
        .required("密码为必填项")
        .isString()
        .isLength(6, 16, "密码必须为6~16位字符");
        ctx
        .validateBody("pwd2")
        .required("密码确认为必填项")
        .isString()
        .eq(ctx.vals.pwd1, "两次密码不一致");
        // 校验数据库是否存在相同值
        // ctx.validateBody('uname')
        //  .check(await db.findUserByUname(ctx.vals.uname), 'Username taken')
        ctx.validateBody("uname").check("jerry", "用户名已存在");
        // 如果走到这里校验通过
        // 校验器会用净化后的值填充 `ctx.vals` 对象
        console.log(ctx.vals);
        console.log("POST /users");
        // const { body: user } = ctx.request; // 请求body
        const user = ctx.vals;
        user.id = users.length + 1;
        users.push(user);
        ctx.body = { ok: 1 };
      } catch (error) {
        if (error instanceof bouncer.ValidationError) {
         ctx.body = '校验失败:'+error.message;
         return;
       }
        throw error
      }
      });
  • 数据库相关

    • 'koa-generic-session' 【登录状态鉴权】

      app.use(session({key: 'mt', prefix: 'mt:uid', store: new Redis()}))
    • 'koa-session' 【登录状态鉴权】

      app.keys \= \['some secret'\];  //设置秘钥
      
      // 配置项  
      const SESS\_CONFIG \= {  
          key: 'kkb:sess', // cookie键名  
        maxAge: 86400000, // 有效期,默认一天  
        httpOnly: true, // 仅服务器修改  
        signed: true, // 签名cookie  
      };
      
      // 注册  ~~~~
      app.use(session(SESS\_CONFIG, app));
    • 'koa-redis' 【redis】

      app.use(session({key: 'mt', prefix: 'mt:uid', store: new Redis()}))
    • 'mongoose' 【面向对象操作数据库】

      //连接
      mongoose.connect(dbConfig.dbs,{  
        useCreateIndex: true,  
        useNewUrlParser:true  
      })
      //操作
      import mongoose from 'mongoose'  
      const Schema \= mongoose.Schema  
      const Cart \= new Schema({  
        id: {  
          type: String,  
          require: true  
        },  
        detail: {  
          type: Array,  
          require: true  
        },  
        cartNo: {  
          type: String,  
          require: true  
        },  
        user: {  
          type: String,  
          require: true  
        },  
        time: {  
          type: String,  
          require: true  
        }  
      })  
      
      export default mongoose.model('Cart', Cart)

你可能感兴趣的:(koa,restful)