JAVA中自定義擴展Swagger的能力,自動生成參數取值含義說明,提升開發效率

来源:https://www.cnblogs.com/softwarearch/archive/2022/09/06/16660562.html
-Advertisement-
Play Games

項目中一個常見的場景,就是介面請求或者響應參數中會有一些欄位的取值會限定為固定幾個可選值,而在代碼中這些可選值會通過枚舉類來承載,本文探討下如何讓swagger介面文檔中自動加上欄位的取值含義說明,解放生產力。 ...


大家好,又見面了。

JAVA做前後端分離的項目開發的時候,服務端需要提供介面文檔供周邊人員做介面的對接指導。越來越多的項目都在嘗試使用一些基於代碼自動生成介面文檔的工具來替代由開發人員手動編寫介面文檔,而Swagger作為一款優秀的線上介面文檔生成工具,以其功能強大、集成方便而得到了廣泛的使用。

在項目中有一種非常常見的場景,就是介面的請求或者響應參數中會有一些欄位的取值會限定為固定的幾個可選值之一,而在代碼中這些可選值往往會通過定義枚舉類的方式來承載,比如:

根據操作類型,過濾對應類型的用戶操作日誌列表
如: http://127.0.0.1:8088/test/queryOperateLogs?operateType=2

這裡的請求參數operateType傳入的值需要在後端約定的取值範圍內,這個取值範圍的定義如下:

@Getter
@AllArgsConstructor
public enum OperateType {
    ADD(1, "新增或者創建操作"),
    MODIFY(2, "更新已有數據操作"),
    DELETE(3, "刪除數據操作"),
    QUERY(4, "查詢數據操作");

    private int value;
    private String desc;
}

這裡就需要我們在介面文檔裡面將此介面中operateType的可選值以及每個可選值對應的含義信息都說明清楚,這樣調用方在使用的時候才知道應該傳入什麼值。

我們基於Swagger提供的基礎註解能力來實現時,比較常見的會看到如下兩種寫法:

  • 寫法1介面定義的時候,指定入參的取值說明

介面URL中攜帶的請求入參信息,通過@ApiImplicitParam註解來告訴調用方此介面允許接收的合法operateType的取值範圍以及各個取值的含義。

比如下麵這種場景:

@GetMapping("/queryOperateLogs")
@ApiOperation("查詢指定操作類型的操作日誌列表")
@ApiImplicitParam(name = "operateType", value = "操作類型,取值說明: 1,新增;2,更新;3,除;4,查詢", dataType = "int", paramType = "query")
public List<OperateLog> queryOperateLogs(int operateType) {
    return testService.queryOperateLogs(operateType);
}

這樣,在swagger界面上就可以顯示出欄位的取值說明信息。

其實還有一種寫法,即在代碼的入參前面添加@ApiParam註解的方式來實現。比如:

    @GetMapping("/queryOperateLogs")
    @ApiOperation("查詢指定操作類型的操作日誌列表")
    public List<OperateLog> queryOperateLogs(@ApiParam(value = "操作類型,取值說明: 1,新增;2,更新;3,刪除;4,查詢") @RequestParam("type") int operateType) {
        return testService.queryOperateLogs(operateType);
    }

這樣也能達到相同的效果。

  • 寫法2請求或者響應的Body體中解釋欄位的取值說明

對於需要使用json體進行傳輸的請求或者響應消息體Model中,可以使用@ApiModelProperty添加含義說明。

@Data
@ApiModel("操作記錄信息")
public class OperateLog {
    @ApiModelProperty("操作類型,取值說明: 1,新增;2,更新;3,刪除;4,查詢")
    private int operateType;
    @ApiModelProperty("操作用戶")
    private String user;
    @ApiModelProperty("操作詳情")
    private String detail;
}

同樣,在Swagger界面就可以清楚的知道每個欄位的具體含義與取值說明。

