导语:之前做过一个小项目,其中后端用的node开发的接口服务,有些感觉挺好的方案,希望可以总结归纳一下,以免日后忘记,留个备份,记录一下。
# 目录
- 简介
- 规范
- 工具
- 案例
# 简介
# 定义
说到接口,大家应该都不陌生,对于程序员朋友来说是很常见的一种东西。API全称是Application Programming Interface,应用程序编程接口。
API是一些预先定义好的函数,提供应用程序和编程开发人员某种软件或硬件的能力,不需要访问源码或者理解其内部工作机制,根据API说明文档即可使用。
# 分类
API分为很多类型,常用的安装中间件划分有远程过程调用、标准查询语言、文件传输、信息交付等类型。
也有人分为系统级API和软件级API。
- 系统级API
系统级API用来控制硬件,是操作系统和应用程序之间的接口,操作系统已经实现了很多功能,都被封装成了一个个的函数,想要使用哪个功能,就调用相关的函数。
比如Windows、Linux、MacOS、Unix等常见的操作系统大部分功能由C语言开发,所以API以C语言形式呈现,这些API成百上千,都有官方的说明文档以供查阅。
- 软件级API
对于应用来说,经常用到一些功能,是由软件提供的API,包括编程语言API,自带的标准库,不需要再重复造轮子了,例如C的printf()
、scanf()
、fopen()
。
还要第三方库的API,比如OpenGL
、OpenCV
、openssl
、iconv
、CURL
,js中也有很多的API,提供操作游览器窗口、网页文档等的功能。
另外还有组织机构、公司和个人提供的API,有的开源免费,有的闭源收费,比如天气获取接口、音乐接口、物流接口。
# 规范
# RESTful架构
REST是由HTTP协议(1.0版和1.1版)的主要设计者、Apache服务器软件的作者之一、Apache基金会的第一任主席Roy Thomas Fielding于2000年在他的博士论文中提出来的。
他在文中说到"我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。"
Fielding将这种互联网软件的架构原则定名为Representational State Transfer,表现层状态转化,缩写是REST,符合这个REST原则的架构,就可以叫做RESTful架构。
- 资源是网络中的一个具体信息,比如一个文本,图片,音乐,视频,服务等;
- 表现层是把资源表现出来的形式,文本用txt格式表现,图片用jpg格式表现;
- 状态转化是客户端和服务器互动过程中数据和状态的变化,GET、POST、PUT、DELETE;
# 设计误区
- URL包含动词:
资源是实体,应该用名词,动词放在HTTP中,比如:/cats/show/1
,show是动词,正确应该是/cats/1
,用get表示show;
再比如动词表示不了,把动词改为名词,/transfer
改为/transition
;
- URL中包含版本号:
比如/api/v1/article
、/api/v2/article
、/api/v3/article
;
不同的版本是同一种资源的不同表现形式,应该采用同一个URI才恰当。
可以放在请求头中,是一种推荐的做法,像Github就是不错的例子。
accept: text/html,application/xhtml+xml,application/xml;v=b3;
accept: application/json;version=1.0;
accept: application/json;version=2.0;
accept: application/json;version=3.0;
2
3
4
# URL设计
- HTTP方法
GET:读取(Read),POST:新建(Create),PUT:更新(Update),PATCH:更新(Update);DELETE:删除(Delete);
Resource | POST | GET | PUT | DELETE |
---|---|---|---|---|
/customers | Create a new customer | Retrieve all customers | Bulk update of customers | Remove all customers |
/customers/1 | Error | Retrieve the details for customer 1 | Update the details of customer 1 if it exists | Remove customer 1 |
/customers/1/orders | Create a new order for customer 1 | Retrieve all orders for customer 1 | Bulk update of orders for customer 1 | Remove all orders for customer 1 |
- HTTP状态码
1xx:相关信息,2xx:操作成功,3xx:重定向,4xx:客户端错误,5xx:服务器错误;
- 返回数据
尽量是json或者xml格式的数据,不要返回文本格式的,请求头的Accept
可以设置成接受application/json
的数据。
- 返回状态
在返回的状态中,操作成功返回2xx,操作错误返回4xx比较好,这样有助于理清关系。
# 工具
这里我罗列了一些与API有关的工具,都是我平时用到的,比较简单方便调试API。
- postman (opens new window)
- hoppscotch (opens new window)
- apifox (opens new window)
- apipost (opens new window)
- yapi (opens new window)
其中,我用的时间最长的就是postman,支持本地和远程访问,存储接口地址、参数,同步远程,导出导入接口文档,非常方便快捷,可以调试接口,很好的一款接口管理工具。
# 案例
下面,通过一个用户的简单增删查改来实现一些功能,其中有些方法可能没有封装优化什么的,请勿见怪。
这是一个商品表,通过/goods
这个接口来进行资源的操作。
# 接口说明
/goods
是接口地址;- GET:用来查询所有的商品;
/goods?id=1
,查询id是1的商品; - POST:用来创建商品;
- PUT:用来更新商品;
- DELETE:用来删除商品,
/goods?id=1
,删除id是1的商品;
# 生成项目
express --view=ejs mygoods
cd mygoods
npm i
npm start
2
3
4
# 配置数据库
在根目录下面创建一个名为model
的文件夹,里面包含一个配置文件和一个调用方法文件。
// /model/config.js
const mysqlConfig = {
host: 'localhost',
port: '3306',
user: 'test',
password: 'test123456.',
database: 'test'
};
module.exports = mysqlConfig;
2
3
4
5
6
7
8
9
10
// /model/db.js
const mysql = require('mysql');
const config = require('./config');
const db = mysql.createConnection(config);
db.connect(function (err) {
if (err) {
console.error(`error connecting: ${err.stack}`);
return;
}
console.log(`Mysql is connected! 连接id: ${db.threadId}`);
});
module.exports = db;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 数据表内容
- 数据表结构
CREATE TABLE `goods` (
`id` int NOT NULL COMMENT 'id',
`name` char(20) NOT NULL COMMENT '商品名称',
`number` int DEFAULT NULL COMMENT '商品数量',
`price` float DEFAULT NULL COMMENT '商品价格',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建日期',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新日期'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品表';
2
3
4
5
6
7
8
- 表的索引
ALTER TABLE `goods`
ADD PRIMARY KEY (`id`);
2
- 使用表AUTO_INCREMENT
ALTER TABLE `goods`
MODIFY `id` int NOT NULL AUTO_INCREMENT COMMENT 'id', AUTO_INCREMENT=5;
COMMIT;
2
3
# 写接口
在routes
文件夹下面增加一个goods.js
的文件用来存放路由。
由于时间问题,我就不增加控制器了,直接在路由里面写方法。
- 引入模块
// goods.js
const express = require('express');
const router = express.Router();
const db = require('../model/db');
2
3
4
- 查询商品
router.get('/', (req, res) => {
let result = {};
let sql = 'SELECT * FROM `goods`';
let params = req.query;
if (params && params.id) {
sql += ' WHERE id =' + params.id;
}
db.query(sql, function (error, results, fields) {
if (error) {
result = {
code: 101,
msg: 'get_fail',
data: {
info: "查询失败!"
}
}
};
result = {
code: 200,
msg: 'get_succ',
data: {
info: "查询成功!",
list: results
}
}
return res.json(result);
});
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- 新增商品
router.post('/', (req, res) => {
let result = {};
let params = req.body;
let names = '',values = '';
for (const key in params) {
names += '`'+ key + '`,';
if (key == 'name') {
values += `"${params[key]}",`;
} else {
values += `${params[key]},`;
}
}
names = names.slice(0, names.length-1);
values = values.slice(0, values.length-1);
db.query('SELECT id FROM `goods` WHERE name= "' + params.name + '"', function (error, results, fields) {
if (error) {
result = {
code: 101,
msg: 'get_fail',
data: {
info: "查询失败!"
}
}
};
if (results && results.length) {
result = {
code: 200,
msg: 'get_succ',
data: {
info: "商品已存在!"
}
}
return res.json(result);
}
db.query('INSERT INTO `goods`(' + names + ') VALUES (' + values + ')', function (error, results, fields) {
if (error) {
result = {
code: 101,
msg: 'save_fail',
data: {
info: "查询失败!"
}
}
};
result = {
code: 200,
msg: 'save_succ',
data: {
info: "保存成功!",
des: {
id: results.insertId
}
}
}
return res.json(result);
});
});
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
- 更新商品
router.put('/', (req, res) => {
let result = {};
let params = req.body;
if (!params.id) {
return res.json({
code: 101,
msg: 'get_fail',
data: {
info: "id不能为空!"
}
})
}
db.query('SELECT id FROM `goods` WHERE `id` = ' + params.id, function (error, results, fields) {
if (error) {
result = {
code: 101,
msg: 'get_fail',
data: {
info: "查询失败!"
}
}
};
if (results && results.length == 0) {
result = {
code: 200,
msg: 'get_succ',
data: {
info: "商品不存在!"
}
}
return res.json(result);
}
db.query('UPDATE `goods` SET `number` = ' + params.number + ', `price` = ' + params.price + ' WHERE `id` = ' + params.id, function (error, results, fields) {
if (error) {
result = {
code: 101,
msg: 'save_fail',
data: {
info: "修改失败!"
}
}
};
result = {
code: 200,
msg: 'save_succ',
data: {
info: "修改成功!"
}
}
return res.json(result);
});
});
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
- 删除商品
router.delete('/', (req, res) => {
let result = {};
let params = req.query;
if (!params.id) {
return res.json({
code: 101,
msg: 'get_fail',
data: {
info: "id不能为空!"
}
})
}
db.query('SELECT id FROM `goods` WHERE `id` = ' + params.id, function (error, results, fields) {
if (error) {
result = {
code: 101,
msg: 'get_fail',
data: {
info: "查询失败!"
}
}
};
if (results && results.length == 0) {
result = {
code: 200,
msg: 'get_succ',
data: {
info: "商品不存在!"
}
}
return res.json(result);
}
db.query('DELETE FROM `goods` WHERE `id` = ' + params.id, function (error, results, fields) {
if (error) {
result = {
code: 101,
msg: 'get_fail',
data: {
info: "删除失败!"
}
}
};
result = {
code: 200,
msg: 'get_succ',
data: {
info: "删除成功!"
}
}
return res.json(result);
});
});
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
- 导出模块
module.exports = router;
- 在
app.js
里面加入下面两行。
var goodsRouter = require('./routes/goods');
app.use('/goods', goodsRouter);
2
# 测试接口
打开postman,配置好环境,添加四个请求,开始测试了。
- 查询所有商品
- 查询单个商品
- 创建商品
- 更新商品
- 删除商品
# 写在最后
互联网开发者社区中,还要非常多的好的设计方案,我这只是我所了解到的一部分而已,有好的也可以联系我分享给我。
以上就是一个node接口设计最佳实践,可能由一些不足之处,还请见谅,可以邮箱发我提建议,谢谢!