定义Swagger以支持自定义入参和出参(过滤器)

4,823 阅读4分钟

这是我参与8月更文挑战的第30天,活动详情查看:8月更文挑战

  • 📢欢迎点赞 :👍 收藏 ⭐留言 📝 如有错误敬请指正,赐人玫瑰,手留余香!
  • 📢本文作者:由webmote 原创,首发于 【掘金】
  • 📢作者格言: 生活在于折腾,当你不折腾生活时,生活就开始折腾你,让我们一起加油!💪💪💪

1.扩展场景

这是Swagger的的高阶应用篇,由于我以前项目中增加了框架级别的入参和出参定义,导致swagger无法识别外部的定义,仅仅识别为控制器方法的定义。

如果追根溯源,这个跟swagger也没太大的关系,应该是asp提供的ApiExplorer的问题,毕竟是静态反射,你想让他支持动态的过滤,这的确是勉为其难了。

2.swagger的结构

我使用的环境是asp.net core 3.1,swagger的包是Swashbuckle.AspNetCore 5.0.0。如果仅仅是产生一个api文档,为什么要关注这个Swagger的包,甚至结构呢,你说是吧!我当时就有股放下的冲动,给前端介绍下,让他们自己理解去... ...

作为一个接近产品的项目,我觉得有必要深入的理解下swagger的结构,完成这个转换就非常完美了。

说起swagger的结构,其实不如说是OpenAPI的结构,这个规范应该起源自swagger,微软定义了一套实现。

3. OPEN API 规范

记住微软的github地址: OpenApi 这里简单过下规范的描述, 目前标准为3.02, OpenApi的根文档对象如下:

# OpenAPI 规范版本号
openapi: 3.0.2

# API 元数据信息
info:

# 服务器连接信息
servers:

# API 的分组标签
tags: 

# 对所提供的 API 有效的路径和操作
paths:

# 一个包含多种纲要的元素,可重复使用组件
components:

# 声明 API 使用的安全机制
security:

# 附加文档
externalDocs:

其中为了修改入参和出参,我们需要更改的是paths对象以及其引用的组件对象,组件对象封装可重用对象,然后通过 $ref 标签进行引用。

4. Path 对象的部分详解

在这里插入图片描述 swagger在启动后通过ApiExplorer获取到所有的控制器和方法信息,并进行组装,最后规范化OpenApi后输出到Json文件。 因此当某些对象不会操作时可以看下微软OpenApi的例程。这方面资料非常少,几乎没有借鉴的东东。

5. 我们的目标

我们的目标简言之是这样的,有一个Api,其定义如下: Resp A (Req req),经过swagger解析后正常描述为 接口A,入参:Req,结果Resp,而经过框架过滤后是需要解析为 接口A,入参: Args,结果Result,也可以理解为需要把目标接口解析为形如Result<Resp> A (Args<Req> req)

怎么干——之一

能否硬上? 我们利用swagger的过滤器,把ActionDescriptor定义的参数、方法都改掉。尝试中我放弃了,动态生成一个ActionDescriptor对象,难度太高了,其中大部分方法都是只读的,因此无法进行修改,只能重新构造一个全新的对象。

怎么干——之二

修改生成的json文件?显然没有类似的接口不太靠谱。最后经过不断浏览Swagger的源代码,终于发现了有四个过滤器:

  1. IDocumentFilter 文档过滤器,对文档描述进行修改
  2. IOperationFilter Api级别过滤器,对不同的api进行定制化过滤
  3. ISchemaFilter 模式过滤器,修改或增删组件定义的模型
  4. IParameterFilter 参数过滤器,对特定参数进行过滤 经过筛选,有两类过滤器可以应用到本次活动中,IOperationFilter和ISchemaFilter,最后最合适的无非是 api级别的过滤器了!

6. 实现思路

上面已经确定了干活方向,那怎么实现呢?其实思路已经跃然纸上了,直接把入参和出参的Schema修改为泛型定义的入参和出参Schema即可。 代码如下:

public class MyOperationFilter : IOperationFilter
    {
        public void Apply(OpenApiOperation operation, OperationFilterContext context)
        {
            if (context.MethodInfo == null) return;
            //对post的入参进行转换,get的没有好的转换,不支持get
            if(operation.RequestBody != null)
            {               
                foreach(var c in operation.RequestBody.Content)
                {
                    var oldRef = c.Value.Schema.Reference;
                    //如果body是引用object对象定义
                    if(oldRef != null)
                    {                       
                        var pt = context.MethodInfo.GetParameters().FirstOrDefault().ParameterType;                        
                        c.Value.Schema = context.SchemaGenerator.GenerateSchema(typeof(ApiArgs<>).MakeGenericType(pt), context.SchemaRepository);
                    }
                    else if (c.Value.Schema?.Type?.ToLower() == "array")
                    {
                        var pt = context.MethodInfo.ReturnType;
                        c.Value.Schema = context.SchemaGenerator.GenerateSchema(typeof(ApiResult<>).MakeGenericType(pt), context.SchemaRepository);
     
                    }
                }
            }
            if(operation.Responses != null)
            {
                foreach (var c in operation.Responses.Values)
                {
                    var oldRef = c.Content.FirstOrDefault().Value?.Schema.Reference;
                    if (oldRef != null)
                    {
                        var pt = context.MethodInfo.ReturnType;
                        c.Content.First().Value.Schema = context.SchemaGenerator.GenerateSchema(typeof(ApiResult<>).MakeGenericType(pt), context.SchemaRepository);

                    }
                    else if(c.Content.FirstOrDefault().Value?.Schema.Type?.ToLower()=="array")
                    {
                        var pt = context.MethodInfo.ReturnType;
                        c.Content.First().Value.Schema = context.SchemaGenerator.GenerateSchema(typeof(ApiResult<>).MakeGenericType(pt), context.SchemaRepository);
                    }
                }
            }
        }

    }

7.最关键的点

c.Value.Schema = context.SchemaGenerator.GenerateSchema(typeof(ApiArgs<>).MakeGenericType(pt), context.SchemaRepository);

该语句在openapi的组件类中增加了个新的泛型对象,然后赋值给原来的schema,无需改动别的东东,生成的json随之更新,太完美了!!!

8.成果

在这里插入图片描述

9. 小结

噢噢噢,愉快的周一结束了!

例行小结,理性看待!

结的是啥啊,结的是我想你点赞而不可得的寂寞。😳😳😳

👓都看到这了,还在乎点个赞吗?

👓都点赞了,还在乎一个收藏吗?

👓都收藏了,还在乎一个评论吗?