整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

手把手搭建koa2后端服务器-API文档生成(番外)

手把手搭建koa2后端服务器-API文档生成(番外)

为我们之前的框架没有接入 API 文档,而大多数 API 文档均采用编写注释来生成,我不是特别喜欢这种方式,所以暂时没有添加,后来发现一个库—koa-swagger-decorator,它对 koa-router 进行了封装,可以自动生成 API 文档,而且自带验证,这种方式比较好,但是项目结构上会有一些细微的差异,因此我决定单开一个分支来使用这种方式初始化一个新的项目,大家可以选择自己喜欢的项目结构来使用,这篇文档我只介绍与之前不同的地方,如果一模一样,我就不单独写了。

创建项目目录

mkdir koa2-demo

yarn init

安装依赖库

yarn add koa koa-body koa-swagger-decorator dotenv log4js typeorm reflect-metadata mysql2 koa2-cors bcryptjs jsonwebtoken

yarn add -D ts-node typescript nodemon @types/dotenv @types/koa @types/log4js @types/reflect-metadata @types/koa2-cors @types/bcryptjs @types/jsonwebtoken

这里我们一次性把常用的库全部安装上,下面我大概介绍以下库的用途:

  • koa-body:解析 post 参数的库,koa 默认会解析查询参数和路径参数,但是对于请求体没有解析,所以需要这个库,我们就能使用 ctx.request.body 来处理请求体了。
  • koa-swagger-decorator:这个库当中集成了 路由(koa-router)、认证(validator)库,这个就不需要我们单独安装了。
  • dotenv:环境变量读取库。
  • log4js:日志处理库。
  • typeorm、reflect-metadata、mysql2:数据库操作库。
  • bcryptjs:加密库。
  • jsonwebtoken:jwt 加解密库。
  • ts-node:支持ts直接运行的库。
  • nodemon:监控文件改变热加载的库。
  • 其他:@types开头的库都是为了支持 ts 提示的,在安装库的时候使用@type/库名 ,如果可以安装就安装,如果没有就不需要安装。

接下来除了请求参数校验和路由动态加载以外,其他的均可参考之前的教程。

路由处理修改

// src/router/index.ts
import path from 'path';
import { SwaggerRouter } from 'koa-swagger-decorator';

const router=new SwaggerRouter();

// 定义 schema 的初始信息
router.swagger({
  title: 'koa2 基础 API',
  description: 'API',
  version: '1.0.0',
});

// 这里会动态的检索 controller 目录下的所有 .js、.ts 文件,并获取默认导出类,生成路由和API
// 因此就不需要我们再收到注册路由和自己实现动态加载路由的功能了,具体的路由格式请参考 controller
// 目录下的实现
router.mapDir(path.resolve(__dirname, '../controller'));

// 重定向/路由到/swagger-html路由,这是默认的API文档路由地址
router.redirect('/', '/swagger-html');

export default router;

业务逻辑修改

之前我们通过在 controller 下创建子目录及指定名称的文件来区分业务逻辑,例如

- controller
  - common
    - view.ts
    - router.ts
    - rules.ts
    - types.ts

现在我们可以简化这个目录,结构如下:

- controller
  - common
    - view.ts
    - schema.ts

其中 view.ts 中实现我们的业务逻辑,schema.ts 中定义我们的参数信息,用来进行参数验证和API文档生成。

修改登录逻辑

// src/controller/common/view.ts
import { Context } from 'koa';
import bcryptjs from 'bcryptjs';
import { request, summary, body, tags } from 'koa-swagger-decorator';
import response from '../../utils/response';
import { loginRules } from './schema';
import User from '../../entity/User';
import { generateToken } from '../../utils/auth';

export default class IndexController {
  @request('post', '/login')
  @summary('登录')
  @tags(['基础接口'])
  @body(loginRules)
  static async login(ctx: Context) {
    const data=ctx.request.body;
    // 校验用户是否存在
    let user: User | undefined=await User.getUserInfo(data.username);
    if (!user) {
      response.error(ctx, '用户不存在');
      return;
    }

    // 校验密码是否正确
    if (!bcryptjs.compareSync(data.password, user.password)) {
      response.error(ctx, '密码错误');
      return;
    }

    const { password, ...rest }=user;
    const token=generateToken(rest);
    response.success(ctx, { token }, '登录成功');
  }
}

主体逻辑基本没有变化,有几个需要注意的点

  • 类必须使用 export default 导出,路由扫描时会通过这个进行验证(必须)
  • 使用@request装饰器修饰函数,参数为方法类型和地址,路由注册是就会根据这些生成,而不是根据我们的函数名(必须)
  • 函数应该定义成静态的,因为在使用的时候并不会创建类对象,使用静态方法更符合状况(可选)

