一、概述

二、技术点说明

2.1、项目结构

凡auto包或文件件,均会被代码生成器再次生成二修改

1、model层  

  

  po:BasePO基础类,统一了数据库的基础字段【数据库必须添加如下,与mybatis-plus生成器匹配】

    strategy.setSuperEntityColumns("id","delflag","status","remark","created_datetime","updated_datetime");

  po:auto数据库表生成的原始类 【不允许修改】,如需扩展可以继承、复合扩展

2、dao层

  

  mapper:auto数据库映射代码生成器自动生成,不允许修改,如需扩展需自定义在auto外,如 AccountBalanceExtMapper

  同时修改xml,注意xml内部配置正确的 namespace ,: <mapper namespace="com.aaa.test.mapper.AccountBalanceExtMapper">

    

3、service层

  

  auto自动生成文件【不允许修改】

  base:IBaseService、BaseServiceImpl 【基础项目不用继承,使用IService即可】

  自定义实现需要在auto同级编写即可。

4、web层

  

  主要在controller包下,auto自动生成mapper,更具代码生成器生成,注意权限控制。base 基础,如后续,重新代码生成可不生成web层代码。

  其他包主要是统一输出使用。参看下面统一输出。

2.2、springboot与mybatisplus整合

  【代码生成器、分页、逻辑删除】参看 代码

  代码生成:test-demo-generator

    

1、重写 基于继承BaseController的模板。框架已提供。

2、代码生成器文件MybatisPlusGenerator

package com.aaa.test.generator;

