博客 / 詳情

返回

我開源了團隊內部基於SpringBoot Web快速開發的API腳手架stater

我們現在使用SpringBoot 做Web 開發已經比之前SprngMvc 那一套強大很多了。
但是 用SpringBoot Web 做API 開發還是不夠簡潔有一些。

每次Web API常用功能都需要重新寫一遍。或者複製之前項目代碼。於是我封裝了這麼一個

抽出SpringBoot Web API 每個項目必備需要重複寫的模塊,和必備功能。
並且擴展了我工作中用到的 所有工具庫。

基於它,你可以輕鬆開發SpringBoot WEB API,提高效率。不在去關心一些繁瑣。重複工作,而是把重點聚焦到業務。

目前更新版本到1.5.2 功能如下

  1. 支持一鍵配置自定義RestFull API 統一格式返回
  2. 支持RestFull API 錯誤國際化
  3. 支持全局異常處理,全局參數驗證處理
  4. 業務錯誤斷言工具封裝,遵循錯誤優先返回原則
  5. 封裝Redis key,value 操作工具類。統一key管理 spring cache緩存實現
  6. RestTemplate 封裝 POST,GET 請求工具
  7. 日誌集成。自定義日誌路徑,按照日誌等級分類,支持壓縮和文件大小分割。按時間顯示
  8. 工具庫集成 集成了lombok,hutool,commons-lang3,guava。不需要自己單個引入
  9. 集成mybatisPlus一鍵代碼生成
  10. 日誌記錄,服務監控,支持日誌鏈路查詢。自定義數據源
  11. OpenApi3文檔一鍵配置。支持多種文檔和自動配置
  12. 接口限流,Ip城市回顯
  13. HttpUserAgent請求設備工具封裝
  14. RequestUtil參數解析封裝工具

後續會持續更新。項目中重複使用,必備模塊和工具。

  • GitHub 地址
  • gitee 地址

rest-api-spring-boot-starter 適用於SpringBoot Web API 快速構建讓開發人員快速構建統一規範的業務RestFull API 不在去關心一些繁瑣。重複工作,而是把重點聚焦到業務。

