欢迎关注「全栈工程师修炼指南」公众号
点击 下方卡片 即可关注我哟!
设为「星标⭐」每天带你 基础入门 到 进阶实践 再到 放弃学习!
“ 花开堪折直须折,莫待无花空折枝。 ”
作者主页:[ https://www.weiyigeek.top ]
博客:[ https://blog.weiyigeek.top ]
作者答疑学习交流群:请关注公众号回复【学习交流群】
文章目录:
0x00 前言简述
0x01 RESTful API 接口简介
0x02 RESTful API 接口设计
域名规范
版本规范
路径规范
请求操作规范
过滤信息规范
状态码规范
返回与错误处理规范
0x03 RESTful API 服务器运行流程
0x04 RESTful API 简单示例
使用内置 net/http 包简单实现
使用三方 Gin Web框架包实现
0x05 RESTful API 企业项目结构
0x00 前言简述
描述: 在当前云原生以及微服务流行的环境下,越来越多的开发者使用API接口实现数据的增删改查(CURD),将应用间的依赖解耦合,提高代码复用,便于水平扩展,所以为SZJX(升职加薪) 作为一名Go语言运开有必要进行相关规范的学习实践。
Q: 什么是API?
API (Application Programming Interface ,应用程序编程接口) 是一些预先定义的函数或者接口,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无须访问源码,或理解内部工作机制的细节。
简单的说,通过API请求接口我们可以实现任意数据操作,并且可以更加直观简约描述该请求的操作,更便于代码复用。
Q: API 服务器的设计规范和组合
我们要实现一个API 接口服务器时,需要考虑两个方面一个是API设计风格,另外一个是返回的数据类型。
此处以Go语言为例,在 API 开发中常见的组合是REST + JSON
或者gRPC + Protobuf
,其中REST
和gRPC
即是API设计风格,而JSON
和Protobuf
则是请求返回的数据类型,当然我们也可以使用返回XML格式,上述组合是开发中常用(推荐),在实际开发中还需根据业务需要以及环境来选型。
此处,由于作者实际学习需要,本章主要介绍 RESTful API 设计规范,在后续文章中也会归纳总结 gRPC 设计规范。
0x01 RESTful API 接口简介
描述: RESTful API 是目前比较成熟的一套互联网应用程序的API设计理念,REST (REpresentational State Transfer
)表现层状态转移,由 Roy Fielding 在他的论文中提出。
REST 是一种软件架构风格不是技术框架, 实际上是一组架构约束条件和原则,当满足REST有一系列规范的 API 均可称为 RESTful API
RESTful 架构具有结构清晰、符合标准、易于理解以及扩展方便等特点,受到越来越多开发者喜爱并广泛应用在各类的网站上!
RESTful API 核心规范
REST 中一切实体都被抽象成资源,每个资源有一个唯一的标识 URI ,所有的行为都应该是在资源上的 CRUD 操作
REST 中是无状态的,即请求都包含了所有足够完成本次操作的依赖信息,服务器端无须依赖Session(会话保持
),PS: 无状态对于服务端的弹性扩容是很重要的。
REST 中天生和 HTTP 协议相辅相成,所以使用标准的HTTP协议方法 POST 、 DELETE 、 PUT 、 GET 方法来对应 REST 资源的增、删、改、查操作。
HTTP方法 | 行为操作 | API URL | 操作说明 |
---|---|---|---|
GET | 获取资源列表 | /users | 获取用户账号列表 |
GET | 获取一个具体的资源 | /users/admin | 获取admin账号的相关详细信息 |
POST | 创建一个新的资源 | /users/weiyigeek | 创建一个weiyigeek的用户账号 |
PUT | 以整体的方式更新一个资源 | /users/10000 | 更新用户ID为10000的账号 |
DELETE | 删除服务器上的一个资源 | /users/10000 | 删除id为10000的账号 |
0x02 RESTful API 接口设计
描述: 前面快速描述 RESTful API 接口规范,本节将实践根据规范来进行 RESTful API 设计,以及在实际开发中应该怎么做。
描述: 应该尽量将API部署在专用域名之下,如果确定API很简单不会有进一步扩展,可以考虑放在主域名下。
# 方式1.独立域名(推荐)
https://api.weiyigeek.top
# 方式2.主域名后的目录
https://weiyigeek.top/api/
描述: 应尽量使用版本( Versioning )和 应用的应用程序名称来规范。
# 方法1.将API的版本号以及该api应用的应用程序名称放入URL(推荐)
http://api.weiyigeek.top/app/1.0/foo
http://api.weiyigeek.top/app/1.1/foo
# 方法2:将版本号放在HTTP头信息中,但不如放入URL方便和直观,Github采用这种做法。
# 因为不同的版本,可以理解成同一种资源的不同表现形式,所以应该采用同一个URL。
# 版本号可以在HTTP请求头信息的Accept字段中进行区分(参见Versioning REST Services):
Accept: app.weiyigeek-top.foo+json; version=1.0
Accept: app.weiyigeek-top.foo+json; version=2.0
描述 : 路径又称 " 终点 " ( endpoint ),表示 API 的具体网址,每个网址代表一种资源(resource)
使用名词
对于一个简洁结构,你应该始终用名词, 资源作为网址,只能有名词不能有动词 ,而且所用的名词往往与
数据库的表名对应
。
# 获取所有产品清单
GET /products
# 获取产品4的信息
GET /products/4
# 更新产品4的信息
PATCH(或)PUT /products/4
# 新建产品
POST /products
#-------------------------------#
# 错误或者不建议的示例
# 获取所有端口
/getProducts
# 获取所有订单
/listOrders
# 订单1的所有回复(排序返回)
/retreiveClientByOrder?orderId=1
使用复数
API 中的名词应该使用复数,无论子资源或者所有资源。
# 获取单个产品
GET http://api.weiyigeek.top/AppName/v1/products/1
# 获取所有产品
GET http://api.weiyigeek.top/AppName/v1/products
描述: 前面我们在讲解RESTful API 核心规范中说到对于资源的具体操作类型通常与HTTP请求方法对应,其中用的HTTP动词如下所示,其中括号里是对应的 SQL 命令。
HTTP动词
GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
DELETE(DELETE):从服务器删除资源。
PATCH(UPDATE):在服务器更新(更新)资源(客户端提供改变的属性)。
HEAD:获取资源的元数据。
OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
示例
# 获取所有产品
GET http://api.weiyigeek.top/AppName/v1/products
# 创建单个产品
POST http://api.weiyigeek.top/AppName/v1/products
# 更新单个产品
PUT http://api.weiyigeek.top/AppName/v1/products/1
# 删除单个产品
DELETE http://api.weiyigeek.top/AppName/v1/products/1
# 当然为了安全你也可以只使用GET(查询操作)与POST请求(关键性操作),例如
# 更新产品ID为1的数据
POST http://api.weiyigeek.top/AppName/v1/products/1/update
# 删除产品ID为2的数据
POST http://api.weiyigeek.top/AppName/v1/products/1/delete
描述: 如果API返回的数量很多,服务器不可能都将它们返回给用户,此时 API 应该提供参数,过滤返回结果。
# 指定返回记录的数量
GET http://api.weiyigeek.top/AppName/v1/products?limit=10
# 指定返回记录的开始位置
GET http://api.weiyigeek.top/AppName/v1/products?offset=10
# 指定第几页以及每页的记录数
GET http://api.weiyigeek.top/AppName/v1/products?page=2&per_page=100
# 指定返回结果按照哪个属性排序以及排序顺序
GET http://api.weiyigeek.top/AppName/v1/products?sortby=name&order=asc
# 指定筛选条件
GET http://api.weiyigeek.top/AppName/v1/products?animal_type_id=1
# 参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。
比如,GET /zoos/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。
描述: 服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的 HTTP 动词)。
状态码 | 适用请求 | 备注 |
---|---|---|
200 OK | [GET] | 服务器成功返回用户请求的数据 |
201 CREATED | [POST/PUT/PATCH] | 用户新建或修改数据成功。 |
202 Accepted | [*] | 表示一个请求已经进入后台排队(异步任务) |
204 NO CONTENT | [DELETE] | 用户删除数据成功。 |
400 INVALID REQUEST | [POST/PUT/PATCH] | 用户发出的请求有错误,服务器没有进行新建或修改数据的操作 |
401 Unauthorized | [*] | 表示用户没有权限(令牌、用户名、密码错误)。 |
403 Forbidden | [*] | 表示用户得到授权(与401错误相对),但是访问是被禁止的。 |
404 NOT FOUND | [*] | 用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。 |
406 Not Acceptable | [GET] | 用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。 |
410 Gone | [GET] | 用户请求的资源被永久删除,且不会再得到的。 |
422 Unprocesable entity | [POST/PUT/PATCH] | 当创建一个对象时,发生一个验证错误。 |
500 INTERNAL SERVER ERROR | [*] | 服务器发生错误,用户将无法判断发出的请求是否成功。 |
偷偷的告诉你哟?【极客全栈修炼】微信小程序已经上线了,
可直接在微信里面直接浏览博主博客了哟,后续将上线更多有趣的小工具。
返回与错误处理规范
描述: 在服务器返回的数据格式,应该尽量使用 JSON 避免使用 XML,在实际开发中常常也是使用 JSON。
标注标准返回结果,实际开发通常返回是200,错误是 0
// # 正常返回
{
"code": 200,
"message": "服务器成功返回用户请求的数据.",
"data": {
"name": "WeiyiGeek",
"blog": "blog.weiyigeek.top"
}
}
// # 异常返回
{
"code": 0,
"message": "服务器未能成功返回用户请求的数据.",
"data": {}
}
定制标准化的返回结果
{
"code": 10002,
"message": "User data operation completed.",
"data": {}
}
定制标准化的错误返回结果 (例如,code 大于等于200)
{
"code": 20002,
"error": "The server failed to successfully return the data requested by the user."
}
0x03 RESTful API 服务器运行流程
描述: 此处以Go语言的Web框架Gin模块包为例设计的API服务器,其启动运行流程大致如下(开发自定义请根据实际情况进行设计):
1.命令启动API服务器后,其首先加载配置文件,根据配置做后面的处理工作。
2.通常会将日志相关的配置记录在配置文件中,在解析完配置文件后加载日志包初始化函数,来初始化日志实例,供后面的程序调用。
3.然后初始化数据库实例,建立数据库连接,供后面对数据库的 CRUD 操作使用。
4.设置HTTP请求,通常包括 3 方面的注册路由
、设置 Header
、注册中间件
5.然后加载设置的路由以及健康检查预警。
5.然后调用 net/http 包的 ListenAndServe() 方法启动 HTTP 服务器(即启动绑定监听接口地址和端口)。
6.启动 HTTP 端口之前,程序会 go 一个协程,来ping HTTP 服务器的 /sd/health
接口,如果程序成功启动,ping 协程在timeout 之前会成功返回,如果程序启动失败,则 ping协程最 timeout,并终止整个程序
0x04 RESTful API 简单示例
描述: Golang 提供了内置的 net/http 包,用来处理这些 HTTP 请求,可以比较方便地开发一个 HTTP 服务。
文本格式
下述代码中符合 RESTful API 的规范,当我们在 handleUsers 函数中增加 GET 、POST、PUT方法时,才获得对应用户的信息,其他情况返回 not found。
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/users",handleUsers)
http.ListenAndServe(":8080", nil)
}
func handleUsers(w http.ResponseWriter, r *http.Request){
switch r.Method {
case "GET":
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w,"ID: 1, Name: WeiyiGeek")
case "POST":
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w,"ID: 2, Name: Geeker")
case "PUT":
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w,"ID: 3, Name: Security")
default:
w.WriteHeader(http.StatusNotFound)
fmt.Fprintln(w,"not found")
}
}
此时在运行程序后,在浏览器中输入 http://localhost:8080/users, 就可以看到如下内容信息:ID: 1, Name: WeiyiGeek
, 由于浏览器默认是GET请求的,如果想实践其他请求返回的数据可使用Postman工具。
RESTful JSON API
在实践项目接口开发中,数据大多数情况下会使用 json 格式来传输呈现,所以再次对示例进行改造,使它返回 json 格式的内容:
package main
import (
"encoding/json"
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/langs",handleLangs)
http.ListenAndServe(":8080", nil)
}
// 模拟数据源假设是MySQL中的数据
var langs = []Lang{
{ID: 1,Name: "Golang"},
{ID: 2,Name: "Java"},
{ID: 3,Name: "PHP"},
{ID: 4,Name: "Python"},
}
// 编程语言结构体
type Lang struct {
ID int
Name string
}
func handleLangs(w http.ResponseWriter, r *http.Request){
switch r.Method {
case "GET":
// json.Marshal 将数据成json字符串 (json序列化)
langs,err:=json.Marshal(langs)
if err!=nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w,"{\"message\": \""+err.Error()+"\"}")
}else {
w.WriteHeader(http.StatusOK)
w.Write(langs)
}
default:
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w,"{\"message\": \"not found\"}")
}
}
描述: 上面我们使用的是 Go 语言自带的 net/http 包,写法比较简单,但是它也有许多不足之处:
不能单独地对请求方法(POST、GET 等)注册特定的处理函数;
不支持 Path 变量参数;
不能自动对 Path 进行校准;
性能一般,扩展性不足;
……
基于以上的不足,我们可以使用其它的 Golang Web 框架, 此处笔者推荐 Gin 。(欢迎浏览博主的gin系列学习文章,正在补充中,博客地址: https://blog.weiyigeek.top )
简单示例:
package main
import (
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
)
// 模拟数据源假设是MySQL中的数据
var langs = []Lang{
{ID: 1, Name: "Golang"},
{ID: 2, Name: "Java"},
{ID: 3, Name: "PHP"},
{ID: 4, Name: "Python"},
}
// 编程语言结构体
type Lang struct {
ID int
Name string
}
// 实例化Lang对象作为临时存放变量
var lang Lang
func main() {
r := gin.Default()
// GET Method
// 在 Gin 框架中,路径中使用冒号表示 Path 路径参数,比如示例中的 :id,然后通过 c.Param("id") 获取需要查询用户的 ID 值。
r.GET("/users/:id", func(c *gin.Context) {
// 获取url路径中的参数
id := c.Param("id")
// 变量声明
found := false
// 类似于数据库的SQL查询
for _, data := range langs {
if strings.EqualFold(id, strconv.Itoa(data.ID)) {
lang = data
found = true
break
}
}
// 若查询到数据则输出
if found {
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": lang,
})
} else {
c.JSON(http.StatusNotFound, gin.H{
"code": "0",
"message": "用户不存在",
})
}
})
// Post Method
// 新增用户的逻辑是获取客户端上传的 name 值,然后生成一个 User 用户,最后把它存储到 users 集合中。
r.POST("/users", func(c *gin.Context) {
param_lang := c.DefaultPostForm("lang", "")
if param_lang != "" {
lang := Lang{ID: len(langs) + 1, Name: param_lang}
langs = append(langs, lang)
c.JSON(http.StatusCreated, gin.H{
"code": 200,
"data": lang,
})
} else {
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "请输入用户名称",
})
}
})
r.Run()
}
运行脚本
$ go run .\main.go
.......
[GIN-debug] GET /users/:id --> main.main.func1 (3 handlers)
[GIN-debug] POST /users --> main.main.func2 (3 handlers)
.......
[GIN-debug] Listening and serving HTTP on :8080
亲,文章就要看完了,不关注一下作者吗?
执行结果:
# GET 请求
curl --location 'http://10.20.172.106:8080/users/1'
# {"code":200,"data":{"ID":1,"Name":"Golang"}}
# POST 请求
curl --location 'http://10.20.172.106:8080/users' \
--form 'lang="C语言"'
# {"code":200,"data":{"ID":5,"Name":"C语言"}}
0x05 RESTful API 项目结构
在 Go API 项目中通常会包括很多功能,例如 Makefile 文件(编译文件)、配置文件目录、 RESTful API 服务器的 handler 目录、 model 目录、工具类目录、 vendor 目录,以及实际处理业务逻辑函数所存放的service 目录。
上述功能代码在结构中有列出,新加功能时将代码放入对应功能的目录/文件中,可以使整个项目代码结构更加清晰,非常 有利于后期的查找和维护 。
# Linux
mkdir -vp ./config/{tls,conf} ./docs/swagger ./handler/sd/user ./model ./pkg/{errno,version} ./router/middleware ./service ./util ./vendor
# windows
mkdir -vp ./config/tls ./config/conf ./docs/swagger ./handler/sd/user ./model ./pkg/errno ./pkg/version ./router/middleware ./service ./util ./vendor
本文至此完毕,更多技术文章,尽情等待下篇好文!
原文地址: https://blog.weiyigeek.top/2023/3-28-727.html
如果此篇文章对你有帮助,请你将它分享给更多的人!
学习书籍推荐 往期发布文章
热文推荐
网安等保-国产Linux操作系统银河麒麟KylinOS-V10SP3常规配置、系统优化与安全加固基线实践文档
硬件玩物 | 闲置物理主机安装群辉NAS-DSM-7.x系统实践试用初体验(保姆篇)
全栈之前端 | 1.HTML基础必备知识学习篇
全栈之前端 | 2.HTML根部头部主体标签元素学习篇
1.Go编程快速入门学习
8.Go语言编程快速入门学习之并发(Goroutine)和通道(Channel)
17.Go语言编程之go-redis操作Redis内存数据库实践
欢迎长按(扫描)二维码 获取更多渠道哟!
欢迎关注 【全栈工程师修炼指南】(^U^)ノ~YO
== 全栈工程师修炼指南 ==
微信沟通交流: weiyigeeker
关注回复【学习交流群】即可加入【安全运维沟通交流小群】
温馨提示: 由于作者水平有限,本章错漏缺点在所难免,希望读者批评指正,若有问题或建议请在文章末尾留下您宝贵的经验知识,或联系邮箱地址
[email protected] 或 关注公众号 [全栈工程师修炼指南] 留言。
点个【 赞 + 在 】看吧!
点击【"阅读原文"】获取更多有趣的知识!