import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType; import java.util.ArrayList;
import java.util.List; public class MybatisPlusGenerator { // region 待修改配置 // 数据库信息 jdbc:mysql://localhost:3358/test?useUnicode=true&useSSL=false&characterEncoding=utf8
private static final String DATABASE_DRIVER_NAME = "com.mysql.cj.jdbc.Driver"; //com.mysql.jdbc.Driver
private static final String DATABASE_HOST = "localhost";
private static final String DATABASE_PORT = "3358";
private static final String DATABASE = "test";
private static final String DATABASE_USERNAME = "root";
private static final String DATABASE_PASSWORD = "123456";
private static final String DATABASE_URL = "jdbc:mysql://" + DATABASE_HOST + ":" + DATABASE_PORT + "/" + DATABASE
+ "?useUnicode=true&useSSL=false&characterEncoding=UTF8&serverTimezone=UTC"; //endregion // 所有文件开启 覆盖配置
// 指定表名配置,不指定代表所有
public static void main(String[] args) {
String[] tableNames = null;//new String[]{"account_balance"};
generator(
//new LayerType[]{LayerType.entity,LayerType.mapperxml},
null,
tableNames);
System.out.println("==========ok=========");
} //包名 com.github.bjlhx15
private static final String packageName = "com.aaa.test"; //代码生成路径 相对当前的路径
private static final String baseProjectPath = System.getProperty("user.dir"); //代码注释作者
private static final String author = "<a>https://www.cnblogs.com/bjlhx/</a>"; // 数据库信息 jdbc:mysql://localhost:3358/test?useUnicode=true&useSSL=false&characterEncoding=utf8
private static final String DATABASE_DRIVER_NAME = "com.mysql.cj.jdbc.Driver"; //com.mysql.jdbc.Driver
private static final String DATABASE_HOST = "localhost";
private static final String DATABASE_PORT = "3358";
private static final String DATABASE = "test";
private static final String DATABASE_USERNAME = "root";
private static final String DATABASE_PASSWORD = "123456";
private static final String DATABASE_URL = "jdbc:mysql://" + DATABASE_HOST + ":" + DATABASE_PORT + "/" + DATABASE
+ "?useUnicode=true&useSSL=false&characterEncoding=UTF8&serverTimezone=UTC"; // 生成的分层代码
enum LayerType {
entity("/test-demo-model"),
mapper("/test-demo-dao"),
mapperxml("/test-demo-web"),
service("/test-demo-service"),
web("/test-demo-web"),
; LayerType(String path) {
this.path = path;
} /**
* 路径
*/
private String path;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
} /**
* 逆向工程 生成分层代码,默认覆盖模式
*
* @param layerTypes 要生成的分层名 null 全部,指定的话只生成指定的包
* @param tableNames 要生成的表名
*/
private static void generator(LayerType[] layerTypes, String[] tableNames) {
if (layerTypes == null) {
layerTypes = LayerType.values();
}
for (LayerType layerType : layerTypes) {
String projectPath = baseProjectPath + layerType.getPath();
// 全局配置
GlobalConfig gc = getGlobalConfig(projectPath);
// 数据源配置
DataSourceConfig dsc = getDataSourceConfig();
// 包配置
PackageConfig pc = getPackageConfig();
// xml配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() { }
};
if (layerType.equals(LayerType.mapperxml)) {
// 自定义配置
cfg = getInjectionConfig(projectPath);
}
// 策略配置**(个性化定制)**
StrategyConfig strategy = getStrategyConfig(tableNames, pc); //代码生成器
new AutoGenerator()
.setGlobalConfig(gc)//全局
.setDataSource(dsc)//数据源
.setPackageInfo(pc)//包配置
.setCfg(cfg)//自定义 xml
//.setTemplate(new TemplateConfig().setXml(null))//自定义 xml 此处设置为null,就不会再java下创建xml的文件夹了
.setTemplate(getTemplateConfig(layerType))
.setStrategy(strategy) //个性化策略
.setTemplateEngine(new FreemarkerTemplateEngine())//模板引擎
.execute();//执行
}
} // 策略配置**(个性化定制)**
private static StrategyConfig getStrategyConfig(String[] tableNames, PackageConfig pc) {
StrategyConfig strategy = new StrategyConfig();
//数据库表映射到实体的命名策略,该处下划线转驼峰命名
strategy.setNaming(NamingStrategy.underline_to_camel);
//数据库表映射到实体的命名策略,该处下划线转驼峰命名
strategy.setColumnNaming(NamingStrategy.underline_to_camel); if (tableNames != null && tableNames.length > 0) {
strategy.setInclude(tableNames); //被扫描的表名 需要包含的表名,允许正则表达式(与exclude二选一配置)
} //实体类自动继承Entity,不需要也可以
strategy.setSuperEntityClass(String.format("%s.po.BasePO", packageName));
//【实体】是否为lombok模型(默认 false)
strategy.setEntityLombokModel(false);
// 写于父类中的公共字段
strategy.setSuperEntityColumns("id","delflag","status","remark","created_datetime","updated_datetime"); // 自定义 service 父类
//strategy.setSuperServiceClass(String.format("%s.service.BaseService", packageName));
// 自定义 service 实现类父类
//strategy.setSuperServiceImplClass(String.format("%s.service.impl.BaseServiceImpl", packageName)); // 控制层是否使用Rest风格 生成 @RestController 控制器
strategy.setRestControllerStyle(true);
//控制层自动继承父类BaseController,不需要也可以 BaseController
strategy.setSuperControllerClass(String.format("%s.controller.base.BaseController", packageName));
strategy.setControllerMappingHyphenStyle(true); //根据表名来建对应的类名,如果你的表名没有什么下划线,比如test,那么你就可以取消这一步
//strategy.setTablePrefix(pc.getModuleName() + "_");
return strategy;
} //xml 定制化
private static InjectionConfig getInjectionConfig(String projectPath) {
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
//xml 配置
List<FileOutConfig> focList = new ArrayList();
focList.add(new FileOutConfig("/templates/mapper.xml.ftl") {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输入文件名称================================模块名(自己设置)
return projectPath + "/src/main/resources/mapper/"
+ "/autoxml/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
return cfg;
} // 数据源配置
private static DataSourceConfig getDataSourceConfig() {
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl(DATABASE_URL)
// dsc.setSchemaName("public");
.setDriverName(DATABASE_DRIVER_NAME)
.setUsername(DATABASE_USERNAME)
.setPassword(DATABASE_PASSWORD)
.setTypeConvert(new MySqlTypeConvert() {
@Override
public DbColumnType processTypeConvert(GlobalConfig globalConfig, String fieldType) {
//将数据库中timestamp转换成date
if (fieldType.toLowerCase().contains("timestamp")) {
return DbColumnType.DATE;
}
return (DbColumnType) super.processTypeConvert(globalConfig, fieldType);
}
});
return dsc;
} // 包配置
private static PackageConfig getPackageConfig() {
PackageConfig pc = new PackageConfig();
//pc.setModuleName(moduleName)//自定义模块名 account
pc.setParent(packageName)//《====包名(自己手动设置)com.lihongxu.test 与 module组合
.setEntity("po.auto")//Entity包名
.setMapper("mapper.auto")//mapper包名
.setService("service.auto")
.setController("controller.auto");
return pc;
} // 全局配置
private static GlobalConfig getGlobalConfig(String projectPath) {
GlobalConfig gc = new GlobalConfig();
gc.setOutputDir(projectPath + "/src/main/java");//生成文件的输出目录
gc.setOpen(false);//是否打开输出目录 默认true
gc.setFileOverride(true);// 是否覆盖已有文件 默认false
gc.setActiveRecord(false);//开启 ActiveRecord 模式 默认false gc.setAuthor(author);// 作者名
gc.setEnableCache(false);// XML 二级缓存
gc.setBaseResultMap(true);//开启 BaseResultMap 默认false
gc.setBaseColumnList(true);//开启 baseColumnList 默认false
gc.setServiceName("I%sService"); //service接口 命名方式 例如:%sBusiness 生成 UserBusiness
gc.setSwagger2(true); //实体属性 Swagger2 注解 // 自定义文件命名,注意 %s 会自动填充表实体属性! 使用定制化生成了
// gc.setMapperName("%sMapper");
// gc.setXmlName("%sMapper");
// gc.setServiceName("%sService");
// gc.setServiceImplName("%sServiceImpl");
// gc.setControllerName("%sController");
return gc;
} //生成分层代码
private static TemplateConfig getTemplateConfig(LayerType layerType) {
TemplateConfig templateConfig = new TemplateConfig();
switch (layerType) {
case entity:
templateConfig.setEntity(new TemplateConfig().getEntity(false))
.setMapper(null)
.setXml(null)
.setService(null)
.setServiceImpl(null)
.setController(null);
break;
case mapper:
templateConfig.setEntity(null)
.setMapper(new TemplateConfig().getMapper())
.setXml(null)
.setService(null)
.setServiceImpl(null)
.setController(null);
break;
case mapperxml:
templateConfig.setEntity(null)
.setMapper(null)
.setXml(null)
.setService(null)
.setServiceImpl(null)
.setController(null);
break;
case service:
templateConfig.setEntity(null)
.setMapper(null)
.setXml(null)
.setService(new TemplateConfig().getService())
.setServiceImpl(new TemplateConfig().getServiceImpl())
.setController(null);
break;
case web:
templateConfig.setEntity(null)
.setMapper(null)
.setXml(null)
.setService(null)
.setServiceImpl(null)
// .setController(new TemplateConfig().getController());
.setController("/templates/basecontroller.java");
break;
default:
throw new IllegalArgumentException("参数匹配错误,请检查");
}
return templateConfig;
}
}

逻辑删除

  1、父类BasePO

    //使用@TableLogic注解实现逻辑删除
@TableLogic
@TableField(value = "delflag", exist = true)
protected Integer delflag = 0;

  2、配置文件

mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0

2.3、log4j2整合

  参看 代码

2.4、统一响应【统一异常处理】

1、设置统一响应输出类 BaseResponse

package com.aaa.test.response;

import com.aaa.test.enums.ErrorCodeEnum;

/**
* 统一返回结果
*/
public class BaseResponse<T> {
/**
* 返回的data
* */
private T data; /**
* 错误码
* */
private Integer errorCode; /**
* 错误信息
* */
private String errorMsg; /**
* 是否成功
* */
private boolean success = false; /**
* 出现异常的构造函数
* */
public BaseResponse(ErrorCodeEnum errorCodeEnum) {
this.errorCode = errorCodeEnum.getErrorCode();
this.errorMsg = errorCodeEnum.getErrorMsg();
} /**
* 成功返回的结果
* */
public BaseResponse(T data) {
success = true;
this.data = data;
} /**
* 成功返回的结果
* */
public BaseResponse(boolean success,ErrorCodeEnum errorCodeEnum,T data) {
this.success = success;
this.data = data;
this.errorCode = errorCodeEnum.getErrorCode();
this.errorMsg = errorCodeEnum.getErrorMsg();
} public static <T> BaseResponse success(T data) {
return new BaseResponse(data);
}
public static <T> BaseResponse success(ErrorCodeEnum errorCodeEnum) {
return new BaseResponse(true,errorCodeEnum,null);
} public static BaseResponse fail(ErrorCodeEnum errorCodeEnum) {
return new BaseResponse(errorCodeEnum);
} public static BaseResponse fail() {
return new BaseResponse(ErrorCodeEnum.result_exception);
} public T getData() {
return data;
} public void setData(T data) {
this.data = data;
} public Integer getErrorCode() {
return errorCode;
} public void setErrorCode(Integer errorCode) {
this.errorCode = errorCode;
} public String getErrorMsg() {
return errorMsg;
} public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
} public boolean isSuccess() {
return success;
} public void setSuccess(boolean success) {
this.success = success;
} @Override
public String toString() {
return "BaseResponse{" +
"data=" + data +
", errorCode='" + errorCode + '\'' +
", errorMsg='" + errorMsg + '\'' +
", success=" + success +
'}';
}
}

