构建一个基础SpirngBoot脚手架框架

目录

一、前言

二、搭建SpirngBoot揽手架框架

1.pom.xml

2. Mybatis-plus 代码生成器

3. 常用封装

4. 异常处理

5.  多环境配置

6.  日志配置

7.  Redis配置

三、测试案例

1. Controller

2. Service

3.DTO


一、前言

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

1. 基础项目该包含哪些东西

  1. Swagger在线接口文档
  2. GeneratorUtil 代码生成器
  3. Mybatis-plus 增删改查基本操作
  4. Redis 缓存数据
  5. 接口统一返回
  6. 通用的分页对象
  7. 常用工具类
  8. 全局异常拦截
  9. 错误枚举
  10. 自定义异常
  11. 多环境配置文件
  12. 日志配置
     

2. Swagger接口文档


写接口文档通常是一件比较头疼的事情,然而swagger就用是用来帮我们解决这个问题的。可以在线生成接口文档,并且可以在页面上进行测试

我选择一个界面清爽Swagger版本 (Knife4j),导入接口文档也比较方便

访问地址 :http://127.0.0.1:8082/swagger/doc.html#/home

可以非常清楚的显示,请求数据已经响应数据。当然这一切都需要在代码中进行配置。

注意:接口文档只能在测试/开发环境开启,其它环境请关闭。

常用的Swagger注解

  • @Api用于Controller
  • @ApiOperation用于Controller内的方法。
  • @ApiResponses用于标识接口返回数据的类型。
  • @ApiModel用于标识类的名称
  • @ApiModelProperty用于标识属性的名称


 

二、搭建SpirngBoot揽手架框架

1.pom.xml


4.0.0mall-springboot-swagger2com.zlp1.0.0-SNAPSHOTorg.springframework.bootspring-boot-starter-parent2.1.8.RELEASE 1.8org.springframework.bootspring-boot-starter-jdbcorg.springframework.bootspring-boot-starter-weborg.mybatis.spring.bootmybatis-spring-boot-starter2.1.3com.baomidoumybatis-plus-boot-starter3.3.1.tmpcom.baomidoumybatis-plus-generator3.3.1.tmporg.apache.velocityvelocity-engine-core2.2mysqlmysql-connector-javaruntimeorg.springframework.bootspring-boot-starter-testtestorg.junit.vintagejunit-vintage-enginecom.alibabafastjson1.2.51org.projectlomboklombok1.18.12com.github.pagehelperpagehelper-spring-boot-starter1.2.10com.github.xiaoyminknife4j-spring-boot-starter2.0.4org.springframework.bootspring-boot-starter-validationcn.hutoolhutool-all5.3.5com.fasterxml.jackson.datatypejackson-datatype-jsr3102.9.8org.springframework.bootspring-boot-starter-data-redisorg.springframework.sessionspring-session-data-redisredis.clientsjedis2.10.1com.google.guavaguava20.0com.alibabafastjson1.1.30com.github.dozermapperdozer-core6.2.0org.springframework.bootspring-boot-maven-plugin2.1.18.RELEASE

2. Mybatis-plus 代码生成器

