我們現在使用SpringBoot 做Web 開發已經比之前SprngMvc 那一套強大很多了。
但是 用SpringBoot Web 做API 開發還是不夠簡潔有一些。
每次Web API常用功能都需要重新寫一遍。或者複製之前項目代碼。於是我封裝了這麼一個
抽出SpringBoot Web API 每個項目必備需要重複寫的模塊,和必備功能。
並且擴展了我工作中用到的 所有工具庫。
基於它,你可以輕鬆開發SpringBoot WEB API,提高效率。不在去關心一些繁瑣。重複工作,而是把重點聚焦到業務。
目前更新版本到1.5.2 功能如下
- 支持一鍵配置自定義RestFull API 統一格式返回
- 支持RestFull API 錯誤國際化
- 支持全局異常處理,全局參數驗證處理
- 業務錯誤斷言工具封裝,遵循錯誤優先返回原則
- 封裝Redis key,value 操作工具類。統一key管理 spring cache緩存實現
- RestTemplate 封裝 POST,GET 請求工具
- 日誌集成。自定義日誌路徑,按照日誌等級分類,支持壓縮和文件大小分割。按時間顯示
- 工具庫集成 集成了lombok,hutool,commons-lang3,guava。不需要自己單個引入
- 集成mybatisPlus一鍵代碼生成
- 日誌記錄,服務監控,支持日誌鏈路查詢。自定義數據源
- OpenApi3文檔一鍵配置。支持多種文檔和自動配置
- 接口限流,Ip城市回顯
- HttpUserAgent請求設備工具封裝
- RequestUtil參數解析封裝工具
後續會持續更新。項目中重複使用,必備模塊和工具。
- GitHub 地址
- gitee 地址
rest-api-spring-boot-starter 適用於SpringBoot Web API 快速構建讓開發人員快速構建統一規範的業務RestFull API 不在去關心一些繁瑣。重複工作,而是把重點聚焦到業務。
快速開始
-
項目pom中引入依賴
<dependency> <groupId>cn.soboys</groupId> <artifactId>rest-api-spring-boot-starter</artifactId> <version>1.5.0</version> </dependency> -
在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);
} -
構建自定義自己的分頁數據
ResultPage<List<EntityParam>> resultPage=new ResultPage<>(); -
通過
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包裝返回定義pageData的key值如records等你需要自定義
key如msg你可能對應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格式 -
可以通過註解
@NoRestFulApi實現如@GetMapping("/test") @NoRestFulApi public Map chatDialogue() { Map m= new HashMap<>(); m.put("name","judy"); m.put("age",26); return m; } -
通過類掃描去實現
默認會過濾String類型認為是頁面路徑。通過屬性配置文件
include-packages需要統一返回包。exclude-packages不需統一返回的包include-packages: cn.soboys.superaide.controller exclude-packages: xx.xxx.xxxOpenApi文檔生成
已經內置自動支持。
swagger文檔。和最新的OpenApi3文檔。項目啓動後即可訪問。 - swagger-ui.html 文檔。路徑
/swagger-ui.html - 基於spring-doc 文檔UI增強 路徑
/doc.html -
接口文檔屬性信息
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: falseOpenAPI 文檔信息,默認可在此 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); } -
構建自定義自己的分頁數據
ResultPage<List<EntityParam>> resultPage=new ResultPage<>(); - 通過
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包裝返回定義pageData的key值如records等
你需要自定義key如 msg你可能對應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格式
-
可以通過註解
@NoRestFulApi實現如@GetMapping("/test") @NoRestFulApi public Map chatDialogue() { Map m= new HashMap<>(); m.put("name","judy"); m.put("age",26); return m; } - 通過類掃描去實現
默認會過濾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: 請求資源不存在




## 業務斷言
封裝了業務錯誤斷言工具。`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);
}
}
# 常見問題
在使用過程中儘量使用最新版本。我會持續更新更多的內容。 會第一時間發佈在我的公眾號
程序員三時。全網同名
可以關注 公眾號 **程序員三時**。用心分享持續輸出優質內容。希望可以給你帶來一點幫助