前言

通常在开始开发项目的时候,首先会建立好数据库相关表,然后根据表结构生成 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. Spring Cloud sleuth with zipkin over RabbitMQ教程

    文章目录 Spring Cloud sleuth with zipkin over RabbitMQ demo zipkin server的搭建(基于mysql和rabbitMQ) 客户端环境的依赖 ...

  2. linux 之学习路线

    原文地址:https://www.oschina.net/question/587367_156024 推荐的发行版如下: UBUNTU 适合纯菜鸟,追求稳定的官方支持,对系统稳定性要求较弱,喜欢最新 ...

  3. js 之 箭头函数 (未学完)

    js之箭头函数表达式 箭头函数表达式的语法比函数表达式更短,并且没有自己的this,arguments,super或 new.target.这些函数表达式更适用于那些本来需要匿名函数的地方,并且它们不 ...

  4. Pytorch中自定义神经网络卷积核权重

    1. 自定义神经网络卷积核权重 神经网络被深度学习者深深喜爱,究其原因之一是神经网络的便利性,使用者只需要根据自己的需求像搭积木一样搭建神经网络框架即可,搭建过程中我们只需要考虑卷积核的尺寸,输入输出 ...

  5. Linux 软链接和硬链接

    系统链接文件 文件有文件名和数据,在Linux上被分成两个部分:用户数据(user data)与元数据(metadata) 用户数据:文件数据块(data block),数据块是记录文件真实内容的地方 ...

  6. Android Studio快捷键动态演示

    Android Studio出来很久了,大部分已经转过来了,相对于Eclipse又是毋庸置疑,更好的使用快捷键必定达到事半功倍的效果. 友情提示:某些电脑按F1-F12键需要先按住FN,比如我的Mac ...

  7. 10倍处理能力 阿里云推云上首个支持12层4K非编NAS产品

    5月23日,阿里云在2017云栖大会·成都峰会上正式推出了云上首个支持广电级非编的文件存储产品------NAS Plus,作为阿里云文件存储NAS的升级款,NAS Plus提供高达200Gbps的吞 ...

  8. 使用ScriptX控件进行Web横向打印

    一个需求需要采用横向打印,目前采用IE自身的打印功能(WebBrowser.ExecWB控件)很难进行横向设置,默认需要调用document.all.WebBrowser.ExecWB(8,1);打开 ...

  9. POJ 2955 区间DP必看的括号匹配问题,经典例题

    Brackets Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 14226 Accepted: 7476 Description ...

  10. 智能称体脂称实现(datasheet篇)

    (本文均出于个人理解而写,仅用于学习和交流,某些过程可能不一定正确,希望各位提出意见进行交流,共同进步) TI的参考方案是采用AFE4300芯片 同时进行生物阻抗身体成分测量和体重测量,以MSP430 ...