我们访问:http://localhost:3100/ 就可以看到API文档

路由修饰参数的意义

路由装饰器分为两种:函数装饰器和类装饰器

函数装饰器

  • request:定义路由方法和地址,用于注册路由和API接口文档
  • summary:API 文档中的路由解释,参考上图
  • tags:路由分类,API 文档中用来对多个路由进行分组显示。
  • query:查询参数:url?name=xxx&age=12
  • path:路径参数:/getuserinfo/{id}
  • body:请求体参数
  • formData:表单数据。
  • middlewares:koa中间件,eg:[md1, md2]
  • security:
  • description:接口描述
  • responses:接口返回值,可以设定不同的状态返回不同的数据
  • deprecated:

类装饰器

  • orderAll:参数为数字,表示在文档中的位置。
  • tagsAll:此类下的所有路由都属于这个tag。
  • responsesAll:此类下的所有路由都返回这种类型。
  • middlewaresAll:此类下的所有路由都拥有的中间件。
  • securityAll:
  • deprecatedAll:
  • queryAll:此类下的所有路由都拥有的查询参数,与函数的查询参数会合并。

参数验证

参数校验支持:string, number, boolean, object, array。在object和array里面的参数也支持校验,对于integer类型是不支持校验的,会直接返回其值。默认是开启校验的,可以通过配置关闭校验。

router.mapDir(__dirname, { doValidation: true})

经过校验的数据可以通过ctx.validatedBody等方式获取经过校验的数据了,对应关系如下:

  • ctx.query ≤==> ctx.validatedQuery
  • ctx.params ≤==> ctx.validatedParams
  • ctx.request.body ≤==> ctx.validatedBody

校验对象配置

  • 基础校验字段

字段

值类型

备注

required

boolean

必须参数

example

any

参数类型,API 文档中使用的示例

description

string

接口描述

readOnly

boolean

只读参数

writeOnly

boolean

只写参数

nullable

boolean

是否可以为空

  • 字符串类型

字段

值类型

备注

type

'string'


minLength

number



在前面

今年国庆假期终于可以憋在家里了不用出门了,不用出去看后脑了,真的是一种享受。这么好的光阴怎么浪费,睡觉、吃饭、打豆豆这怎么可能(耍多了也烦),完全不符合我们程序员的作风,赶紧起来把文章写完。

这篇文章比较基础,在国庆期间的业余时间写的,这几天又完善了下,力求把更多的前端所涉及到的关于文件上传的各种场景和应用都涵盖了,若有疏漏和问题还请留言斧正和补充。

自测读不读

以下是本文所涉及到的知识点,break or continue ?

  • 文件上传原理
  • 最原始的文件上传
  • 使用 koa2 作为服务端写一个文件上传接口
  • 单文件上传和上传进度
  • 多文件上传和上传进度
  • 拖拽上传
  • 剪贴板上传
  • 大文件上传之分片上传
  • 大文件上传之断点续传
  • node 端文件上传

原理概述

原理很简单,就是根据 http 协议的规范和定义,完成请求消息体的封装和消息体的解析,然后将二进制内容保存到文件。

我们都知道如果要上传一个文件,需要把 form 标签的enctype设置为multipart/form-data,同时method必须为post方法。

那么multipart/form-data表示什么呢?

multipart互联网上的混合资源,就是资源由多种元素组成,form-data表示可以使用HTML Forms 和 POST 方法上传文件,具体的定义可以参考RFC 7578。

multipart/form-data 结构

看下 http 请求的消息体



  • 请求头:

Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryDCntfiXcSkPhS4PN 表示本次请求要上传文件,其中boundary表示分隔符,如果要上传多个表单项,就要使用boundary分割,每个表单项由———XXX开始,以———XXX结尾。

  • 消息体- Form Data 部分

每一个表单项又由Content-Type和Content-Disposition组成。

Content-Disposition: form-data 为固定值,表示一个表单元素,name 表示表单元素的 名称,回车换行后面就是name的值,如果是上传文件就是文件的二进制内容。

Content-Type:表示当前的内容的 MIME 类型,是图片还是文本还是二进制数据。

解析

客户端发送请求到服务器后,服务器会收到请求的消息体,然后对消息体进行解析,解析出哪是普通表单哪些是附件。

可能大家马上能想到通过正则或者字符串处理分割出内容,不过这样是行不通的,二进制buffer转化为string,对字符串进行截取后,其索引和字符串是不一致的,所以结果就不会正确,除非上传的就是字符串。

不过一般情况下不需要自行解析,目前已经有很成熟的三方库可以使用。

至于如何解析,这个也会占用很大篇幅,后面的文章在详细说。

最原始的文件上传

使用 form 表单上传文件

