1. 程式人生 > 實用技巧 >SpringBoot專案骨架

SpringBoot專案骨架

前言

建立一個全新的專案,或者把舊的龐大的專案,進行拆分成多個專案。在建立新的專案中,經常需要做一些重複的工作,比如說拷貝一下常用的工具類,通用程式碼等等。

所以就可以做一個基礎的專案方便使用,在經歷新專案的時候,直接在基礎專案上進行簡單配置就可以開發業務程式碼了。

基礎專案該包含哪些東西。

  • Swagger線上介面文件。

  • CodeGenerator 程式碼生成器。

  • 統一返回。

  • 通用的分頁物件。

  • 常用工具類。

  • 全域性異常攔截。

  • 錯誤列舉。

  • 自定義異常。

  • 多環境配置檔案。

  • Maven多環境配置。

  • 日誌配置。

  • JenkinsFile。

可以在評論區進行補充


Swagger

寫介面文件通常是一件比較頭疼的事情,然而swagger就用是用來幫我們解決這個問題的。可以線上生成介面文件,並且可以在頁面上進行測試。

可以非常清楚的顯示,請求資料已經響應資料。當然這一切都需要在程式碼中進行配置。

「注意的點:介面文件只能在測試/開發環境開啟,其他環境請關閉。」

常用的Swagger註解

  • @Api用於Controller

  • @ApiOperation用於Controller內的方法。

  • @ApiResponses用於標識介面返回資料的型別。

  • @ApiModel用於標識類的名稱

  • @ApiModelProperty用於標識屬性的名稱

案例

@RestController
@Api(tags="使用者")
@AllArgsConstructor
@RequestMapping("/user")
publicclassUserController{

privateIUserServiceuserService;

/**
*獲取使用者列表
*@paramlistUserForm表單資料
*@return使用者列表
*/
@ApiOperation("獲取使用者列表")
@GetMapping("/listUser")
@ApiResponses(
@ApiResponse(code=200,message="操作成功",response=UserVo.class)
)
publicResultVolistUser(@ValidatedListUserFormlistUserForm){
returnResultVoUtil.success(userService.listUser(listUserForm));
}

}

@Data
@ApiModel("獲取使用者列表需要的表單資料")
@EqualsAndHashCode(callSuper=false)
publicclassListUserFormextendsPageForm<ListUserForm>{

/**
*使用者狀態
*/
@ApiModelProperty("使用者狀態")
@NotEmpty(message="使用者狀態不能為空")
@Range(min=-1,max=1,message="使用者狀態有誤")
privateStringstatus;

}

對應的swagger的配置可以檢視基礎專案內的SwaggerConfiguration.java.

CodeGenerator程式碼生成器。

mybatis_plus程式碼生成器可以幫我們生成entity,service,serviceImpl,mapper,mapper.xml。省去了建立一大堆實體類的麻煩。

由於配置太長這裡就不貼出來了,對應的CodeGenerator的配置可以檢視基礎專案內的CodeGenerator.java.


常用的封裝

統一返回 ResultVo

將所有的介面的響應資料的格式進行統一。

@Data
@ApiModel("固定返回格式")
publicclassResultVo{

/**
*錯誤碼
*/
@ApiModelProperty("錯誤碼")
privateIntegercode;

/**
*提示資訊
*/
@ApiModelProperty("提示資訊")
privateStringmessage;

/**
*具體的內容
*/
@ApiModelProperty("響應資料")
privateObjectdata;

}

抽象表單 BaseForm

publicabstractclassBaseForm<T>{

/**
*獲取例項
*@return返回實體類
*/
publicabstractTbuildEntity();

}

有小夥伴可能有疑問了,這個類有啥用呢。先看一下,下面的程式碼。

/**
*新增使用者
*@paramuserForm表單資料
*@returntrue或者false
*/
@Override
publicbooleanaddUser(AddUserFormuserForm){
Useruser=newUser();
user.setNickname(userForm.getNickname());
user.setBirthday(userForm.getBirthday());
user.setUsername(userForm.getUsername());
user.setPassword(userForm.getPassword());
returnsave(user);
}

