基础RESTful风格接口支持
REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。
------来自百度百科
大部分的API服务使用的都是基于HTTP协议,然后自定义数据结构,我也不例外,不过我推荐使用RESTful风格的接口设计,使用JSON作为传输数据的格式,然后尽量使用统一一致的风格来制定数据中的字段。
RESTful路由支持
基础介绍
我们基于koa-router开源组件来实现对RESTful接口的路由设置。
基础的用法
router.get('/path', (ctx, next) => {
// ctx.router available
});
从而指定了一个对路径为/path的GET请求设置处理函数。post,delete等也是类似的。
我们使用一下四种HTTP method对应不同种请求:
'GET' 获取数据
"POST" 创建数据
"DELETE" 删除数据
"PUT" 更新数据
项目配置修改
修改package.json dependencies 中增加以下,同时每一次增加新的依赖之后自行运行npm install安装依赖。
"koa-router": "7.4.0",
工程化代码
当然可以按照基础的用法把所有的接口方法路径和处理方法关联起来,但是这是一种很笨的方法,需要维护一个很大的路由表。
我这里使用的方法是基于文件目录自动生成请求path
具体方法是这样滴:
根目录下创建controllers目录,后续所有接口的业务代码都放在这个目录下。
根目录下新建文件router.js,内容如下:
const Fs = require('fs');
const kSupportMethods = ['GET', "POST", "DELETE", "PUT"]
function parseFileToRouter(router, file_path, base_url_path) {
let module_export = require(file_path);
for (let key in module_export) {
for (let method of kSupportMethods) {
if (key.startsWith(method)) {
let path = base_url_path + key.substring(method.length + 1)
router[method.toLowerCase()](path, module_export[key])
console.log(`${method} ${path}`)
}
}
}
}
function parseDirToRouter(router, dir, base_url_path = '/') {
if (!base_url_path.endsWith('/')) {
base_url_path += '/';
}
Fs.readdirSync(dir).filter((f) => {
if (f.endsWith('.js')) {
return true;
}
var stat = Fs.lstatSync(dir + '/' + f);
return stat.isDirectory();
}).forEach((f) => {
if (f.endsWith('.js')) {
parseFileToRouter(router, dir + '/' + f, base_url_path);
} else {
parseDirToRouter(router, dir + "/" + f, base_url_path + f + "/");
}
});
}
function setupRouter(koa_app, controllers_dir = './controllers') {
let router = require('koa-router')()
parseDirToRouter(router, controllers_dir)
koa_app.use(router.routes()).use(router.allowedMethods())
}
module.exports = {
setupRouter
};
具体解析一下:
function parseFileToRouter(router, file_path, base_url_path)
router:路由器
base_url_path:请求路径前缀
file_path:被解析的文件
遍历file_path对应文件导出函数,如果函数名以 'GET', "POST", "DELETE", "PUT" 开头的话则认为对应是一个RESTful接口处理函数,然后设置到路由器中。
base_url_path = '/abc/'
导出接口为: 'GET def'
则最终处理函数对应请求path为/abc/def的GET请求
function parseDirToRouter(router, dir, base_url_path = '/')
函数主要功能是遍历指定目录,目录中的文件夹则把文件夹名字加到base_url_path上去,然后再递归调用该函数,针对文件的话则调用parseFileToRouter函数从而解析文件,将对应的处理函数添加到响应方法和路径上。
现在修改app.js文件
const Koa = require('koa');
const Config = require('./configs');
const Router = require('./router')
function startApp() {
const app = new Koa();
Router.setupRouter(app);
app.listen(Config.port);
console.log(`server start of:${Config.port}`);
}
startApp()
然后在controllers目录中创建hello.js
module.exports = {
'GET hello': async (ctx, next) => {
ctx.body = 'GET Hello World';
},
'POST hello': async (ctx, next) => {
ctx.body = 'POST Hello World';
}
}
创建个目录hello,目录下创建hello.js
module.exports = {
'GET hello': async (ctx, next) => {
ctx.body = 'GET Hello/Hello World';
}
}
启动程序命令行输出:
virl@virldeMacBook-Pro-2 ~/w/k/kite_server> npm start
> kite_server@1.0.0 start /Users/virl/wksp/kite/kite_server
> node app.js
GET /hello/hello
GET /hello
POST /hello
server start of:3000
然后用Postman GET请求http://localhost:3000/hello返回结果:
GET Hello World
POST请求http://localhost:3000/hello返回结果:
POST Hello World
GET请求http://localhost:3000/hello/hello返回结果:
GET Hello/Hello World
POST请求http://localhost:3000/hello/hello返回结果:
Method Not Allowed
HTTP请求的解析
使用koa-bodyparser这个开源库做这个事情,同样添加依赖
"koa-bodyparser": "4.2.1"
修改app.js
const BodyParser = require('koa-bodyparser');
app.use(BodyParser());
然后后续解析好的请求数据存在两个地方
ctx.request.query:获取在url中传递的参数
ctx.request.body:获取POST/PUT包体中的参数
后面代码会一并展示效果
JSON数据返回格式
然后就是封装返回的数据,让我们更方便使用统一的数据格式把结果返回给请求方。
新建目录common/koa,创建文件ctx_ex.js。这里ex表示扩展的意思。
const kHttpErrorCodeMsgMap = {
400: 'Bad Request',
401: 'Unauthorized',
403: 'Forbidden',
404: 'Not Found',
409: 'Conflict',
}
async function setupCtxFunc(ctx, next) {
ctx.rest = (jsonData = undefined) => {
ctx.set('Content-Type', 'application/json')
let responseJson = {
code: 200,
msg: 'ok',
data: jsonData
}
ctx.body = JSON.stringify(responseJson)
};
ctx.onError = (code, msg = undefined, jsonData = undefined) => {
ctx.set('Content-Type', 'application/json')
if (!msg) {
msg = kHttpErrorCodeMsgMap[code] || 'Unknown Error'
}
let responseJson = {
code,
msg,
data: jsonData
}
ctx.body = JSON.stringify(responseJson)
}
await next()
}
module.exports = setupCtxFunc
这部分代码主要给ctx增加两个函数:
- rest将结果数据封装返回,如果没有需要返回的数据则返回默认的ok格式。
- onError当出现错误的时候,返回统一的错误格式。我们这里错误码会尽量与HTTP标准状态保持一致
同时要注意设置Respons头为json格式:ctx.set('Content-Type', 'application/json')
现在修改app.js
const Koa = require('koa');
const Config = require('./configs');
const BodyParser = require('koa-bodyparser');
const Router = require('./router')
const CtxEx = require('./common/koa/ctx_ex')
function startApp() {
const app = new Koa();
app.use(BodyParser());
app.use(CtxEx);
Router.setupRouter(app);
app.listen(Config.port);
console.log(`server start of:${Config.port}`);
}
startApp()
修改hello.js
module.exports = {
'GET hello': async (ctx, next) => {
console.log(`******** request query *************`)
console.log(ctx.request.query)
console.log(`******** request body *************`)
console.log(ctx.request.body)
ctx.rest({
query: ctx.request.query,
body: ctx.request.body
})
},
'POST hello': async (ctx, next) => {
ctx.body = 'Hello World';
console.log(`******** request query *************`)
console.log(ctx.request.query)
console.log(`******** request body *************`)
console.log(ctx.request.body)
ctx.rest({
query: ctx.request.query,
body: ctx.request.body
})
},
'GET error': async (ctx, next) => {
ctx.onError(400)
}
}
启动服务试一试,不过要记得Postman发送post请求的时候需要用x-www-form-urlencoded或者json做包体的格式。