快速開始

  1. 項目pom中引入依賴

    <dependency>
     <groupId>cn.soboys</groupId>
     <artifactId>rest-api-spring-boot-starter</artifactId>
     <version>1.5.0</version>
    </dependency>
  2. 在SpringBoot啓動類或者配置類上通過 @EnableRestFullApi註解開啓rest-api

    
    @SpringBootApplication
    @EnableRestFullApi
    public class SuperaideApplication {
    
     public static void main(String[] args) {
         SpringApplication.run(SuperaideApplication.class, args);
     }
    }
    ````
    
    到此你項目中就可以使用所有的功能了。
    
    ## RestFull API
    在`Controller`中我們寫普通的請求接口如:

    @PostMapping("/chat")
    public HashMap chatDialogue() {
    HashMap m = new HashMap();
    m.put("age", 26);
    m.put("name", "Judy");
    return m;
    }

    返回的就是全局統一RestFull API

    {
    "success": true,
    "code": "OK",
    "msg": "操作成功",
    "requestId": "IPbHLE5SZ1fqI0lgNXlB",
    "timestamp": "2023-07-09 02:39:40",
    "data": {

     "name": "judy",
     "hobby": "swing",
     "age": 18

    }
    }

    
    也可以基於`Result`構建

    @PostMapping("/chat")
    public Result chatDialogue(@Validated EntityParam s) {
    return Result.buildSuccess(s);
    }

    ## 分頁支持
    
    我們在日常中分頁是一個比較特殊返回。也是非常常用的。
    

    @PostMapping("/page")
    @Log("分頁查詢用户數據")
    public Result page(@Validated EntityParam s) {
    ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
    List a=new ArrayList();
    a.add(s);
    resultPage.setPageData(a);
    return ResultPage.buildSuccess(resultPage);
    }

  3. 構建自定義自己的分頁數據

    ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
  4. 通過ResultPage.buildSuccess(resultPage)進行構建返回

    返回統一響應格式

    {
     "previousPage": 1,
     "nextPage": 1,
     "pageSize": 1,
     "totalPageSize": 1,
     "hasNext": "false",
     "success": true,
     "code": "OK",
     "msg": "操作成功",
     "requestId": "D9AMALgkZ6gVfe6Pi0Oh",
     "timestamp": "2023-07-09 02:39:40",
     "data": [
         {
             "name": "judy",
             "hobby": "swing",
             "age": 18
         }
     ]
    }

    自定義返回格式

    {
     "previousPage": 1,
     "nextPage": 1,
     "pageSize": 1,
     "totalPageSize": 1,
     "hasNext": "false",
     "success": true,
     "code": "OK",
     "msg": "操作成功",
     "requestId": "D9AMALgkZ6gVfe6Pi0Oh",
     "timestamp": "2023-07-09 02:39:40",
     "data": [
         {
             "name": "judy",
             "hobby": "swing",
             "age": 18
         }
     ]
    }

    上述統一返回格式,可能不符合你項目中接口統一格式如:

    {
     "success": true,
     "code": "OK",
     "msg": "操作成功",
     "requestId": "ztf4S-lP9yrtKPSiwldZ",
     "timestamp": "2023-07-11 13:46:53",
     "data": {
         "previousPage": 1,
         "nextPage": 1,
         "pageSize": 1,
         "totalPageSize": 1,
         "hasNext": "false",
         "pageData": [
             {
                 "name": "judy",
                 "hobby": "swing",
                 "age": 18
             }
         ]
     }
    }

    page分頁數據是在data裏面你可以定義pageWrap屬性true包裝返回定義pageDatakey值如records

    你需要自定義keymsg你可能對應message,success你可能對應status只需要在配置文件中配置自定義key

    自定義返回成功值你的成功返回可能是200你可以配置code-success-value

    rest-api:
      enabled: false
      msg: msg
      code: code
      code-success-value: OK
      success: success
      previousPage: previousPage
      nextPage: nextPage
      pageSize: pageSize
      hasNext: hasNext
      totalPageSize: totalPageSize
      data: info

    enabled開啓後會讀取你自定義配置的key

    rest-api:
      enabled: true
      msg: msg1
      code: code1
      code-success-value: 200
      success: success1
      previousPage: previousPage1
      nextPage: nextPage1
      pageSize: pageSize1
      hasNext: hasNext1
      totalPageSize: totalPageSize1
      data: info

    對應返回內容

    {
     "success": true,
     "code": "OK",
     "msg": "操作成功",
     "requestId": "ztf4S-lP9yrtKPSiwldZ",
     "timestamp": "2023-07-11 13:46:53",
     "data": {
         "previousPage": 1,
         "nextPage": 1,
         "pageSize": 1,
         "totalPageSize": 1,
         "hasNext": "false",
         "pageData": [
             {
                 "name": "judy",
                 "hobby": "swing",
                 "age": 18
             }
         ]
     }
    }

    自定義返回

    有時候我們需要自定義返回。不去包裝統一響應RestFull API格式

  5. 可以通過註解@NoRestFulApi實現如

    @GetMapping("/test")
    @NoRestFulApi
    public Map chatDialogue() {
     Map  m= new HashMap<>();
     m.put("name","judy");
     m.put("age",26);
     return m;
    }
  6. 通過類掃描去實現
    默認會過濾String類型認為是頁面路徑。

    通過屬性配置文件include-packages需要統一返回包。exclude-packages不需統一返回的包

    include-packages: cn.soboys.superaide.controller
    exclude-packages: xx.xxx.xxx

    OpenApi文檔生成

    已經內置自動支持。swagger文檔。和最新的OpenApi3 文檔。項目啓動後即可訪問。

  7. swagger-ui.html 文檔。路徑/swagger-ui.html
  8. 基於spring-doc 文檔UI增強 路徑/doc.html
  9. 接口文檔屬性信息

      openapi:
     description:
     title:
     version:
     license: 
     contact:
       name:
       email:
       url: 
  10. 啓動項目後,訪問 http://server:port/context-path/swagger-ui.html 即可進入 Swagger UI 頁面,OpenAPI 描述將在以下 json 格式的 url 中 提供:http://server:port/context-path/v3/api-docs
  11. server:域名 或 IP
  12. port:服務器端口
  13. context-path:應用程序的上下文路徑,springboot 默認為空
  14. 文檔也可以 yaml 格式提供,位於以下路徑:/v3/api-docs.yaml

    如果嫌棄官方提供的 swagger-ui 不美觀,或者使用不順手,可以選擇關閉 ui,還可以剔除掉 ui 相關的 webjar 的引入。

    springdoc:
      swagger-ui:
     enabled: false

    OpenAPI 文檔信息,默認可在此 url 中獲取: http://server:port/context-path/v3/api-docs。
    可以利用其他支持 OpenAPI 協議的工具,通過此地址,進行 API 展示,如 Apifox。
    ( Postman 的 api 測試也可以利用此地址進行導入生成 )

    Knife4j (原 swagger-bootstrap-ui) 3.x 版本提供了對於 OpenAPI 協議的部分支持。

    ::: tip
    Knife4j 很多地方沒有按照協議規範實現,所以使用起來會有很多問題,另外項目也很久沒有維護了,不推薦使用。
    :::

    由於 knife4j 對於規範支持的不全面,無法直接使用單文檔源數據,所以必須進行分組或者 urls 的指定。
    # urls
    springdoc:
      swagger-ui:
     urls:
       - { name: 'sample', url: '/v3/api-docs' }

    或者

    #分組
    springdoc:
      group-configs:
     - { group: 'sample', packages-to-scan: 'com.example' }

    Knife4j 的 UI 訪問地址有所不同,頁面映射在 doc.html 路徑下,啓動項目後,訪問 http://server:port/context-path/doc.html

    即可進入 Knife4j 的 Swagger UI 頁面。

    全局錯誤攔截,參數校驗

    幫你封裝好了所有http常見錯誤,和所有請求參數驗證錯誤。

    如請求錯誤

    {
     "success": false,
     "code": "405",
     "msg": "方法不被允許",
     "timestamp": "2023-07-03 22:36:47",
     "data": "Request method 'GET' not supported"
    }

    請求資源不存在等

    {
     "success": false,
     "code": "404",
     "msg": "請求資源不存在",
     "timestamp": "2023-07-03 22:42:35",
     "data": "/api"
    }
    如果需要攔截上面錯誤請在springboot 配置文件中加入
    #出現錯誤時, 直接拋出異常
    spring.mvc.throw-exception-if-no-handler-found=true
    #不要為我們工程中的資源文件建立映射
    spring.web.resources.add-mappings=false

    參數校驗錯誤

    驗證Studen對象參數

    /**
     * @author 公眾號 程序員三時
     * @version 1.0
     * @date 2023/6/26 22:10
     * @webSite https://github.com/coder-amiao
     */
    @Data
    public class Student {
     @NotBlank
     private String nam;
     @NotBlank
     private String hobby;
    }
     @PostMapping("/chat")
     public HashMap chatDialogue(@Validated  Student student) {
         HashMap m = new HashMap();
         m.put("age", 26);
         m.put("name", "Judy");
         return m;
     }

    請求結果

    JSON Body參數

     @PostMapping("/chat")
     public HashMap chatDialogue(@RequestBody @Validated  Student student) {
         HashMap m = new HashMap();
         m.put("age", 26);
         m.put("name", "Judy");
         return m;
     }

    錯誤國際化

    內置封裝錯誤默認支持英文和中文兩種國際化。你不做任何配置自動支持

    如果需要內置支持更多語言,覆蓋即可。

    自定義自己錯誤國際化和語言

      i18n:
     # 若前端無header傳參則返回中文信息
     i18n-header: Lang
     default-lang: cn
     message:
       # admin
       internal_server_error:
         en: Internal Server Error
         cn: 系統錯誤
       not_found:
         en: Not Found
         cn: 請求資源不存在

    message 對應錯誤提示
    對應internal_server_error 自定義
    下面語言自己定義 和前端傳入i18n-header 對應上,就顯你定義錯誤語言

    我不傳錯誤國際化默認就是中文在 default-lang: cn
    進行配置

    當我傳入 指定語言 就會按照你配置的國際化自定義返回錯誤提示

    日誌鏈路追蹤

    RestFull API 統一返回有一個requestId 它是每個接口唯一標識。用於接口請求日誌鏈路追蹤。日誌查詢。 如:

    {
     "msg": "操作成功",
     "code": "OK",
     "previousPage": 1,
     "success": true,
     "requestId": "udYNdbbMFE45R84OPu9m",
     "nextPage": 1,
     "pageSize": 1,
     "totalPageSize": 1,
     "hasNext": "false",
     "timestamp": "2023-07-09 03:00:27",
     "info": [
         {
             "name": "judy",
             "hobby": "swing",
             "age": 18
         }
     ]
    }

    通過requestId你可以很輕鬆的在你的日誌文件查詢定位到每次錯誤的請求。

    通過Log註解記錄你想要記錄請求

    @PostMapping("/page")
    @Log(value = "查詢用户數據",apiType= LogApiTypeEnum.USER,CURDType= LogCURDTypeEnum.RETRIEVE)
    public Result page(@Validated EntityParam s) {
     ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
     List a=new ArrayList();
     a.add(s);
     resultPage.setPageData(a);
     return ResultPage.buildSuccess(resultPage);
    }
    系統默認日誌記錄數據源為日誌文件。如
    2023-07-13 11:21:25 INFO  http-nio-8888-exec-2 cn.soboys.restapispringbootstarter.aop.LimitAspect IP:192.168.1.8 第 1 次訪問key為 [_kenx:chat192.168.1.8],描述為 [接口限流] 的接口
    2023-07-13 11:21:26 INFO  http-nio-8888-exec-2 cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource {
     "description": "日誌記錄測試",
     "method": "cn.soboys.restapispringbootstarter.controller.ApiRestController.chatDialogue()",
     "params": {
     },
     "logType": "INFO",
     "requestIp": "192.168.1.8",
     "path": "/chat",
     "address": "0|0|0|內網IP|內網IP",
     "time": 128,
     "os": "Mac",
     "browser": "Chrome",
     "result": {
         "success": true,
         "code": "OK",
         "msg": "操作成功",
         "requestId": "5RgKzWGFNa9XSPwhw2Pi",
         "timestamp": "2023-07-13 11:21:25",
         "data": "接口限流測試"
     },
     "apiType": "USER",
     "device": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
    }

    你可以自定義自己的日誌數據源實現LogDataSource接口 日誌操作支持異步。需要在配置類。或者啓動類加上@EnableAsync
    註解

    package cn.soboys.restapispringbootstarter.log;
    
    import org.springframework.scheduling.annotation.Async;
    
    import java.util.Map;
    
    /**
     * @Author: kenx
     * @Since: 2021/6/23 13:55
     * @Description:
     */
    public interface LogDataSource {
    
     /**
      * 獲取拓展數據
      * @return
      * @param logEntry
      */
     @Async
     void  save(LogEntry logEntry);
    }
    

    或者你可以繼承我默認的日誌數據源實現類LogFileDefaultDataSource 重寫save(LogEntry logEntry)方法。

    @Slf4j
    public class LogFileDefaultDataSource implements LogDataSource {
    
     /**
      * 自定義保存數據源
      *
      * @param
      * @return LogEntry
      */
     @Override
     public void save(LogEntry logEntry) {
         log.info(JSONUtil.toJsonPrettyStr(logEntry));
     }
    }
    如果是自定義日誌數據源實現需要再配置文件,配置日誌數據源。如:
    logging:
     path: ./logs   #日誌存儲路徑(服務器上絕對)
     max-history: 90 # 保存多少天
     max-file-size: 3MB  # 每個文件大小
     max-total-size-cap: 1GB  #總文件大小超過多少壓縮
     level-root: INFO    # 這裏的INFO可以替換為其他日誌等級,如DEBUG, WARN, ERROR, TRACE, FATAL, OFF等。 日誌等級由低到高分別是debugger-info-warn-error
     logDataSourceClass: cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource # 日誌數據源

    Ip城市記錄

    日誌記錄提供Ip城市回顯記錄

    @PostMapping("/page")
    @Log(value = "查詢用户數據", apiType = LogApiTypeEnum.USER, CURDType = LogCURDTypeEnum.RETRIEVE,ipCity = true)
    public Result page(@Validated EntityParam s) {
     ResultPage<List<EntityParam>> resultPage = new ResultPage<>();
     List a = new ArrayList();
     a.add(s);
     resultPage.setPageData(a);
     return ResultPage.buildSuccess(resultPage);
    }

    通過配置ipCity屬性,默認是true會記錄IP對應物理地址詳細信息即國家城市等
    Ip城市查詢通過ip2region獲取。

    你可配置屬性 location 配置自己的ip2region.xdb文件。

      ip2region:
     external: false
     location: classpath:ip2region/ip2region.xdb

    默認不配置會幫你自動生成一個。基於2.7.x最新的數據
    獲取最新自定義ip數據 Github 倉庫

    當然也幫你封裝了。你可以通過工具類HttpUserAgent的靜態方法getIpToCityInfo(String ip) 去獲取查詢ip對應城市信息

    屬性配置

    配置語言國際化,日誌等,

    ::: tip
    默認不用配置任何參數。會使用默認的,配置了會使用你項目中的配置。
    :::

    默認配置

    rest-api:
      enabled: false
      msg: msg
      code: code
      code-success-value: OK
      success: success
      previousPage: previousPage
      nextPage: nextPage
      pageSize: pageSize
      hasNext: hasNext
      totalPageSize: totalPageSize
      data: info
      include-packages: cn.soboys.superaide.controller
      exclude-packages: xx.xxx.xxx
      redis:
     key-prefix: rest
      openapi:
     description:
     title:
     version:
     license: 
     contact:
       name:
       email:
       url:
      logging:
     path: ./logs   #日誌存儲路徑(服務器上絕對)
     max-history: 90 # 保存多少天
     max-file-size: 3MB  # 每個文件大小
     max-total-size-cap: 1GB  #總文件大小超過多少壓縮
     level-root: INFO    # 這裏的INFO可以替換為其他日誌等級,如DEBUG, WARN, ERROR, TRACE, FATAL, OFF等。 日誌等級由低到高分別是debugger-info-warn-error
     logDataSourceClass: cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource # 日誌數據源
      i18n:
     # 若前端無header傳參則返回中文信息
     i18n-header: Lang
     default-lang: cn
     message:
       # admin
       internal_server_error:
         en: Internal Server Error
         cn: 系統錯誤
       bad_gateway:
         en: Bad Gateway
         cn: 錯誤的請求
       unauthorized:
         en: Unauthorized
         cn: 未授權
       forbidden:
         en: Forbidden
         cn: 資源禁止訪問
       method_not_allowed:
         en: Method Not Allowed
         cn: 方法不被允許
       request_timeout:
         en: Request Timeout
         cn: 請求超時
       invalid_argument:
         en: Invalid Argument {}
         cn: 參數錯誤 {}
       argument_analyze:
         en: Argument Analyze {}
         cn: 參數解析異常 {}
       business_exception:
         en: Business Exception
         cn: 業務錯誤
       not_found:
         en: Not Found
         cn: 請求資源不存在
    

    代碼生成配置

    支持MybatisPlus代碼一鍵生成 默認不引入MybatisPlus生成依賴需要手動引入

    package cn.soboys.restapispringbootstarter.config;
    
    import lombok.Data;
    
    /**
     * @author 公眾號 程序員三時
     * @version 1.0
     * @date 2023/7/5 00:05
     * @webSite https://github.com/coder-amiao
     */
    @Data
    public class GenerateCodeConfig {
     /**
      * 數據庫驅動
      */
     private String driverName;
     /**
      * 數據庫連接用户名
      */
     private String username;
     /**
      * 數據庫連接密碼
      */
     private String password;
     /**
      * 數據庫連接url
      */
     private String url;
     /**
      * 生成代碼 保存路徑。默認當前項目下。
      * 如需修改,使用覺得路徑
      */
     private String projectPath;
     /**
      * 代碼生成包位置
      */
     private String packages;
    }
    

    RestFull API

    Controller中直接使用

    @PostMapping("/chat")
    public HashMap chatDialogue() {
     HashMap m = new HashMap();
     m.put("age", 26);
     m.put("name", "Judy");
     return m;
    }

    Result構建返回

    @PostMapping("/chat")
    public Result chatDialogue() {
     HashMap m = new HashMap();
     m.put("age", 26);
     m.put("name", "Judy");
     return Result.buildSuccess(m);
    }

    分頁支持

    我們在日常中分頁是一個比較特殊返回。也是非常常用的。

    @PostMapping("/page")
    @Log("分頁查詢用户數據")
    public Result page(@Validated EntityParam s) {
     ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
     List a=new ArrayList();
     a.add(s);
     resultPage.setPageData(a);
     return ResultPage.buildSuccess(resultPage);
    }
  15. 構建自定義自己的分頁數據

    ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
  16. 通過ResultPage.buildSuccess(resultPage)進行構建返回

