前言

通常在开始开发项目的时候,首先会建立好数据库相关表,然后根据表结构生成 Controller、Service、DAO、Model以及一些前端页面。

如果开发前没有强制的约束,而每个程序员都有自己的编码习惯,最终会导致一个项目呈现出多种编码风格。再有就是一些CRUD的列表功能,基本是没啥挑战性的,纯粹苦力活,浪费时间。

所以,根据公司现有框架,开发一款统一风格的代码生成器还是很有必要的。

技术选型

开发框架:SpringBoot+JPA,考虑到会生成各种前后端代码文件,这里我们选用freemarker模板引擎来制作相应的模板。

实现思路

获取表结构信息

首先我们定义一个实体类,为了使用方便,把表和字段信息放到了一个类中:

/**
* 表以及相关字段信息
*/
@Data
public class AppGen extends PageBean implements Serializable { /**
* 表名
*/
private String tableName;
/**
* 实体类名
*/
private String entityName;
/**
* 实体类名 首字母小写
*/
private String lowerEntityName;
/**
* 表备注
*/
private String tableComment;
/**
* 表前缀
*/
private String prefix;
/**
* 功能描述
*/
private String function; /**
* 列名
*/
private String columnName;
/**
* 实体列名
*/
private String entityColumnName;
/**
* 列描述
*/
private String columnComment; /**
* 类型
*/
private String dataType; /**
* 自增
*/
private Object columnExtra;
/**
* 长度
*/
private Object columnLength; private List<AppGen> list; }

获取表列表:

@Override
@Transactional(readOnly = true)
public Result list(AppGen gen){
String countSql = "SELECT COUNT(*) FROM information_schema.tables ";
countSql +="WHERE table_schema='tools'";
Long totalCount = dynamicQuery.nativeQueryCount(countSql);
PageBean<AppGen> data = new PageBean<>();
if(totalCount>0){
String nativeSql = "SELECT table_name as tableName,table_comment as tableComment ";
nativeSql+="FROM information_schema.tables WHERE table_schema='tools'";
Pageable pageable = PageRequest.of(gen.getPageNo(),gen.getPageSize());
List<AppGen> list = dynamicQuery.nativeQueryPagingListModel(AppGen.class,pageable, nativeSql);
data = new PageBean<>(list, totalCount);
}
return Result.ok(data);
}

制作模板

模板太多了,这里只以Controller模板为例,贴一下实现代码,更多模板见源码:

package com.tools.module.${prefix}.web;

import com.tools.common.config.AbstractController;
import com.tools.common.model.Result;
import com.tools.module.${prefix}.entity.${entityName};
import com.tools.module.${prefix}.service.${entityName}Service;
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; @RestController
@RequestMapping("/${prefix}/${function}")
public class ${entityName}Controller extends AbstractController { @Autowired
private ${entityName}Service ${function}Service; /**
* 列表
*/
@PostMapping("/list")
public Result list(${entityName} ${function}){
return ${function}Service.list(${function});
}
/**
* 查询
*/
@PostMapping("/get")
public Result get(Long id){
return ${function}Service.get(id);
}
/**
* 保存
*/
@PostMapping("/save")
public Result save(@RequestBody ${entityName} ${function}){
return ${function}Service.save(${function});
} /**
* 删除
*/
@PostMapping("/delete")
public Result delete(Long id){
return ${function}Service.delete(id);
}
}

说白了其实就是传递参数,把一些可变的代码片段使用${name}形式编写。

代码生成

有点长,慢慢看,其实就是渲染各种前后端模板:

/**
* 生成代码
* @param gen
* @return
* @throws IOException
* @throws TemplateException
*/
@PostMapping("/create")
public Result create(@RequestBody AppGen gen) throws IOException, TemplateException {
/**
* 获取表字段以及注释
*/
List<AppGen> list = genService.getByTable(gen);
String name = gen.getTableName();
String[] table = StringUtils.split(name,"_");
gen.setPrefix(table[0]);
gen.setFunction(table[1]);
gen.setEntityName(GenUtils.allInitialCapital(gen.getTableName()));
list.stream().forEach(column-> {
column.setEntityColumnName(GenUtils.secInitialCapital(column.getColumnName()));
});
gen.setList(list);
String baseFile = filePath+ SystemConstant.SF_FILE_SEPARATOR+"com"+
SystemConstant.SF_FILE_SEPARATOR+ "tools"+
SystemConstant.SF_FILE_SEPARATOR+ "module"+
SystemConstant.SF_FILE_SEPARATOR+ gen.getPrefix()+SystemConstant.SF_FILE_SEPARATOR;
/**
* 后端代码
*/
File entityFile = FileUtil.touch(baseFile+"entity"+
SystemConstant.SF_FILE_SEPARATOR+gen.getEntityName()+".java");
File repositoryFile = FileUtil.touch(baseFile+"repository"+
SystemConstant.SF_FILE_SEPARATOR+gen.getEntityName()+"Repository.java");
File serviceFile = FileUtil.touch(baseFile+"service"+
SystemConstant.SF_FILE_SEPARATOR+gen.getEntityName()+"Service.java");
File serviceImplFile = FileUtil.touch(baseFile+"service"+
SystemConstant.SF_FILE_SEPARATOR+"impl"+SystemConstant.SF_FILE_SEPARATOR+
gen.getEntityName()+"ServiceImpl.java");
File controllerFile = FileUtil.touch(baseFile+"web"+
SystemConstant.SF_FILE_SEPARATOR + gen.getEntityName() + "Controller.java");
/**
* 前端代码
*/
String htmlPath = filePath+
SystemConstant.SF_FILE_SEPARATOR + "templates"+
SystemConstant.SF_FILE_SEPARATOR + gen.getPrefix()+
SystemConstant.SF_FILE_SEPARATOR + gen.getFunction()+SystemConstant.SF_FILE_SEPARATOR;
File listFile = FileUtil.touch(htmlPath + "list.html");
File formFile = FileUtil.touch(htmlPath + "form.html");
/**
* 生成静态页面
*/
Template template = configuration.getTemplate("html/list.ftl");
String text = FreeMarkerTemplateUtils.processTemplateIntoString(
template, gen);
FileUtil.writeString(text,listFile,"UTF-8");
template = configuration.getTemplate("html/form.ftl");
text = FreeMarkerTemplateUtils.processTemplateIntoString(
template, gen);
FileUtil.writeString(text,formFile,"UTF-8");
/**
* 生成后端代码 repository
*/
template = configuration.getTemplate("java/repository.ftl");
text = FreeMarkerTemplateUtils.processTemplateIntoString(
template, gen);
FileUtil.writeString(text,repositoryFile,"UTF-8");
/**
* 生成后端代码 entity
*/
template = configuration.getTemplate("java/entity.ftl");
text = FreeMarkerTemplateUtils.processTemplateIntoString(
template, gen);
FileUtil.writeString(text,entityFile,"UTF-8");
/**
* 生成后端代码 service
*/
template = configuration.getTemplate("java/service.ftl");
text = FreeMarkerTemplateUtils.processTemplateIntoString(
template, gen);
FileUtil.writeString(text,serviceFile,"UTF-8");
/**
* 生成后端代码 service 实现
*/
template = configuration.getTemplate("java/serviceImpl.ftl");
text = FreeMarkerTemplateUtils.processTemplateIntoString(
template, gen);
FileUtil.writeString(text,serviceImplFile,"UTF-8");
/**
* 生成后端代码 controller 实现
*/
template = configuration.getTemplate("java/controller.ftl");
text = FreeMarkerTemplateUtils.processTemplateIntoString(
template, gen);
FileUtil.writeString(text,controllerFile,"UTF-8");
return Result.ok();
}

生成逻辑还是很傻瓜的,后期会慢慢优化,比如根据字段类型生成不同的表单形式,可以自定义字段是否显示等的。

小结

总的来说,还是比较容易上手的,相对于一些简单的列表功能分分钟撸出效果,开发一分钟,喝茶一整天。当然对于一些复杂的效果,还是自己一一去实现。

源码

https://gitee.com/52itstyle/SPTools