重構一下,感覺清爽了一些。

/**
*新增使用者
*@paramuserForm表單資料
*@returntrue或者false
*/
@Override
publicbooleanaddUser(AddUserFormuserForm){
Useruser=newUser();
BeanUtils.copyProperties(this,user);
returnsave(user);
}

使用BaseForm進行重構 AddUserForm 繼承 BaseForm並重寫buildEntity

@Data
@EqualsAndHashCode(callSuper=false)
publicclassAddUserFormextendsBaseForm<User>{

/**
*暱稱
*/
privateStringnickname;

/**
*生日
*/
privateDatebirthday;

/**
*使用者名稱
*/
privateStringusername;

/**
*密碼
*/
privateStringpassword;

/**
*構造實體
*@return實體物件
*/
@Override
publicUserbuildEntity(){
Useruser=newUser();
BeanUtils.copyProperties(this,user);
returnuser;
}
}
/**
*新增使用者
*@paramuserForm表單資料
*@returntrue或者false
*/
@Override
publicbooleanaddUser(AddUserFormuserForm){
returnsave(userForm.buildEntity());
}

上面的程式碼有沒有種似曾相識的感覺,很多情況都是將接受到的引數,轉變成對應的實體類然後「儲存」或者「更新」

所以對於這類的form可以繼承baseform並實現buildEntity()這樣可以更加符合面向物件,service不需要關心form如何轉變成entity,只需要在使用的時候呼叫buildEntity()即可,尤其是在form->entity相對複雜的時候,這樣做可以減少service內的程式碼。讓程式碼邏輯看起來更加清晰。


通用的分頁物件

涉及到查詢的時候,絕大多數都需要用到分頁,所以說封裝分頁物件就很有必要。可以注意下PageForm.calcCurrent()PageVo.setCurrentAndSize()PageVo.setTotal()這個幾個方法。

PageForm

@Data
@ApiModel(value="分頁資料",description="分頁需要的表單資料")
publicclassPageForm<TextendsPageForm<?>>{

/**
*頁碼
*/
@ApiModelProperty(value="頁碼從第一頁開始1")
@Min(value=1,message="頁碼輸入有誤")
privateIntegercurrent;

/**
*每頁顯示的數量
*/
@ApiModelProperty(value="每頁顯示的數量範圍在1~100")
@Range(min=1,max=100,message="每頁顯示的數量輸入有誤")
privateIntegersize;

/**
*計算當前頁,方便mysql進行分頁查詢
*@return返回pageForm
*/
@ApiModelProperty(hidden=true)
publicTcalcCurrent(){
current=(current-1)*size;
return(T)this;
}
}

PageVo

@Data
publicclassPageVo<T>{
/**
*分頁資料
*/
@ApiModelProperty(value="分頁資料")
privateList<T>records;
/**
*總條數
*/
@ApiModelProperty(value="總條數")
privateIntegertotal;

/**
*總頁數
*/
@ApiModelProperty(value="總頁數")
privateIntegerpages;

/**
*當前頁
*/
@ApiModelProperty(value="當前頁")
privateIntegercurrent;

/**
*查詢數量
*/
@ApiModelProperty(value="查詢數量")
privateIntegersize;

/**
*設定當前頁和每頁顯示的數量
*@parampageForm分頁表單
*@return返回分頁資訊
*/
@ApiModelProperty(hidden=true)
publicPageVo<T>setCurrentAndSize(PageForm<?>pageForm){
BeanUtils.copyProperties(pageForm,this);
returnthis;
}

/**
*設定總記錄數
*@paramtotal總記錄數
*/
@ApiModelProperty(hidden=true)
publicvoidsetTotal(Integertotal){
this.total=total;
this.setPages(this.total%this.size>0?this.total/this.size+1:this.total/this.size);
}
}

案例