返回統一響應格式

{
    "previousPage": 1,
    "nextPage": 1,
    "pageSize": 1,
    "totalPageSize": 1,
    "hasNext": "false",
    "success": true,
    "code": "OK",
    "msg": "操作成功",
    "requestId": "D9AMALgkZ6gVfe6Pi0Oh",
    "timestamp": "2023-07-09 02:39:40",
    "data": [
        {
            "name": "judy",
            "hobby": "swing",
            "age": 18
        }
    ]
}

自定義返回格式

{
    "previousPage": 1,
    "nextPage": 1,
    "pageSize": 1,
    "totalPageSize": 1,
    "hasNext": "false",
    "success": true,
    "code": "OK",
    "msg": "操作成功",
    "requestId": "D9AMALgkZ6gVfe6Pi0Oh",
    "timestamp": "2023-07-09 02:39:40",
    "data": [
        {
            "name": "judy",
            "hobby": "swing",
            "age": 18
        }
    ]
}

上述統一返回格式,可能不符合你項目中接口統一格式如:

{
    "success": true,
    "code": "OK",
    "msg": "操作成功",
    "requestId": "ztf4S-lP9yrtKPSiwldZ",
    "timestamp": "2023-07-11 13:46:53",
    "data": {
        "previousPage": 1,
        "nextPage": 1,
        "pageSize": 1,
        "totalPageSize": 1,
        "hasNext": "false",
        "pageData": [
            {
                "name": "judy",
                "hobby": "swing",
                "age": 18
            }
        ]
    }
}