但是上面的兩個寫法,都存在著同一個問題,就是如果枚舉類中的值內容含義有變更,比如OperateType枚舉類中新增了一個BATCH_DELETE(5, "批量刪除"), 則必須手動去修改所有涉及的介面上的Swagger描述信息。如果有大量場景都涉及此欄位,則要改動的地方就非常多,且極易漏掉(因為不好通過代碼關聯關係直接搜索到)。這樣對於開發人員維護起來的成本就會增加,久而久之會導致介面文檔的內容與實際代碼處理情況不相匹配。

那麼,有沒有什麼簡單的方式,可以讓介面文檔自動根據對應枚舉類的內容變更而動態變更呢?

Swagger沒有提供原生的此方面能力支持,但是我們可以通過一些簡單的方式對Swagger的能力進行擴展,讓Swagger支持我們的這種訴求。一起來看下如何實現吧。

擴展可行性分析

既然想要改變生成的Swagger文檔中指定欄位的描述內容,那麼首先就應該是要搞清楚Swagger中現在的內容生成邏輯是如何處理的。我們以@ApiParam為例進行分析。因為@ApiParam中指定的內容會被顯示到Swagger界面上,那麼在Swagger的框架中,一定有個地方會嘗試去獲取此註解中指定的相關欄位值,然後將註解的內容轉為界面上的文檔內容。所以想要定製,首先必須要瞭解當前是如何處理的。

翻看Swagger的源碼,發現在ApiParamParameterBuilder類中進行此部分邏輯的處理,處理邏輯如下:

看了下此類是ParameterBuilderPlugin介面的一個實現類,Swagger框架在遍歷並逐個生成parameter說明信息的時候會被調用此實現類的邏輯來執行。

到這裡其實問題就已經很明顯了,我們可以自定義一個處理類並實現ParameterBuilderPlugin介面,然後將我們的訴求在自定義的處理類中進行實現,這樣不就可以實現我們的訴求了嗎?

相同的策略,我們可以找到處理@ApiImplicitParam@ApiModelProperty對應的介面類。

根據上面的分析,我們只需要提供個自定義實現類,然後分別實現這幾個介面就可以搞定我們的訴求了。那應該如何進行封裝,將其作為一個通用能力供所有場景使用呢,下麵詳細討論下。

自定義註解實現基於枚舉類生成描述

前面已經找到了一種思路將我們的定製邏輯註入到Swagger的文檔生成框架中進行調用,那麼下一步我們就得確認一種相對簡單的策略,告訴框架哪個欄位需要使用枚舉來自動生成取值說明,以及使用哪個枚舉類來生成。

這裡我們使用自定義註解的方式來實現。Swagger為不同的場景分別提供了@APIParam@ApiImplicitParam@ApiModelProperty等不同的註解,我們可以簡化下,提供一個統一的自定義註解即可。

比如:

@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiPropertyReference {
    // 介面文檔上的顯示的欄位名稱,不設置則使用field本來名稱
    String name() default "";
    // 欄位簡要描述,可選
    String value() default "";
    // 標識欄位是否必填
    boolean required() default  false;
    // 指定取值對應的枚舉類
    Class<? extends Enum> referenceClazz();
}

這樣呢,對於需要添加取值說明的欄位或者介面上,我們就可以添加@ApiPropertyReference並指定對應的枚舉類即可。

比如下麵這樣:

@Data
@ApiModel("操作記錄信息")
public class OperateLog {
    @ApiPropertyReference(value = "操作類型", referenceClazz = OperateType.class)
    private int operateType;
    // ... 
}

上面示例代碼中,OperateType是一個已經定義好的枚舉類。現在又遇到一個問題,枚舉類的實現形式其實也不一樣,要如何才能讓我們的自動內容生成服務知道獲取枚舉類中的哪些內容進行處理呢?當然我們可以約定用於Swagger註解中的枚舉類必須遵循某個固定的格式,但顯然這樣實施的難度就會提升,並非是我們想要的結果。