package com.zlp.utils;import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** mybatis-plus 生成器*/
public class GeneratorUtil {//项目路径private static String canonicalPath;private static String projectPath = "mall-springboot-swagger2";//基本包名private static String basePackage = "com.zlp";//作者private static String authorName = "LiPing.Zou";//要生成的表名 JS_ENFOR","JS_DOCprivate static String[] tables = {"sys_user"};//table前缀private static String prefix = "sys_";//数据库类型private static DbType dbType = DbType.MYSQL;//数据库配置四要素private static String driverName = "com.mysql.cj.jdbc.Driver";private static String url = "jdbc:mysql://47.103.20.21:3307/zlp-mall?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8";private static String username = "root";private static String password = "zlpxxxxxx";public static void main(String[] args) {AutoGenerator gen = new AutoGenerator();/*** 获取项目路径*/try {// 默认定位到的当前用户目录("user.dir")(即工程根目录)canonicalPath = System.getProperty("user.dir") + "/"+projectPath;} catch (Exception e) {e.printStackTrace();}/*** 数据库配置*/gen.setDataSource(new DataSourceConfig().setDbType(dbType).setDriverName(driverName).setUrl(url).setUsername(username).setPassword(password).setTypeConvert(new MySqlTypeConvert() {}));/*** 全局配置*/gen.setGlobalConfig(new GlobalConfig().setOutputDir(canonicalPath + "/src/main/java")//输出目录.setFileOverride(true)// 是否覆盖文件.setActiveRecord(true)// 开启 activeRecord 模式.setEnableCache(false)// XML 二级缓存.setBaseResultMap(true)// XML ResultMap.setBaseColumnList(true)// XML columList.setOpen(false)//生成后打开文件夹.setAuthor(authorName).setDateType(DateType.ONLY_DATE)//mysql timestamp由java.util.Date类型映射               .// 自定义文件命名,注意 %s 会自动填充表实体属性!.setMapperName("%sMapper").setXmlName("%sMapper").setServiceName("%sService").setServiceImplName("%sServiceImpl").setControllerName("%sController"));// 自动填充配置TableFill createTime = new TableFill("create_time", FieldFill.INSERT);TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);ArrayList tableFills = new ArrayList<>();tableFills.add(createTime);tableFills.add(updateTime);/*** 策略配置*/gen.setStrategy(new StrategyConfig()// .setCapitalMode(true)// 全局大写命名//.setDbColumnUnderline(true)//全局下划线命名.setTablePrefix(new String[]{prefix})// 此处可以修改为您的表前缀.setNaming(NamingStrategy.underline_to_camel)// 表名生成策略.setInclude(tables) // 需要生成的表.setRestControllerStyle(true).setTableFillList(tableFills)// 乐观锁配置
//                        .setVersionFieldName("version").setRestControllerStyle(true)//.setExclude(new String[]{"test"}) // 排除生成的表// 自定义实体父类// .setSuperEntityClass("com.baomidou.demo.TestEntity")// 自定义实体,公共字段//.setSuperEntityColumns(new String[]{"test_id"})//.setTableFillList(tableFillList)// 自定义 mapper 父类 默认BaseMapper//.setSuperMapperClass("com.baomidou.mybatisplus.mapper.BaseMapper")// 自定义 service 父类 默认IService// .setSuperServiceClass("com.baomidou.demo.TestService")// 自定义 service 实现类父类 默认ServiceImpl// .setSuperServiceImplClass("com.baomidou.demo.TestServiceImpl")// 自定义 controller 父类//.setSuperControllerClass("com.kichun."+packageName+".controller.AbstractController")// 【实体】是否生成字段常量(默认 false)// public static final String ID = "test_id";// .setEntityColumnConstant(true)// 【实体】是否为构建者模型(默认 false)// public User setName(String name) {this.name = name; return this;}// .setEntityBuilderModel(true)// 【实体】是否为lombok模型(默认 false)document.setEntityLombokModel(true)// Boolean类型字段是否移除is前缀处理// .setEntityBooleanColumnRemoveIsPrefix(true)// .setRestControllerStyle(true)// .setControllerMappingHyphenStyle(true));/*** 包配置*/gen.setPackageInfo(new PackageConfig()//.setModuleName("User").setParent(basePackage)// 自定义包路径.setController("controller")// 这里是控制器包名,默认 web.setEntity("entity") // 设置Entity包名,默认entity.setMapper("mapper") // 设置Mapper包名,默认mapper.setService("service") // 设置Service包名,默认service.setServiceImpl("service.impl") // 设置Service Impl包名,默认service.impl.setXml("mapper") // 设置Mapper XML包名,默认mapper.xml);/*** 注入自定义配置*/// 注入自定义配置,可以在 VM 中使用 cfg.abc 设置的值InjectionConfig abc = new InjectionConfig() {@Overridepublic void initMap() {Map map = new HashMap();map.put("abc", this.getConfig().getGlobalConfig().getAuthor() + "-mp");this.setMap(map);}};//自定义文件输出位置(非必须)List fileOutList = new ArrayList();fileOutList.add(new FileOutConfig("/templates/mapper.xml.vm") {@Overridepublic String outputFile(TableInfo tableInfo) {return canonicalPath + "/src/main/resources/mapper/" + tableInfo.getEntityName() + "Mapper.xml";}});abc.setFileOutConfigList(fileOutList);gen.setCfg(abc);/*** 指定模板引擎 默认是VelocityTemplateEngine ,需要引入相关引擎依赖*///gen.setTemplateEngine(new FreemarkerTemplateEngine());/*** 模板配置*/gen.setTemplate(// 关闭默认 xml 生成,调整生成 至 根目录new TemplateConfig().setXml(null)// 自定义模板配置,模板可以参考源码 /mybatis-plus/src/main/resources/template 使用 copy// 至您项目 src/main/resources/template 目录下,模板名称也可自定义如下配置:// .setController("...");// .setEntity("...");// .setMapper("...");// .setXml("...");// .setService("...");// .setServiceImpl("..."););// 执行生成gen.execute();}
}