page分頁數據是在data裏面你可以定義pageWrap屬性true包裝返回定義pageDatakey值如records

你需要自定義keymsg你可能對應message,success你可能對應status只需要在配置文件中配置自定義key

自定義返回成功值你的成功返回可能是200你可以配置code-success-value

rest-api:
  enabled: false
  msg: msg
  code: code
  code-success-value: OK
  success: success
  previousPage: previousPage
  nextPage: nextPage
  pageSize: pageSize
  hasNext: hasNext
  totalPageSize: totalPageSize
  data: info

enabled開啓後會讀取你自定義配置的key

rest-api:
  enabled: true
  msg: msg1
  code: code1
  code-success-value: 200
  success: success1
  previousPage: previousPage1
  nextPage: nextPage1
  pageSize: pageSize1
  hasNext: hasNext1
  totalPageSize: totalPageSize1
  data: info

對應返回內容

{
    "success": true,
    "code": "OK",
    "msg": "操作成功",
    "requestId": "ztf4S-lP9yrtKPSiwldZ",
    "timestamp": "2023-07-11 13:46:53",
    "data": {
        "previousPage": 1,
        "nextPage": 1,
        "pageSize": 1,
        "totalPageSize": 1,
        "hasNext": "false",
        "pageData": [
            {
                "name": "judy",
                "hobby": "swing",
                "age": 18
            }
        ]
    }
}

