乐趣区

KOA2 Restful方式路由初探

前言
最近考虑将服务器资源整合一下,作为多端调用的 API 看到 Restful 标准和 ORM 眼前一亮, 但是找了不少版本路由写的都比较麻烦,于是自己折腾了半天
API 库结构
考虑到全部对象置于顶层将会造成对象名越来长,同时不便于维护,故采取部分的分层结构
如 workflow 模块内的 prototypes,instances 等等,分层的深度定义为层级
可访问的对象集合 (collection) 的属性满足 Restful 设计
— workflow(category)
— prototypes(collection)
— [method] …
— [method] …
— instances(collection)
— users(collection)
–[method] List #get :object/
–[method] Instance #get :object/:id
— …
— …
RESTFUL API 接口
将 Restful API 接口进行标准化命名
.get(‘/’, ctx=>{ctx.error(‘ 路径匹配失败 ’)})
.get(‘/:object’, RestfulAPIMethods.List)
.get(‘/:object/:id’, RestfulAPIMethods.Get)
.post(‘/:object’, RestfulAPIMethods.Post)
.put(‘/:object/:id’, RestfulAPIMethods.Replace)
.patch(‘/:object/:id’, RestfulAPIMethods.Patch)
.delete(‘/:object/:id’, RestfulAPIMethods.Delete)
.get(‘/:object/:id/:related’, RestfulAPIMethods.Related)
.post(‘/:object/:id/:related’, RestfulAPIMethods.AddRelated)
.delete(‘/:object/:id/:related/:relatedId’, RestfulAPIMethods.DelRelated)
API 对象
这个文件是来自微信小程序 demo,觉得很方便就拿来用了,放于需要引用的根目录,引用后直接获得文件目录结构 API 对象
const _ = require(‘lodash’)
const fs = require(‘fs’)
const path = require(‘path’)

/**
* 映射 d 文件夹下的文件为模块
*/
const mapDir = d => {
const tree = {}

// 获得当前文件夹下的所有的文件夹和文件
const [dirs, files] = _(fs.readdirSync(d)).partition(p => fs.statSync(path.join(d, p)).isDirectory())

// 映射文件夹
dirs.forEach(dir => {
tree[dir] = mapDir(path.join(d, dir))
})

// 映射文件
files.forEach(file => {
if (path.extname(file) === ‘.js’) {
tree[path.basename(file, ‘.js’)] = require(path.join(d, file))
tree[path.basename(file, ‘.js’)].isCollection = true
}
})

return tree
}

// 默认导出当前文件夹下的映射
module.exports = mapDir(path.join(__dirname))
koa-router 分层路由的实现
创建多层路由及其传递关系执行顺序为
1 — 路径匹配
— 匹配到‘/’结束
— 匹配到对应的 RestfulAPI 执行并结束
— 继续
2 — 传递中间件 Nest
3 — 下一级路由
4 — 循环 to 1
const DefinedRouterDepth = 2
let routers = []
for (let i = 0; i < DefinedRouterDepth; i++) {
let route = require(‘koa-router’)()
if (i == DefinedRouterDepth – 1) {
// 嵌套路由中间件
route.use(async (ctx, next) => {
// 根据版本号选择库
let apiVersion = ctx.headers[‘api-version’]
ctx.debug(`——- (API 版本 [${apiVersion}]) –=——-`)
if (!apiVersion) {
ctx.error(‘ 版本号未标记 ’)
return
}
let APIRoot = null
try {
APIRoot = require(`../restful/${apiVersion}`)
} catch (e) {
ctx.error (‘API 不存在, 请检查 Header 中的版本号 ’)
return
}
ctx.debug(APIRoot)
ctx.apiRoot = APIRoot
ctx.debug(‘———————————————‘)
// for(let i=0;i<)
await next()
})
}
route
.get(‘/’, ctx=>{ctx.error(‘ 路径匹配失败 ’)})
.get(‘/:object’, RestfulAPIMethods.List)
.get(‘/:object/:id’, RestfulAPIMethods.Get)
.post(‘/:object’, RestfulAPIMethods.Post)
.put(‘/:object/:id’, RestfulAPIMethods.Replace)
.patch(‘/:object/:id’, RestfulAPIMethods.Patch)
.delete(‘/:object/:id’, RestfulAPIMethods.Delete)
.get(‘/:object/:id/:related’, RestfulAPIMethods.Related)
.post(‘/:object/:id/:related’, RestfulAPIMethods.AddRelated)
.delete(‘/:object/:id/:related/:relatedId’, RestfulAPIMethods.DelRelated)

if (i != 0) {
route.use(‘/:object’, Nest, routers[i – 1].routes())
}
routers.push(route)
}
let = router = routers[routers.length – 1]
Nest 中间件
将 ctx.apiObject 设置为当前层的 API 对象
const Nest= async (ctx, next) => {
let object = ctx.params.object
let apiObject = ctx.apiObject || ctx.apiRoot
if(!apiObject){
ctx.error(‘API 装载异常 ’)
return
}

if (apiObject[object]) {
ctx.debug(`ctx.apiObject=>ctx.apiObject[object]`)
ctx.debug(apiObject[object])
ctx.debug(`————————————`)
ctx.apiObject = apiObject[object]
} else {
ctx.error(`API 接口 ${object}不存在 `)
return
}

await next()
}

RestfulAPIMethods
let RestfulAPIMethods = {}
let Methods = [‘List’, ‘Get’, ‘Post’, ‘Replace’, ‘Patch’, ‘Delete’, ‘Related’, ‘AddRelated’, ‘DelRelated’]
for (let i = 0; i < Methods.length; i++) {
let v = Methods[i]
RestfulAPIMethods[v] = async function (ctx, next) {

let apiObject = ctx.apiObject || ctx.apiRoot
if (!apiObject) {
ctx.error (‘API 装载异常 ’)
return
}
let object = ctx.params.object
if (apiObject[object] && apiObject[object].isCollection) {
ctx.debug(` — Restful API [${v}] 调用 — `)
if (typeof apiObject[object][v] == ‘function’) {
ctx.state.data = await apiObject[object][v](ctx)
ctx.debug(‘ 路由结束 ’)
return
//ctx.debug(ctx.state.data)
} else {
ctx.error(` 对象 ${object}不存在操作 ${v}`)
return
}
}
ctx.debug(` — 当前对象 ${object}并不是可访问对象 — `)
await next()
}
}

需要注意的点
1、koa-router 的调用顺序 2、涉及到 async 注意 next()需要加 await

退出移动版