金樱子

首页 » 常识 » 常识 » 一份SpringBoot项目搭建模
TUhjnbcbe - 2021/4/22 21:45:00

点击上方“芋道源码”,选择“设为星标”

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天8:55更新文章,每天掉亿点点头发...

源码精品专栏

原创

Java超神之路,很肝~

中文详细注释的开源项目

RPC框架Dubbo源码解析

网络应用框架Netty源码解析

消息中间件RocketMQ源码解析

数据库中间件Sharding-JDBC和MyCAT源码解析

作业调度中间件Elastic-Job源码解析

分布式事务中间件TCC-Transaction源码解析

Eureka和Hystrix源码解析

Java并发源码

来源:juejin.im/post/

前言基础项目该包含哪些东西。SwaggerCodeGenerator代码生成器。常用的封装通用的分页对象常用工具类异常处理多环境配置日志配置JenkinsFile代码地址结尾前言

建立一个全新的项目,或者把旧的庞大的项目,进行拆分成多个项目。在建立新的项目中,经常需要做一些重复的工作,比如说拷贝一下常用的工具类,通用代码等等。所以就可以做一个基础的项目方便使用,在经历新项目的时候,直接在基础项目上进行简单配置就可以开发业务代码了。

基础项目该包含哪些东西。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=,message="操作成功",response=UserVo.class))publicResultVolistUser(

ValidatedListUserFormlistUserForm){returnResultVoUtil.success(userService.listUser(listUserForm));}}

Data

ApiModel("获取用户列表需要的表单数据")

EqualsAndHashCode(callSuper=false)publicclassListUserFormextendsPageFormListUserForm{/***用户状态*/

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

publicabstractclassBaseFormT{/***获取实例*

return返回实体类*/publicabstractTbuildEntity();}

有小伙伴可能有疑问了,这个类有啥用呢。先看一下,下面的代码。

/***添加用户*

paramuserForm表单数据*

returntrue或者false*/

OverridepublicbooleanaddUser(AddUserFormuserForm){Useruser=newUser();user.setNickname(userForm.getNickname());user.setBirthday(userForm.getBirthday());user.setUsername(userForm.getUsername());user.setPassword(userForm.getPassword());returnsave(user);}

重构一下,感觉清爽了一些。

/***添加用户*

paramuserForm表单数据*

returntrue或者false*/

OverridepublicbooleanaddUser(AddUserFormuserForm){Useruser=newUser();BeanUtils.copyProperties(this,user);returnsave(user);}

使用BaseForm进行重构AddUserForm继承BaseForm并重写buildEntity

Data

EqualsAndHashCode(callSuper=false)publicclassAddUserFormextendsBaseFormUser{/***昵称*/privateStringnickname;/***生日*/privateDatebirthday;/***用户名*/privateStringusername;/***密码*/privateStringpassword;/***构造实体*

return实体对象*/

OverridepublicUserbuildEntity(){Useruser=newUser();BeanUtils.copyProperties(this,user);returnuser;}}/***添加用户*

paramuserForm表单数据*

returntrue或者false*/

OverridepublicbooleanaddUser(AddUserFormuserForm){returnsave(userForm.buildEntity());}

上面的代码有没有种似曾相识的感觉,很多情况都是将接受到的参数,转变成对应的实体类然后保存或者更新。所以对于这类的form可以继承baseform并实现buildEntity()这样可以更加符合面向对象,service不需要关心form如何转变成entity,只需要在使用的时候调用buildEntity()即可,尤其是在form-entity相对复杂的时候,这样做可以减少service内的代码。让代码逻辑看起来更加清晰。

通用的分页对象

涉及到查询的时候,绝大多数都需要用到分页,所以说封装分页对象就很有必要。可以注意下PageForm.calcCurrent()、PageVo.setCurrentAndSize()、PageVo.setTotal()这个几个方法。

PageFormData

ApiModel(value="分页数据",description="分页需要的表单数据")publicclassPageFormTextendsPageForm?{/***页码*/

ApiModelProperty(value="页码从第一页开始1")

Min(value=1,message="页码输入有误")privateIntegercurrent;/***每页显示的数量*/

ApiModelProperty(value="每页显示的数量范围在1~")

Range(min=1,max=,message="每页显示的数量输入有误")privateIntegersize;/***计算当前页,方便mysql进行分页查询*

return返回pageForm*/

ApiModelProperty(hidden=true)publicTcalcCurrent(){current=(current-1)*size;return(T)this;}}PageVoDatapublicclassPageVoT{/***分页数据*/

ApiModelProperty(value="分页数据")privateListTrecords;/***总条数*/

ApiModelProperty(value="总条数")privateIntegertotal;/***总页数*/

ApiModelProperty(value="总页数")privateIntegerpages;/***当前页*/

ApiModelProperty(value="当前页")privateIntegercurrent;/***查询数量*/

ApiModelProperty(value="查询数量")privateIntegersize;/***设置当前页和每页显示的数量*

parampageForm分页表单*

return返回分页信息*/

ApiModelProperty(hidden=true)publicPageVoTsetCurrentAndSize(PageForm?pageForm){BeanUtils.copyProperties(pageForm,this);returnthis;}/***设置总记录数*

paramtotal总记录数*/

ApiModelProperty(hidden=true)publicvoidsetTotal(Integertotal){this.total=total;this.setPages(this.total%this.size0?this.total/this.size+1:this.total/this.size);}}案例ListUserFormData

ApiModel("获取用户列表需要的表单数据")

EqualsAndHashCode(callSuper=false)publicclassListUserFormextendsPageFormListUserForm{/***用户状态*/

ApiModelProperty("用户状态")

NotEmpty(message="用户状态不能为空")

Range(min=-1,max=1,message="用户状态有误")privateStringstatus;}UserServiceImpl

/***获取用户列表*

paramlistUserForm表单数据*

return用户列表*/

OverridepublicPageVoUserVolistUser(ListUserFormlistUserForm){PageVoUserVopageVo=newPageVoUserVo().setCurrentAndSize(listUserForm);pageVo.setTotal(countUser(listUserForm.getStatus()));pageVo.setRecords(userMapper.listUser(listUserForm.calcCurrent()));returnpageVo;}/***获取用户数量*

paramstatus状态*

return用户数量*/privateIntegercountUser(Stringstatus){returncount(newQueryWrapperUser().eq("status",status));}UserController

/***获取用户列表*

paramlistUserForm表单数据*

return用户列表*/

ApiOperation("获取用户列表")

GetMapping("/listUser")

ApiResponses(

ApiResponse(code=,message="操作成功",response=UserVo.class))publicResultVolistUser(

ValidatedListUserFormlistUserForm){returnResultVoUtil.success(userService.listUser(listUserForm));}注意的点PageVo在实例化的时候需要设置当前页和每页显示的数量可以调用setCurrentAndSize()完成。进行分页查询的时候,需要计算偏移量。listUserForm.calcCurrent()

为什么要计算偏移量呢?

假如查询第1页每页显示10条记录,前端传递过来的参数是current=1size=10,这个时候limit1,10没有问题。假如查询第2页每页显示10条记录,前端传递过来的参数是current=2size=10,这个时候limit2,10就有问题,实际应该是limit10,10。calcCurrent()的作用就是如此。

为什么不用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;}}错误信息枚举

