刚入职没多久,连夜手写了一个代码生成器,项目开发速度瞬间屌炸了!

一、简介
最近刚入职一个新团队,还没来得及熟悉业务,甲方爸爸就要求项目要在2个月内完成开发并上线!
本想着往后推迟1个月在交付,但是甲方爸爸不同意,只能赶鸭子上架了!
然后根据业务需求,设计出了大概30多张表,如果这30多张表,全靠开发人员手写 crud,开发所需的时间肯定会大大的延长,甚至可能直接会影响交付时间!
于是就想着,能不能通过代码生成器一键搞定全部的 crud?
本来计划是用mybatis-plus的,但是生成的代码,根据现有的框架标准,很多代码也需要自己改,有些地方还不如自己手写用的舒服,因此就决定手写一套代码生成器!
很多新手会觉得代码生成器很个高深的东西。其实不然,一点都不高深,当你看完本文的时候,你会完全掌握代码生成器的逻辑,甚至可以根据自己的项目情况,进行深度定制。
废话也不多说了,直接代码撸上!
二、实现思路
下面我就以SpringBoot项目为例,数据持久化操作采用Mybatis,数据库采用Mysql,编写一个自动生成增、删、改、查等基础功能的代码生成器,内容包括controllerservicedaoentitydtovo等信息。
实现思路如下:

  • 第一步:获取表字段名称、类型、表注释等信息
  • 第二步:基于 freemarker 模板引擎,编写相应的模板
  • 第三步:根据对应的模板,生成相应的 java 代码
