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

高级用法

高级用法

编辑推荐精选

讯飞智文

讯飞智文

一键生成PPT和Word,让学习生活更轻松

讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。

AI办公办公工具AI工具讯飞智文AI在线生成PPTAI撰写助手多语种文档生成AI自动配图热门
讯飞星火

讯飞星火

深度推理能力全新升级,全面对标OpenAI o1

科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。

热门AI开发模型训练AI工具讯飞星火大模型智能问答内容创作多语种支持智慧生活
Spark-TTS

Spark-TTS

一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型

Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。

Trae

Trae

字节跳动发布的AI编程神器IDE

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

AI工具TraeAI IDE协作生产力转型热门
咔片PPT

咔片PPT

AI助力,做PPT更简单!

咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。

讯飞绘文

讯飞绘文

选题、配图、成文,一站式创作,让内容运营更高效

讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。

热门AI辅助写作AI工具讯飞绘文内容运营AI创作个性化文章多平台分发AI助手
材料星

材料星

专业的AI公文写作平台,公文写作神器

AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。

openai-agents-python

openai-agents-python

OpenAI Agents SDK,助力开发者便捷使用 OpenAI 相关功能。

openai-agents-python 是 OpenAI 推出的一款强大 Python SDK,它为开发者提供了与 OpenAI 模型交互的高效工具,支持工具调用、结果处理、追踪等功能,涵盖多种应用场景,如研究助手、财务研究等,能显著提升开发效率,让开发者更轻松地利用 OpenAI 的技术优势。

Hunyuan3D-2

Hunyuan3D-2

高分辨率纹理 3D 资产生成

Hunyuan3D-2 是腾讯开发的用于 3D 资产生成的强大工具,支持从文本描述、单张图片或多视角图片生成 3D 模型,具备快速形状生成能力,可生成带纹理的高质量 3D 模型,适用于多个领域,为 3D 创作提供了高效解决方案。

3FS

3FS

一个具备存储、管理和客户端操作等多种功能的分布式文件系统相关项目。

3FS 是一个功能强大的分布式文件系统项目,涵盖了存储引擎、元数据管理、客户端工具等多个模块。它支持多种文件操作,如创建文件和目录、设置布局等,同时具备高效的事件循环、节点选择和协程池管理等特性。适用于需要大规模数据存储和管理的场景,能够提高系统的性能和可靠性,是分布式存储领域的优质解决方案。

下拉加载更多