建立一個(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)單配置就可以開發(fā)業(yè)務(wù)代碼了颓哮。
基礎(chǔ)項(xiàng)目該包含哪些東西:
Swagger在線接口文檔。
CodeGenerator 代碼生成器伤极。
統(tǒng)一返回哨坪。
通用的分頁(yè)對(duì)象乍楚。
常用工具類徒溪。
全局異常攔截。
錯(cuò)誤枚舉牵舱。
自定義異常。
多環(huán)境配置文件礁凡。
Maven多環(huán)境配置顷牌。
日志配置。
JenkinsFile罪裹。
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è)試/開發(fā)環(huán)境開啟,其他環(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 {
privateIUserServiceuserService;
@ApiOperation("獲取用戶列表")
@GetMapping("/listUser")
@ApiResponses(
@ApiResponse(code =?200, message =?"操作成功", response = UserVo.class)
)
publicResultVolistUser(@ValidatedListUserForm listUserForm){
returnResultVoUtil.success(userService.listUser(listUserForm));
}
}
@Data
@ApiModel("獲取用戶列表需要的表單數(shù)據(jù)")
@EqualsAndHashCode(callSuper = false)
public class ListUserForm extends PageForm {
@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 {
@ApiModelProperty("錯(cuò)誤碼")
private Integer code;
@ApiModelProperty("提示信息")
private String message;
@ApiModelProperty("響應(yīng)數(shù)據(jù)")
private Object data;
}
抽象表單 BaseForm
publicabstractclassBaseForm?{
publicabstractT?buildEntity();
}
有小伙伴可能有疑問(wèn)了作儿,這個(gè)類有啥用呢洛二。先看一下,下面的代碼攻锰。
@Override
publicbooleanaddUser(AddUserForm userForm){
User user =?newUser();
user.setNickname(userForm.getNickname());
user.setBirthday(userForm.getBirthday());
user.setUsername(userForm.getUsername());
user.setPassword(userForm.getPassword());
returnsave(user);
}
重構(gòu)一下晾嘶,感覺清爽了一些。
@Override
publicbooleanaddUser(AddUserForm userForm){
User user =?newUser();
BeanUtils.copyProperties(this,user);
returnsave(user);
}
使用BaseForm進(jìn)行重構(gòu) AddUserForm 繼承 BaseForm并重寫buildEntity
@Data
@EqualsAndHashCode(callSuper =?false)
publicclassAddUserForm?extendsBaseForm {
privateStringnickname;
privateDatebirthday;
privateStringusername;
privateStringpassword;
@Override
publicUser buildEntity() {
User user =?newUser();
BeanUtils.copyProperties(this,user);
returnuser;
}
}
@Override
publicbooleanaddUser(AddUserForm userForm){
returnsave(userForm.buildEntity());
}
上面的代碼有沒有種似曾相識(shí)的感覺娶吞,很多情況都是將接受到的參數(shù)垒迂,轉(zhuǎn)變成對(duì)應(yīng)的實(shí)體類然后保存或者更新。所以對(duì)于這類的form可以繼承baseform并實(shí)現(xiàn)buildEntity()這樣可以更加符合面向?qū)ο蠖噬撸瑂ervice不需要關(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ù)")
publicclassPageForm>{
@ApiModelProperty(value =?"頁(yè)碼 從第一頁(yè)開始 1")
@Min(value =?1, message =?"頁(yè)碼輸入有誤")
privateInteger current;
@ApiModelProperty(value =?"每頁(yè)顯示的數(shù)量 范圍在1~100")
@Range(min =?1, max =?100, message =?"每頁(yè)顯示的數(shù)量輸入有誤")
privateInteger size;
@ApiModelProperty(hidden =?true)
publicT?calcCurrent(){
current = (current -?1) * size;
return(T)?this;
}
}
PageVo
@Data
publicclassPageVo?{
@ApiModelProperty(value =?"分頁(yè)數(shù)據(jù)")
privateList records;
@ApiModelProperty(value =?"總條數(shù)")
privateInteger total;
@ApiModelProperty(value =?"總頁(yè)數(shù)")
privateInteger pages;
@ApiModelProperty(value =?"當(dāng)前頁(yè)")
privateInteger current;
@ApiModelProperty(value =?"查詢數(shù)量")
privateInteger size;
@ApiModelProperty(hidden =?true)
publicPageVo?setCurrentAndSize(PageForm<?> pageForm){
BeanUtils.copyProperties(pageForm,this);
returnthis;
}
@ApiModelProperty(hidden =?true)
publicvoidsetTotal(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 {
@ApiModelProperty("用戶狀態(tài)")
@NotEmpty(message =?"用戶狀態(tài)不能為空")
@Range(min = -1, max =?1, message =?"用戶狀態(tài)有誤")
private String status;
}
UserServiceImpl
@Override
publicPageVo?listUser(ListUserForm listUserForm){
PageVo pageVo =?newPageVo().setCurrentAndSize(listUserForm);
pageVo.setTotal(countUser(listUserForm.getStatus()));
pageVo.setRecords(userMapper.listUser(listUserForm.calcCurrent()));
returnpageVo;
}
privateInteger?countUser(String status){
returncount(newQueryWrapper().eq("status",status));
}
UserController
@ApiOperation("獲取用戶列表")
@GetMapping("/listUser")
@ApiResponses(
@ApiResponse(code =?200, message =?"操作成功", response = UserVo.class)
)
public ResultVo listUser(@ValidatedListUserForm listUserForm){
returnResultVoUtil.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沒有問(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ù)自己的開發(fā)習(xí)慣引入。
異常處理
異常處理的大致流程主要如下匠题。
異常信息拋出 ->?ControllerAdvice?進(jìn)行捕獲格式化輸出內(nèi)容
手動(dòng)拋出CustomException并傳入ReulstEnum?——> 進(jìn)行捕獲錯(cuò)誤信息輸出錯(cuò)誤信息拯坟。
自定義異常
@Data
@EqualsAndHashCode(callSuper =?false)
publicclassCustomExceptionextendsRuntimeException{
privatefinalInteger code;
privatefinalString method;
publicCustomException(ResultEnum resultEnum, String method){
super(resultEnum.getMsg());
this.code = resultEnum.getCode();
this.method = method;
}
publicCustomException(Integer code, String message, String method){
super(message);
this.code = code;
this.method = method;
}
}
錯(cuò)誤信息枚舉
根據(jù)業(yè)務(wù)進(jìn)行添加。
@Getter
publicenumResultEnum {
UNKNOWN_EXCEPTION(100,?"未知異常"),
ADD_ERROR(103,?"添加失敗"),
UPDATE_ERROR(104,?"更新失敗"),
DELETE_ERROR(105,?"刪除失敗"),
GET_ERROR(106,?"查找失敗"),
;
privateInteger code;
privateStringmsg;
ResultEnum(Integer code,?Stringmsg) {
this.code = code;
this.msg = msg;
}
publicstaticResultEnum getByCode(int code){
for(ResultEnum resultEnum : ResultEnum.values()) {
if(code == resultEnum.getCode()){
returnresultEnum;
}
}
returnnull;
}
}
全局異常攔截
全局異常攔截是使用@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());
returnResultVoUtil.error(Objects.requireNonNull(ResultEnum.getByCode(e.getCode())));
}
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(Exception.class)
public ResultVo exception(Exception e) {
e.printStackTrace();
returnResultVoUtil.error(ResultEnum.UNKNOWN_EXCEPTION);
}
}
案例
Controller
@ApiOperation("刪除用戶")
@DeleteMapping("/deleteUser/{id}")
public ResultVo deleteUser(@PathVariable("id") String id){
userService.deleteUser(id);
returnResultVoUtil.success();
}
Service
@Override
publicvoiddeleteUser(Stringid) {
if(!removeById(id)){
thrownewCustomException(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
打包完可以解壓開查看application.yml?會(huì)發(fā)現(xiàn)spring.profiles.active=@activatedProperties@?發(fā)生了改變洁桌。
日志配置
采用logback日志配置
JenkinsFile
JenkinsFile肯定顧名思義是給jenkins用的渴丸。主要是配置項(xiàng)目根據(jù)如何進(jìn)行構(gòu)建并發(fā)布到不同的環(huán)境。需要去了解pipeline語(yǔ)法另凌,以及如何配置jenkins谱轨。
JenkinsFileDemo?https://gitee.com/huangxunhui/basic_project/blob/master/Jenkinsfile