一個(gè) SpringBoot 項(xiàng)目該包含哪些?
作者:不一樣的科技宅?
juejin.im/post/6844904083942277127
前言
建立一個(gè)全新的項(xiàng)目,或者把舊的龐大的項(xiàng)目,進(jìn)行拆分成多個(gè)項(xiàng)目。在建立新的項(xiàng)目中,經(jīng)常需要做一些重復(fù)的工作,比如說(shuō)拷貝一下常用的工具類,通用代碼等等。
所以就可以做一個(gè)基礎(chǔ)的項(xiàng)目方便使用,在經(jīng)歷新項(xiàng)目的時(shí)候,直接在基礎(chǔ)項(xiàng)目上進(jìn)行簡(jiǎn)單配置就可以開(kāi)發(fā)業(yè)務(wù)代碼了。
基礎(chǔ)項(xiàng)目該包含哪些東西。
Swagger在線接口文檔。
CodeGenerator 代碼生成器。
統(tǒng)一返回。
通用的分頁(yè)對(duì)象。
常用工具類。
全局異常攔截。
錯(cuò)誤枚舉。
自定義異常。
多環(huán)境配置文件。
Maven多環(huán)境配置。
日志配置。
JenkinsFile。
?可以在評(píng)論區(qū)進(jìn)行補(bǔ)充
?
Swagger
寫接口文檔通常是一件比較頭疼的事情,然而swagger就用是用來(lái)幫我們解決這個(gè)問(wèn)題的。可以在線生成接口文檔,并且可以在頁(yè)面上進(jìn)行測(cè)試。
可以非常清楚的顯示,請(qǐng)求數(shù)據(jù)已經(jīng)響應(yīng)數(shù)據(jù)。當(dāng)然這一切都需要在代碼中進(jìn)行配置。
「注意的點(diǎn):接口文檔只能在測(cè)試/開(kāi)發(fā)環(huán)境開(kāi)啟,其他環(huán)境請(qǐng)關(guān)閉。」
常用的Swagger注解
@Api用于Controller@ApiOperation用于Controller內(nèi)的方法。@ApiResponses用于標(biāo)識(shí)接口返回?cái)?shù)據(jù)的類型。@ApiModel用于標(biāo)識(shí)類的名稱@ApiModelProperty用于標(biāo)識(shí)屬性的名稱
案例
@RestController??
@Api(tags?=?"用戶")??
@AllArgsConstructor??
@RequestMapping("/user")??
public?class?UserController?{??
??
????private?IUserService?userService;??
??
????/**??
?????*?獲取用戶列表??
?????*?@param?listUserForm?表單數(shù)據(jù)??
?????*?@return?用戶列表??
?????*/??
????@ApiOperation("獲取用戶列表")??
????@GetMapping("/listUser")??
????@ApiResponses(??
????????????@ApiResponse(code?=?200,?message?=?"操作成功",?response?=?UserVo.class)??
????)??
????public?ResultVo?listUser(@Validated?ListUserForm?listUserForm){??
????????return?ResultVoUtil.success(userService.listUser(listUserForm));??
????}??
??
}??
?
@Data??
@ApiModel("獲取用戶列表需要的表單數(shù)據(jù)")??
@EqualsAndHashCode(callSuper?=?false)??
public?class?ListUserForm?extends?PageForm?{??
??
????/**??
?????*?用戶狀態(tài)??
?????*/??
????@ApiModelProperty("用戶狀態(tài)")??
????@NotEmpty(message?=?"用戶狀態(tài)不能為空")??
????@Range(min?=??-1?,?max?=?1?,?message?=?"用戶狀態(tài)有誤")??
????private?String?status;??
??
}??
對(duì)應(yīng)的swagger的配置可以查看基礎(chǔ)項(xiàng)目?jī)?nèi)的SwaggerConfiguration.java.
CodeGenerator代碼生成器。
mybatis_plus代碼生成器可以幫我們生成entity,service,serviceImpl,mapper,mapper.xml。省去了建立一大堆實(shí)體類的麻煩。
由于配置太長(zhǎng)這里就不貼出來(lái)了,對(duì)應(yīng)的CodeGenerator的配置可以查看基礎(chǔ)項(xiàng)目?jī)?nèi)的CodeGenerator.java.
常用的封裝
統(tǒng)一返回 ResultVo
將所有的接口的響應(yīng)數(shù)據(jù)的格式進(jìn)行統(tǒng)一。
@Data??
@ApiModel("固定返回格式")??
public?class?ResultVo?{??
??
????/**??
?????*?錯(cuò)誤碼??
?????*/??
????@ApiModelProperty("錯(cuò)誤碼")??
????private?Integer?code;??
??
????/**??
?????*?提示信息??
?????*/??
????@ApiModelProperty("提示信息")??
????private?String?message;??
??
????/**??
?????*?具體的內(nèi)容??
?????*/??
????@ApiModelProperty("響應(yīng)數(shù)據(jù)")??
????private?Object?data;??
??
}??
抽象表單 BaseForm
public?abstract?class?BaseForm?{??
??
????/**??
?????*?獲取實(shí)例??
?????*?@return?返回實(shí)體類??
?????*/??
????public?abstract?T?buildEntity();??
??
}??
有小伙伴可能有疑問(wèn)了,這個(gè)類有啥用呢。先看一下,下面的代碼。
/**??
?*?添加用戶??
?*?@param?userForm?表單數(shù)據(jù)??
?*?@return?true?或者?false??
?*/??
@Override??
public?boolean?addUser(AddUserForm?userForm)?{??
????User?user?=?new?User();??
????user.setNickname(userForm.getNickname());??
????user.setBirthday(userForm.getBirthday());??
????user.setUsername(userForm.getUsername());??
????user.setPassword(userForm.getPassword());??
????return?save(user);??
}??
重構(gòu)一下,感覺(jué)清爽了一些。
/**??
?*?添加用戶??
?*?@param?userForm?表單數(shù)據(jù)??
?*?@return?true?或者?false??
?*/??
@Override??
public?boolean?addUser(AddUserForm?userForm)?{??
????User?user?=?new?User();??
????BeanUtils.copyProperties(this,user);??
????return?save(user);??
}??
?
使用BaseForm進(jìn)行重構(gòu) AddUserForm 繼承 BaseForm并重寫buildEntity
@Data??
@EqualsAndHashCode(callSuper?=?false)??
public?class?AddUserForm?extends?BaseForm?{??
??
????/**??
?????*?昵稱??
?????*/??
????private?String?nickname;??
??
????/**??
?????*?生日??
?????*/??
????private?Date?birthday;??
??
????/**??
?????*?用戶名??
?????*/??
????private?String?username;??
??
????/**??
?????*?密碼??
?????*/??
????private?String?password;??
??
????/**??
?????*?構(gòu)造實(shí)體??
?????*?@return?實(shí)體對(duì)象??
?????*/??
????@Override??
????public?User?buildEntity()?{??
????????User?user?=?new?User();??
????????BeanUtils.copyProperties(this,user);??
????????return?user;??
????}??
}??
/**??
?*?添加用戶??
?*?@param?userForm?表單數(shù)據(jù)??
?*?@return?true?或者?false??
?*/??
@Override??
public?boolean?addUser(AddUserForm?userForm)?{??
????return?save(userForm.buildEntity());??
}??
上面的代碼有沒(méi)有種似曾相識(shí)的感覺(jué),很多情況都是將接受到的參數(shù),轉(zhuǎn)變成對(duì)應(yīng)的實(shí)體類然后「保存」或者「更新」。
所以對(duì)于這類的form可以繼承baseform并實(shí)現(xiàn)buildEntity()這樣可以更加符合面向?qū)ο螅?code style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">service不需要關(guān)心form如何轉(zhuǎn)變成entity,只需要在使用的時(shí)候調(diào)用buildEntity()即可,尤其是在form?->?entity相對(duì)復(fù)雜的時(shí)候,這樣做可以減少service內(nèi)的代碼。讓代碼邏輯看起來(lái)更加清晰。
通用的分頁(yè)對(duì)象
涉及到查詢的時(shí)候,絕大多數(shù)都需要用到分頁(yè),所以說(shuō)封裝分頁(yè)對(duì)象就很有必要。可以注意下?PageForm.calcCurrent()、PageVo.setCurrentAndSize()、PageVo.setTotal()這個(gè)幾個(gè)方法。
PageForm
@Data??
@ApiModel(value?=?"分頁(yè)數(shù)據(jù)",?description?=?"分頁(yè)需要的表單數(shù)據(jù)")??
public?class?PageForm>{??
??
????/**??
?????*?頁(yè)碼??
?????*/??
????@ApiModelProperty(value?=?"頁(yè)碼?從第一頁(yè)開(kāi)始?1")??
????@Min(value?=?1,?message?=?"頁(yè)碼輸入有誤")??
????private?Integer?current;??
??
????/**??
?????*?每頁(yè)顯示的數(shù)量??
?????*/??
????@ApiModelProperty(value?=?"每頁(yè)顯示的數(shù)量?范圍在1~100")??
????@Range(min?=?1,?max?=?100,?message?=?"每頁(yè)顯示的數(shù)量輸入有誤")??
????private?Integer?size;??
??
????/**??
?????*?計(jì)算當(dāng)前頁(yè)?,方便mysql?進(jìn)行分頁(yè)查詢??
?????*?@return?返回?pageForm??
?????*/??
????@ApiModelProperty(hidden?=?true)??
????public?T?calcCurrent(){??
????????current?=?(current?-?1?)?*?size;??
????????return?(T)?this;??
????}??
}??
PageVo
@Data??
public?class?PageVo?{??
????/**??
?????*?分頁(yè)數(shù)據(jù)??
?????*/??
????@ApiModelProperty(value?=?"分頁(yè)數(shù)據(jù)")??
????private?List?records;??
????/**??
?????*?總條數(shù)??
?????*/??
????@ApiModelProperty(value?=?"總條數(shù)")??
????private?Integer?total;??
??
????/**??
?????*?總頁(yè)數(shù)??
?????*/??
????@ApiModelProperty(value?=?"總頁(yè)數(shù)")??
????private?Integer?pages;??
??
????/**??
?????*?當(dāng)前頁(yè)??
?????*/??
????@ApiModelProperty(value?=?"當(dāng)前頁(yè)")??
????private?Integer?current;??
??
????/**??
?????*?查詢數(shù)量??
?????*/??
????@ApiModelProperty(value?=?"查詢數(shù)量")??
????private?Integer?size;??
??
????/**??
?????*?設(shè)置當(dāng)前頁(yè)和每頁(yè)顯示的數(shù)量??
?????*?@param?pageForm?分頁(yè)表單??
?????*?@return?返回分頁(yè)信息??
?????*/??
????@ApiModelProperty(hidden?=?true)??
????public?PageVo?setCurrentAndSize(PageForm>?pageForm){??
????????BeanUtils.copyProperties(pageForm,this);??
????????return?this;??
????}??
??
????/**??
?????*?設(shè)置總記錄數(shù)??
?????*?@param?total?總記錄數(shù)??
?????*/??
????@ApiModelProperty(hidden?=?true)??
????public?void?setTotal(Integer?total)?{??
????????this.total?=?total;??
????????this.setPages(this.total?%?this.size?>?0???this.total?/?this.size?+?1?:?this.total?/?this.size);??
????}??
}??
案例
ListUserForm
@Data??
@ApiModel("獲取用戶列表需要的表單數(shù)據(jù)")??
@EqualsAndHashCode(callSuper?=?false)??
public?class?ListUserForm?extends?PageForm?{??
??
????/**??
?????*?用戶狀態(tài)??
?????*/??
????@ApiModelProperty("用戶狀態(tài)")??
????@NotEmpty(message?=?"用戶狀態(tài)不能為空")??
????@Range(min?=??-1?,?max?=?1?,?message?=?"用戶狀態(tài)有誤")??
????private?String?status;??
??
}??
UserServiceImpl
/**??
?*?獲取用戶列表??
?*?@param?listUserForm?表單數(shù)據(jù)??
?*?@return?用戶列表??
?*/??
@Override??
public?PageVo?listUser(ListUserForm?listUserForm)?{??
????PageVo?pageVo?=?new?PageVo().setCurrentAndSize(listUserForm);??
????pageVo.setTotal(countUser(listUserForm.getStatus()));??
????pageVo.setRecords(userMapper.listUser(listUserForm.calcCurrent()));??
????return?pageVo;??
}??
??
/**??
?*?獲取用戶數(shù)量??
?*?@param?status?狀態(tài)??
?*?@return?用戶數(shù)量??
?*/??
private?Integer?countUser(String?status){??
????return?count(new?QueryWrapper().eq("status",status));??
}??
UserController
/**??
?*?獲取用戶列表??
?*?@param?listUserForm?表單數(shù)據(jù)??
?*?@return?用戶列表??
?*/??
@ApiOperation("獲取用戶列表")??
@GetMapping("/listUser")??
@ApiResponses(??
????????@ApiResponse(code?=?200,?message?=?"操作成功",?response?=?UserVo.class)??
)??
public?ResultVo?listUser(@Validated?ListUserForm?listUserForm){??
????return?ResultVoUtil.success(userService.listUser(listUserForm));??
}??
注意的點(diǎn)
PageVo在實(shí)例化的時(shí)候需要設(shè)置「當(dāng)前頁(yè)」和「每頁(yè)顯示的數(shù)量」?可以調(diào)用
setCurrentAndSize()完成。進(jìn)行分頁(yè)查詢的時(shí)候,需要計(jì)算偏移量。
listUserForm.calcCurrent()
為什么要計(jì)算偏移量呢?
假如查詢第1頁(yè)每頁(yè)顯示10條記錄,前端傳遞過(guò)來(lái)的參數(shù)是
current=1&&size=10,這個(gè)時(shí)候limit 1,10沒(méi)有問(wèn)題。假如查詢第2頁(yè)每頁(yè)顯示10條記錄,前端傳遞過(guò)來(lái)的參數(shù)是
current=2&&size=10,這個(gè)時(shí)候limit 2,10就有問(wèn)題,實(shí)際應(yīng)該是limit 10,10。calcCurrent()的作用就是如此。
為什么不用MybatisPlus自帶的分頁(yè)插件呢?
?自帶的分頁(yè)查詢?cè)诖罅繑?shù)據(jù)下,會(huì)出現(xiàn)性能問(wèn)題。
?
常用工具類
常用工具類可以根據(jù)自己的開(kāi)發(fā)習(xí)慣引入。
異常處理
異常處理的大致流程主要如下。
異常信息拋出 ->?
ControllerAdvice?進(jìn)行捕獲格式化輸出內(nèi)容手動(dòng)拋出
CustomException并傳入ReulstEnum?——> 進(jìn)行捕獲錯(cuò)誤信息輸出錯(cuò)誤信息。
自定義異常
@Data??
@EqualsAndHashCode(callSuper?=?false)??
public?class?CustomException?extends?RuntimeException?{??
??
????/**??
?????*?狀態(tài)碼??
?????*/??
????private?final?Integer?code;??
??
????/**??
?????*?方法名稱??
?????*/??
????private?final?String?method;??
??
??
????/**??
?????*?自定義異常??
?????*??
?????*?@param?resultEnum?返回枚舉對(duì)象??
?????*?@param?method?????方法??
?????*/??
????public?CustomException(ResultEnum?resultEnum,?String?method)?{??
????????super(resultEnum.getMsg());??
????????this.code?=?resultEnum.getCode();??
????????this.method?=?method;??
????}??
??
????/**??
?????*?@param?code????狀態(tài)碼??
?????*?@param?message?錯(cuò)誤信息??
?????*?@param?method??方法??
?????*/??
????public?CustomException(Integer?code,?String?message,?String?method)?{??
????????super(message);??
????????this.code?=?code;??
????????this.method?=?method;??
????}??
??
}??
錯(cuò)誤信息枚舉
根據(jù)業(yè)務(wù)進(jìn)行添加。
@Getter??
public?enum?ResultEnum?{??
??
????/**??
?????*?未知異常??
?????*/??
????UNKNOWN_EXCEPTION(100,?"未知異常"),??
??
????/**??
?????*?添加失敗??
?????*/??
????ADD_ERROR(103,?"添加失敗"),??
??
????/**??
?????*?更新失敗??
?????*/??
????UPDATE_ERROR(104,?"更新失敗"),??
??
????/**??
?????*?刪除失敗??
?????*/??
????DELETE_ERROR(105,?"刪除失敗"),??
??
????/**??
?????*?查找失敗??
?????*/??
????GET_ERROR(106,?"查找失敗"),??
??
????;??
??
????private?Integer?code;??
??
????private?String?msg;??
??
????ResultEnum(Integer?code,?String?msg)?{??
????????this.code?=?code;??
????????this.msg?=?msg;??
????}??
??
????/**??
?????*?通過(guò)狀態(tài)碼獲取枚舉對(duì)象??
?????*?@param?code?狀態(tài)碼??
?????*?@return?枚舉對(duì)象??
?????*/??
????public?static?ResultEnum?getByCode(int?code){??
????????for?(ResultEnum?resultEnum?:?ResultEnum.values())?{??
????????????if(code?==?resultEnum.getCode()){??
????????????????return?resultEnum;??
????????????}??
????????}??
????????return?null;??
????}??
??
}??
全局異常攔截
全局異常攔截是使用@ControllerAdvice進(jìn)行實(shí)現(xiàn),常用的異常攔截配置可以查看 GlobalExceptionHandling。
@Slf4j??
@RestControllerAdvice??
public?class?GlobalExceptionHandling?{??
??
????/**??
?????*?自定義異常??
?????*/??
????@ExceptionHandler(value?=?CustomException.class)??
????public?ResultVo?processException(CustomException?e)?{??
????????log.error("位置:{}?->?錯(cuò)誤信息:{}",?e.getMethod()?,e.getLocalizedMessage());??
????????return?ResultVoUtil.error(Objects.requireNonNull(ResultEnum.getByCode(e.getCode())));??
????}??
??
????/**??
?????*?通用異常??
?????*/??
????@ResponseStatus(HttpStatus.OK)??
????@ExceptionHandler(Exception.class)??
????public?ResultVo?exception(Exception?e)?{??
????????e.printStackTrace();??
????????return?ResultVoUtil.error(ResultEnum.UNKNOWN_EXCEPTION);??
????}??
}??
案例
Controller
/**??
?*?刪除用戶??
?*?@param?id?用戶編號(hào)??
?*?@return?成功或者失敗??
?*/??
@ApiOperation("刪除用戶")??
@DeleteMapping("/deleteUser/{id}")??
public?ResultVo?deleteUser(@PathVariable("id")?String?id){??
????userService.deleteUser(id);??
????return?ResultVoUtil.success();??
}??
Service
/**??
?*?刪除用戶??
?*?@param?id?id??
?*/??
@Override??
public?void?deleteUser(String?id)?{??
????//?如果刪除失敗拋出異常。?--?演示而已不推薦這樣干??
????if(!removeById(id)){??
????????throw?new?CustomException(ResultEnum.DELETE_ERROR,?MethodUtil.getLineInfo());??
????}??
}??
結(jié)果


