rest

rest

基于Clean Architecture的Go语言RESTful API框架

REST with Clean Architecture for Go是一个高效的Go语言框架,用于构建RESTful服务。该框架实现HTTP传输层,采用Clean Architecture设计模式,自动生成OpenAPI文档,支持JSON Schema验证和动态gzip压缩。它注重提升开发效率和系统可靠性,同时保持良好的灵活性和性能。框架与net/http兼容,使用chi路由器,并采用模块化结构,便于开发和维护。

RESTGoClean ArchitectureOpenAPIHTTPGithub开源项目

Go的REST和Clean Architecture实现

构建状态 覆盖率状态 GoDevDoc 时间追踪 代码行数 注释

这个模块为github.com/swaggest/usecase实现了HTTP传输层,用于构建REST服务。

目标

  • 维护HTTP API文档、验证和输入/输出的单一事实来源。
  • 避免依赖编译时代码生成。
  • 通过抽象HTTP细节并提供简单的API来提高生产力和可靠性。
  • 允许高级情况下的低级定制。
  • 保持合理的性能和低GC影响。

非目标

  • 支持旧版文档模式如Swagger 2.0或RAML。
  • 零内存分配。
  • 明确支持请求或响应体中的XML。

特性

使用方法

请查看这个教程了解端到端使用示例。

请求解码器

带有字段标签的Go结构体定义了输入端口。 请求解码器在调用用例交互器之前,从http.Request数据填充字段值。

// 声明输入端口类型 type helloInput struct { Locale string `query:"locale" default:"en-US" pattern:"^[a-z]{2}-[A-Z]{2}$" enum:"ru-RU,en-US"` Name string `path:"name" minLength:"3"` // 字段标签定义参数位置和JSON模式约束 // 未命名字段的字段标签应用于父模式, // 它们是可选的,可用于禁止未知参数。 // 对于非正文参数,必须明确提供name标签。 // 例如,这里不允许未知的`query`和`cookie`参数, // 未知的`header`参数是允许的。 _ struct{} `query:"_" cookie:"_" additionalProperties:"false"` }

输入数据可以位于:

  • 请求URI中的path参数,例如/users/{name}
  • 请求URI中的query参数,例如/users?locale=en-US
  • 请求正文中的formData参数,内容类型为application/x-www-form-urlencodedmultipart/form-data
  • form参数作为formDataquery
  • 请求正文中的json参数,内容类型为application/json
  • 请求cookie中的cookie参数,
  • 请求头中的header参数。

为了更明确地分离用例和传输之间的关注点,可以在初始化处理程序时单独提供请求映射(请注意,这种映射不适用于json正文)。

// 声明输入端口类型 type helloInput struct { Locale string `default:"en-US" pattern:"^[a-z]{2}-[A-Z]{2}$"` Name string `minLength:"3"` // 字段标签定义参数位置和JSON模式约束 }
// 将带有自定义输入映射的用例处理程序添加到路由器 r.Method(http.MethodGet, "/hello/{name}", nethttp.NewHandler(u, nethttp.RequestMapping(new(struct { Locale string `query:"locale"` Name string `path:"name"` // 字段标签定义参数位置和JSON模式约束 })), ))

额外的字段标签描述了JSON模式约束,请查看文档

可以使用github.com/swaggest/jsonschema-go接口进行更多模式定制。

默认情况下,default标签仅用于文档贡献, 如果设置了request.DecoderFactory.ApplyDefaultstrue,请求结构中没有显式值但有default的字段将被填充为默认值。

如果输入结构实现了request.Loader, 那么将调用LoadFromHTTPRequest(r *http.Request) error方法来填充输入结构,而不是自动解码。这允许对需要的情况进行低级控制。