其中用到了错误码 ErrorCodeEnum

package com.aaa.test.enums;

/**
* 错误代码枚举类
*/
public enum ErrorCodeEnum {
// 成功类响应
success(200000, "成功"),
no_response_data(200001, "没有返回数据"), //请求类响应码
Param_does_not_exist(400001, "查找参数不存在"),
Param_does_not_correct(400002, "所传参数格式不正确"),
HttpMediaTypeNotSupportedException(400003, "不支持的Content-type类型"), result_exception(600000, "待处理服务端异常"), ; ErrorCodeEnum(Integer errorCode, String errorMsg) {
this.errorCode = errorCode;
this.errorMsg = errorMsg;
} /**
* 错误码
*/
private Integer errorCode; /**
* 错误信息
*/
private String errorMsg; public Integer getErrorCode() {
return errorCode;
} public void setErrorCode(Integer errorCode) {
this.errorCode = errorCode;
} public String getErrorMsg() {
return errorMsg;
} public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
}

技术需求

  1、统一响应类支持静态的成功失败方法

  2、错误码,设计应为6位数值类型,其中200000 类别为成功类型,400000为请求类型码,500000为服务端异常,其他大于 600000码为客户自定义码,

2、Controller编写

  支持controller多种响应参数

    常见类型String,int,Integer、Map、Object、单个类等

    支持使用上述:BaseResponse 包装响应类

    支持使用:ResponseEntity接收类型

  通过统一响应处理返回统一:BaseResponse格式