3. 常用封装

1. 统一返回 Result

/*** 通用返回对象* */
@Data
@ApiModel("通用返回对象格式")
public class Result {@ApiModelProperty("错误码")private long code;@ApiModelProperty("提示信息")private String message;@ApiModelProperty("响应数据")private T data;protected Result() {}protected Result(long code, String message, T data) {this.code = code;this.message = message;this.data = data;}/*** 成功返回结果** @param data 获取的数据*/public static  Result success(T data) {return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);}/*** 成功返回结果** @param data 获取的数据* @param  message 提示信息*/public static  Result success(T data, String message) {return new Result<>(ResultCode.SUCCESS.getCode(), message, data);}/*** 失败返回结果* @param errorCode 错误码*/public static  Result failed(IErrorCode errorCode) {return new Result<>(errorCode.getCode(), errorCode.getMessage(), null);}/*** 失败返回结果* @param message 提示信息*/public static  Result failed(String message) {return new Result(ResultCode.FAILED.getCode(), message, null);}/*** 失败返回结果*/public static  Result failed() {return failed(ResultCode.FAILED);}/*** 参数验证失败返回结果*/public static  Result validateFailed() {return failed(ResultCode.VALIDATE_FAILED);}/*** 参数验证失败返回结果* @param message 提示信息*/public static  Result validateFailed(String message) {return new Result(ResultCode.VALIDATE_FAILED.getCode(), message, null);}/*** 未登录返回结果*/public static  Result unauthorized(T data) {return new Result(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data);}/*** 未授权返回结果*/public static  Result forbidden(T data) {return new Result(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data);}}

2. 抽象查询 PageQuery

@Data
@ApiModel(value = "公共分页数据", description = "分页需要的表单数据")
public class PageQuery implements Serializable {@NotNull(message = "页码为空")@ApiModelProperty(value = "页码 从第一页开始 1")@Min(value = 1, message = "页码输入有误")private Integer pageNumber = 1;@NotNull(message = "每页显示的数量为空")@ApiModelProperty(value = "每页显示的数量 范围在1~100")@Range(min = 1, max = 100, message = "每页显示的数量输入有误")private Integer pageSize = 10;}

3. Page分页数据封装类

@Data
public class Pager {/*** 页码*/private Integer pageNum;/*** 每页显示多少条*/private Integer pageSize;/*** 当前页总条数*/private Integer totalPage;/*** 总记录数*/private Long total;/***  数据集 list*/private List list;public Pager(){}public Pager(Integer pageNum, Integer pageSize, Integer totalPage, Long total, List list) {this.pageNum = pageNum;this.pageSize = pageSize;this.totalPage = totalPage;this.total = total;this.list = list;}/*** 将PageHelper分页后的list转为分页信息*/public static  Pager restPage(List list) {PageInfo pageInfo = new PageInfo<>(list);Pager result = new Pager<>();result.setTotalPage(pageInfo.getPages());result.setPageNum(pageInfo.getPageNum());result.setPageSize(pageInfo.getPageSize());result.setTotal(pageInfo.getTotal());result.setList(pageInfo.getList());return result;}}

4. 异常处理

1. 自定义异常

/*** 自定义异常* @date: 2021/3/11 16:02*/
@Data
@EqualsAndHashCode(callSuper = false)
public class CustomException extends RuntimeException {/*** 状态码*/private final Long code;/*** 方法名称*/private final String method;/*** 自定义异常** @param resultEnum 返回枚举对象* @param method     方法*/public CustomException(ResultCode resultEnum, String method) {super(resultEnum.getMessage());this.code = resultEnum.getCode();this.method = method;}/*** @param code    状态码* @param message 错误信息* @param method  方法*/public CustomException(Long code, String message, String method) {super(message);this.code = code;this.method = method;}}