自定義返回

有時候我們需要自定義返回。不去包裝統一響應RestFull API格式

  1. 可以通過註解@NoRestFulApi實現如

    @GetMapping("/test")
    @NoRestFulApi
    public Map chatDialogue() {
     Map  m= new HashMap<>();
     m.put("name","judy");
     m.put("age",26);
     return m;
    }
  2. 通過類掃描去實現
    默認會過濾String類型認為是頁面路徑。

通過屬性配置文件include-packages需要統一返回包。exclude-packages不需統一返回的包

include-packages: cn.soboys.superaide.controller
exclude-packages: xx.xxx.xxx

錯誤國際化支持

內置常見的錯誤。可以看HttpStatus。默認錯誤支持中文和英文兩種國際化。配置如下

  i18n:
    # 若前端無header傳參則返回中文信息
    i18n-header: Lang
    default-lang: cn
    message:
      # admin
      internal_server_error:
        en: Internal Server Error
        cn: 系統錯誤
      bad_gateway:
        en: Bad Gateway
        cn: 錯誤的請求
      unauthorized:
        en: Unauthorized
        cn: 未授權
      forbidden:
        en: Forbidden
        cn: 資源禁止訪問
      method_not_allowed:
        en: Method Not Allowed
        cn: 方法不被允許
      request_timeout:
        en: Request Timeout
        cn: 請求超時
      invalid_argument:
        en: Invalid Argument {}
        cn: 參數錯誤 {}
      argument_analyze:
        en: Argument Analyze {}
        cn: 參數解析異常 {}
      business_exception:
        en: Business Exception
        cn: 業務錯誤
      not_found:
        en: Not Found
        cn: 請求資源不存在

可以自行覆蓋擴充

全局錯誤攔截和響應

默認攔所有未知錯誤異常和validation參數校驗失敗異常,以及Http請求異常。
還有全局自定義BusinessException 業務異常 自動集成spring-boot-starter-validation 你項目中不需要再單獨引入

<!--參數校驗-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

也內置擴展了許多自定義參數校驗參考

第三方請求

有時候我們項目中需要調用第三方接口服務。基於RestTemplate 進一步封裝了直接的POST,GET,請求。

在需要使用地方注入RestFulTemp

@Resource
private RestFulTemp restFulTemp;

GET請求

@GetMapping("/doGet")
public Result doGet() {
    ResponseEntity<String> response = restFulTemp.doGet("http://127.0.0.1:9000/redis/get");
    return Result.buildSuccess();
}

POST 請求

/**
 * POST 請求參 數為body json體格式
 * @return
 */
@PostMapping("/doPost")
public Result doPost() {
    Student s=new Student();
    s.setHobby("swing");
    s.setNam("judy");
    //自動把對象轉換為JSON
    ResponseEntity<String> response = 
            restFulTemp.doPost("http://127.0.0.1:9000/redis/get",s);
    return Result.buildSuccess();
}
/**
 * POST請求 參數為FORM 表單參數
 * @return
 */
@PostMapping("/doPost")
public Result doPostForm() {
    EntityParam s=new EntityParam();
    s.setAge(19);
    s.setHobby("swing");
    s.setName("judy");

    ResponseEntity<String> response =
            restFulTemp.doPostForm("http://127.0.0.1:8000/chat", BeanUtil.beanToMap(s));
    return Result.buildSuccess(response.getBody());
}

DELETE請求

@GetMapping("/doDelete")
public Result doDelete() {
    restFulTemp.doDelete("http://127.0.0.1:8000/chat");
    return Result.buildSuccess();
}

PUT請求

@GetMapping("/doPut")
public Result doPut() {
    EntityParam s=new EntityParam();
    restFulTemp.doPut("http://127.0.0.1:8000/chat",s);
    return Result.buildSuccess(s);
}

錯誤異常自定義

我內置錯誤異常和業務異常可能無法滿足你自身接口業務異常需要。你可以自定義錯誤異常類,和錯誤響應枚舉碼。

自定義錯誤枚舉 需要實現ResultCode接口
package cn.soboys.restapispringbootstarter;

import cn.soboys.restapispringbootstarter.i18n.I18NKey;

