前言

通常在开始开发项目的时候,首先会建立好数据库相关表,然后根据表结构生成 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. java中Runnable和Callable的区别

    文章目录 运行机制 返回值的不同 Exception处理 java中Runnable和Callable的区别 在java的多线程开发中Runnable一直以来都是多线程的核心,而Callable是ja ...

  2. CSAPP Chapter 8:Exception Control Flow

    prcesssor在运行时,假设program counter的值为a0, a1, ... , an-1,每个ak表示相对应的instruction的地址.从ak到ak+1的变化被称为control ...

  3. Ubuntu登陆时忘记密码怎么办

    有时候由于各种原因,用户会忘记自己登陆Ubuntu的登陆密码,这个时候我们能怎么办呢? 第一步:先重启电脑,开机时长按shift键,进入grub菜单: 第二步:按“e”键编辑启动项,显示如下图,将下图 ...

  4. Java本地的项目,怎么可以让别人通过外网访问-内网穿透

    2019独角兽企业重金招聘Python工程师标准>>> 一.点击链接 https://natapp.cn/ 注册个免费的账户 NATAPP官网 二.登陆进去以后查看authtoken ...

  5. 【阅读笔记】Ranking Relevance in Yahoo Search (四 / 完结篇)—— recency-sensitive ranking

    7. RECENCY-SENSITIVE RANKING 作用: 为recency-sensitive的query提高排序质量: 对于这类query,用户不仅要相关的还需要最新的信息: 方法:rece ...

  6. 聊聊select, poll 和 epoll_wait

    聊聊select, poll 和 epoll 假设项目上需要实现一个TCP的客户端和服务器从而进行跨机器的数据收发,我们很可能翻阅一些资料,然后写出如下的代码. 服务端 客户端 那么问题来了,如果有一 ...

  7. Vxlan L2

    VXLAN(Virtual eXtensible LAN可扩展虚拟局域网)诞生了,基于IP网络之上,采用的是MAC in UDP技术 跨三层实现二层通信 总结为何需要Vxlan: 虚拟机规模受到网络规 ...

  8. 阿里云服务器连接AWS-S3

    1.找到一个路径下载 aws-cli  (使用离线包安装) wget -P /usr/local/software https://s3.amazonaws.com/aws-cli/awscli-bu ...

  9. RDP凭据离线脱取

    内网渗透-获取本地RDP密码 获取RDP 连接记录: reg query "HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Clie ...

  10. vue.prototype和vue.use的区别和注意点

    1.vue.prototype:实例上挂载属性/方法,例如Vue.prototype.axios = axios; 2.vue.use:引入插件,例如vuex,vue.use(vuex)如图,vue. ...