在 ie时代,如果实现一个无刷新的文件上传那可是费老劲了,大部分都是用 iframe 来实现局部刷新或者使用 flash 插件来搞定,在那个时代 ie 就是最好用的浏览器(别无选择)。

DEMO



这种方式上传文件,不需要 js ,而且没有兼容问题,所有浏览器都支持,就是体验很差,导致页面刷新,页面其他数据丢失。

HTML

 <form method="post" action="http://localhost:8100" enctype="multipart/form-data">

        选择文件:
            <input type="file" name="f1"/> input 必须设置 name 属性,否则数据无法发送<br/>
<br/>
            标题:<input type="text" name="title"/><br/><br/><br/>

        <button type="submit" id="btn-0">上 传</button>

</form>

复制代码

文件上传接口

服务端文件的保存基于现有的库koa-body结合 koa2实现服务端文件的保存和数据的返回。

在项目开发中,文件上传本身和业务无关,代码基本上都可通用。

在这里我们使用koa-body库来实现解析和文件的保存。

koa-body 会自动保存文件到系统临时目录下,也可以指定保存的文件路径。



然后在后续中间件内得到已保存的文件的信息,再做二次处理。

  • ctx.request.files.f1 得到文件信息,f1为input file 标签的 name
  • 获得文件的扩展名,重命名文件

NODE

/**
 * 服务入口
 */
var http=require('http');
var koaStatic=require('koa-static');
var path=require('path');
var koaBody=require('koa-body');//文件保存库
var fs=require('fs');
var Koa=require('koa2');

var app=new Koa();
var port=process.env.PORT || '8100';

var uploadHost=`http://localhost:${port}/uploads/`;

app.use(koaBody({
    formidable: {
        //设置文件的默认保存目录,不设置则保存在系统临时目录下  os
        uploadDir: path.resolve(__dirname, '../static/uploads')
    },
    multipart: true // 开启文件上传,默认是关闭
}));

//开启静态文件访问
app.use(koaStatic(
    path.resolve(__dirname, '../static') 
));

//文件二次处理,修改名称
app.use((ctx)=> {
    var file=ctx.request.files.f1;//得道文件对象
    var path=file.path;
    var fname=file.name;//原文件名称
    var nextPath=path+fname;
    if(file.size>0 && path){
        //得到扩展名
        var extArr=fname.split('.');
        var ext=extArr[extArr.length-1];
        var nextPath=path+'.'+ext;
        //重命名文件
        fs.renameSync(path, nextPath);
    }
    //以 json 形式输出上传文件地址
    ctx.body=`{
        "fileUrl":"${uploadHost}${nextPath.slice(nextPath.lastIndexOf('/')+1)}"
    }`;
});

/**
 * http server
 */
var server=http.createServer(app.callback());
server.listen(port);
console.log('demo1 server start ......   ');
复制代码

CODE

https://github.com/Bigerfe/fe-learn-code/

Koa 是基于 Node.js 平台的下一代 web 开发框架,参考了不少资料都是介绍 Koa 和 Express 之间的异同以及简单的对比。

由于之前学习 Express 主要是因为需要通过 Express 进行服务端的编码,所以本期小编也尝试用 Koa 进行服务端编码,看看 Koa 是如何运行的。


koa地址:https://koa.bootcss.com/


一、引入koa

const Koa=require("koa");
const app=new Koa();


二、引入路由koa-router

编写服务端一般都需要路由名称,koa-router 可以帮助你定义的路由一次性进行映射。

const router=require("koa-router")();
app.use(router.routes())         // 启动路由

三、引入cors

cors 是 koa 处理跨域请求的一个包,只需要引用一下即可处理跨域。

const cors=require("@koa/cors");
app.use(cors())


四、编写请求

1. get 请求

// http://localhost:8080/news
router.get("/news", async ctx=> {
    console.log(ctx.query);       // 请求参数
});
// http://localhost:8080/news2/aaa
router.get("/news2/:id", async ctx=> {
    console.log(ctx.params);   // {id:'aaa'}
});


2. post 请求

post 请求获取请求参数需要通过 koa-bodyparser 模块来进行获取。

const bodyparser=require(“koa-bodyparser”)
app.use(bodyparser());
// http://localhost:8080/news3
router.post("/news3", async ctx=> {
    let data=await ctx.request.body
});


五、ctx参数

ctx.body:为回调参数
ctx.type:回调类型
ctx.success:成功回调,需要自己定义成函数并执行
ctx.fail:失败回调,需要自己定义成函数并执行
ctx.status:状态码


以上就是本期关于 Koa 框架学习的小分享,希望能给大家带来帮助。

下期给大家分享更多实战中的点滴,如果大家对此感兴趣,欢迎各位关注、留言,大家的支持就是我的动力!