/**
* @author 公眾號 程序員三時
* @version 1.0
* @date 2023/6/26 10:21
* @webSite https://github.com/coder-amiao
* 響應碼接口,自定義響應碼,實現此接口
*/
public interface ResultCode extends I18NKey {

   String getCode();

   String getMessage();

}
````

如果要支持國際化還需要實現國際化接口**I18NKey** 參考我內部**HttpStatus**實現即可

package cn.soboys.restapispringbootstarter;

import cn.soboys.restapispringbootstarter.i18n.I18NKey;

/**

  • @author 公眾號 程序員三時
  • @version 1.0
  • @date 2023/6/26 11:01
  • @webSite https://github.com/coder-amiao
    */

public enum HttpStatus implements ResultCode, I18NKey {

/**
 * 系統內部錯誤
 */
INTERNAL_SERVER_ERROR("500", "internal_server_error"),
BAD_GATEWAY("502", "bad_gateway"),
NOT_FOUND("404", "not_found"),
UNAUTHORIZED("401", "unauthorized"),
FORBIDDEN("403", "forbidden"),
METHOD_NOT_ALLOWED("405", "method_not_allowed"),
REQUEST_TIMEOUT("408", "request_timeout"),

INVALID_ARGUMENT("10000", "invalid_argument"),
ARGUMENT_ANALYZE("10001", "argument_analyze"),
BUSINESS_EXCEPTION("20000", "business_exception");


private final String value;

private final String message;

HttpStatus(String value, String message) {
    this.value = value;
    this.message = message;
}


@Override
public String getCode() {
    return value;
}

@Override
public String getMessage() {
    return message;
}


@Override
public String key() {
    return message;
}

}

rest-api:
enabled: false
i18n:

# 若前端無header傳參則返回中文信息
i18n-header: Lang
default-lang: cn
message:
  # admin
  internal_server_error:
    en: Internal Server Error
    cn: 系統錯誤
  bad_gateway:
    en: Bad Gateway
    cn: 錯誤的請求
  unauthorized:
    en: Unauthorized
    cn: 未授權
  forbidden:
    en: Forbidden
    cn: 資源禁止訪問
  method_not_allowed:
    en: Method Not Allowed
    cn: 方法不被允許
  request_timeout:
    en: Request Timeout
    cn: 請求超時
  invalid_argument:
    en: Invalid Argument {}
    cn: 參數錯誤 {}
  argument_analyze:
    en: Argument Analyze {}
    cn: 參數解析異常 {}
  business_exception:
    en: Business Exception
    cn: 業務錯誤
  not_found:
    en: Not Found
    cn: 請求資源不存在