先來看下麵給定的這個枚舉類,其中包含ordervaluedesc三個屬性值,而value欄位是我們的介面欄位需要傳入的真實取值,desc是其對應的含義描述,那麼該如何讓我們自定義Swagger擴展類知曉應該使用valuedesc欄位來生成文檔描述內容呢?

@Getter
@AllArgsConstructor
public enum OperateType {
    ADD(1, 11, "新增"),
    MODIFY(2, 22, "更新"),
    DELETE(3, 33, "刪除");
    private int order;
    private int value;
    private String desc;
}

答案其實不陌生,依舊是自定義註解!只要提供個自定義註解,然後添加到枚舉類上,指定到底使用枚舉類中的哪個欄位作為value值,以及哪個欄位用作含義描述desc欄位值就行了。

@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface SwaggerDisplayEnum {
    String value() default "value";
    String desc() default "desc";
}

這樣,在枚舉類上添加下@SwaggerDisplayEnum並指定下欄位的映射,即可用於Swagger註解中:

到這裡呢,我們需要的數據來源以及取值轉換規則就已經全部確定,剩下的就是如何將一個枚舉類中需要的值與描述欄位給拼接成想要的內容了。因為是通用能力,所以此處需要通過反射的方式來實現:

private String generateValueDesc(ApiPropertyReference propertyReference) {
    Class<? extends Enum> rawPrimaryType = propertyReference.referenceClazz();
    SwaggerDisplayEnum swaggerDisplayEnum = AnnotationUtils.findAnnotation(rawPrimaryType,
            SwaggerDisplayEnum.class);
    String enumFullDesc = Arrays.stream(rawPrimaryType.getEnumConstants())
            .filter(Objects::nonNull)
            .map(enumConsts -> {
                Object fieldValue = ReflectUtil.getFieldValue(enumConsts, swaggerDisplayEnum.value());
                Object fieldDesc = ReflectUtil.getFieldValue(enumConsts, swaggerDisplayEnum.desc());
                return fieldValue + ":" + fieldDesc;
            }).collect(Collectors.joining(";"));
    return propertyReference.value() + "(" + enumFullDesc + ")";
}

測試下輸出如下麵的格式,自動將枚舉類中所有的枚舉值及其描述信息都展示出來了。

(1:新增;2:更新;3:刪除)

實現自定義擴展處理器

至此呢,我們已經做好了全部的準備工作,下麵就可以按照前面分析的策略,來自定義一個實現類去實現相關介面,將我們的處理轉換邏輯註入到Swagger框架中去。

@Component
@Primary
public class SwaggerEnumBuilderPlugin implements ModelPropertyBuilderPlugin, ParameterBuilderPlugin {
    @Override
    public void apply(ModelPropertyContext context) {
        // Model中field欄位描述的自定義處理策略
    }
    @Override
    public void apply(ParameterContext parameterContext) {
        // API中入參的自定義處理策略
    }
    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }
}

下麵只需要在apply方法中補充上我們的自定義處理邏輯即可。

自動生成API入參的取值說明

前面已經講瞭如何將指定的枚舉類中的枚舉值生成為描述字元串,在這裡我們直接調用,然後將結果設置到context上下文中即可。

@Override
public void apply(ParameterContext context) {
    ApiPropertyReference reference =
            context.getOperationContext().findAnnotation(ApiPropertyReference.class).orNull();
    String desc = generateValueDesc(reference);
    if (StringUtils.isNotEmpty(reference.name())) {
        context.parameterBuilder().name(reference.name());
    }
    context.parameterBuilder().description(desc);
    AllowableListValues allowableListValues = getAllowValues(reference);
    context.parameterBuilder().allowableValues(allowableListValues);
}

自動生成Model中欄位取值說明

同樣的策略,我們處理下數據實體類中的field對應的含義說明。

