1、概述java
凡auto包或文件件,均會被代碼生成器再次生成二修改mysql
一、model層 git
po:BasePO基礎類,統一了數據庫的基礎字段【數據庫必須添加以下,與mybatis-plus生成器匹配】github
strategy.setSuperEntityColumns("id","delflag","status","remark","created_datetime","updated_datetime");web
po:auto數據庫表生成的原始類 【不容許修改】,如需擴展能夠繼承、複合擴展正則表達式
二、dao層spring
mapper:auto數據庫映射代碼生成器自動生成,不容許修改,如需擴展需自定義在auto外,如 AccountBalanceExtMappersql
同時修改xml,注意xml內部配置正確的 namespace ,: <mapper namespace="com.aaa.test.mapper.AccountBalanceExtMapper">數據庫
三、service層json
auto自動生成文件【不容許修改】
base:IBaseService、BaseServiceImpl 【基礎項目不用繼承,使用IService便可】
自定義實現須要在auto同級編寫便可。
四、web層
主要在controller包下,auto自動生成mapper,更具代碼生成器生成,注意權限控制。base 基礎,如後續,從新代碼生成可不生成web層代碼。
其餘包主要是統一輸出使用。參看下面統一輸出。
【代碼生成器、分頁、邏輯刪除】參看 代碼
代碼生成:test-demo-generator
一、重寫 基於繼承BaseController的模板。框架已提供。
二、代碼生成器文件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; } }
說明一、packageName 包名,無需修改,已經在使用上述命令是生成
參看 代碼
一、設置統一響應輸出類 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; } }
技術需求
一、統一響應類支持靜態的成功失敗方法
二、錯誤碼,設計應爲6位數值類型,其中200000 類別爲成功類型,400000爲請求類型碼,500000爲服務端異常,其餘大於 600000碼爲客戶自定義碼,
二、Controller編寫
支持controller多種響應參數
常見類型String,int,Integer、Map、Object、單個類等
支持使用上述:BaseResponse 包裝響應類
支持使用:ResponseEntity接收類型
經過統一響應處理返回統一:BaseResponse格式
三、統一響應處理方案
編寫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) { 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等類型或者爲空時會出現類型轉換異常。作如下處理:
一、配置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類型
二、編寫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; } }