每一个系统都是由多张表的增删改查所构成,只不过对于业务复杂的系统而言,其系统中的表之间存在着千丝万缕的关系;
对于程序员而言,做系统应该将更多精力投放到处理业务上面,也就是说精力应该放在处理表与表之间的关系上面,而并非是重复的增删改查的代码编写;
而本篇博客的目的在于,制作一个代码生成器,将项目中所用到的表,增删改查的前端后端代码都一并自动生成;让程序员解放双手,安心处理系统业务;
代码生成器的制作方式有很多种,有纯js前端版的,有后端版本的,这里介绍依赖于后端的代码生成器,需要用到的技术有springboot、mybatis、velocity模板引擎、IO流技术等等;
代码生成器制作思路
1、读取到指定数据库下的所有表,然后在界面上展示出来;
2、按照表名查询出来其对应的所有的表字段;
3、读取generator.properties获取表、表列段与类、类属性的对应关系;
4、将表名、列名通过工具类,生成对应的类名、类属性名;
5、获取velocity资源加载器,加载模板集合(dao层模板、service层模板,controler层模板、html模板、js模板等等),将模板中的变量绑定到velocity中的模板
6、最终渲染模板以zip包的形式写出
Zip压缩包中的清单列表如下
其中一个生成出来的类内容
package com.javaxl.bootdo.system.controller; import java.util.List; import java.util.Map; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.ui.Model; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; 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.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import com.javaxl.bootdo.system.domain.MenuDO; import com.javaxl.bootdo.system.service.MenuService; import com.javaxl.bootdo.common.utils.PageUtils; import com.javaxl.bootdo.common.utils.Query; import com.javaxl.bootdo.common.utils.R; /** * 菜单管理 * * @author chglee * @email 1992lcg@163.com * @date 2019-06-20 16:13:26 */ @Controller @RequestMapping("/system/menu") public class MenuController { @Autowired private MenuService menuService; @GetMapping() @RequiresPermissions("system:menu:menu") String Menu(){ return "system/menu/menu"; } @ResponseBody @GetMapping("/list") @RequiresPermissions("system:menu:menu") public PageUtils list(@RequestParam Map<String, Object> params){ //查询列表数据 Query query = new Query(params); List<MenuDO> menuList = menuService.list(query); int total = menuService.count(query); PageUtils pageUtils = new PageUtils(menuList, total); return pageUtils; } @GetMapping("/add") @RequiresPermissions("system:menu:add") String add(){ return "system/menu/add"; } @GetMapping("/edit/{menuId}") @RequiresPermissions("system:menu:edit") String edit(@PathVariable("menuId") Long menuId,Model model){ MenuDO menu = menuService.get(menuId); model.addAttribute("menu", menu); return "system/menu/edit"; } /** * 保存 */ @ResponseBody @PostMapping("/save") @RequiresPermissions("system:menu:add") public R save( MenuDO menu){ if(menuService.save(menu)>0){ return R.ok(); } return R.error(); } /** * 修改 */ @ResponseBody @RequestMapping("/update") @RequiresPermissions("system:menu:edit") public R update( MenuDO menu){ menuService.update(menu); return R.ok(); } /** * 删除 */ @PostMapping( "/remove") @ResponseBody @RequiresPermissions("system:menu:remove") public R remove( Long menuId){ if(menuService.remove(menuId)>0){ return R.ok(); } return R.error(); } /** * 删除 */ @PostMapping( "/batchRemove") @ResponseBody @RequiresPermissions("system:menu:batchRemove") public R remove(@RequestParam("ids[]") Long[] menuIds){ menuService.batchRemove(menuIds); return R.ok(); } }
下面开始上代码生成器相关的核心代码了
Dao层
package com.javaxl.bootdo.common.dao; import org.apache.ibatis.annotations.Select; import java.util.List; import java.util.Map; public interface GeneratorMapper { /** * 查询出当前用户对应的所有表 * @return */ @Select("select table_name tableName, engine, table_comment tableComment, create_time createTime from information_schema.tables" + " where table_schema = (select database())") List<Map<String, Object>> list(); @Select("select count(*) from information_schema.tables where table_schema = (select database())") int count(Map<String, Object> map); @Select("select table_name tableName, engine, table_comment tableComment, create_time createTime from information_schema.tables \r\n" + " where table_schema = (select database()) and table_name = #{tableName}") Map<String, String> get(String tableName); /** * 查询当前表所有的列段 * @param tableName * @return */ @Select("select column_name columnName, data_type dataType, column_comment columnComment, column_key columnKey, extra from information_schema.columns\r\n" + " where table_name = #{tableName} and table_schema = (select database()) order by ordinal_position") List<Map<String, String>> listColumns(String tableName); }
Service层
@Override public List<Map<String, Object>> list() { List<Map<String, Object>> list = generatorMapper.list(); return list; }
Controller层
@ResponseBody @GetMapping("/list") List<Map<String, Object>> list() { List<Map<String, Object>> list = generatorService.list(); return list; }
第一张效果图中的前端对应代码
---------------------------------------------以下为代码生成核心代码-----------------------------------------
存放表、表列段与类、类属性的映射关系,换一句话说,该文件存放着代码生成规则
需要的模板引擎列表
代码生成器模板列表(重要)
实体类
package com.javaxl.bootdo.common.domain; /** * 列的属性 * * */ public class ColumnDO { // 列名 private String columnName; // 列名类型 private String dataType; // 列名备注 private String comments; // 属性名称(第一个字母大写),如:user_name => UserName private String attrName; // 属性名称(第一个字母小写),如:user_name => userName private String attrname; // 属性类型 private String attrType; // auto_increment private String extra; public String getColumnName() { return columnName; } public void setColumnName(String columnName) { this.columnName = columnName; } public String getDataType() { return dataType; } public void setDataType(String dataType) { this.dataType = dataType; } public String getComments() { return comments; } public void setComments(String comments) { this.comments = comments; } public String getAttrname() { return attrname; } public void setAttrname(String attrname) { this.attrname = attrname; } public String getAttrName() { return attrName; } public void setAttrName(String attrName) { this.attrName = attrName; } public String getAttrType() { return attrType; } public void setAttrType(String attrType) { this.attrType = attrType; } public String getExtra() { return extra; } public void setExtra(String extra) { this.extra = extra; } @Override public String toString() { return "ColumnDO{" + "columnName='" + columnName + '\'' + ", dataType='" + dataType + '\'' + ", comments='" + comments + '\'' + ", attrName='" + attrName + '\'' + ", attrname='" + attrname + '\'' + ", attrType='" + attrType + '\'' + ", extra='" + extra + '\'' + '}'; } }
package com.javaxl.bootdo.common.domain; import java.util.List; /** * 表数据 * * @author chenshun * @email sunlightcs@gmail.com * @date 2016年12月20日 上午12:02:55 */ public class TableDO { //表的名称 private String tableName; //表的备注 private String comments; //表的主键 private ColumnDO pk; //表的列名(不包含主键) private List<ColumnDO> columns; //类名(第一个字母大写),如:sys_user => SysUser private String className; //类名(第一个字母小写),如:sys_user => sysUser private String classname; public String getTableName() { return tableName; } public void setTableName(String tableName) { this.tableName = tableName; } public String getComments() { return comments; } public void setComments(String comments) { this.comments = comments; } public ColumnDO getPk() { return pk; } public void setPk(ColumnDO pk) { this.pk = pk; } public List<ColumnDO> getColumns() { return columns; } public void setColumns(List<ColumnDO> columns) { this.columns = columns; } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public String getClassname() { return classname; } public void setClassname(String classname) { this.classname = classname; } @Override public String toString() { return "TableDO{" + "tableName='" + tableName + '\'' + ", comments='" + comments + '\'' + ", pk=" + pk + ", columns=" + columns + ", className='" + className + '\'' + ", classname='" + classname + '\'' + '}'; } }
Controler层代码
@RequestMapping("/code/{tableName}")
public void code(HttpServletRequest request, HttpServletResponse response,
@PathVariable("tableName") String tableName) throws IOException {
String[] tableNames = new String[] { tableName };
byte[] data = generatorService.generatorCode(tableNames);
response.reset();
response.setHeader("Content-Disposition", "attachment; filename=\"bootdo.zip\"");
response.addHeader("Content-Length", "" + data.length);
response.setContentType("application/octet-stream; charset=UTF-8");
IOUtils.write(data, response.getOutputStream());
}
Service层
@Override
public byte[] generatorCode(String[] tableNames) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ZipOutputStream zip = new ZipOutputStream(outputStream);
for(String tableName : tableNames){
//查询表信息
Map<String, String> table = generatorMapper.get(tableName);
//查询列信息
List<Map<String, String>> columns = generatorMapper.listColumns(tableName);
//生成代码
GenUtils.generatorCode(table, columns, zip);
}
IOUtils.closeQuietly(zip);
return outputStream.toByteArray();
}
代码生成工具类
package com.javaxl.bootdo.common.utils; import com.javaxl.bootdo.common.config.Constant; import com.javaxl.bootdo.common.domain.ColumnDO; import com.javaxl.bootdo.common.domain.TableDO; import org.apache.commons.configuration.Configuration; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.PropertiesConfiguration; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.WordUtils; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.Velocity; import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.util.*; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; /** * 代码生成器 工具类 */ public class GenUtils { public static List<String> getTemplates() { List<String> templates = new ArrayList<String>(); templates.add("templates/common/generator/domain.java.vm"); templates.add("templates/common/generator/Dao.java.vm"); //templates.add("templates/common/generator/Mapper.java.vm"); templates.add("templates/common/generator/Mapper.xml.vm"); templates.add("templates/common/generator/Service.java.vm"); templates.add("templates/common/generator/ServiceImpl.java.vm"); templates.add("templates/common/generator/Controller.java.vm"); templates.add("templates/common/generator/list.html.vm"); templates.add("templates/common/generator/add.html.vm"); templates.add("templates/common/generator/edit.html.vm"); templates.add("templates/common/generator/list.js.vm"); templates.add("templates/common/generator/add.js.vm"); templates.add("templates/common/generator/edit.js.vm"); //templates.add("templates/common/generator/menu.sql.vm"); return templates; } /** * 生成代码 */ public static void generatorCode(Map<String, String> table, List<Map<String, String>> columns, ZipOutputStream zip) { //配置信息 Configuration config = getConfig(); //表信息 TableDO tableDO = new TableDO(); tableDO.setTableName(table.get("tableName")); tableDO.setComments(table.get("tableComment")); //表名转换成Java类名 String className = tableToJava(tableDO.getTableName(), config.getString("tablePrefix"),config.getString("autoRemovePre")); tableDO.setClassName(className); tableDO.setClassname(StringUtils.uncapitalize(className)); //列信息 List<ColumnDO> columsList = new ArrayList<>(); for (Map<String, String> column : columns) { ColumnDO columnDO = new ColumnDO(); columnDO.setColumnName(column.get("columnName")); columnDO.setDataType(column.get("dataType")); columnDO.setComments(column.get("columnComment")); columnDO.setExtra(column.get("extra")); //列名转换成Java属性名 String attrName = columnToJava(columnDO.getColumnName()); columnDO.setAttrName(attrName); columnDO.setAttrname(StringUtils.uncapitalize(attrName)); //列的数据类型,转换成Java类型 String attrType = config.getString(columnDO.getDataType(), "unknowType"); columnDO.setAttrType(attrType); //是否主键 if ("PRI".equalsIgnoreCase(column.get("columnKey")) && tableDO.getPk() == null) { tableDO.setPk(columnDO); } columsList.add(columnDO); } tableDO.setColumns(columsList); //没主键,则第一个字段为主键 if (tableDO.getPk() == null) { tableDO.setPk(tableDO.getColumns().get(0)); } //设置velocity资源加载器 Properties prop = new Properties(); prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); Velocity.init(prop); //封装模板数据 Map<String, Object> map = new HashMap<>(16); map.put("tableName", tableDO.getTableName()); map.put("comments", tableDO.getComments()); map.put("pk", tableDO.getPk()); map.put("className", tableDO.getClassName()); map.put("classname", tableDO.getClassname()); map.put("pathName", config.getString("package").substring(config.getString("package").lastIndexOf(".") + 1)); map.put("columns", tableDO.getColumns()); map.put("package", config.getString("package")); map.put("author", config.getString("author")); map.put("email", config.getString("email")); map.put("datetime", DateUtils.format(new Date(), DateUtils.DATE_TIME_PATTERN)); VelocityContext context = new VelocityContext(map); //获取模板列表 List<String> templates = getTemplates(); for (String template : templates) { //渲染模板 StringWriter sw = new StringWriter(); Template tpl = Velocity.getTemplate(template, "UTF-8"); tpl.merge(context, sw); try { //添加到zip zip.putNextEntry(new ZipEntry(getFileName(template, tableDO.getClassname(), tableDO.getClassName(), config.getString("package").substring(config.getString("package").lastIndexOf(".") + 1)))); IOUtils.write(sw.toString(), zip, "UTF-8"); IOUtils.closeQuietly(sw); zip.closeEntry(); } catch (IOException e) { throw new BDException("渲染模板失败,表名:" + tableDO.getTableName(), e); } } } /** * 列名转换成Java属性名 */ public static String columnToJava(String columnName) { return WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", ""); } /** * 表名转换成Java类名 */ public static String tableToJava(String tableName, String tablePrefix, String autoRemovePre) { if (Constant.AUTO_REOMVE_PRE.equals(autoRemovePre)) { tableName = tableName.substring(tableName.indexOf("_") + 1); } if (StringUtils.isNotBlank(tablePrefix)) { tableName = tableName.replace(tablePrefix, ""); } return columnToJava(tableName); } /** * 获取配置信息 */ public static Configuration getConfig() { try { return new PropertiesConfiguration("generator.properties"); } catch (ConfigurationException e) { throw new BDException("获取配置文件失败,", e); } } /** * 获取文件名 */ public static String getFileName(String template, String classname, String className, String packageName) { String packagePath = "main" + File.separator + "java" + File.separator; //String modulesname=config.getString("packageName"); if (StringUtils.isNotBlank(packageName)) { packagePath += packageName.replace(".", File.separator) + File.separator; } if (template.contains("domain.java.vm")) { return packagePath + "domain" + File.separator + className + "DO.java"; } if (template.contains("Dao.java.vm")) { return packagePath + "dao" + File.separator + className + "Dao.java"; } // if(template.contains("Mapper.java.vm")){ // return packagePath + "dao" + File.separator + className + "Mapper.java"; // } if (template.contains("Service.java.vm")) { return packagePath + "service" + File.separator + className + "Service.java"; } if (template.contains("ServiceImpl.java.vm")) { return packagePath + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java"; } if (template.contains("Controller.java.vm")) { return packagePath + "controller" + File.separator + className + "Controller.java"; } if (template.contains("Mapper.xml.vm")) { return "main" + File.separator + "resources" + File.separator + "mapper" + File.separator + packageName + File.separator + className + "Mapper.xml"; } if (template.contains("list.html.vm")) { return "main" + File.separator + "resources" + File.separator + "templates" + File.separator + packageName + File.separator + classname + File.separator + classname + ".html"; // + "modules" + File.separator + "generator" + File.separator + className.toLowerCase() + ".html"; } if (template.contains("add.html.vm")) { return "main" + File.separator + "resources" + File.separator + "templates" + File.separator + packageName + File.separator + classname + File.separator + "add.html"; } if (template.contains("edit.html.vm")) { return "main" + File.separator + "resources" + File.separator + "templates" + File.separator + packageName + File.separator + classname + File.separator + "edit.html"; } if (template.contains("list.js.vm")) { return "main" + File.separator + "resources" + File.separator + "static" + File.separator + "js" + File.separator + "appjs" + File.separator + packageName + File.separator + classname + File.separator + classname + ".js"; // + "modules" + File.separator + "generator" + File.separator + className.toLowerCase() + ".js"; } if (template.contains("add.js.vm")) { return "main" + File.separator + "resources" + File.separator + "static" + File.separator + "js" + File.separator + "appjs" + File.separator + packageName + File.separator + classname + File.separator + "add.js"; } if (template.contains("edit.js.vm")) { return "main" + File.separator + "resources" + File.separator + "static" + File.separator + "js" + File.separator + "appjs" + File.separator + packageName + File.separator + classname + File.separator + "edit.js"; } // if(template.contains("menu.sql.vm")){ // return className.toLowerCase() + "_menu.sql"; // } return null; } }
String工具类所用到的方法
public static String uncapitalize(String str) {
int strLen;
if (str == null || (strLen = str.length()) == 0) {
return str;
}
return new StrBuilder(strLen)
.append(Character.toLowerCase(str.charAt(0)))
.append(str.substring(1))
.toString();
}
代码介绍完毕......
备案号:湘ICP备19000029号
Copyright © 2018-2019 javaxl晓码阁 版权所有