<details> <summary>请求解码器可以独立使用,在已有的`ServeHTTP`中。</summary>
type MyRequest struct { Foo int `header:"X-Foo"` Bar string `formData:"bar"` Baz bool `query:"baz"` } // 特定结构的解码器,可以重复用于多个HTTP请求。 myDecoder := request.NewDecoderFactory().MakeDecoder(http.MethodPost, new(MyRequest), nil) // 来自ServeHTTP的请求和响应写入器。 var ( rw http.ResponseWriter req *http.Request ) // 这段代码通常会在ServeHTTP中。 var myReq MyRequest if err := myDecoder.Decode(req, &myReq, nil); err != nil { http.Error(rw, err.Error(), http.StatusBadRequest) }
</details>

响应编码器

带有字段标签的Go结构体定义了输出端口。 响应编码器在用例交互器调用完成后,将输出数据写入http.ResponseWriter

// 声明输出端口类型 type helloOutput struct { Now time.Time `header:"X-Now" json:"-"` Message string `json:"message"` Sess string `cookie:"sess,httponly,secure,max-age=86400,samesite=lax"` }

输出数据可以位于:

  • json用于响应正文,内容类型为application/json
  • header用于响应头中的值,
  • cookie用于cookie值,cookie字段可以在字段标签中有配置(与实际cookie相同,但使用逗号分隔)。

为了更明确地分离用例和传输之间的关注点,可以在初始化处理程序时单独提供响应头映射。

// 声明输出端口类型 type helloOutput struct { Now time.Time `json:"-"` Message string `json:"message"` }
// 将带有自定义输出头映射的用例处理程序添加到路由器 r.Method(http.MethodGet, "/hello/{name}", nethttp.NewHandler(u, nethttp.ResponseHeaderMapping(new(struct { Now time.Time `header:"X-Now"` })), ))

额外的字段标签描述了JSON模式约束,请查看文档

创建用例交互器

HTTP传输通过适配用例交互器与业务逻辑解耦。

用例交互器可以定义用于在Go值和传输之间映射数据的输入和输出端口。 它可以提供关于自身的信息,这些信息将在生成的文档中暴露。

// 创建用例交互器,引用输入/输出类型和交互函数。 u := usecase.NewInteractor(func(ctx context.Context, input helloInput, output *helloOutput) error { msg, available := messages[input.Locale] if !available { return status.Wrap(errors.New("unknown locale"), status.InvalidArgument) } output.Message = fmt.Sprintf(msg, input.Name) output.Now = time.Now() return nil })

初始化Web服务

Web服务是路由器前面的一个工具化外观,它简化了配置并提供了更紧凑的API来添加用例。

// 服务初始化路由器并添加所需的中间件。 service := web.NewService(openapi31.NewReflector()) // 它允许OpenAPI配置。 service.OpenAPISchema().SetTitle("相册API") service.OpenAPISchema().SetDescription("此服务提供管理相册的API。") service.OpenAPISchema().SetVersion("v1.0.0") // 可以添加额外的中间件。 service.Use( middleware.StripSlashes, // cors.AllowAll().Handler, // "github.com/rs/cors", 第三方CORS中间件也可以在此处配置。 ) // 可以使用简短语法.<Method>(...)挂载用例。 service.Post("/albums", postAlbums(), nethttp.SuccessStatus(http.StatusCreated)) log.Println("在 http://localhost:8080 启动服务") if err := http.ListenAndServe("localhost:8080", service); err != nil { log.Fatal(err) }

通常,web.Service API 已经足够,但如果不够用,可以手动配置路由器,请查看下面的文档。

安全设置

使用HTTP基本认证的示例。

// 准备具有适当安全方案的中间件。 // 它将对每个相关请求执行实际的安全检查。 adminAuth := middleware.BasicAuth("管理员访问", map[string]string{"admin": "admin"}) // 准备API模式更新器中间件。 // 它将用安全方案注释处理程序文档。 adminSecuritySchema := nethttp.HTTPBasicSecurityMiddleware(apiSchema, "管理员", "管理员访问") // 具有管理员访问权限的端点。 r.Route("/admin", func(r chi.Router) { r.Group(func(r chi.Router) { r.Use(adminAuth, adminSecuritySchema) // 将两个中间件添加到路由组以强制执行和记录安全性。 r.Method(http.MethodPut, "/hello/{name}", nethttp.NewHandler(u)) }) })