![](https://images.soboys.cn/202307040127708.png)

![](https://images.soboys.cn/202307040127574.png)

![](https://images.soboys.cn/202307042336110.png)

![](https://images.soboys.cn/202307042337644.png)

## 業務斷言
封裝了業務錯誤斷言工具。`Assert` 遵循錯誤優先返回原則。

你要自定義自己的業務異常。繼承`BusinessException`
重寫對應方法

package cn.soboys.restapispringbootstarter.exception;

import cn.soboys.restapispringbootstarter.HttpStatus;
import cn.soboys.restapispringbootstarter.ResultCode;
import lombok.Data;

/**

  • @author 公眾號 程序員三時
  • @version 1.0
  • @date 2023/6/26 16:45
  • @webSite https://github.com/coder-amiao
    */

@Data
public class BusinessException extends RuntimeException {

/**
 * 錯誤碼
 */
private String code="20000";

/**
 * 錯誤提示
 */
private String message;


public BusinessException(String message) {
    this.message = message;

}

public BusinessException(String message, String code) {
    this.message = message;
    this.code = code;

}

public BusinessException(ResultCode resultCode) {
    this.message = resultCode.getMessage();
    this.code = resultCode.getCode();

}

}


項目中日誌是非常常用的,而且還是必須的。已經自動配置集成`spring-boot-starter-logging` 你不需要在項目中單獨引入

<!--日誌集成-->
<dependency>

<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>

</dependency>

## 默認日誌配置

logging:

path: ./logs   #日誌存儲路徑(服務器上絕對)
max-history: 90 # 保存多少天
max-file-size: 3MB  # 每個文件大小
max-total-size-cap: 1GB  #總文件大小超過多少壓縮
level-root: INFO    # 這裏的INFO可以替換為其他日誌等級,如DEBUG, WARN, ERROR, TRACE, FATAL, OFF等。 日誌等級由低到高分別是debugger-info-warn-error
logDataSourceClass: cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource # 日誌數據源
## 日誌記錄與追蹤
RestFull API 統一返回有一個`requestId `它是每個接口唯一標識。用於接口請求日誌鏈路追蹤。日誌查詢。 如:

{

"msg": "操作成功",
"code": "OK",
"previousPage": 1,
"success": true,
"requestId": "udYNdbbMFE45R84OPu9m",
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"timestamp": "2023-07-09 03:00:27",
"info": [
    {
        "name": "judy",
        "hobby": "swing",
        "age": 18
    }
]

}

通過requestId你可以很輕鬆的在你的日誌文件查詢定位到每次錯誤的請求。

通過`Log`註解記錄你想要記錄請求

@PostMapping("/page")
@Log(value = "查詢用户數據",apiType= LogApiTypeEnum.USER,CURDType= LogCURDTypeEnum.RETRIEVE)
public Result page(@Validated EntityParam s) {

ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
List a=new ArrayList();
a.add(s);
resultPage.setPageData(a);
return ResultPage.buildSuccess(resultPage);

}


>系統默認日誌記錄數據源為日誌文件。如

2023-07-09 03:00:32 INFO http-nio-8000-exec-2 cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource {

"description": "查詢用户數據",
"method": "cn.soboys.restapispringbootstarter.controller.ApiRestController.page()",
"logType": "INFO",
"time": 3,
"result": {
    "success": true,
    "code": "OK",
    "msg": "操作成功",
    "requestId": "udYNdbbMFE45R84OPu9m",
    "timestamp": "2023-07-09 03:00:27",
    "data": {
        "previousPage": 1,
        "nextPage": 1,
        "pageSize": 1,
        "totalPageSize": 1,
        "hasNext": "false",
        "pageData": [
            {
                "name": "judy",
                "hobby": "swing",
                "age": 18
            }
        ],
        "requestId": "qJTOejQmY-OOf7fagegB",
        "timestamp": "2023-07-09 03:00:27"
    }
},
"apiType": "USER"

}
2023-07-09 03:08:03 INFO http-nio-8000-exec-4 cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource {

"description": "查詢用户數據",
"method": "cn.soboys.restapispringbootstarter.controller.ApiRestController.page()",
"logType": "INFO",
"time": 1,
"result": {
    "success": true,
    "code": "OK",
    "msg": "操作成功",
    "requestId": "kP3yPP-H7wI2x1ak6YFA",
    "timestamp": "2023-07-09 03:00:27",
    "data": {
        "previousPage": 1,
        "nextPage": 1,
        "pageSize": 1,
        "totalPageSize": 1,
        "hasNext": "false",
        "pageData": [
            {
                "name": "judy",
                "hobby": "swing",
                "age": 18
            }
        ],
        "requestId": "pGbbiEj8GQ1eTxQpF2Jr",
        "timestamp": "2023-07-09 03:00:27"
    }
},
"apiType": "USER"

}

你可以自定義自己的日誌數據源實現`LogDataSource`接口 日誌操作支持異步。需要在配置類。或者啓動類加上`@EnableAsync`
註解

package cn.soboys.restapispringbootstarter.log;

import org.springframework.scheduling.annotation.Async;

import java.util.Map;

/**

  • @Author: kenx
  • @Since: 2021/6/23 13:55
  • @Description:
    */

public interface LogDataSource {

/**
 * 獲取拓展數據
 * @return
 * @param logEntry
 */
@Async
void  save(LogEntry logEntry);

}


或者你可以繼承我默認的日誌數據源實現類`LogFileDefaultDataSource` 重寫`save(LogEntry logEntry)`方法。

@Slf4j
public class LogFileDefaultDataSource implements LogDataSource {

/**
 * 自定義保存數據源
 *
 * @param
 * @return LogEntry
 */
@Override
public void save(LogEntry logEntry) {
    log.info(JSONUtil.toJsonPrettyStr(logEntry));
}

}

>如果是自定義日誌數據源實現需要再配置文件,配置日誌數據源。如:

logging:

path: ./logs   #日誌存儲路徑(服務器上絕對)
max-history: 90 # 保存多少天
max-file-size: 3MB  # 每個文件大小
max-total-size-cap: 1GB  #總文件大小超過多少壓縮
level-root: INFO    # 這裏的INFO可以替換為其他日誌等級,如DEBUG, WARN, ERROR, TRACE, FATAL, OFF等。 日誌等級由低到高分別是debugger-info-warn-error
logDataSourceClass: cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource # 日誌數據源

# 緩存和redis

項目中緩存使用是非常常見的。用的最多的是基於`Redis`緩存。於是我封裝了對於`Redis`Key和Value常用操作。

::: tip
默認不引入Redis依賴,如果要使用Redis需要自己單獨引入
:::

<dependency>

<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>

</dependency>


## 項目使用

注入`redisTempUtil` 

@Autowired
private RedisTempUtil redisTempUtil;


如示列

@Autowired
private RedisTempUtil redisTempUtil;

@GetMapping("/redis")
public Result chatDialogue( ) {

redisTempUtil.set("test","111");
redisTempUtil.get("test");
redisTempUtil.set("user","userObj",7200l);
redisTempUtil.getAllKey("xx");  //*表達式
redisTempUtil.clean();
redisTempUtil.deleteObject("test");
redisTempUtil.hasKey("");
return Result.buildSuccess();

}

## 統一緩存管理
上面我們是直接通過工具類`redisTempUtil`直接自己定義`key`然後去存儲,這種方式是不可取的如果`key很多隨意定義就會很混亂`。我提供了統一緩存key管理接口`CacheTmp` 參考實現`CacheKey` 基於枚舉形式,把所有key集中管理

package cn.soboys.restapispringbootstarter.cache;

import lombok.Getter;

/**

  • @author 公眾號 程序員三時
  • @version 1.0
  • @date 2023/7/2 11:04
  • @webSite https://github.com/coder-amiao
  • 緩存枚舉
    */

@Getter
public enum CacheKey implements CacheTmp {

// 密碼的重置碼
PWD_RESET_CODE("reset:code:", true),
;

private String key;

/**
 * Key是否是Key前綴, true時直接取key=key,如果false時key=key+suffix
 */
private boolean hasPrefix;

CacheKey(String key, boolean hasPrefix) {
    this.key = key;
    this.hasPrefix = hasPrefix;
}


@Override
public Boolean getHasPrefix() {
    return this.hasPrefix;
}

@Override
public String getKey() {
    return this.key;
}

}


使用
1. 存儲對於key

@GetMapping("/redis")
public Result chatDialogue() {

CacheKey.PWD_RESET_CODE.valueSetAndExpire("test", 60l, TimeUnit.SECONDS, "judy");
return Result.buildSuccess();

}

2. 獲取對應的key

@GetMapping("/redis/get")
public Result redisGet() {

String a = CacheKey.PWD_RESET_CODE.valueGet("judy");
return Result.buildSuccess(a);

}


## spring Cache實現

封裝了`spring Cache`進一步使用 項目中在配置類或者啓動類通過註解`@EnableCaching`開啓直接使用即可

@Cacheable(cacheNames = "testCache", keyGenerator = "keyGeneratorStrategy")
@GetMapping("/redis/springCache")
public Result springCache() {

String a = "test cache";
return Result.buildSuccess(a);

}

工具類使用`springCacheUtil` 支持提供不是基於註解的使用方式

@GetMapping("/redis/springCache")
public Result redisSpringCache() {

String a = "111344";
springCacheUtil.putCache("test","key","121e1");
return Result.buildSuccess(a);

}

::: tip
默認不引入Redis依賴,緩存基於內存實現(你項目引入redis依賴後會自定切換數據源為Redis緩存)
:::

## redis配置
多個項目或者模塊使用一個key可能會造成混亂,於是提供了一個全局配置key。

redis:

key-prefix: rest 
代碼中添加一個 String 類型的 `key:testKey`,其實際在 redis 中存儲的 key name 為 `rest:testKey`

全局 key 前綴的配置,並不影響對 key 的其他操作,例如獲取對應的 value 時,依然是傳入 `testKey`,而不是 `rest:testKey`

String key = "testKey";
String value = redisTempUtil.get(key);
String value1 = CacheKey.PWD_RESET_CODE.valueGet(key);


## OpenApi文檔生成
已經內置自動支持。`swagger`文檔。和最新的`OpenApi3 `文檔。項目啓動後即可訪問。

1. swagger-ui.html 文檔。路徑`/swagger-ui.html`
2. 基於spring-doc 文檔UI增強 路徑`/doc.html`

3. 接口文檔屬性信息

openapi:

description:
title:
version:
license: 
contact:
  name:
  email:
  url: 

- 啓動項目後,訪問 http://server:port/context-path/swagger-ui.html 即可進入 Swagger UI 頁面,OpenAPI 描述將在以下 json 格式的 url 中 提供:http://server:port/context-path/v3/api-docs
- server:域名 或 IP
- port:服務器端口
- context-path:應用程序的上下文路徑,springboot 默認為空
- 文檔也可以 yaml 格式提供,位於以下路徑:/v3/api-docs.yaml

如果嫌棄官方提供的 swagger-ui 不美觀,或者使用不順手,可以選擇關閉 ui,還可以剔除掉 ui 相關的 webjar 的引入。

springdoc:
swagger-ui:

enabled: false

OpenAPI 文檔信息,默認可在此 url 中獲取: http://server:port/context-path/v3/api-docs。
可以利用其他支持 OpenAPI 協議的工具,通過此地址,進行 API 展示,如 Apifox。
( Postman 的 api 測試也可以利用此地址進行導入生成 )

Knife4j (原 swagger-bootstrap-ui) 3.x 版本提供了對於 OpenAPI 協議的部分支持。

::: tip
Knife4j 很多地方沒有按照協議規範實現,所以使用起來會有很多問題,另外項目也很久沒有維護了,不推薦使用。
::: 

>由於 knife4j 對於規範支持的不全面,無法直接使用單文檔源數據,所以必須進行分組或者 urls 的指定。

urls

springdoc:
swagger-ui:

urls:
  - { name: 'sample', url: '/v3/api-docs' }
或者

分組

springdoc:
group-configs:

- { group: 'sample', packages-to-scan: 'com.example' }

Knife4j 的 UI 訪問地址有所不同,頁面映射在 `doc.html` 路徑下,啓動項目後,訪問 `http://server:port/context-path/doc.html`

即可進入 Knife4j 的 Swagger UI 頁面。

# 代碼自動生成
項目中我們使用`mybatis` 或者`mybatisPlus` 一些簡單的單表業務代碼,增刪改成。我們可以一鍵生成。不需要重複寫。 我封裝了`mybatisPlus` 代碼生成工具

::: tip
默認不引入`mybatisPlus`代碼生成依賴,如果要使用`mybatisPlus`代碼生成需自行單獨引入
:::

<dependency>

<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>

</dependency>
<!-- MySQL -->
<dependency>

<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>

</dependency>
<!--代碼生成依賴的模板引擎-->
<dependency>

<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>

</dependency>


## 項目使用
1.  代碼生成配置類

package cn.soboys.restapispringbootstarter.config;

import lombok.Data;

/**

  • @author 公眾號 程序員三時
  • @version 1.0
  • @date 2023/7/5 00:05
  • @webSite https://github.com/coder-amiao
    */

@Data
public class GenerateCodeConfig {

/**
 * 數據庫驅動
 */
private String driverName;
/**
 * 數據庫連接用户名
 */
private String username;
/**
 * 數據庫連接密碼
 */
private String password;
/**
 * 數據庫連接url
 */
private String url;
/**
 * 生成代碼 保存路徑。默認當前項目下。
 * 如需修改,使用絕對路徑
 */
private String projectPath;
/**
 * 代碼生成包位置
 */
private String packages;

}


示列如:

public class Test {

public static void main(String[] args) {
    GenerateCodeConfig config=new GenerateCodeConfig();
    config.setDriverName("com.mysql.cj.jdbc.Driver");
    config.setUsername("root");
    config.setPassword("root");
    config.setUrl("jdbc:mysql://127.0.0.1:3306/ry?useUnicode=true&useSSL=false&characterEncoding=utf8");
    //config.setProjectPath("superaide");
    config.setPackages("cn.soboys.superaide");
    MyBatisPlusGenerator.generate(config);
}

}



# 常見問題

在使用過程中儘量使用最新版本。我會持續更新更多的內容。 會第一時間發佈在我的公眾號
程序員三時。全網同名


可以關注 公眾號 **程序員三時**。用心分享持續輸出優質內容。希望可以給你帶來一點幫助












user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.