ListUserForm
@Data
@ApiModel("獲取使用者列表需要的表單資料")
@EqualsAndHashCode(callSuper=false)
publicclassListUserFormextendsPageForm<ListUserForm>{

/**
*使用者狀態
*/
@ApiModelProperty("使用者狀態")
@NotEmpty(message="使用者狀態不能為空")
@Range(min=-1,max=1,message="使用者狀態有誤")
privateStringstatus;

}
UserServiceImpl
/**
*獲取使用者列表
*@paramlistUserForm表單資料
*@return使用者列表
*/
@Override
publicPageVo<UserVo>listUser(ListUserFormlistUserForm){
PageVo<UserVo>pageVo=newPageVo<UserVo>().setCurrentAndSize(listUserForm);
pageVo.setTotal(countUser(listUserForm.getStatus()));
pageVo.setRecords(userMapper.listUser(listUserForm.calcCurrent()));
returnpageVo;
}

/**
*獲取使用者數量
*@paramstatus狀態
*@return使用者數量
*/
privateIntegercountUser(Stringstatus){
returncount(newQueryWrapper<User>().eq("status",status));
}
UserController
/**
*獲取使用者列表
*@paramlistUserForm表單資料
*@return使用者列表
*/
@ApiOperation("獲取使用者列表")
@GetMapping("/listUser")
@ApiResponses(
@ApiResponse(code=200,message="操作成功",response=UserVo.class)
)
publicResultVolistUser(@ValidatedListUserFormlistUserForm){
returnResultVoUtil.success(userService.listUser(listUserForm));
}

注意的點
  • PageVo在例項化的時候需要設定「當前頁」「每頁顯示的數量」可以呼叫setCurrentAndSize()完成。

  • 進行分頁查詢的時候,需要計算偏移量。listUserForm.calcCurrent()

為什麼要計算偏移量呢?

  • 假如查詢第1頁每頁顯示10條記錄,前端傳遞過來的引數是current=1&amp;&amp;size=10,這個時候limit 1,10沒有問題。

  • 假如查詢第2頁每頁顯示10條記錄,前端傳遞過來的引數是current=2&amp;&amp;size=10,這個時候limit 2,10就有問題,實際應該是limit 10,10calcCurrent()的作用就是如此

為什麼不用MybatisPlus自帶的分頁外掛呢?

自帶的分頁查詢在大量資料下,會出現效能問題。

常用工具類

常用工具類可以根據自己的開發習慣引入。


異常處理

異常處理的大致流程主要如下。

  • 異常資訊丟擲 ->ControllerAdvice進行捕獲格式化輸出內容

  • 手動丟擲CustomException並傳入ReulstEnum——> 進行捕獲錯誤資訊輸出錯誤資訊。

自定義異常

@Data
@EqualsAndHashCode(callSuper=false)
publicclassCustomExceptionextendsRuntimeException{

/**
*狀態碼
*/
privatefinalIntegercode;

/**
*方法名稱
*/
privatefinalStringmethod;


/**
*自定義異常
*
*@paramresultEnum返回列舉物件
*@parammethod方法
*/
publicCustomException(ResultEnumresultEnum,Stringmethod){
super(resultEnum.getMsg());
this.code=resultEnum.getCode();
this.method=method;
}

/**
*@paramcode狀態碼
*@parammessage錯誤資訊
*@parammethod方法
*/
publicCustomException(Integercode,Stringmessage,Stringmethod){
super(message);
this.code=code;
this.method=method;
}

}

錯誤資訊列舉

根據業務進行新增。

@Getter
publicenumResultEnum{

/**
*未知異常
*/
UNKNOWN_EXCEPTION(100,"未知異常"),

/**
*新增失敗
*/
ADD_ERROR(103,"新增失敗"),

/**
*更新失敗
*/
UPDATE_ERROR(104,"更新失敗"),

/**
*刪除失敗
*/
DELETE_ERROR(105,"刪除失敗"),

/**
*查詢失敗
*/
GET_ERROR(106,"查詢失敗"),

;

privateIntegercode;

privateStringmsg;

ResultEnum(Integercode,Stringmsg){
this.code=code;
this.msg=msg;
}

/**
*通過狀態碼獲取列舉物件
*@paramcode狀態碼
*@return列舉物件
*/
publicstaticResultEnumgetByCode(intcode){
for(ResultEnumresultEnum:ResultEnum.values()){
if(code==resultEnum.getCode()){
returnresultEnum;
}
}
returnnull;
}

}