根据业务进行添加。

GetterpublicenumResultEnum{/***未知异常*/UNKNOWN_EXCEPTION(,"未知异常"),/***添加失败*/ADD_ERROR(,"添加失败"),/***更新失败*/UPDATE_ERROR(,"更新失败"),/***删除失败*/DELETE_ERROR(,"删除失败"),/***查找失败*/GET_ERROR(,"查找失败"),;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

RestControllerAdvicepublicclassGlobalExceptionHandling{/***自定义异常*/

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*/

OverridepublicvoiddeleteUser(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=prodMaven多环境配置

假如想在打包的时候动态指定环境,这个时候就需要借助Maven的xml来实现。

配置XML

!--配置环境--profilesprofile!--开发--iddev/idactivationactiveByDefaulttrue/activeByDefault/activationpropertiesactivatedPropertiesdev/activatedProperties/properties/profileprofile!--测试--idtest/idpropertiesactivatedPropertiestest/activatedProperties/properties/profileprofile!--准生产--idpre/idpropertiesactivatedPropertiespre/activatedProperties/properties/profileprofile!--生产--idprod/idpropertiesactivatedPropertiesprod/activatedProperties/properties/profile/profiles更改application.yml

spring:profiles:#选择环境active:

activatedProperties

使用案例

mvncleanpackage-Pprodmvncleanpackage-Ppremvncleanpackage-Ptest

打包完可以解压开查看application.yml会发现spring.profiles.active=

activatedProperties

发生了改变。

日志配置

采用logback日志配置

JenkinsFile

JenkinsFile肯定顾名思义是给jenkins用的。主要是配置项目根据如何进行构建并发布到不同的环境。需要去了解pipeline语法,以及如何配置jenkins。JenkinsFileDemo

代码地址首先点击右下方在看,再长按下方
1
查看完整版本: 一份SpringBoot项目搭建模