3、统一响应处理方案

  编写RestControllerAdvice,对响应,以及异常统一处理

/**
* 统一返回结果异常处理类
*/
@RestControllerAdvice
public class ResponseResultBodyAdvice implements ResponseBodyAdvice<Object> {
private final Logger log = LoggerFactory.getLogger(ResponseResultBodyAdvice.class);
private static final Class<? extends Annotation> ANNOTATION_TYPE = ResponseBody.class; /**
* 判断类或者方法是否使用了 @ResponseResultBody
*/
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// swagger2 过滤
if (returnType.getMethod().getDeclaringClass().getName().contains("springfox.documentation.swagger")) {
//因为使用了 RestControllerAdvice 与swagger2 而swagger2也会响应 responsebody 故会被此拦截
// 如果想使用swagger2生效 需要不被拦截处理 或者自定义一个 ResponseBody 注解来处理
return false;
}
return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ANNOTATION_TYPE) || returnType.hasMethodAnnotation(ANNOTATION_TYPE);
} /**
* 当类或者方法使用了 @ResponseResultBody 就会调用这个方法
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 避免已经返回基础类型 多次转换
if (body instanceof BaseResponse) {
return body;
}
return BaseResponse.success(body);
} /**
* 提供对标准Spring MVC异常的处理
*
* @param ex the target exception
* @param request the current request
*/
@ExceptionHandler(Exception.class)
public final ResponseEntity<BaseResponse<?>> exceptionHandler(Exception ex, WebRequest request) {
log.error("ExceptionHandler: {}", ex.getMessage());
HttpHeaders headers = new HttpHeaders();
if (ex instanceof HttpMediaTypeNotSupportedException) {
// 针对 返回String 类型 需要客户端设置Content-type 为application/json
return this.handleResultException(new ResultException(ErrorCodeEnum.HttpMediaTypeNotSupportedException),
headers, request);
} else if (ex instanceof ResultException) {
return this.handleResultException((ResultException) ex, headers, request);
}
//TODO: 这里可以自定义其他的异常拦截
return this.handleException(ex, headers, request);
} /**
* 对ResultException类返回返回结果的处理
*/
protected ResponseEntity<BaseResponse<?>> handleResultException(ResultException ex, HttpHeaders headers, WebRequest request) {
BaseResponse<?> body = BaseResponse.fail((ex.getErrorCodeEnum()));
HttpStatus status = null;
if (ex.getErrorCodeEnum().getErrorCode().intValue() >= 500000) {
status = HttpStatus.INTERNAL_SERVER_ERROR;
} else if (ex.getErrorCodeEnum().getErrorCode().intValue() >= 400000) {
status = HttpStatus.BAD_REQUEST;
}
return this.handleExceptionInternal(ex, body, headers, status, request);
} /**
* 异常类的统一处理
*/
protected ResponseEntity<BaseResponse<?>> handleException(Exception ex, HttpHeaders headers, WebRequest request) {
BaseResponse<?> body = BaseResponse.fail();
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return this.handleExceptionInternal(ex, body, headers, status, request);
} /**
* org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
* handleExceptionInternal(java.lang.Exception, java.lang.Object, org.springframework.http.HttpHeaders,
* org.springframework.http.HttpStatus, org.springframework.web.context.request.WebRequest)
* <p>
* A single place to customize the response body of all exception types.
* <p>The default implementation sets the {@link WebUtils#ERROR_EXCEPTION_ATTRIBUTE}
* request attribute and creates a {@link ResponseEntity} from the given
* body, headers, and status.
*/
protected ResponseEntity<BaseResponse<?>> handleExceptionInternal(
Exception ex, BaseResponse<?> body, HttpHeaders headers, HttpStatus status, WebRequest request) {
if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST);
}
return new ResponseEntity<>(body, headers, status);
}
}

  上述在返回String类型,以及Object等类型或者为空时会出现类型转换异常。做以下处理:

  1、配置WebMvcConfigurer,将 MappingJackson2HttpMessageConverter