SpringBoot代码生成器,从此不用手撸代码的更多相关文章

  1. 你试过不用if撸代码吗?

    译者按: 试着不用if撸代码,是件很有趣的事,而且,万一你领会了什么是“数据即代码,代码即数据”呢? 原文: Coding Tip: Try to Code Without If-statements ...

  2. 手撸代码实现equals方法

    重点都在注释里面写了,这里就不再重复叙述,贴上代码到博客主要是备用. package equals; class Book extends Object { private String title; ...

  3. 手撸一个SpringBoot的Starter,简单易上手

    前言:今天介绍一SpringBoot的Starter,并手写一个自己的Starter,在SpringBoot项目中,有各种的Starter提供给开发者使用,Starter则提供各种API,这样使开发S ...

  4. 【分布式锁的演化】终章!手撸ZK分布式锁!

    前言 这应该是分布式锁演化的最后一个章节了,相信很多小伙伴们看完这个章节之后在应对高并发的情况下,如何保证线程安全心里肯定也会有谱了.在实际的项目中也可以参考一下老猫的github上的例子,当然代码没 ...

  5. 《Spring 手撸专栏》第 3 章:初显身手,运用设计模式,实现 Bean 的定义、注册、获取

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你是否能预见复杂内容的设计问题? 讲道理,无论产品功能是否复杂,都有很大一部分程序员 ...

  6. 手撸一个SpringBoot-Starter

    1. 简介 通过了解SpringBoot的原理后,我们可以手撸一个spring-boot-starter来加深理解. 1.1 什么是starter spring官网解释 starters是一组方便的依 ...

  7. ClownFish:比手写代码还快的通用数据访问层

    http://www.cnblogs.com/fish-li/archive/2012/07/17/ClownFish.html 阅读目录 开始 ClownFish是什么? 比手写代码还快的执行速度 ...

  8. .NET手撸绘制TypeScript类图——下篇

    .NET手撸绘制TypeScript类图--下篇 在上篇的文章中,我们介绍了如何使用.NET解析TypeScript,这篇将介绍如何使用代码将类图渲染出来. 注:以防有人错过了,上篇链接如下:http ...

  9. 99%的程序员都在用Lombok,原理竟然这么简单?我也手撸了一个!|建议收藏!!!

    罗曼罗兰说过:世界上只有一种英雄主义,就是看清生活的真相之后依然热爱生活. 对于 Lombok 我相信大部分人都不陌生,但对于它的实现原理以及缺点却鲜为人知,而本文将会从 Lombok 的原理出发,手 ...

随机推荐

  1. 【集群实战】fatab开机挂载失败案例

    1. nfs挂载加入fstab案例 NFS客户端实现fstab开机自启动挂载 现象:nfs开机挂载卸载了/etc/fstab中,结果无法开机自动挂载nfs 解答:1. nfs客户端命令放在/etc/r ...

  2. IT服务,共享经济的下一个风口?

    前两天,在上千名CIO参加.释放10亿采购需求的2017华南CIO大会暨信息技术交易会上,一款"一站式IT工程师共享平台"成为大会关注焦点--这就是神州数码旗下的神州邦邦. 其实最 ...

  3. Vim Install-Could not get lock......

    在Ubuntu下安装Vim编辑器时,出现错误: 可能原因: 上次安装或更新没有正常完成,导致资源被锁. 解决方案: 删除异常资源,重新下载: 注意 删除异常资源时,一定要切换到root用户,否则操作无 ...

  4. MySQL Windows 环境安装

    1.下载 MySQL Windows 安装包 下载地址:https://downloads.mysql.com/archives/installer/ 我这个是 MySQL 5.7 版本 2.直接双击 ...

  5. 学习笔记之pip的基本使用

    粗略学习了pip的基础知识,便将此作为学习笔记记录下来同样希望分享的能帮到大家! 如果自己电脑没有pip,小澈在此分享如何安装,解决办法很多呢 1.使用easy_install安装: 各种进入到eas ...

  6. request中跟路径有关的api的分析

    最近重在研究struts的源码,其中涉及到了request中的几个api,看了文档后还是觉得不清楚,所以在自己原来的工程中 测试了一下各个api的具体效果.在这里跟大家分享一下. 这是我具体测试的代码 ...

  7. Asp.Net Core MVC在View中,根据用户权限动态生成菜单

    1. 用户登录时,将用户的权限写入Cookie: //将需要的信息写入claims后 var identity = new ClaimsIdentity(claims, IdentityConstan ...

  8. 【Hadoop离线基础总结】Sqoop常用命令及参数

    目录 常用命令 常用公用参数 公用参数:数据库连接 公用参数:import 公用参数:export 公用参数:hive 常用命令&参数 从关系表导入--import 导出到关系表--expor ...

  9. fork...join的用法

    如果希望在仿真的某一时刻同时启动多个任务,可以使用fork....join语句.例如,在仿真开始的 100 ns 后,希望同时启动发送和接收任务,而不是发送完毕后再进行接收,如下所示: initial ...

  10. [coj 1353 Guessing the Number]kmp,字符串最小表示法

    题意:给一个字符串,求它的最小子串,使得原串是通过它重复得到的字符串的一个子串. 思路:先求最小长度,最小循环长度可以利用kmp的next数组快速得到,求出长度后然后利用字符串最小表示法求循环节的最小 ...