使用cookie的示例。

// 安全中间件。 // - sessMW是实际的请求级处理器, // - sessDoc是处理程序级包装器,用于公开文档。 sessMW := func(handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if c, err := r.Cookie("sessid"); err == nil { r = r.WithContext(context.WithValue(r.Context(), "sessionID", c.Value)) } handler.ServeHTTP(w, r) }) } sessDoc := nethttp.APIKeySecurityMiddleware(s.OpenAPICollector, "用户", "sessid", oapi.InCookie, "会话cookie。") // 为单个顶级路由配置安全方案。 s.With(sessMW, sessDoc).Method(http.MethodGet, "/root-with-session", nethttp.NewHandler(dummy())) // 在子路由器上配置安全方案。 s.Route("/deeper-with-session", func(r chi.Router) { r.Group(func(r chi.Router) { r.Use(sessMW, sessDoc) r.Method(http.MethodGet, "/one", nethttp.NewHandler(dummy())) r.Method(http.MethodGet, "/two", nethttp.NewHandler(dummy())) }) })

参见示例

处理程序设置

处理程序是用例交互器的通用适配器,因此通常设置很简单。

// 将用例处理程序添加到路由器。 r.Method(http.MethodGet, "/hello/{name}", nethttp.NewHandler(u))

示例

对于非泛型用例,请参见另一个示例

