SpringBoot代码生成器,从此不用手撸代码

image

前言

通常在开始开发项目的时候,首先会建立好数据库相关表,然后根据表结构生成 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);
}
image

制作模板

模板太多了,这里只以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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335