全域性異常攔截

全域性異常攔截是使用@ControllerAdvice進行實現,常用的異常攔截配置可以檢視 GlobalExceptionHandling。

@Slf4j
@RestControllerAdvice
publicclassGlobalExceptionHandling{

/**
*自定義異常
*/
@ExceptionHandler(value=CustomException.class)
publicResultVoprocessException(CustomExceptione){
log.error("位置:{}->錯誤資訊:{}",e.getMethod(),e.getLocalizedMessage());
returnResultVoUtil.error(Objects.requireNonNull(ResultEnum.getByCode(e.getCode())));
}

/**
*通用異常
*/
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(Exception.class)
publicResultVoexception(Exceptione){
e.printStackTrace();
returnResultVoUtil.error(ResultEnum.UNKNOWN_EXCEPTION);
}
}

案例

Controller
/**
*刪除使用者
*@paramid使用者編號
*@return成功或者失敗
*/
@ApiOperation("刪除使用者")
@DeleteMapping("/deleteUser/{id}")
publicResultVodeleteUser(@PathVariable("id")Stringid){
userService.deleteUser(id);
returnResultVoUtil.success();
}
Service
/**
*刪除使用者
*@paramidid
*/
@Override
publicvoiddeleteUser(Stringid){
//如果刪除失敗丟擲異常。--演示而已不推薦這樣幹
if(!removeById(id)){
thrownewCustomException(ResultEnum.DELETE_ERROR,MethodUtil.getLineInfo());
}
}
結果

「將報錯程式碼所在的檔案第多少行都打印出來。方便排查。」

注意的點

所有手動丟擲的錯誤資訊,都應在錯誤資訊列舉ResultEnum進行統一維護。不同的業務使用不同的錯誤碼。方便在報錯時進行分辨。快速定位問題。


多環境配置

SpringBoot多環境配置

對於一個專案來講基本都4有個環境dev,test,pre,prod,對於SpringBoot專案多建立幾個配置檔案就可以了。

然後啟動的時候可以通過配置spring.profiles.active來選擇啟動的環境。

java-jarBasicProject.jar--spring.profiles.active=prod

Maven多環境配置

假如想在打包的時候動態指定環境,這個時候就需要藉助Maven的xml來實現。

配置XML
<!--配置環境-->
<profiles>
<profile>
<!--開發-->
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<activatedProperties>dev</activatedProperties>
</properties>
</profile>
<profile>
<!--測試-->
<id>test</id>
<properties>
<activatedProperties>test</activatedProperties>
</properties>
</profile>
<profile>
<!--準生產-->
<id>pre</id>
<properties>
<activatedProperties>pre</activatedProperties>
</properties>
</profile>
<profile>
<!--生產-->
<id>prod</id>
<properties>
<activatedProperties>prod</activatedProperties>
</properties>
</profile>
</profiles>
更改application.yml
spring:
profiles:
#選擇環境
active:@activatedProperties@
使用案例
mvncleanpackage-Pprod
mvncleanpackage-Ppre
mvncleanpackage-Ptest

打包完可以解壓開檢視application.yml會發現spring.profiles.active=@activatedProperties@發生了改變。


日誌配置

採用logback日誌配置,參考

https://gitee.com/huangxunhui/basic_project/blob/master/src/main/resources/logback-spring.xml

JenkinsFile

JenkinsFile肯定顧名思義是給jenkins用的。主要是配置專案根據如何進行構建併發布到不同的環境。需要去了解pipeline語法,以及如何配置jenkins。

結尾

如果覺得對你有幫助,可以隨手點個在看哦,謝謝。