@Override
public void apply(ModelPropertyContext modelPropertyContext) {
    if (!modelPropertyContext.getBeanPropertyDefinition().isPresent()) {
        return;
    }
    BeanPropertyDefinition beanPropertyDefinition = modelPropertyContext.getBeanPropertyDefinition().get();
    // 生成需要拼接的取值含義描述內容
    String valueDesc = generateValueDesc(beanPropertyDefinition);
    modelPropertyContext.getBuilder().description(valueDesc)
            .type(modelPropertyContext.getResolver()
                    .resolve(beanPropertyDefinition.getField().getRawType()));
}
}

效果演示

到這裡呢,代碼層面的處理就全部完成了。接下來運行下程式,看下效果。先來看下API介面中入參的含義描述效果:

從界面效果上可以看出,不僅自動將取值說明描述給顯示出來,同時界面調測的時候,輸入框也變為了下拉框 (因為我們自動給設置了allowableValues屬性),只能輸入允許的值。同樣的,再來看下Model中的欄位的含義說明描述效果:

可以看到,介面文檔中的參數描述信息中,已經自動帶上了枚舉類中定義的候選取值內容與說明。我們僅修改下枚舉類中的內容,其餘地方不做修改,再次看下界面,發現Swagger介面中的描述內容已經同步更新為最新的內容。

完美,大功告成

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 哈嘍兄弟們,今天來試試批量獲取公眾號文章,emmm… 雖然名義上是文章,單其實它是一篇純圖片文,至於為什麼不是文字,小姐姐不比文字香? 事前準備 我們需要用到 Fiddler Everywhere 這個軟體,Crack是本次要使用到的文件,以及要安裝微信PC版客戶端,我專門錄了一個安裝 及使用的教程 ...
  • Swarm集群編排 什麼是Swarm ​ Swarm是Docker公司自研發的容器集群管理系統,Swarm在早期是作為一個獨立服務存在,在Docker Engine v1.12中集成了Swarm的集群管理,和編排功能。可以通過初始化Swarm或加入現有Swarm來啟用Docker引擎的Swarm模式 ...
  • Windows環境下 一、開啟 Imagick 擴展 1、安裝PHP擴展:Imagick,下載地址 https://pecl.php.net/package/imagick 註意和php版本保持一致; 2、將下載下來的文件解壓,把php_imagick.dll複製到php/ext下,即php的擴展目 ...
  • IP地址 = 網路地址 + 主機地址。 | 分類 | 信息 | 公有IP |私有地址 | 預設網關 | | : : | : : | : : | : : | : : | | A | 最高位為0,1位元組網路地址,3位元組主機地址 | 1.0.0.0 - 126.0.0.0 | 10.0.0.0 ~ 10. ...
  • 1.什麼是request對象 在django中,當一個頁面被請求時,Django就會創建一個包含本次請求原信息的HttpRequest對象;Django會將這個對象自動傳遞給響應的視圖函數,一般視圖函數約定俗成地使用 request 參數承接這個對象。 2.request對象的作用 request對 ...
  • “JVM 為什麼使用元空間替換了永久代?” 這是一個工作6年的同學去位元組第一面遇到的問題,很遺憾,他沒有回答出來 大家好,我是Mic,一個工作了14年的Java程式員。 關於這個問題,我們怎麼回答?面試官到底關註什麼呢? 面試解析 我們都知道Java8以及以後的版本中,JVM運行時數據區的結構都在慢 ...
  • C++ Primer學習筆記:string、vector、迭代器以及數組,只記錄不會或不熟悉的地方 博客小站:blog.smartdog.top 命名空間 std::cin 使用標準輸入輸出命名空間,:: 域操作符表示:編譯器應從操作符左側名字所示的作用域中尋找右側那個名字。 使用 using 可以 ...
  • 來源 | Info ,整理 | 鈺瑩、Tina 回擊就代表輸了?! 今年年中,一位前谷歌、前亞馬遜的工程師推出了他創作的開源記憶體數據緩存系統 Dragonfly,用 C/C++ 編寫,基於 BSL 許可(Business Source License)分發。 根據過往的基準測試結果來看, Drago ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...