定义好 库表结构
后,下一步就是直接开发接口了。
yiapi 封装了大量的细节,提供了大量的内置功能,没有那么多弯弯绕绕的开发流程。
总体的大步骤就这几步:
- 定义数据库的库表
- 写接口,写业务。
- 让客户端调用接口。
- 完事。
接口前置说明
所有的接口,都放在 apis
目录下,该目录下的每个 .js
文件,都会被作为一个接口定义。
同时,可以给接口分门别类,继续创建子目录即可。
比如,订单相关的接口可以放到 apis/order
目录下,如果你有增删改查四个接口,那么可以这样组织:
apis/order/insert.js
添加订单apis/order/delete.js
删除订单apis/order/update.js
修改订单apis/order/select.js
查询订单
同时呢,每个目录下,都要有一个 _meta.js
文件,内容如下:
export const metaConfig = {
// 目录名称
dirName: '订单接口组',
// 接口名称
apiNames: {
insert: '添加订单',
delete: '删除订单',
select: '查询订单',
update: '修改订单'
}
};
_meta.js
文件必需具名导出 metaConfig
变量。
其中有两个必需属性,一个是 dirName
表示这一些列接口的含义。
一个是 apiNames
属性,用来映射其中每个接口的名称。
接口定义示例
接下来,我们来看一下,在 yiapi 中,一个接口是如何写的。
// 工具函数
import { fnRoute, fnSchema } from '@yicode/yiapi/fn.js';
// 配置文件
import { httpConfig } from '@yicode/yiapi/config/http.js';
import { schemaHelperConfig } from '@yicode/yiapi/config/schemaHelper.js';
// 数据库表
import { tableData } from '../../tables/example.js';
// 接口元数据
import { metaConfig } from './_meta.js';
export default async (fastify) => {
// 当前文件的路径,fastify 实例
fnRoute(import.meta.url, fastify, metaConfig, {
// 请求参数约束
schemaRequest: {
type: 'object',
properties: {
nickname: fnSchema(tableData.nickname),
age: fnSchema(tableData.age)
},
required: ['title']
},
// 执行函数
apiHandler: async (req, res) => {
const trx = await fastify.mysql.transaction();
try {
const newsModel = trx('news');
const result = await newsModel //
.clone()
.insertData({
title: req.body.title,
thumbnail: req.body.thumbnail,
content: req.body.content
});
await trx.commit();
return {
...httpConfig.INSERT_SUCCESS,
data: result
};
} catch (err) {
await trx.rollback();
fastify.log.error(err);
return httpConfig.INSERT_FAIL;
}
}
});
};
注意
每个接口,都用一个单独的文件来定义,请不要多个接口共用一个文件。
最简单的接口
每个接口的格式如下:
// 工具函数
import { fnRoute } from '@yicode/yiapi/fn.js';
// 接口元数据
import { metaConfig } from './_meta.js';
// 接口定义
export default async (fastify) => {
// 当前文件的路径,fastify 实例
fnRoute(import.meta.url, fastify, metaConfig, {
method: 'POST', // 可选
schemaRequest: {}, // 必需
schemaResponse: {}, // 可选
apiHandler: async () => {
// 必须
return {
code: 0,
msg: '第一个接口'
};
}
});
};
如果你想定义一个最简单的接口,那么除去以上可选部分,其他的就是必须要有的。
注意
请保证每个接口都必包含以上元素,否则就是无效接口。
这里,我们来对这个最简接口进行说明。
🔖 fnRoute
的导出
这是接口定义的核心,必须从 @yicode/yiapi/fn.js
中导出 fnRoute
这个函数,才能定义接口。
🔖 metaConfig
接口元数据
然后,从 ./_meta.js
文件中,导出 metaConfig
以做备用。
注意
metaConfig
必须使用具名定义,具名导出,也不能换成别的名字导出,否则接口会报错。
🔖 导出默认函数
每个接口,都必需对外导出一个如下所示的默认 async 函数。
export default async (fastify) => {};
其中,函数的参数使用 fastify
接收。
因为 yiapi
接口快速开发框架是在 fastify
框架的基础上进行的深度封装。
这里我们致敬和感谢开源社区的 fastify
和它的开发者们,就没必要改名了。
🔖 fnRoute
的定义
fnRoute
函数接收 4 个参数,分别是:
import.meta.url
当前文件路径fastify
fastify 实例metaConfig
接口元数据object
接口具体逻辑
前三个参数,所有接口都是一样的,不要改名称,也不要改顺序。
第四个参数,就是我们编写具体业务逻辑的地方,这是一个 object
对象,一共也有 4 个属性,分别是:
method(可选)
请求方法 (仅支持POST
和GET
,默认为POST
)schemaRequest
请求参数约束schemaResponse(可选)
返回参数约束apiHandler
接口具体逻辑
那么,method
和 schemaResponse
是可选的,你写不写都没关系。
yiapi
的设计理念就是,所有接口都用 POST
请求,实在不行再用 GET
请求。
提高对接口定义的灵活度,不同的功能,不由 Restful
范式的 delete
,put
,post
,get
来定义,而是由路由来定义。
比如订单的增删改查,分别对应 /order/insert
、/order/delete
、/order/update
、/order/select
。
而不是 post order
、delete order
、put order
、get order
。
Restful
范式表达的动作是有限的,超出它表达范围之外的东西用哪个都别扭。
而用路由可以表达无数种动作,请大家摒弃这种曾经盛极一时,实则漏洞百出的范式。
注意
本框架不支持、不认同、也不使用 Restful
规范。
如果跟本框架理念不合,请移驾别处。
接口的请求和返回都是用 json
数据格式。
请求参数验证
这是接口定义中,一个非常重要的内容,也就是 schemaRequest
部分的定义。
// 工具函数
import { fnRoute, fnSchema } from '@yicode/yiapi/fn.js';
// 数据库表
import { tableData } from '../../tables/example.js';
// 接口元数据
import { metaConfig } from './_meta.js';
// 接口定义
export default async (fastify) => {
// 当前文件的路径,fastify 实例
fnRoute(import.meta.url, fastify, metaConfig, {
method: 'POST', // 可选
// 请求参数约束
schemaRequest: {
type: 'object',
properties: {
nickname: fnSchema(tableData.nickname),
age: fnSchema(tableData.age)
},
required: ['title']
},
// 返回参数约束
schemaResponse: {}, // 可选
apiHandler: async () => {
// 必须
return {
code: 0,
msg: '第一个接口'
};
}
});
};
如上所示,schemaRequest
的值,必须是一个 object
类型,用来接受客户端调用接口传入的对象参数。
如果我们用的是 axios
,那么请求接口大致如下:
axios({
url: 'http://127.0.0.1:3000/api/example/insert',
data: {
nickname: '陈随易',
age: 18
}
});
schemaRequest
就是 json-schema
协议,对与 json-schema
不了解的,可以看查看 https://json-schema.apifox.cn
学习和了解。
其实不了解也没事,我们的 fnSchema
函数已经对此进行了封装。
并且此时呢,我们在上一个章节 库表定义
的内容就发挥作用了。
{
nickname: fnSchema(tableData.nickname),
age: fnSchema(tableData.age)
}
可以看到,properties
属性的值中,就同时用到了 fnSchema
函数和 库表定义
中的字段。
那么按照我们上个章节对库表的定义来看:
nickname: {
name: '昵称',
type: 'string',
default: '',
max: 50
}
对于 nickname
字段来说,其值必须为字符串,且不能超过 50 个字符,否则接口逻辑就不会执行。
这就是对接口参数的约束和使用。
业务逻辑开发
定义好接口的参数验证,保证拿到的参数是需要的,正确的,合法的之后,下一步就是写具体的业务逻辑,然后返回给客户端了。
这就是 apiHandler
部分的内容。
apiHandler: async () => {
// 必须
return {
code: 0,
msg: '查询成功',
data: {
id: 1,
name: '陈随易',
age: 18,
like: 'book'
}
};
};
那么最简单的就是,直接返回一个对象,必要的 2 个属性是 code
和 msg
。
code
为 0
表示一切正常,其他则为各种异常。
msg
则是接口的提示信息。
如果要返回数据,则返回 data
即可。
以上只是一个小示例,真正的业务开发,肯定是需要对接数据库的,数据库的使用如下:
🔖 无事务
// 执行函数
apiHandler: async (req, res) => {
try {
const newsModel = fastify.mysql.table('news');
const result = await newsModel //
.clone()
.insertData({
nickname: req.body.nickname,
age: req.body.age
});
return {
...httpConfig.INSERT_SUCCESS,
data: result
};
} catch (err) {
fastify.log.error(err);
return httpConfig.INSERT_FAIL;
}
};
🔖 有事务
// 执行函数
apiHandler: async (req, res) => {
const trx = await fastify.mysql.transaction();
try {
const newsModel = trx('news');
const result = await newsModel //
.clone()
.insertData({
nickname: req.body.nickname,
age: req.body.age
});
await trx.commit();
return {
...httpConfig.INSERT_SUCCESS,
data: result
};
} catch (err) {
await trx.rollback();
fastify.log.error(err);
return httpConfig.INSERT_FAIL;
}
};
以上功能,就是将客户端请求的参数插入到数据库中,然后返回插入的数据库的自增 ID。
那么,这就是接口的定义和使用了,整个开发流程,就这两步反复操作。
关于数据库的方法,配置的含义等,请看后续文章。