2.  封装API的错误码

public interface IErrorCode {long getCode();String getMessage();
}public enum ResultCode implements IErrorCode {/*** 通用异常信息*/UNKNOWN_EXCEPTION(100, "未知异常"),FORMAT_ERROR(101, "参数格式错误"),TIME_OUT(102, "超时"),ADD_ERROR(103, "添加失败"),UPDATE_ERROR(104, "更新失败"),DELETE_ERROR(105, "删除失败"),GET_ERROR(106, "查找失败"),ARGUMENT_TYPE_MISMATCH(107, "参数类型不匹配"),REQ_METHOD_NOT_SUPPORT(110,"请求方式不支持"),SUCCESS(200, "操作成功"),FAILED(500, "操作失败"),VALIDATE_FAILED(404, "参数检验失败"),UNAUTHORIZED(401, "暂未登录或token已经过期"),FORBIDDEN(403, "没有相关权限"),/** * 10000 系统模块*/SYS_10001(10001,"该用户不存在"),SYS_10002(10002,"用户名或密码错误"),SYS_10003(10003,"用户ID未找到相对应的用户信息");private long code;private String message;ResultCode(long code, String message) {this.code = code;this.message = message;}/*** 通过状态码获取枚举对象* @param code 状态码* @return 枚举对象*/public static ResultCode getByCode(Long code){for (ResultCode resultCode : ResultCode.values()) {if(code == resultCode.getCode()){return resultCode;}}return null;}public long getCode() {return code;}public String getMessage() {return message;}
}

3. 全局异常拦截

全局异常拦截是使用@ControllerAdvice进行实现,常用的异常拦截配置可以查看 GlobalExceptionHandling。

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {/*** 未知异常* @param e 异常* @return*/          @ResponseStatus(HttpStatus.OK)@ExceptionHandler(Exception.class)public Result handleGlobalException(Exception e) {log.error("全局异常信息 message={},e={}", e.getMessage(), e);return Result.failed(ResultCode.UNKNOWN_EXCEPTION);}/*** validation Exception* @param exception* @return*/@ResponseStatus(HttpStatus.OK)@ExceptionHandler({ MethodArgumentNotValidException.class, BindException.class })public Result handleBodyValidException(MethodArgumentNotValidException exception) {List fieldErrors = exception.getBindingResult().getFieldErrors();log.error("参数绑定异常,ex = {}", fieldErrors.get(0).getDefaultMessage());return Result.failed(fieldErrors.get(0).getDefaultMessage());}/*** 自定义异常*/@ExceptionHandler(value = CustomException.class)public Result processException(CustomException e) {log.error("位置=:{} -> 错误信息=:{}", e.getMethod() ,e.getLocalizedMessage());ResultCode resultCode = ResultCode.getByCode(e.getCode());return Result.failed(Objects.requireNonNull(resultCode));}/*** 参数格式错误*/@ExceptionHandler(MethodArgumentTypeMismatchException.class)public Result methodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {log.error("错误信息{}", e.getLocalizedMessage());return Result.failed(ResultCode.ARGUMENT_TYPE_MISMATCH);}/*** 参数格式错误*/@ExceptionHandler(HttpMessageNotReadableException.class)public Result httpMessageNotReadable(HttpMessageNotReadableException e) {log.error("错误信息:{}", e.getLocalizedMessage());return Result.failed(ResultCode.FORMAT_ERROR);}/*** 请求方式不支持*/@ExceptionHandler(HttpRequestMethodNotSupportedException.class)public Result httpReqMethodNotSupported(HttpRequestMethodNotSupportedException e) {log.error("错误信息:{}", e.getLocalizedMessage());return Result.failed(ResultCode.REQ_METHOD_NOT_SUPPORT);}}

5.  多环境配置

 对于一个项目来讲基本都4有个环境dev,test,uat,prod,对于SpringBoot项目多建立几个配置文件就可以了。然后启动的时候可以通过配置spring.profiles.active 来选择启动的环境。

application.yml

spring:profiles:active: dev

application-dev.yml

