构建一个基础SpirngBoot脚手架框架
目录
一、前言
二、搭建SpirngBoot揽手架框架
1.pom.xml
2. Mybatis-plus 代码生成器
3. 常用封装
4. 异常处理
5. 多环境配置
6. 日志配置
7. Redis配置
三、测试案例
1. Controller
2. Service
3.DTO
一、前言
建立一个全新的项目,或者把旧的庞大的项目,进行拆分成多个项目。在建立新的项目中,经常需要做一些重复的工作,比如说拷贝一下常用的工具类,通用代码等等。所以就可以做一个基础的项目方便使用,在经历新项目的时候,直接在基础项目上进行简单配置就可以开发业务代码了。
1. 基础项目该包含哪些东西
- Swagger在线接口文档
- GeneratorUtil 代码生成器
- Mybatis-plus 增删改查基本操作
- Redis 缓存数据
- 接口统一返回
- 通用的分页对象
- 常用工具类
- 全局异常拦截
- 错误枚举
- 自定义异常
- 多环境配置文件
- 日志配置
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.0 mall-springboot-swagger2 com.zlp 1.0.0-SNAPSHOT org.springframework.boot spring-boot-starter-parent 2.1.8.RELEASE 1.8 org.springframework.boot spring-boot-starter-jdbc org.springframework.boot spring-boot-starter-web org.mybatis.spring.boot mybatis-spring-boot-starter 2.1.3 com.baomidou mybatis-plus-boot-starter 3.3.1.tmp com.baomidou mybatis-plus-generator 3.3.1.tmp org.apache.velocity velocity-engine-core 2.2 mysql mysql-connector-java runtime org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine com.alibaba fastjson 1.2.51 org.projectlombok lombok 1.18.12 com.github.pagehelper pagehelper-spring-boot-starter 1.2.10 com.github.xiaoymin knife4j-spring-boot-starter 2.0.4 org.springframework.boot spring-boot-starter-validation cn.hutool hutool-all 5.3.5 com.fasterxml.jackson.datatype jackson-datatype-jsr310 2.9.8 org.springframework.boot spring-boot-starter-data-redis org.springframework.session spring-session-data-redis redis.clients jedis 2.10.1 com.google.guava guava 20.0 com.alibaba fastjson 1.1.30 com.github.dozermapper dozer-core 6.2.0 org.springframework.boot spring-boot-maven-plugin 2.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
logback info ${CONSOLE_LOG_PATTERN} UTF-8 ${log.path}/debug.log %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n UTF-8 ${log.path}/debug/debug-%d{yyyy-MM-dd}.%i.log 100MB 15 debug ACCEPT DENY ${log.path}/info.log %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n UTF-8 ${log.path}/info/info-%d{yyyy-MM-dd}.%i.log 1024MB 15 info ACCEPT DENY ${log.path}/warn.log %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n UTF-8 ${log.path}/warn/warn-%d{yyyy-MM-dd}.%i.log 100MB 15 warn ACCEPT DENY ${log.path}/error.log %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n UTF-8 ${log.path}/error/error-%d{yyyy-MM-dd}.%i.log 1024MB 15 ERROR ACCEPT DENY
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项目该包含哪些
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