2.1、获取表结构 首先我们创建一张test_db表,脚本如下:
CREATE TABLE test_db ( id bigint(20) unsigned NOT NULL COMMENT '主键ID', name varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名称', is_delete tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否删除 1:已删除;0:未删除', create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (id), KEY idx_create_time (create_time) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='测试表';

表创建完成之后,基于test_db表,我们查询对应的表结果字段名称、类型、备注信息,这些信息收集将用于后续进行代码生成器所使用!
# 获取对应表结构 SELECT column_name, data_type, column_comment FROM information_schema.columns WHERE table_schema = 'yjgj_base' AND table_name = 'test_db'

刚入职没多久,连夜手写了一个代码生成器,项目开发速度瞬间屌炸了!
文章图片

同时,获取对应表注释,用于生成备注信息!
# 获取对应表注释 SELECT TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = 'yjgj_base' AND table_name = 'test_db'

刚入职没多久,连夜手写了一个代码生成器,项目开发速度瞬间屌炸了!
文章图片

2.2、编写模板
  • 编写mapper模板,涵盖新增、修改、删除、查询等信息
<#list columns as pro> <#if pro.proName == primaryId> <#else> <#list columns as pro> <#if pro_index == 0>${pro.fieldName}<#else>,${pro.fieldName} insert into ${tableName} ( <#list columns as pro> <#if pro_index == 0>${pro.fieldName},<#elseif pro_index == 1>${pro.fieldName}<#else>,${pro.fieldName} ) values <#list columns as pro> ${r"#{obj." + pro.proName + r"}"}, insert into ${tableName} <#list columns as pro> ${pro.fieldName}, <#list columns as pro> ${r"#{" + pro.proName + r",jdbcType=" + pro.fieldType +r"}"}, update ${tableName}<#list columns as pro> <#if pro.fieldName != primaryId && pro.fieldName != primaryId> ${pro.fieldName} = ${r"#{" + pro.proName + r",jdbcType=" + pro.fieldType +r"}"}, where ${primaryId} = ${r"#{" + "${primaryId}" + r",jdbcType=BIGINT}"} update ${tableName} <#list columns as pro> <#if pro.fieldName != primaryId && pro.fieldName != primaryId> when id = ${r"#{" + "obj.id" + r"}"} then${r"#{obj." + pro.proName + r",jdbcType=" + pro.fieldType +r"}"} where id = ${r"#{" + "obj.id" + r"}"} delete from ${tableName} where ${primaryId} = ${r"#{" + "${primaryId}" + r",jdbcType=BIGINT}"} select from ${tableName} where ${primaryId} = ${r"#{" + "${primaryId}" + r",jdbcType=BIGINT}"} select from ${tableName} select from ${tableName} and ${primaryId} in ${r"#{" + "item" + r"}"} select from ${tableName} select count(${primaryId}) from ${tableName} select from ${tableName} limit ${r"#{" + "start,jdbcType=INTEGER" + r"}"},${r"#{" + "end,jdbcType=INTEGER" + r"}"}

  • 编写dao数据访问模板
package ${daoPackageName}; import com.example.generator.core.BaseMapper; import java.util.List; import ${entityPackageName}.${entityName}; import ${dtoPackageName}.${dtoName}; /** * * @ClassName: ${daoName} * @Description: 数据访问接口 * @author ${authorName} * @date ${currentTime} * */ public interface ${daoName} extends BaseMapper<${entityName}>{ int countPage(${dtoName} ${dtoName?uncap_first}); List<${entityName}> selectPage(${dtoName} ${dtoName?uncap_first}); }

  • 编写service服务接口模板
package ${servicePackageName}; import com.example.generator.core.BaseService; import com.example.generator.common.Pager; import ${voPackageName}.${voName}; import ${dtoPackageName}.${dtoName}; import ${entityPackageName}.${entityName}; /** * * @ClassName: ${serviceName} * @Description: ${entityName}业务访问接口 * @author ${authorName} * @date ${currentTime} * */ public interface ${serviceName} extends BaseService<${entityName}> { /** * 分页列表查询 * @param request */ Pager<${voName}> getPage(${dtoName} request); }

  • 编写serviceImpl服务实现类模板
package ${serviceImplPackageName}; import com.example.generator.common.Pager; import com.example.generator.core.BaseServiceImpl; import com.example.generator.test.service.TestEntityService; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; import ${daoPackageName}.${daoName}; import ${entityPackageName}.${entityName}; import ${dtoPackageName}.${dtoName}; import ${voPackageName}.${voName}; @Service public class ${serviceImplName} extends BaseServiceImpl<${daoName}, ${entityName}> implements ${serviceName} { private static final Logger log = LoggerFactory.getLogger(${serviceImplName}.class); /** * 分页列表查询 * @param request */ public Pager<${voName}> getPage(${dtoName} request) { List<${voName}> resultList = new ArrayList(); int count = super.baseMapper.countPage(request); List<${entityName}> dbList = count > 0 ? super.baseMapper.selectPage(request) : new ArrayList<>(); if(!CollectionUtils.isEmpty(dbList)){ dbList.forEach(source->{ ${voName} target = new ${voName}(); BeanUtils.copyProperties(source, target); resultList.add(target); }); } return new Pager(request.getCurrPage(), request.getPageSize(), count, resultList); } }

  • 编写controller控制层模板
package ${controllerPackageName}; import com.example.generator.common.IdRequest; import com.example.generator.common.Pager; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Objects; import ${servicePackageName}.${serviceName}; import ${entityPackageName}.${entityName}; import ${dtoPackageName}.${dtoName}; import ${voPackageName}.${voName}; /** * * @ClassName: ${controllerName} * @Description: 外部访问接口 * @author ${authorName} * @date ${currentTime} * */ @RestController @RequestMapping("/${entityName?uncap_first}") public class ${controllerName} { @Autowired private ${serviceName} ${serviceName?uncap_first}; /** * 分页列表查询 * @param request */ @PostMapping(value = "https://www.it610.com/getPage") public Pager<${voName}> getPage(@RequestBody ${dtoName} request){ return ${serviceName?uncap_first}.getPage(request); } /** * 查询详情 * @param request */ @PostMapping(value = "https://www.it610.com/getDetail") public ${voName} getDetail(@RequestBody IdRequest request){ ${entityName} source = ${serviceName?uncap_first}.selectById(request.getId()); if(Objects.nonNull(source)){ ${voName} result = new ${voName}(); BeanUtils.copyProperties(source, result); return result; } return null; } /** * 新增操作 * @param request */ @PostMapping(value = "https://www.it610.com/save") public void save(${dtoName} request){ ${entityName} entity = new ${entityName}(); BeanUtils.copyProperties(request, entity); ${serviceName?uncap_first}.insert(entity); } /** * 编辑操作 * @param request */ @PostMapping(value = "https://www.it610.com/edit") public void edit(${dtoName} request){ ${entityName} entity = new ${entityName}(); BeanUtils.copyProperties(request, entity); ${serviceName?uncap_first}.updateById(entity); } /** * 删除操作 * @param request */ @PostMapping(value = "https://www.it610.com/delete") public void delete(IdRequest request){ ${serviceName?uncap_first}.deleteById(request.getId()); } }

  • 编写entity实体类模板
package ${entityPackageName}; import java.io.Serializable; import java.math.BigDecimal; import java.util.Date; /** * * @ClassName: ${entityName} * @Description: ${tableDes!}实体类 * @author ${authorName} * @date ${currentTime} * */ public class ${entityName} implements Serializable { private static final long serialVersionUID = 1L; <#--属性遍历--> <#list columns as pro> <#--<#if pro.proName != primaryId && pro.proName != 'remarks' && pro.proName != 'createBy' && pro.proName != 'createDate' && pro.proName != 'updateBy' && pro.proName != 'updateDate' && pro.proName != 'delFlag' && pro.proName != 'currentUser' && pro.proName != 'page' && pro.proName != 'sqlMap' && pro.proName != 'isNewRecord' >--> /** * ${pro.proDes!} */ private ${pro.proType} ${pro.proName}; <#--属性get||set方法--> <#list columns as pro> public ${pro.proType} get${pro.proName?cap_first}() { return this.${pro.proName}; } public ${entityName} set${pro.proName?cap_first}(${pro.proType} ${pro.proName}) { this.${pro.proName} = ${pro.proName}; return this; } }

  • 编写dto实体类模板
package ${dtoPackageName}; import com.example.generator.core.BaseDTO; import java.io.Serializable; /** * @ClassName: ${dtoName} * @Description: 请求实体类 * @author ${authorName} * @date ${currentTime} * */ public class ${dtoName} extends BaseDTO {}

  • 编写vo视图实体类模板
package ${voPackageName}; import java.io.Serializable; /** * @ClassName: ${voName} * @Description: 返回视图实体类 * @author ${authorName} * @date ${currentTime} * */ public class ${voName} implements Serializable { private static final long serialVersionUID = 1L; }

可能细心的网友已经看到了,在模板中我们用到了BaseMapperBaseServiceBaseServiceImpl等等服务类。
之所以有这三个类,是因为在模板中,我们有大量的相同的方法名包括逻辑也相似,除了所在实体类不一样意以外,其他都一样,因此我们可以借助泛型类来将这些服务抽成公共的部分。
  • BaseMapper,主要负责将dao层的公共方法抽出来
package com.example.generator.core; import org.apache.ibatis.annotations.Param; import java.io.Serializable; import java.util.List; import java.util.Map; /** * @author pzblog * @Description * @since 2020-11-11 */ public interface BaseMapper {/** * 批量插入 * @param list * @return */ int insertList(@Param("list") List list); /** * 按需插入一条记录 * @param entity * @return */ int insertPrimaryKeySelective(T entity); /** * 按需修改一条记录(通过主键ID) * @return */ int updatePrimaryKeySelective(T entity); /** * 批量按需修改记录(通过主键ID) * @param list * @return */ int updateBatchByIds(@Param("list") List list); /** * 根据ID删除 * @param id 主键ID * @return */ int deleteByPrimaryKey(Serializable id); /** * 根据ID查询 * @param id 主键ID * @return */ T selectByPrimaryKey(Serializable id); /** * 按需查询 * @param entity * @return */ List selectByPrimaryKeySelective(T entity); /** * 批量查询 * @param ids 主键ID集合 * @return */ List selectByIds(@Param("ids") List ids); /** * 查询(根据 columnMap 条件) * @param columnMap 表字段 map 对象 * @return */ List selectByMap(Map columnMap); }

  • BaseService,主要负责将service层的公共方法抽出来
package com.example.generator.core; import java.io.Serializable; import java.util.List; import java.util.Map; /** * @author pzblog * @Description 服务类 * @since 2020-11-11 */ public interface BaseService {/** * 新增 * @param entity * @return boolean */ boolean insert(T entity); /** * 批量新增 * @param list * @return boolean */ boolean insertList(List list); /** * 通过ID修改记录(如果想全部更新,只需保证字段都不为NULL) * @param entity * @return boolean */ boolean updateById(T entity); /** * 通过ID批量修改记录(如果想全部更新,只需保证字段都不为NULL) * @param list * @return boolean */ boolean updateBatchByIds(List list); /** * 根据ID删除 * @param id 主键ID * @return boolean */ boolean deleteById(Serializable id); /** * 根据ID查询 * @param id 主键ID * @return */ T selectById(Serializable id); /** * 按需查询 * @param entity * @return */ List selectByPrimaryKeySelective(T entity); /** * 批量查询 * @param ids * @return */ List selectByIds(List ids); /** * 根据条件查询 * @param columnMap * @return */ List selectByMap(Map columnMap); }

  • BaseServiceImplservice层的公共方法具体实现类
package com.example.generator.core; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import java.io.Serializable; import java.util.List; import java.util.Map; /** * @author pzblog * @Description 实现类( 泛型说明:M 是 mapper 对象,T 是实体) * @since 2020-11-11 */ public abstract class BaseServiceImpl, T> implements BaseService{@Autowired protected M baseMapper; /** * 新增 * @param entity * @return boolean */ @Override @Transactional(rollbackFor = {Exception.class}) public boolean insert(T entity){ return returnBool(baseMapper.insertPrimaryKeySelective(entity)); }/** * 批量新增 * @param list * @return boolean */ @Override @Transactional(rollbackFor = {Exception.class}) public boolean insertList(List list){ return returnBool(baseMapper.insertList(list)); }/** * 通过ID修改记录(如果想全部更新,只需保证字段都不为NULL) * @param entity * @return boolean */ @Override @Transactional(rollbackFor = {Exception.class}) public boolean updateById(T entity){ return returnBool(baseMapper.updatePrimaryKeySelective(entity)); }/** * 通过ID批量修改记录(如果想全部更新,只需保证字段都不为NULL) * @param list * @return boolean */ @Override @Transactional(rollbackFor = {Exception.class}) public boolean updateBatchByIds(List list){ return returnBool(baseMapper.updateBatchByIds(list)); }/** * 根据ID删除 * @param id 主键ID * @return boolean */ @Override @Transactional(rollbackFor = {Exception.class}) public boolean deleteById(Serializable id){ return returnBool(baseMapper.deleteByPrimaryKey(id)); }/** * 根据ID查询 * @param id 主键ID * @return */ @Override public T selectById(Serializable id){ return baseMapper.selectByPrimaryKey(id); }/** * 按需查询 * @param entity * @return */ @Override public List selectByPrimaryKeySelective(T entity){ return baseMapper.selectByPrimaryKeySelective(entity); }/** * 批量查询 * @param ids * @return */ @Override public List selectByIds(List ids){ return baseMapper.selectByIds(ids); }/** * 根据条件查询 * @param columnMap * @return */ @Override public List selectByMap(Map columnMap){ return baseMapper.selectByMap(columnMap); }/** * 判断数据库操作是否成功 * @param result 数据库操作返回影响条数 * @return boolean */ protected boolean returnBool(Integer result) { return null != result && result >= 1; }}

在此,还封装来其他的类,例如 dto 公共类BaseDTO,分页类Pager,还有 id 请求类IdRequest
  • BaseDTO公共类
public class BaseDTO implements Serializable {/** * 请求token */ private String token; /** * 当前页数 */ private Integer currPage = 1; /** * 每页记录数 */ private Integer pageSize = 20; /** * 分页参数(第几行) */ private Integer start; /** * 分页参数(行数) */ private Integer end; /** * 登录人ID */ private String loginUserId; /** * 登录人名称 */ private String loginUserName; public String getToken() { return token; }public BaseDTO setToken(String token) { this.token = token; return this; }public Integer getCurrPage() { return currPage; }public BaseDTO setCurrPage(Integer currPage) { this.currPage = currPage; return this; }public Integer getPageSize() { return pageSize; }public BaseDTO setPageSize(Integer pageSize) { this.pageSize = pageSize; return this; }public Integer getStart() { if (this.currPage != null && this.currPage > 0) { start = (currPage - 1) * getPageSize(); return start; } return start == null ? 0 : start; }public BaseDTO setStart(Integer start) { this.start = start; return this; }public Integer getEnd() { return getPageSize(); }public BaseDTO setEnd(Integer end) { this.end = end; return this; }public String getLoginUserId() { return loginUserId; }public BaseDTO setLoginUserId(String loginUserId) { this.loginUserId = loginUserId; return this; }public String getLoginUserName() { return loginUserName; }public BaseDTO setLoginUserName(String loginUserName) { this.loginUserName = loginUserName; return this; }}

  • Pager分页类
public class Pager implements Serializable {private static final long serialVersionUID = -6557244954523041805L; /** * 当前页数 */ private int currPage; /** * 每页记录数 */ private int pageSize; /** * 总页数 */ private int totalPage; /** * 总记录数 */ private int totalCount; /** * 列表数据 */ private List list; public Pager(int currPage, int pageSize) { this.currPage = currPage; this.pageSize = pageSize; }public Pager(int currPage, int pageSize, int totalCount, List list) { this.currPage = currPage; this.pageSize = pageSize; this.totalPage = (int) Math.ceil((double) totalCount / pageSize); ; this.totalCount = totalCount; this.list = list; }public int getCurrPage() { return currPage; }public Pager setCurrPage(int currPage) { this.currPage = currPage; return this; }public int getPageSize() { return pageSize; }public Pager setPageSize(int pageSize) { this.pageSize = pageSize; return this; }public int getTotalPage() { return totalPage; }public Pager setTotalPage(int totalPage) { this.totalPage = totalPage; return this; }public int getTotalCount() { return totalCount; }public Pager setTotalCount(int totalCount) { this.totalCount = totalCount; return this; }public List getList() { return list; }public Pager setList(List list) { this.list = list; return this; } }

  • IdRequest公共请求类
public class IdRequest extends BaseDTO {private Long id; public Long getId() { return id; }public IdRequest setId(Long id) { this.id = id; return this; } }

2.3、编写代码生成器 前两部分主要介绍的是如何获取对应的表结构,以及代码器运行之前的准备工作。
其实代码生成器,很简单,其实就是一个main方法,没有想象中的那么复杂。
【刚入职没多久,连夜手写了一个代码生成器,项目开发速度瞬间屌炸了!】处理思路也很简单,过程如下:
  • 1、定义基本变量,例如包名路径、模块名、表名、转换后的实体类、以及数据库连接配置,我们可以将其写入配置文件
  • 2、读取配置文件,封装对应的模板中定义的变量
  • 3、根据对应的模板文件和变量,生成对应的java文件
2.3.1、创建配置文件,定义变量 小编我用的是application.properties配置文件来定义变量,这个没啥规定,你也可以自定义文件名,内容如下:
#包前缀 packageNamePre=com.example.generator #模块名称 moduleName=test #表 tableName=test_db #实体类名称 entityName=TestEntity #主键ID primaryId=id #作者 authorName=pzblog #数据库名称 databaseName=yjgj_base#数据库服务器IP地址 ipName=127.0.0.1 #数据库服务器端口 portName=3306 #用户名 userName=root #密码 passWord=123456#文件输出路径,支持自定义输出路径,如果为空,默认取当前工程的src/main/java路径 outUrl=

2.3.2、根据模板生成对应的java代码
  • 首先,读取配置文件变量
public class SystemConstant {private static Properties properties = new Properties(); static { try { // 加载上传文件设置参数:配置文件 properties.load(SystemConstant.class.getClassLoader().getResourceAsStream("application.properties")); } catch (IOException e) { e.printStackTrace(); } }public static final String tableName = properties.getProperty("tableName"); public static final String entityName = properties.getProperty("entityName"); public static final String packageNamePre = properties.getProperty("packageNamePre"); public static final String outUrl = properties.getProperty("outUrl"); public static final String databaseName = properties.getProperty("databaseName"); public static final String ipName = properties.getProperty("ipName"); public static final String portName = properties.getProperty("portName"); public static final String userName = properties.getProperty("userName"); public static final String passWord = properties.getProperty("passWord"); public static final String authorName = properties.getProperty("authorName"); public static final String primaryId = properties.getProperty("primaryId"); public static final String moduleName = properties.getProperty("moduleName"); }

  • 然后,封装对应的模板中定义的变量
public class CodeService {public void generate(Map templateData) { //包前缀 String packagePreAndModuleName = getPackagePreAndModuleName(templateData); //支持对应实体插入在前面,需要带上%s templateData.put("entityPackageName", String.format(packagePreAndModuleName + ".entity", templateData.get("entityName").toString().toLowerCase())); templateData.put("dtoPackageName", String.format(packagePreAndModuleName + ".dto", templateData.get("entityName").toString().toLowerCase())); templateData.put("voPackageName", String.format(packagePreAndModuleName + ".vo", templateData.get("entityName").toString().toLowerCase())); templateData.put("daoPackageName", String.format(packagePreAndModuleName + ".dao", templateData.get("entityName").toString().toLowerCase())); templateData.put("mapperPackageName", packagePreAndModuleName + ".mapper"); templateData.put("servicePackageName", String.format(packagePreAndModuleName + ".service", templateData.get("entityName").toString().toLowerCase())); templateData.put("serviceImplPackageName", String.format(packagePreAndModuleName + ".service.impl", templateData.get("entityName").toString().toLowerCase())); templateData.put("controllerPackageName", String.format(packagePreAndModuleName + ".web", templateData.get("entityName").toString().toLowerCase())); templateData.put("apiTestPackageName", String.format(packagePreAndModuleName + ".junit", templateData.get("entityName").toString().toLowerCase())); templateData.put("currentTime", new SimpleDateFormat("yyyy-MM-dd").format(new Date())); //======================生成文件配置====================== try { // 生成Entity String entityName = String.format("%s", templateData.get("entityName").toString()); generateFile("entity.ftl", templateData, templateData.get("entityPackageName").toString(), entityName+".java"); // 生成dto String dtoName = String.format("%sDTO", templateData.get("entityName").toString()); templateData.put("dtoName", dtoName); generateFile("dto.ftl", templateData, templateData.get("dtoPackageName").toString(), dtoName + ".java"); // 生成VO String voName = String.format("%sVO", templateData.get("entityName").toString()); templateData.put("voName", voName); generateFile("vo.ftl", templateData, templateData.get("voPackageName").toString(), voName + ".java"); // 生成DAO String daoName = String.format("%sDao", templateData.get("entityName").toString()); templateData.put("daoName", daoName); generateFile("dao.ftl", templateData, templateData.get("daoPackageName").toString(), daoName + ".java"); // 生成Mapper String mapperName = String.format("%sMapper", templateData.get("entityName").toString()); generateFile("mapper.ftl", templateData, templateData.get("mapperPackageName").toString(), mapperName+".xml"); // 生成Service String serviceName = String.format("%sService", templateData.get("entityName").toString()); templateData.put("serviceName", serviceName); generateFile("service.ftl", templateData, templateData.get("servicePackageName").toString(), serviceName + ".java"); // 生成ServiceImpl String serviceImplName = String.format("%sServiceImpl", templateData.get("entityName").toString()); templateData.put("serviceImplName", serviceImplName); generateFile("serviceImpl.ftl", templateData, templateData.get("serviceImplPackageName").toString(), serviceImplName + ".java"); // 生成Controller String controllerName = String.format("%sController", templateData.get("entityName").toString()); templateData.put("controllerName", controllerName); generateFile("controller.ftl", templateData, templateData.get("controllerPackageName").toString(), controllerName + ".java"); //// 生成junit测试类 //String apiTestName = String.format("%sApiTest", templateData.get("entityName").toString()); //templateData.put("apiTestName", apiTestName); //generateFile("test.ftl", templateData, templateData.get("apiTestPackageName").toString(), //apiTestName + ".java"); } catch (Exception e) { e.printStackTrace(); } }/** * 生成文件 * @param templateName 模板名称 * @param templateData 参数名 * @param packageName 包名 * @param fileName 文件名 */ public void generateFile(String templateName, Map templateData, String packageName, String fileName) { templateData.put("fileName", fileName); DaseService dbService = new DaseService(templateData); // 获取数据库参数 if("entity.ftl".equals(templateName) || "mapper.ftl".equals(templateName)){ dbService.getAllColumns(templateData); } try { // 默认生成文件的路径 FreeMakerUtil freeMakerUtil = new FreeMakerUtil(); freeMakerUtil.generateFile(templateName, templateData, packageName, fileName); } catch (Exception e) { e.printStackTrace(); } }/** * 封装包名前缀 * @return */ private String getPackagePreAndModuleName(Map templateData){ String packageNamePre = templateData.get("packageNamePre").toString(); String moduleName = templateData.get("moduleName").toString(); if(StringUtils.isNotBlank(moduleName)){ return packageNamePre + "." + moduleName; } return packageNamePre; }}

  • 接着,获取模板文件,并生成相应的模板文件
public class FreeMakerUtil {/** * 根据Freemark模板,生成文件 * @param templateName:模板名 * @param root:数据原型 * @throws Exception */ public void generateFile(String templateName, Map root, String packageName, String fileName) throws Exception { FileOutputStream fos=null; Writer out =null; try { // 通过一个文件输出流,就可以写到相应的文件中,此处用的是绝对路径 String entityName = (String) root.get("entityName"); String fileFullName = String.format(fileName, entityName); packageName = String.format(packageName, entityName.toLowerCase()); String fileStylePackageName = packageName.replaceAll("\\.", "/"); File file = new File(root.get("outUrl").toString() + "/" + fileStylePackageName + "/" + fileFullName); if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } file.createNewFile(); Template template = getTemplate(templateName); fos = new FileOutputStream(file); out = new OutputStreamWriter(fos); template.process(root, out); out.flush(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (fos != null){ fos.close(); } if(out != null){ out.close(); } } catch (IOException e) { e.printStackTrace(); } } }/** * * 获取模板文件 * * @param name * @return */ public Template getTemplate(String name) { try { Configuration cfg = new Configuration(Configuration.VERSION_2_3_23); cfg.setClassForTemplateLoading(this.getClass(), "/ftl"); Template template = cfg.getTemplate(name); return template; } catch (IOException e) { e.printStackTrace(); } return null; } }

  • 最后,我们编写一个main方法,看看运行之后的效果
public class GeneratorMain {public static void main(String[] args) { System.out.println("生成代码start......"); //获取页面或者配置文件的参数 Map templateData = https://www.it610.com/article/new HashMap(); templateData.put("tableName", SystemConstant.tableName); System.out.println("表名=="+ SystemConstant.tableName); templateData.put("entityName", SystemConstant.entityName); System.out.println("实体类名称=="+ SystemConstant.entityName); templateData.put("packageNamePre", SystemConstant.packageNamePre); System.out.println("包名前缀=="+ SystemConstant.packageNamePre); //支持自定义输出路径 if(StringUtils.isNotBlank(SystemConstant.outUrl)){ templateData.put("outUrl", SystemConstant.outUrl); } else { String path = GeneratorMain.class.getClassLoader().getResource("").getPath() + "../../src/main/java"; templateData.put("outUrl", path); } System.out.println("生成文件路径为=="+ templateData.get("outUrl")); templateData.put("authorName", SystemConstant.authorName); System.out.println("以后代码出问题找=="+ SystemConstant.authorName); templateData.put("databaseName", SystemConstant.databaseName); templateData.put("ipName", SystemConstant.ipName); templateData.put("portName", SystemConstant.portName); templateData.put("userName", SystemConstant.userName); templateData.put("passWord", SystemConstant.passWord); //主键ID templateData.put("primaryId", SystemConstant.primaryId); //模块名称 templateData.put("moduleName", SystemConstant.moduleName); CodeService dataService = new CodeService(); try { //生成代码文件 dataService.generate(templateData); } catch (Exception e) { e.printStackTrace(); }System.out.println("生成代码end......"); } }

结果如下:
刚入职没多久,连夜手写了一个代码生成器,项目开发速度瞬间屌炸了!
文章图片

  • 生成的 Controller 层代码如下
/** * * @ClassName: TestEntityController * @Description: 外部访问接口 * @author pzblog * @date 2020-11-16 * */ @RestController @RequestMapping("/testEntity") public class TestEntityController { @Autowired private TestEntityService testEntityService; /** * 分页列表查询 * @param request */ @PostMapping(value = "https://www.it610.com/getPage") public Pager getPage(@RequestBody TestEntityDTO request){ return testEntityService.getPage(request); } /** * 查询详情 * @param request */ @PostMapping(value = "https://www.it610.com/getDetail") public TestEntityVO getDetail(@RequestBody IdRequest request){ TestEntity source = testEntityService.selectById(request.getId()); if(Objects.nonNull(source)){ TestEntityVO result = new TestEntityVO(); BeanUtils.copyProperties(source, result); return result; } return null; } /** * 新增操作 * @param request */ @PostMapping(value = "https://www.it610.com/save") public void save(TestEntityDTO request){ TestEntity entity = new TestEntity(); BeanUtils.copyProperties(request, entity); testEntityService.insert(entity); } /** * 编辑操作 * @param request */ @PostMapping(value = "https://www.it610.com/edit") public void edit(TestEntityDTO request){ TestEntity entity = new TestEntity(); BeanUtils.copyProperties(request, entity); testEntityService.updateById(entity); } /** * 删除操作 * @param request */ @PostMapping(value = "https://www.it610.com/delete") public void delete(IdRequest request){ testEntityService.deleteById(request.getId()); } }

至此,一张单表的90%的基础工作量全部开发完毕!
三、总结
代码生成器,在实际的项目开发中应用非常的广,本文主要以freemaker模板引擎为基础,开发的一套全自动代码生成器,一张单表的CRUD,只需要5秒钟就可以完成!
最后多说一句,如果你是项目中的核心开发,那么掌握代码生成器的规则,对项目开发效率的提升会有非常直观的帮助!
如果想要获取源代码,关注下方公众号,并回复【cccc4】即可获取!

    推荐阅读