server:port: 8082## 项目名称servlet:context-path: /swaggerspring:#数据库连接配置datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://47.103.20.21:3307/zlp-mall?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8username: rootpassword: zlp123456## redis配置redis:database: 0host: 47.103.20.21password: zlp123456port: 6379timeout: 5000#mybatis的相关配置
mybatis-plus:#mapper配置文件mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.zlp.entity#开启驼峰命名configuration:map-underscore-to-camel-case: true## 打印日志太详详细了
#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
## 打印日志
logging:level:com.zlp.mapper: DEBUG

java -jar 方式启动

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

6.  日志配置

logback-spring.xml






logbackinfo${CONSOLE_LOG_PATTERN}UTF-8${log.path}/debug.log%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%nUTF-8 ${log.path}/debug/debug-%d{yyyy-MM-dd}.%i.log100MB15debugACCEPTDENY${log.path}/info.log%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%nUTF-8${log.path}/info/info-%d{yyyy-MM-dd}.%i.log1024MB15infoACCEPTDENY${log.path}/warn.log%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%nUTF-8 ${log.path}/warn/warn-%d{yyyy-MM-dd}.%i.log100MB15warnACCEPTDENY${log.path}/error.log%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%nUTF-8 ${log.path}/error/error-%d{yyyy-MM-dd}.%i.log1024MB15ERRORACCEPTDENY

7.  Redis配置

    可以下载源码,代码有点长

三、测试案例

1. Controller

控制层请求案例包含Page分页返回数据,单个参数请求,JSON对象请求参数

@RestController
@RequestMapping("/user")
@AllArgsConstructor
@Api(value = "user", tags = "用户模块")
public class UserController {private final UserService userService;@ApiOperation( "登录")@PostMapping(value = "login")public Result login(@Valid @RequestBody LoginUserReq loginReq){return Result.success(userService.login(loginReq));}@PostMapping("getUserPage")@ApiOperation("获取用户列表")public Result> getUserPage(@Valid @RequestBody UserQueryReq userQueryReq){return Result.success(userService.getUserPage(userQueryReq));}@GetMapping("getUserInfo")@ApiOperation("获取用户信息")public Result getUserInfo(@RequestParam(value="userId" )  @ApiParam(name="userId",value="用户ID") Long userId){return Result.success(userService.getUserInfo(userId));}}

2. Service

@Overridepublic CacheInfo login(LoginUserReq loginReq) {log.info("login.req loginReq={}", JSON.toJSONString(loginReq));CacheInfo cacheInfo = new CacheInfo();Wrapper wapper = new QueryWrapper<>(new User()).eq("username",loginReq.getUsername()).eq("password",loginReq.getPassword());User user = this.getOne(wapper);if (Objects.isNull(user)){throw new CustomException(ResultCode.SYS_10002, MethodUtil.getLineInfo());}bulidCache(cacheInfo, user);return cacheInfo;}@Overridepublic Pager getUserPage(UserQueryReq userQueryReq) {log.info("getUserPage.req userQueryReq={}", JSON.toJSONString(userQueryReq));IPage page = new Page<>(userQueryReq.getPageNumber(),userQueryReq.getPageSize());QueryWrapper queryWapper = new QueryWrapper<>();IPage pager = this.page(page, queryWapper);if (Objects.nonNull(pager) && CollectionUtil.isNotEmpty(pager.getRecords())) {List records = pager.getRecords();List userRespList = BeanToUtils.entityToList(records, UserResp.class);return new Pager<>(userQueryReq.getPageNumber(),userQueryReq.getPageSize(),(int)pager.getPages(),pager.getTotal(),userRespList);}return new Pager<>();}@Overridepublic UserResp getUserInfo(Long userId) {log.info("getUserInfo.req userId={}", userId);User user = this.getById(userId);if (Objects.isNull(user)){throw new CustomException(ResultCode.SYS_10003, MethodUtil.getLineInfo());}return BeanToUtils.doToDto(user,UserResp.class);}

3.DTO

@Data
@ApiModel(value = "用户返回信息")
public class UserResp implements Serializable {@ApiModelProperty(value = "ID")private Long id;@ApiModelProperty(value = "账号")private String username;@ApiModelProperty(value = "用户名称")private String nickname;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")@ApiModelProperty(value = "创建时间")private Date createTime;@ApiModelProperty(value = "马甲名称")private String roleName;}

Swagger请求实例

请求接口返回错误信息

请求接口返回成功

代码地址
https://gitee.com/gaibianzlp/basic-project.git

参考文档

1. 一个基础的SpringBoot项目该包含哪些


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部