课程目标
- 编写RESTful API
- 文件上传
- 表单校验
- 图形验证码
- 发送短信
- 案例:用户注册
- 掌握Koa中编写Restful风格API
- 掌握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());
文件上传
- 安装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)
-