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中描述。

高级用法

高级用法

编辑推荐精选

GPT Plus|Pro充值

GPT Plus|Pro充值

GPT充值

支持 ChatGPT Plus / Pro 充值服务,支付便捷,自动发货,售后可查。

GPT Image 2中文站

GPT Image 2中文站

AI 图片生成平台

GPT Image 2 是面向用户的 AI 图片生成平台,支持文生图、图生图及多模型创意工作流。

Vecbase

Vecbase

你的AI Agent团队

Vecbase 是专为 AI 团队打造的智能工作空间,将数据管理、模型协作与知识沉淀整合于一处。算法、产品与业务在同一平台无缝协同,让从数据到 AI 应用的落地更快一步。

音述AI

音述AI

全球首个AI音乐社区

音述AI是全球首个AI音乐社区,致力让每个人都能用音乐表达自我。音述AI提供零门槛AI创作工具,独创GETI法则帮助用户精准定义音乐风格,AI润色功能支持自动优化作品质感。音述AI支持交流讨论、二次创作与价值变现。针对中文用户的语言习惯与文化背景进行专门优化,支持国风融合、C-pop等本土音乐标签,让技术更好地承载人文表达。

QoderWork

QoderWork

阿里Qoder团队推出的桌面端AI智能体

QoderWork 是阿里推出的本地优先桌面 AI 智能体,适配 macOS14+/Windows10+,以自然语言交互实现文件管理、数据分析、AI 视觉生成、浏览器自动化等办公任务,自主拆解执行复杂工作流,数据本地运行零上传,技能市场可无限扩展,是高效的 Agentic 生产力办公助手。

lynote.ai

lynote.ai

一站式搞定所有学习需求

不再被海量信息淹没,开始真正理解知识。Lynote 可摘要 YouTube 视频、PDF、文章等内容。即时创建笔记,检测 AI 内容并下载资料,将您的学习效率提升 10 倍。

AniShort

AniShort

为AI短剧协作而生

专为AI短剧协作而生的AniShort正式发布,深度重构AI短剧全流程生产模式,整合创意策划、制作执行、实时协作、在线审片、资产复用等全链路功能,独创无限画布、双轨并行工业化工作流与Ani智能体助手,集成多款主流AI大模型,破解素材零散、版本混乱、沟通低效等行业痛点,助力3人团队效率提升800%,打造标准化、可追溯的AI短剧量产体系,是AI短剧团队协同创作、提升制作效率的核心工具。

seedancetwo2.0

seedancetwo2.0

能听懂你表达的视频模型

Seedance two是基于seedance2.0的中国大模型,支持图像、视频、音频、文本四种模态输入,表达方式更丰富,生成也更可控。

nano-banana纳米香蕉中文站

nano-banana纳米香蕉中文站

国内直接访问,限时3折

输入简单文字,生成想要的图片,纳米香蕉中文站基于 Google 模型的 AI 图片生成网站,支持文字生图、图生图。官网价格限时3折活动

扣子-AI办公

扣子-AI办公

职场AI,就用扣子

AI办公助手,复杂任务高效处理。办公效率低?扣子空间AI助手支持播客生成、PPT制作、网页开发及报告写作,覆盖科研、商业、舆情等领域的专家Agent 7x24小时响应,生活工作无缝切换,提升50%效率!

下拉加载更多