/旧版本 WebMvcConfigurerAdapter  spring5弃用了 WebMvcConfigurerAdapter
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//提到最前面 ,主要为了处理基础类型时 XXX not cast String type 类似问题,结合请求类型必须为application/json
// 提前使用MappingJackson2HttpMessageConverter 处理 避免使用 StringHttpMessageConverter处理 String类型
converters.add(0,new MappingJackson2HttpMessageConverter());
}
}

    主要作用://提到最前面 ,主要为了处理基础类型时 XXX not cast String type 类似问题,结合请求类型必须为application/json

        // 提前使用MappingJackson2HttpMessageConverter 处理 避免使用 StringHttpMessageConverter处理 String类型

  2、编写controller级别aop,主要作用处理,对象为null,以及String为null,转换为BaseResponse时的类型转换异常

/**
* 统一结果处理切面, 注意返回时 null 时候 处理
*/
@Aspect
@Component
public class ResponseAspect {
@Around("execution(* com.aaa.test.controller..*(..))")
public Object controllerProcess(ProceedingJoinPoint pjd) throws Throwable {
Object result = pjd.proceed();
// 是null特殊处理
if (result == null) {
try {
MethodSignature signature = (MethodSignature) pjd.getSignature();
Class returnType = signature.getReturnType();
if("java.lang.Object".equals(returnType.getName())){
// object 使用此方式
return BaseResponse.success(result);
}else if("java.lang.String".equals(returnType.getName())){
// 字符串 初始化一个新的返回
return returnType.newInstance();
}
//其他默认 返回
return result;
}catch (Exception ex){
ex.printStackTrace();
}
}
return result;
}
}

  针对RestControllerAdvice 与swagger2 问题

    如果 RestControllerAdvice 增加了 ResponseBody的拦截 此时swagger2会接收不到服务端资源。swagger2会接收自己的格式的数据,所以出问题,需要在 supports 方法处理过滤,其他有工具插件也需要过滤,或者所有controller的自定一个ResponseBody。如定义个注解ResponseResultBody,给对应的Controller使用 这里拦截 这个即可

最新文章

  1. Python 3.4 Library setup
  2. Java-类与类之间的关系
  3. MySQL数据库优化的八种方式(经典必看)
  4. 读《程序员的SQL金典》[2]--函数
  5. 使用Sqlserver Management Studio 导入导出 Excel的方法
  6. SciTE 文本编辑器
  7. MTK android flash配置
  8. EXTJS 4.2 资料 控件之Grid 行编辑绑定下拉框,并点一次触发一次事件
  9. 单核CPU,多线程与性能
  10. Swift中子类必须包含的构造器和析构器
  11. pwm最后的解释
  12. 使用Jmeter进行http接口测试 ---------成都杀手
  13. setTimeout,setInterval你不知道的事
  14. DBUtils源码分析
  15. 物理dataguard 正常切换 脚色转换,switchover_status 状态改变
  16. UVA11925-Generating Permutations(贪心)
  17. 自定义实现moveable button
  18. 检测到包降级: Microsoft.Extensions.Configuration.Abstractions 从 2.1.1 降 2.1.0
  19. shmdt() 与 shmctl() 的区别?
  20. STL源码剖析01-allocator

热门文章

  1. Python数据结构汇总
  2. Linux服务-bind
  3. heapq 对有序的数组列表进行整体排序
  4. CSS——设置边框渐变色
  5. tensorflow API _ 3 (tf.train.polynomial_decay)
  6. OKR的两个基本原则
  7. CLR 调试体系结构
  8. Xamarin移动开发之路
  9. My journey introducing the data build tool (dbt) in project’s analytical stacks
  10. CSS块元素