系列综述:
💞目的:本系列是个人整理为了学习RESTful API规范
的,整理期间苛求每个知识点,平衡理解简易度与深入程度。
🥰来源:材料架构主要源于GPT
进行的,每个知识点的修正和深入主要参考各平台大佬的文章,其中也可能含有少量的个人实验自证。
🤭结语:如果有帮到你的地方,就点个赞和关注一下呗,谢谢🎈🎄🌷!!!
概述
基本概念
- REST:表述状态转移(Representational State Transfer)是一种
软件架构设计风格
,而不是标准
,只是提供了一组设计原则和约束条件- 表述:是指客户端获取的资源的一种外在表示形式,包括
数据
和描述数据的元数据
- 状态转移:
- 前提:客户端负责维护应用状态,而服务端维护资源状态,但每次交互是无状态的(每次请求包含请求所需所有信息)
- 本质:会话被客户端作为应用状态进行跟踪,客户端通过HTTP协议的资源操作进行状态转移
- RESTful:基于
REST 风格
构建的网络应用程序接口(API)的约定设计规范
- 表述:是指客户端获取的资源的一种外在表示形式,包括
- URI
统一资源标识符
- 定义:用来标识抽象资源的字符串,通常是
资源名称
和位置
的结合(资源和URI是一对多的关系) - 分类
- URL
统一资源定位
:用于定位资源的具体位置
,并提供访问资源的方式(如协议、主机名、路径等) - URN
统一资源名称
:用于标识资源的唯一名称
,与资源的位置无关,即使资源位置变化,URN 仍然有效
- URL
- 特点:
一切皆资源
- 将网络中所有能被操作的对象都视为资源,每个资源都用
唯一的 URI
来标识,方便客户端准确地定位和访问 - 通过统一资源标识符(URI)来对资源进行定位,使用 HTTP 协议中定义的方法(如 GET、POST、PUT、DELETE 等)对这些资源进行操作,从而实现客户端与服务器之间的通信。
- 将网络中所有能被操作的对象都视为资源,每个资源都用
- 定义:用来标识抽象资源的字符串,通常是
- RESTful 6 大原则(REST 架构的 6 个限制条件)
接口统一(Uniform Interface)
:使用 HTTP 协议的标准方法(如 GET、POST、PUT等)来操作资源,请求和响应都遵循格式规范无状态(Stateless)
:每次请求包含所有的必要信息,服务端不会保存客户端状态CS分离(C-S Separation)
:强调客户端与服务端分离,服务端具备独立性,从而能够应用于前端、安卓、IOS 等多种客户端设备可缓存性(Cacheability)
:服务端 回复是否客户端缓存的响应,从而使客户端缓存提高效率分层系统(Layered System)
:客户端只需按规范发送请求,无需关心中间经过的多层代理或服务资源导向(Resource-Based)
:将系统中的所有内容抽象为资源,每个资源有唯一的标识符(URI)
- 传统和Restful请求方式的不同
- 本质:通过
请求方法类型
表示对于请求资源的不同处理方式,从而实现同一路径的多态处理
- 本质:通过
设计规范
Request设计规范
- Request构成
请求路径
:常用多个名词
组成一个URL
标识一个资源,注意请求行为
:通常使用动词
(GET、POST、PUT、DELETE等 )操作URL资源
- 请求路径规范
- 基础规范:
scheme "://" host ":" port "/" path [ "?" query ][ "#" fragment ]
- scheme: 指底层用的协议,如http、https、ftp
- host: 服务器的IP地址或者域名
- port: 端口,http默认为80端口
- path: 访问资源的路径,就是各种web 框架中定义的route路由
- query: 查询字符串,为发送给服务器的参数,在这里更多发送数据分页、排序等参数。
- fragment: 锚点,定位到页面的具体资源
- path规范:
/{版本号}/ ... /{资源集合s}/{资源id}
(遵循可寻址
和自描述性
)- 路径反映请求含义
- 使用
小写英文名词
和连字符中杠-
组成 - 通过
/
标识资源层级(结尾没有),不要过深且靠前的层级应该相对稳定
- 使用
- 资源集用复数:如
/articles
而非/article
- 同级资源:通过
,
或;
来表示分隔表示 - 资源格式:通过
资源名后缀
来区分不同的资源格式,如xml、json等
- 路径反映请求含义
- 基础规范:
- 请求行为规范
HTTP Method | 安全性 (不修改服务器数据) | 幂等性(每次执行结果一致) | 解释 | 示例 |
---|---|---|---|---|
GET | 安全(读安全,写危险) | 幂等 | 从服务器查询SELECT 资源对象或集合 | 获取用户列表:[GET] /v1/users 获取特定用户: [GET] /v1/users/{id} |
POST | 非安全 | 非幂等 | 在服务器中创建CREATE 资源,返回新资源对象 | 新建用户:[POST] /v1/users |
PUT | 非安全 | 幂等 | 用于更新UPDATE 资源,返回完整资源对象 | 更新用户:[PUT] /v1/users/{id} |
DELETE | 非安全 | 幂等 | 用于删除DELETE 资源,通常返回一个空文档或删除成功状态码 | 删除用户:[DELETE] /v1/users/{id} |
- 遵循 RESTful 分层系统架构的请求处理过程
- 客户端发起请求:客户端根据
业务需求
,按照RESTful 规范
构建请求。包含资源访问的请求路径URL
和合适的HTTP请求行为
,以及其他必要的请求信息,如用户登录token和期望资源格式等 - 代理服务器处理请求(若存在):
- 验证请求:接收客户端发送的请求后,对请求进行身份、IP地址等合法性的检查
- 修改请求:保持核心内容不会改变,但可能修改请求头中的部分字段,比如添加标识信息,或根据配置对请求参数进行调整等
- 转发请求:根据配置策略,转发到负载均衡器或其他中间服务器
- 负载均衡器进行分发(若存在):
- 负载均衡决策:负载均衡器接收到请求后,会根据内置负载均衡算法来决定将该请求分配到哪一台资源服务器上
- 请求分配:将请求转发到对应的资源服务器,确保各个资源服务器的负载相对均衡
- 资源服务器处理请求
- 解析请求:资源服务器接收到来自负载均衡器的请求后,会解析请求的资源路径和行为
- 执行业务逻辑:根据解析出的请求内容,资源服务器执行相应的业务逻辑,如数据库操作、格式转换等
- 生成响应:在完成对请求的处理后,资源服务器会根据处理结果生成响应信息。
- 客户端发起请求:客户端根据
Response设计规范
- Response构成
响应状态码
:用于表示请求的处理结果,通常遵循 HTTP 标准状态码。响应头(Headers)
:包含响应的元数据信息。响应体(Body)
:包含请求的详细结果或资源数据。
- 响应状态码设计规范
2xx状态码
:表示请求成功。- 200 OK:请求成功,返回请求的资源或操作结果。
- 201 Created:资源创建成功,通常用于 POST 请求,返回新创建的资源。
- 204 No Content:请求成功,但无内容返回,通常用于 DELETE 请求。
4xx状态码
:表示客户端错误。- 400 Bad Request:请求无效,通常因为请求数据格式错误。
- 401 Unauthorized:请求未授权,用户未通过身份验证。
- 403 Forbidden:用户无权访问该资源。
- 404 Not Found:请求的资源不存在。
5xx状态码
:表示服务器错误。- 500 Internal Server Error:服务器内部错误,无法完成请求。
- 503 Service Unavailable:服务器暂时无法处理请求,通常用于维护或过载状态。
- 响应头(Headers)设计规范
- Content-Type:指定响应体的格式,例如:
- application/json:表示响应体是 JSON 格式。
- application/xml:表示响应体是 XML 格式。
- text/plain:表示响应体是纯文本格式。
- Location:用于 POST 请求,返回新创建资源的 URI
- Cache-Control:指示客户端是否可以缓存响应,以及缓存的有效期。
- ETag:用于资源版本控制,帮助客户端判断资源是否已更新。
- 注意:响应头的格式是固定的(键值对形式),与响应体的格式无关。
- Content-Type:指定响应体的格式,例如:
- 响应体(Body)设计规范
- 成功响应
- 返回请求的资源数据(如 GET 请求)或操作结果(如 POST、PUT 请求)。
- 通常以 JSON 格式返回,例如:
{ "id": 123, "name": "John Doe", "email": "john.doe@example.com", "createdAt": "2023-10-01T12:00:00Z" }
- 返回错误详情,帮助客户端定位问题。
{ "error": { "code": 400, "message": "Invalid request: missing required field 'email'." } }
- 成功响应
示例
RESTful API设计规范示例
- 路由器示例:
/project-name/components/xxx-api/pkg/routers.go
// Gin路由器
func NewRouter() *gin.Engine {
// 1. 初始化一个 Gin 路由器,否则无法注册路由和处理请求。
router := gin.New()
// 2. 路由分组:基于RESTful API规范将不同功能的路由聚合在一起
rootRouter := router.Group("/")
consoleRouter := router.Group("/apis")
// 3. 注册中间件:将路由处理函数的重复逻辑提取到中间件处理函数中,
// 如身份验证、日志记录、错误恢复等,从而避免重复实现导致的代码冗余
router.Use(LoggerMiddleware()) // 注册全局中间件
ErrorHandlerMiddleware(consoleRouter) // 注册局部中间件
ErrorHandlerMiddleware(rootRouter)
// 4. 注册路由:
consoleRouter.GET("/apps/v1/resPack", console.ApisAppsV1GetResPacks) // 获取资源包列表(集合)
consoleRouter.GET("/apps/v1/resPack/:resPackId", console.ApisAppsV1GetResPack) // 获取资源包详情(具体)
consoleRouter.POST("/apps/v1/resPack", console.ApisAppsV1CreateResPack) // 创建资源包
consoleRouter.PUT("/apps/v1/resPack/:resPackId", console.ApisAppsV1UpdateResPackName) // 修改资源包名称
consoleRouter.DELETE("/apps/v1/resPack/:resPackId", console.ApisAppsV1DeleteResPack) // 删除资源包
// 5. 返回路由实例
return router
}
// 中间件函数
// 全局中间件函数
func ErrorHandlerMiddleware() gin.HandlerFunc { // 错误处理中间件示例
return func(c *gin.Context) {
// 调用下一个中间件或路由处理函数
c.Next()
// 检查是否有错误
if len(c.Errors) > 0 {
c.JSON(http.StatusBadRequest, gin.H{
"error": c.Errors.Last().Error(),
})
}
}
}
// 局部中间件函数
func LoggerMiddleware() gin.HandlerFunc { // 日志记录中间件示例
return gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
return fmt.Sprintf("[%s] %s %s %d %s\n",
param.TimeStamp.Format(time.RFC3339),
param.Method,
param.Path,
param.StatusCode,
param.Latency,
)
})
}
- 路由处理函数文件:
/project-name/components/xxx-api/pkg/handler/console/res_pack_handler.go
// ApisAppsV1DeleteResPack 删除资源包
func ApisAppsV1DeleteResPack(c *gin.Context) {
// 0. 解析请求:包括请求头、请求体、路径参数和查询参数。
var requestHeader common.AccountHeader
var responseMsg = consolemodel.V1RespCommonWithId{
Code: http.StatusOK,
Message: "success",
}
parameters := &struct {
ResPackId string `form:"resPackId" uri:"resPackId"`
}{}
// 1. 校验请求:判断请求是否有效,处理错误情况。
if err := utils.Parse(c, &requestHeader, nil, parameters, nil); err != nil {
responseMsg.Code = http.StatusBadRequest
responseMsg.Message = err.Error()
}
// 2. 调用服务层:执行业务逻辑,如创建、删除、更新、查询等操作。
resPackId := parameters.ResPackId
service := registry.GetResPackService()
err := service.DeleteResPack(c.Request.Context(), resPackId) // NOTE: call function
if err != nil {
errCode := aicperror.ErrCodeInternalError.WithResult(aicperror.Wrap(err, err.Error()))
c.Error(errCode)
return
}
// 3. 返回响应:根据业务逻辑的结果,返回相应的HTTP状态码和响应体。
responseMsg.Data.Id = resPackId
c.JSON(http.StatusOK, responseMsg)
// 4. 记录日志:在必要时记录操作日志,便于后续追踪和审计。
}
- 路由处理函数文件:
/project-name/components/xxx-api/pkg/handler/console/res_pack_handler.go
// DeleteResPack 删除资源包
func (s *ResPackService) DeleteResPack(ctx context.Context, id string) (err error) {
rpdao := dao.ResPackDao{}
err = rpdao.Delete(ctx, id)
if err != nil {
return err
}
return nil
}
- 数据库处理文件:
aicp\components\common\pkg\database\dao\res_pack_dao.go
func (s *ResPackDao) Delete(ctx context.Context, id string) (err error) {
deleted := &models.ResPack{}
return DB(ctx).Model(&models.ResPack{}).Where("id = ?", id).Delete(deleted).Error
}