使用nodejs + ts + koa搭建服务端脚手架 --- 2. 接口开发与优化

7/26/2021 jstsnodejskoa2cli

# 使用nodejs + ts + koa搭建服务端脚手架 --- 2. 接口开发与优化

# 响应请求

koa服务器搭建起来之后,就可以请求服务器,并让服务器对请求作出对应的响应。

在上一节中,我们在app.ts,写过这样的代码:

...
app.use(async (ctx: Koa.Context) => {
  ctx.body = 'Hello World';
});
...

这其实就是一个最简单的接口,它表示,只要收到请求,就返回Hello World

但是这样的功能还远远不够。

  1. 解析请求路径,读取query参数

ctx: Koa.Context是koa里的上下文对象,通过ctx.request,就可以获取请求信息

// app.ts

app.use(async (ctx: Koa.Context) => {
  const req:Koa.Request = ctx.request
  const originalUrl:string = req.originalUrl
  const method:string = req.method
  const query = req.query
  switch (originalUrl){
    case 'login':
      login(query.userName,query.password)
      break;
    case 'getUserInfo': 
      getUserInfo(query.userName)
      break;
    default 
      other()
  }
});

function login(){
  // 登录
}
function getUserInfo(){
  // 获取用户信息
}
function other(){
  // 其它
  ctx.body = '请求错误,无响应路径'
}

我们可以通过originalUrl,获取请求的路径;通过method,获取请求的类型;通过query,获取请求的参数;然后根据不同的路径,进行不同的操作。

  1. 读取body数据

现在只是能读取query,如果要读取post的body,还需要安装koa-bodyparser

安装依赖:

npm i koa-bodyparser @type/koa-bodyparser

代码:

// app.ts
import Koa from "koa"
import koaBodyParser from "koa-bodyparser"
import http from 'http'
const app = new Koa()

app.use(koaBodyParser())
app.use(async (ctx: Koa.Context) => {
    switch (originalUrl){
      case 'login':
        login(query.userName,query.password)
        break;
      case 'getUserInfo': 
        getUserInfo(query.userName)
        break;
      default 
        other()
    }
});

function login(){
  // 登录
}
function getUserInfo(){
  // 获取用户信息
}
function other(){
  // 其它
  ctx.body = '请求错误,无响应路径'
}

const server: http.Server = new http.Server(app.callback())
server.listen(3001,()=> {
    console.log('app started at port 3001...')
})



# 优化接口

目前我们的接口已经具备基本的功能,但还远远不够优雅。

理想的情况下,至少应该具备一下功能:

  • 一个ts文件只写一个接口或者一个功能

  • 约定一个文件夹,程序自动读取对应路径下的所有ts文件并创建对应的接口

  • 能够简单地设置接口能响应的请求类型(post还是get等)

  • 统一返回格式,帮助前后端定位错位

  • 请求参数校验

为了实现上述功能,我们首先需要一个路由系统

  1. 路由系统

上面我们通过switch去判断不同请求路径,并执行不同的方法,其实就是路由系统的雏形,我们可以选择在这个基础上继续去实现和优化代码,但是前辈们已经封装过好用的轮子了--koa-router,可以直接使用。

安装依赖:

npm i koa-router @type/koa-router

在src目录下新建api目录,当前是第一个版本,我们可以在api下新建一个v1,将当前版本的接口都放在v1目录下,然后我们在v1目录下新建第一个接口helloWorld.ts:

// api/v1/helloWorld.ts

import KoaRouter from 'koa-router'
const router = new KoaRouter({
    prefix:'/api/v1'
})
router.get('/helloWorld',async (ctx: Koa.Context)=> {
    
    const {
        user
    } = ctx.query
    ctx.body = `Hello world, ${user}`
})

export default router

在src目录下创建common/utils目录,在这里存放通用的工具方法,然后新建Utils.ts。

在Utils.ts里,我们写一个fileDisplay方法,能够遍历对应目录下的所有文件,并引入其导出,最后use导出的接口

// common/utils/utils.ts
import * as fs from 'fs';  
import * as path from 'path'; 
import Koa from 'koa'
class Utils {

    /**
     * 文件遍历方法
     * @param filePath 需要遍历的文件路径
     * @param app koa实例
     */
    public static async fileDisplay(filePath: string,app: Koa) {
        // 根据文件路径读取文件,返回一个文件列表
        const files:string[] = await fs.readdirSync(filePath);
        // 遍历读取到的文件列表
        for(let i = 0;i<files.length;i++){
            const fileName = files[i]
            // path.join得到当前文件的绝对路径
            const _filePath:string = path.join(filePath, fileName);
            const stats:fs.Stats = await fs.statSync(_filePath)
            const isFile = stats.isFile(); // 是否为文件
            const isDir = stats.isDirectory(); // 是否为文件夹
            if (isFile) {
                const file = require(_filePath)
                app.use(file.default.routes())
                console.log(`加载接口:${_filePath.replace(process.cwd(),'')}成功`)
            }
            if (isDir) {
                await Utils.fileDisplay(_filePath,app); // 递归,如果是文件夹,就继续遍历该文件夹里面的文件;
            }
        }
        
        
    }
}

export default Utils

在src目录下创建core目录,新建Init.ts,这里就是我们的核心控制器,所有的功能包括接口初始化都会在这里完成

import Koa from 'koa'
import path from 'path'
import Utils from '../common/utils/Utils'
import http from 'http'
class Init{
    public static app: Koa<Koa.DefaultState, Koa.DefaultContext>
    public static server: http.Server
    public static initCore(app: Koa<Koa.DefaultState, Koa.DefaultContext>, server: http.Server) {
        Init.app = app
        Init.server = server
        Init.initLoadRouters()
    }
    static async initLoadRouters() {
        const dirPath = path.join(`${process.cwd()}/src/api/`)
        await Utils.fileDisplay(dirPath,Init.app)
        console.log('接口加载完成')
    }

}
export default Init.initCore

最后再在app.ts里引入Init.ts

import Koa from "koa"
import initCore from './core/Init'
import koaBodyParser from "koa-bodyparser"
import http from 'http'
const app = new Koa()

app.use(koaBodyParser())

initCore(app,server)
const server: http.Server = new http.Server(app.callback())
server.listen(3001,()=> {
    console.log('app started at port 3001...')
})

  1. 参数校验

Ajv