package main import ( "context" "errors" "fmt" "log" "net/http" "time" "github.com/swaggest/openapi-go/openapi31" "github.com/swaggest/rest/response/gzip" "github.com/swaggest/rest/web" swgui "github.com/swaggest/swgui/v5emb" "github.com/swaggest/usecase" "github.com/swaggest/usecase/status" ) func main() { s := web.NewService(openapi31.NewReflector()) // 初始化API文档模式。 s.OpenAPISchema().SetTitle("基本示例") s.OpenAPISchema().SetDescription("此应用展示了一个简单的REST API。") s.OpenAPISchema().SetVersion("v1.2.3") // 设置中间件。 s.Wrap( gzip.Middleware, // 响应压缩,支持直接gzip传递。 ) // 声明输入端口类型。 type helloInput struct { Locale string `query:"locale" default:"en-US" pattern:"^[a-z]{2}-[A-Z]{2}$" enum:"ru-RU,en-US"` Name string `path:"name" minLength:"3"` // 字段标签定义参数位置和JSON模式约束。 // 未命名字段的字段标签应用于父模式。 // 它们是可选的,可用于禁止未知参数。 // 对于非body参数,必须明确提供name标签。 // 例如,这里不允许未知的`query`和`cookie`参数, // 未知的`header`参数是可以的。 _ struct{} `query:"_" cookie:"_" additionalProperties:"false"` } // 声明输出端口类型。 type helloOutput struct { Now time.Time `header:"X-Now" json:"-"` Message string `json:"message"` } messages := map[string]string{ "en-US": "Hello, %s!", "ru-RU": "Привет, %s!", } // 创建带有输入/输出类型引用和交互功能的用例交互器。 u := usecase.NewInteractor(func(ctx context.Context, input helloInput, output *helloOutput) error { msg, available := messages[input.Locale] if !available { return status.Wrap(errors.New("未知语言"), status.InvalidArgument) } output.Message = fmt.Sprintf(msg, input.Name) output.Now = time.Now() return nil }) // 描述用例交互器。 u.SetTitle("问候者") u.SetDescription("问候者向您问好。") u.SetExpectedErrors(status.InvalidArgument) // 将用例处理程序添加到路由器。 s.Get("/hello/{name}", u) // Swagger UI端点位于/docs。 s.Docs("/docs", swgui.New) // 启动服务器。 log.Println("http://localhost:8011/docs") if err := http.ListenAndServe("localhost:8011", s); err != nil { log.Fatal(err) } }

文档页面

其他集成

性能优化

如果服务或特定端点的最高性能至关重要,您可以通过在输入类型上实现手动请求加载器来用性能换取简单性。

func (i *myInput) LoadFromHTTPRequest(r *http.Request) (err error) { i.Header = r.Header.Get("X-Header") return nil }

如果实现了request.Loader,它将被调用而不是自动解码和验证。

查看高级示例

要进一步提高性能,您可以尝试使用rest-fasthttp分支,用fasthttp代替net/http

版本控制

本项目遵循语义化版本控制

在1.0.0版本之前,破坏性变更用MINOR版本号标记,功能和修复用PATCH版本号标记。 在1.0.0版本之后,破坏性变更用MAJOR版本号标记。

破坏性变更在UPGRADE.md中描述。

高级用法

高级用法

编辑推荐精选

Vora

Vora

免费创建高清无水印Sora视频

Vora是一个免费创建高清无水印Sora视频的AI工具

Refly.AI

Refly.AI

最适合小白的AI自动化工作流平台

无需编码,轻松生成可复用、可变现的AI自动化工作流

酷表ChatExcel

酷表ChatExcel

大模型驱动的Excel数据处理工具

基于大模型交互的表格处理系统,允许用户通过对话方式完成数据整理和可视化分析。系统采用机器学习算法解析用户指令,自动执行排序、公式计算和数据透视等操作,支持多种文件格式导入导出。数据处理响应速度保持在0.8秒以内,支持超过100万行数据的即时分析。

AI工具酷表ChatExcelAI智能客服AI营销产品使用教程
TRAE编程

TRAE编程

AI辅助编程,代码自动修复

Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。

AI工具TraeAI IDE协作生产力转型热门
AIWritePaper论文写作

AIWritePaper论文写作

AI论文写作指导平台

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

AI辅助写作AI工具AI论文工具论文写作智能生成大纲数据安全AI助手热门
博思AIPPT

博思AIPPT

AI一键生成PPT,就用博思AIPPT!

博思AIPPT,新一代的AI生成PPT平台,支持智能生成PPT、AI美化PPT、文本&链接生成PPT、导入Word/PDF/Markdown文档生成PPT等,内置海量精美PPT模板,涵盖商务、教育、科技等不同风格,同时针对每个页面提供多种版式,一键自适应切换,完美适配各种办公场景。

AI办公办公工具AI工具博思AIPPTAI生成PPT智能排版海量精品模板AI创作热门
潮际好麦

潮际好麦

AI赋能电商视觉革命,一站式智能商拍平台

潮际好麦深耕服装行业,是国内AI试衣效果最好的软件。使用先进AIGC能力为电商卖家批量提供优质的、低成本的商拍图。合作品牌有Shein、Lazada、安踏、百丽等65个国内外头部品牌,以及国内10万+淘宝、天猫、京东等主流平台的品牌商家,为卖家节省将近85%的出图成本,提升约3倍出图效率,让品牌能够快速上架。

iTerms

iTerms

企业专属的AI法律顾问

iTerms是法大大集团旗下法律子品牌,基于最先进的大语言模型(LLM)、专业的法律知识库和强大的智能体架构,帮助企业扫清合规障碍,筑牢风控防线,成为您企业专属的AI法律顾问。

SimilarWeb流量提升

SimilarWeb流量提升

稳定高效的流量提升解决方案,助力品牌曝光

稳定高效的流量提升解决方案,助力品牌曝光

Sora2视频免费生成

Sora2视频免费生成

最新版Sora2模型免费使用,一键生成无水印视频

最新版Sora2模型免费使用,一键生成无水印视频

下拉加载更多