「將報(bào)錯(cuò)代碼所在的文件第多少行都打印出來(lái)。方便排查。」
注意的點(diǎn)
所有手動(dòng)拋出的錯(cuò)誤信息,都應(yīng)在錯(cuò)誤信息枚舉ResultEnum進(jìn)行統(tǒng)一維護(hù)。不同的業(yè)務(wù)使用不同的錯(cuò)誤碼。方便在報(bào)錯(cuò)時(shí)進(jìn)行分辨。快速定位問(wèn)題。
多環(huán)境配置
SpringBoot多環(huán)境配置
對(duì)于一個(gè)項(xiàng)目來(lái)講基本都4有個(gè)環(huán)境dev,test,pre,prod,對(duì)于SpringBoot項(xiàng)目多建立幾個(gè)配置文件就可以了。
然后啟動(dòng)的時(shí)候可以通過(guò)配置spring.profiles.active?來(lái)選擇啟動(dòng)的環(huán)境。

java?-jar?BasicProject.jar?--spring.profiles.active=prod??
Maven多環(huán)境配置
假如想在打包的時(shí)候動(dòng)態(tài)指定環(huán)境,這個(gè)時(shí)候就需要借助Maven的xml來(lái)實(shí)現(xiàn)。
配置XML
??
??
??????
??????????
????????dev ??
??????????
????????????true ??
???????? ??
??????????
????????????dev ??
???????? ??
???? ??
??????
??????????
????????test ??
??????????
????????????test ??
???????? ??
???? ??
??????
??????????
????????pre ??
??????????
????????????pre ??
???????? ??
???? ??
??????
??????????
????????prod ??
??????????
????????????prod ??
???????? ??
???? ??
??
更改application.yml
spring:??
??profiles:??
????#?選擇環(huán)境??
????active:?@activatedProperties@??
使用案例
mvn?clean?package?-P?prod??
mvn?clean?package?-P?pre??
mvn?clean?package?-P?test??
打包完可以解壓開(kāi)查看application.yml?會(huì)發(fā)現(xiàn)spring.profiles.active=@activatedProperties@?發(fā)生了改變。
日志配置
采用logback日志配置,參考
https://gitee.com/huangxunhui/basic_project/blob/master/src/main/resources/logback-spring.xml
JenkinsFile
JenkinsFile肯定顧名思義是給jenkins用的。主要是配置項(xiàng)目根據(jù)如何進(jìn)行構(gòu)建并發(fā)布到不同的環(huán)境。需要去了解pipeline語(yǔ)法,以及如何配置jenkins。
