AutoScript 代码生成Saas 创建模版手册
前言
为了满足用户自定义代码模版需求,特意编写该手册,方便用户使用AutoScript 工具来快速配置模版。
该工具基于FreeMarker 技术,如果首次接触,可以先了解附录的FreeMarker语法,以便迅速地编写模版。某些功能FreeMarker不能很好支持,需要平台自定义一些函数来补充完善代码生成需要。
模版工程,工程参数,模版关系如下图:
一个模版工程只需定义一组工程参数,可以若干个模版,一个模版通常对应一组文件
本人也是做开发多年,深感IT民工“搬砖”辛苦,想想如何减轻天下开发人员工作量负担,酝酿良久。多版改造,终于出一版BS结构代码生成器!我知道任何系统都不是完美,必经迭代改造,希望用户使用过程中,多提宝贵意见,逐步完善该工具。实现初心:让天下程序员脱离繁复工作!能按点上下班,多抽出时间陪伴家人,保养好身体,为社会做出更大贡献。
系统特点
ü 该系统只是提供模版配置,所以它具有平台无关性,语言无关性。能生成任何平台,任何框架,任何语言代码及相关配置
ü 一键生成,支持批量生成;一个功能生成一般都是秒级生成,一个项目一次生成也是分钟级别而已!
ü 支持从设计到代码,一气呵成!目前设计支持pdm格式,如果还有其他,可以提出。应网友要求支持sql表结构导入。
ü BS结构,本地无需任何安装,直接访问云服务器即可。系统升级服务就升级。
ü 支持定制页面,可能有些客户需求不同,比如直接连接云上系统,挂接菜单等。
编写模版次序
1.数据库设计,目前只支持pdm及SQL文件格式
2.根据业务需求,归纳总结系统模版,比如单表,主子表等。
3.根据模版做出比较通用的功能,切勿加有业务的功能,因为生成代码是统一个“模”生成的,必须具有通用性。
4.着手编写模版。编写模版时,通常一个模版功能分为前后端,一般模版也分为前端模版和后端模版。便于开发人员使用。为了加快开发人员入门,本文以生成bean vo 为例子,详细说明如何创建模版。
这个例子熟练后,可以参考代码生成平台的其他工程创建其他模版。
本文以bean:AsKnowledgeBase.java为例,示范如何配置模版
package top.tangyh.lamp.learnpm.entity;
import java.time.LocalDateTime;
/**
* <p>
* 知识库实体类
* </p>
*
* @author lsb
* @date 2022-06-26
*/
@Data
public class AsKnowledgeBase {
private static final long serialVersionUID = 1L;
/**
* 类型
*/
private String kbType;
/**
* 关键字
*/
private String kbKeyword;
/**
* 语法
*/
private String kbGrammar;
/**
* 描述
*/
private String desc;
}
新建工程
在浏览器(推荐用谷歌浏览器)地址栏录入:http://jz-fw.com:8081/,如果地址栏有变化,发邮件及短信通知
首次使用,需要注册用户
填写上面各项,手机号和邮箱为了忘记密码,系统变更时发通知用,不会对外暴露公开,请放心注册。
登陆进去后,点击菜单:模版维护->模版列表
点击新增后,出现如下界面:
工程名称:录入一个有意义的名称,通常为带有业务含义,比如XXX平台单表模版,工程名称不能与系统现有工程名称相同。
字符集:即jdk支持字符集名称。通常为UTF-8
是否选数据源:是否需要上传表结构(pdm文件等),通常用于创建新项目模版。生成功能代码,一般选择是,如图绿色;新建项目,选否,开关往左拨。变红色。
自定义运行页面URL:用于定制生成代码页面,需要联系平台定制,然后才能配进去。
备注:对于工程的普通描述。
填完,点击保存按钮。
配置工程参数
工程参数通常用于配置一些公共参数,比如根包名,作者,根目录,数据源名称等等。需要根据平台框架需要配置。
在工程列表点击六边形图标,进行参数配置,如下图:
点击六边形图标后,进入下图:
点击新增按钮,进入下图:
各项说明如下:
参数类型:是生成代码时,配置工程参数页面参数界面控件是什么类型,通常类型有如下图:
单行输入框,多行输入框(通常说的大文本框),日期,时间,数字输入框,单选框,多选框等
参数标题:在界面控件前面标题,提示界面控件的作用。
宽度:界面控件占用宽度,通常为50%,100%
验证规则:通常填必填验证规则。如果该参数不是必填,可以为空
选择内容:用于下拉框选择,下拉框有那些选项
参数名:这个非常重要,用于模版中如何得到参数输入的值,就是靠这个参数名来获取。同一个模版工程中,参数名唯一。
占位符:就是提示使用者文本,比如请输入xxxx之类
最大长度:指定参数能录入的最大长度
CS风格:目前尚不支持
初始化值:就是参数默认值
最大值:用于数字输入框的最大值
最小值:用于数字输入框的最小值
小数位:用于数字输入框的能录入小数位数
步长:目前尚未支持
最大行数:用于多行输入框的最多输入行数
最小行数:用于多行输入框的最小输入行数
初始行数:用于多行输入框的初始行数(一开始就显示有多少行)
初始列数:用于多行输入框的初始列数(一开始就显示有多少列)
下面以单行输入框和下拉框为例子
单行输入框:
下拉框:
选择内容截图看不全,下面复制出文本:
[{“value”:“DsConstant.DEFAULTS”,“label”:“缺省数据源”},{“value”:“DsConstant.BASE_TENANT”,“label”:“动态租户base库”}, {“value”:“DsConstant.EXTEND_TENANT”,“label”:“动态租户extend库”}]
它是一个JSON数组,每一项都包含value,label,其他label为显示名称,value为对应值
有多少项,就构造多少个{}
我们观察一下demo的Bean,会发现文件根位置,根包名和作者是可以提取成参数,如下图:
我们创建三个输入框的参数即可,如下图:
【注意】:目录必须以/作为文件分隔符
--------------------------------根包名参数--------------------------
--------------------------------作者--------------------------
全部录入完,如下图:
新建模版
配置完模版后,可以正式进入编制模版阶段了,点击如下按钮:
点击上图图标,进入模版维护列表
点击新增,进入模版编辑界面
模版名称可以任意起,同一个模版工程里不能重复,这个例子我们可以起bean作为模版名称;后面到关键模版内容配置地方,在开始配置之前,先观察实际的bean内容,如下图:
可以看到需要两层循环才能完成bean vo生成
循环一个表写法如下:
<#list rootnode.getChildNodesByPath(“Table”) as tableNode>
</#list>
<#list …> 表示循环开始
</#list> 表示循环结束
循环一个表字段写法如下:
<#list tableNode.getChildNodesByPath(“Column”) as columnNode>
</#list>
循环问题搞掂后,还有一些细节入下图:
上图还漏了,创建文件及根包名
取出表中文名写法:
${tableNode.getAttributeVal("name")}
取作者写法:
${author}
author是配置工程参数的参数名
取当前时间写法:
${currentdate("yyyy-MM-dd")}
Yyyy-MM-dd 是日期格式
表名转换为类名是一个宏,调用如下:
<#assign tablenametrimmodule><@getTableName tableName='${tableNode.getAttributeVal("code")}' />
字段名转属性,字段类型转java类型都是宏,在下面的模版内容逐一标出
取字段中文名写法:
${columnNode.getAttributeVal("name")}
创建文件写法
@{createTextFile("xxx",false)}
文件内容
@{closeFile()}
xxx表示文件名,可以动态或静态
文件内容是将写入文件内容
@{closeFile()}表示文件结束
通过上面几种命令组合,可以配置如下:
<#--
转换表名为类名 首字母大写
-->
<#macro getTableName tableName>
${wordfirstupper(replace(wordfirstupper(lowercase(tableName),"_"),"_",""),"_")}<#t>
</#macro>
<#--
转换字段类型为java类型
-->
<#macro toJavaType fieldType>
<#if startswithignorecase(fieldType,"char")||startswithignorecase(fieldType,"varchar")||
startswithignorecase(fieldType,"blob")||startswithignorecase(fieldType,"clob")||
startswithignorecase(fieldType,"nchar")||startswithignorecase(fieldType,"nvarchar")||
startswithignorecase(fieldType,"nblob")||startswithignorecase(fieldType,"nclob")||
lowercase(fieldType)=="text"||lowercase(fieldType)=="longtext"||
lowercase(fieldType)=="mediumtext" ||lowercase(fieldType)=="json">
String<#t>
<#elseif startswithignorecase(fieldType,"datetime") >
Date<#t>
<#elseif startswithignorecase(fieldType,"date") >
Date<#t>
<#elseif startswithignorecase(fieldType,"float") >
Float<#t>
<#elseif startswithignorecase(fieldType,"integer") || startswithignorecase(fieldType,"int")||startswithignorecase(fieldType,"tinyint")>
Integer<#t>
<#elseif startswithignorecase(fieldType,"long")||lowercase(fieldType)=="bigint" >
Long<#t>
<#elseif startswithignorecase(fieldType,"timestamp") >
Timestamp<#t>
<#elseif (startswithignorecase(fieldType,"number") && index(fieldType,",")>=0) || startswithignorecase(fieldType,"decimal")|| startswithignorecase(fieldType,"numeric")>
BigDecimal<#t>
<#elseif (startswithignorecase(fieldType,"number") && index(fieldType,",")==-1) >
Long<#t>
<#else>
String<#t>
</#if>
</#macro>
<#--
转换字段名为属性
-->
<#macro toPropertyName fieldName>
${wordfirstlower(replace(wordfirstupper(lowercase(fieldName),"_"),"_",""),"_")}<#t>
</#macro>
<#list rootnode.getChildNodesByPath("Table") as tableNode>
<#assign tablenametrimmodule><@getTableName tableName='${tableNode.getAttributeVal("code")}' /></#assign>
<#assign fileName>${tablenametrimmodule}.java</#assign>
@{createTextFile("${rootPath}/entity/${fileName}",false)}
package ${rootPack}.entity;
import java.time.LocalDateTime;
/**
* <p>
* ${tableNode.getAttributeVal("name")}实体类
* </p>
*
* @author ${author}
* @date ${currentdate("yyyy-MM-dd")}
*/
@Data
public class ${tablenametrimmodule} {
private static final long serialVersionUID = 1L;
<#list tableNode.getChildNodesByPath("Column") as columnNode>
<#if (lowercase(columnNode.getAttributeVal("code"))!="id")>
/**
* ${columnNode.getAttributeVal("name")}
*/
<#assign javaType><@toJavaType fieldType='${columnNode.getAttributeVal("datatype")}' /></#assign>
private ${javaType} <@toPropertyName fieldName='${columnNode.getAttributeVal("code")}'/>;
</#if>
</#list>
}
@{closeFile()}
</#list>
模版文件结构:前面是几个宏,宏可以理解为函数,供后面调用
格式为:
<#macro …>
…
</#macro>
凡是<#开头的都是freemarker语句,具体见附录
还有一些扩展函数意义,也参见附录
模版配置完如下:
运行
找到刚才配置模版,点击运行图标,即可生成代码了!
大体步骤,点击运行按钮,进入运行界面,填工程参数,选择表,点击生成按钮。生成成功自动下载结果文件
模版优化
如果每个页面都把宏定义一遍,显得臃肿,不好维护。可以把公共的宏抽出来,放到一个模版里头,其他模版公用。类似java jar
假设抽取后的模版名称为:pubmacro,则它内容如下:
<#--
转换表名为类名 首字母大写
-->
<#macro getTableName tableName>
${wordfirstupper(replace(wordfirstupper(lowercase(tableName),"_"),"_",""),"_")}<#t>
</#macro>
<#--
转换字段类型为java类型
-->
<#macro toJavaType fieldType>
<#if startswithignorecase(fieldType,"char")||startswithignorecase(fieldType,"varchar")||
startswithignorecase(fieldType,"blob")||startswithignorecase(fieldType,"clob")||
startswithignorecase(fieldType,"nchar")||startswithignorecase(fieldType,"nvarchar")||
startswithignorecase(fieldType,"nblob")||startswithignorecase(fieldType,"nclob")||
lowercase(fieldType)=="text"||lowercase(fieldType)=="longtext"||
lowercase(fieldType)=="mediumtext" ||lowercase(fieldType)=="json">
String<#t>
<#elseif startswithignorecase(fieldType,"datetime") >
Date<#t>
<#elseif startswithignorecase(fieldType,"date") >
Date<#t>
<#elseif startswithignorecase(fieldType,"float") >
Float<#t>
<#elseif startswithignorecase(fieldType,"integer") || startswithignorecase(fieldType,"int")||startswithignorecase(fieldType,"tinyint")>
Integer<#t>
<#elseif startswithignorecase(fieldType,"long")||lowercase(fieldType)=="bigint" >
Long<#t>
<#elseif startswithignorecase(fieldType,"timestamp") >
Timestamp<#t>
<#elseif (startswithignorecase(fieldType,"number") && index(fieldType,",")>=0) || startswithignorecase(fieldType,"decimal")|| startswithignorecase(fieldType,"numeric")>
BigDecimal<#t>
<#elseif (startswithignorecase(fieldType,"number") && index(fieldType,",")==-1) >
Long<#t>
<#else>
String<#t>
</#if>
</#macro>
<#--
转换字段名为属性
-->
<#macro toPropertyName fieldName>
${wordfirstlower(replace(wordfirstupper(lowercase(fieldName),"_"),"_",""),"_")}<#t>
</#macro>
而bean模版内容,则调整为:
<#include "pubmacro.ftl" encoding="UTF-8" parse=true>
<#list rootnode.getChildNodesByPath("Table") as tableNode>
<#assign tablenametrimmodule><@getTableName tableName='${tableNode.getAttributeVal("code")}' /></#assign>
<#assign fileName>${tablenametrimmodule}.java</#assign>
@{createTextFile("${rootPath}/entity/${fileName}",false)}
package ${rootPack}.entity;
import java.time.LocalDateTime;
/**
* <p>
* ${tableNode.getAttributeVal("name")}实体类
* </p>
*
* @author ${author}
* @date ${currentdate("yyyy-MM-dd")}
*/
@Data
public class ${tablenametrimmodule} {
private static final long serialVersionUID = 1L;
<#list tableNode.getChildNodesByPath("Column") as columnNode>
<#if (lowercase(columnNode.getAttributeVal("code"))!="id")>
/**
* ${columnNode.getAttributeVal("name")}
*/
<#assign javaType><@toJavaType fieldType='${columnNode.getAttributeVal("datatype")}' /></#assign>
private ${javaType} <@toPropertyName fieldName='${columnNode.getAttributeVal("code")}'/>;
</#if>
</#list>
}
@{closeFile()}
</#list>
这样一来,一个宏文件调整,所有引用地方就调整,方便统一维护
附录
FreeMarker语法
指令 | 含义及语法 |
---|---|
<#if | 判断语句 <#if condition> … <#elseif condition2> … <#elseif condition3> … … <#else> … </#if> |
<#switch | 多路判断 <#switch value> <#case refValue1> … <#break> <#case refValue2> … <#break> … <#case refValueN> … <#break> <#default> … </#switch> |
<#list | 循环 <#list sequence as item> … <#if item = “spring”><#break></#if> … </#list> |
<#include | 引入另一个模版 <#include filename encoding=“GBK” parse=true>引用公共模板.<#include filename options> options包含两个属性 encoding=”GBK” 编码格式 parse=true 是否作为ftl语法解析,默认是true,false就是以文本方式引入.注意在ftl文件里布尔值都是直接赋值的如parse=true,而不是parse=”true” |
<#import | 引入宏 <#import path as hash>类似于java里的import,它导入文件,然后就可以在当前文件里使用被导入文件里的宏组件 用例 假设mylib.ftl里定义了宏copyright那么我们在其他模板页面里可以这样使用 <#import “/libs/mylib.ftl” as my> <@my.copyright date=“1999-2002”/> "my"在freemarker里被称作namespace |
<#compress> | 用来压缩空白空间和空白的行 <#compress> … </#compress>用来压缩空白空间和空白的行.<#compress> 1 2 3 4 5 ${moo} test only I said, test only </#compress> 输出 (1 2 3 4 5 moo test only I said, test only) escape, noescape |
<#escape | 主要使用在相似的字符串变量输出,比如某一个模块的所有字符串输出都必须是html安全的,这个时候就可以使用该表达式 <#escape identifier as expression> … <#noescape>…</#noescape> … </#escape> <#escape x as x?html> First name: ${firstName} <#noescape>Last name: ${lastName}</#noescape> Maiden name: ${maidenName} </#escape> 相同表达式 First name: ${firstName?html} Last name: ${lastName } Maiden name: ${maidenName?html} |
<#assign | 赋值语句 给变量赋值。语法 <#assign name=value> or <#assign name1=value1 name2=value2 … nameN=valueN> or <#assign same as above… in namespacehash> or <#assign name> capture this </#assign> or <#assign name in namespacehash> capture this </#assign> |
<#global | 全局赋值语法,利用这个语法给变量赋值,那么这个变量在所有的namespace中是可见的,如果这个变量被当前的assign语法覆盖 如<#global x=2> <#assign x=1> 在当前页面里x=2将被隐藏,或者通过${global.x}来访问 语法 <#global name=value> or <#global name1=value1 name2=value2 … nameN=valueN> or <#global name> capture this </#global> |
<#setting | 用来设置整个系统的一个环境. 用来设置整个系统的一个环境 locale number_format boolean_format date_format, time_format, datetime_format time_zone classic_compatible 用例 假如当前是匈牙利的设置,然后修改成美国 ${1.2} <#setting locale=“en_US”> ${1.2} 输出 1,2 1.2 因为匈牙利是采用“,”作为十进制的分隔符,美国是用“.” |
<#macro | 宏定义 宏定义.用例 <#macro test foo bar=“Bar” baaz=-1> Test text, and the params: ${foo}, ${bar}, ${baaz} </#macro> <@test foo=“a” bar=“b” baaz=55-2/> <@test foo=“a” bar=“b”/> <@test foo=“a” baaz=55-2/> <@test foo=“a”/> 输出 Test text, and the params: a, b, 23 Test text, and the params: a, b, -1 Test text, and the params: a, Bar, 23 Test text, and the params: a, Bar, -1 |
<#t> | 去掉左右空白和回车换行 xxxxx<#t> |
<#lt> | 去掉左边空白和回车换行xxxx<#lt> |
<#rt> | 去掉右边空白和回车换行 xxxx<#rt> |
<#nt> | 取消<#t>,<#lt>,<#rt>的效果 xxxx<#t><#nt> |
${expression} | ${expression}计算expression并输出 |
#{ expression } | #{ expression }数字计算#{ expression ;format}安格式输出数字format为M和m M表示小数点后最多的位数,m表示小数点后最少的位数如#{121.2322;m2M2}输出121.23 |
扩展函数
函数名 | 含义及语法 |
---|---|
startswith | 判断字符串是否以指定的字符串开头startswith(“abc","”)返回布尔型 |
currentdate | 获取当前日期时间调用举例currentdate(‘yyyy-MM-dd’) 参数为java 日期格式 |
dateformat | 日期格式化 dateformat(date,‘yyyy/MM/dd’) date为java.util.Date类型 返回字符串 |
wordfirstlower | 多个单词首字符变小写 调用举例:wordfirstlower(‘Word1_Word2’,‘_’) 第二个参数为单词分隔符 |
wordfirstupper | 多个单词首字符变大写 调用举例:wordfirstupper(‘Word1_Word2’,‘_’) 第二个参数为单词分隔符 |
uppercase | 字符串转换为大写 调用举例:uppercase(“test”) |
endswith | 判断字符串以什么结束 调用示例:endwith(“xxxx”,“x”) 返回布尔型 |
endswithignorecase | 忽略大小写判断字符串以什么结束 调用示例:endswithignorecase(“xxxx”,“x”) 返回布尔型 |
startswithignorecase | 忽略大小写判断字符串以什么开始调用示例:startswithignorecase(“xxxx”,“x”) 返回布尔型 |
substring | 字符串截断 调用示例:substring(“abc”,1)返回"bc",substring(“abcd”,1,2)返回"bc" |
index | 字符串查找函数 调用示例:index(“abc”,“a”)返回0,index(“abc”,“bc”)返回1 |
splitstring | 字符串拆分函数调用示例:splitstring(“a,b,c”,“,”)返回list对象,包含a,b,c三个元素 |
filecontentfind | 文件内容查找函数 调用示例:filecontentfind(“c:\1.txt”,“abc”) 存在返回true;否则返回false |
trim | 截断字符串前后空格和回车函数 调用示例:trim(" abc “)返回"abc” |
randomnumber | 产生随机数 调用示例:randomnumber(8,10)返回31302931;randomnumber(8,16)返回A7C2F7C2 第一个参数指定返回随机数位数,第二个参数为进位 |
createmap | 新建map对象 调用示例:createmap() |
mapcontain | 检查哈希表是否包含指定的key 调用示例:<#if mapcontain(map,“key”)> </#if> |
mapget | 根据key获取哈希表值调用示例:<#assign value = mapget(map,“key”)/> |
mapkeys | 获取哈希表key列表 ,调用示例:<#list mapkeys(map) as key> @{createTextFile(“${xwlrootpath}${key}\page\folder.json”,false)} <#assign indexContent=mapget(map,key)> {“title”:“”,“index”:[${indexContent?substring(1,indexContent?length)}],“hidden”:false,“iconCls”:“”}</#assign> @{closeFile()} </#list> |
stringformat | 字符串格式化 用法:stringformat(参数格式,对象1,对象2,…),例子:stringformat(“%s%d”,“测试”,1) |
toint | 字符串转整型 用法:toint(字符串) |
getshortuuid | 获取短的唯一数用法:getshortuuid(10) 10表示产生随机数位数 如果随机数位数为10位,10万两次 没重复,大于10万次没验证 |
fileisexist | 判断文件是否存在 用法:fileIsexist(文件名) |
readtextfile | 读文件 ${readtextfile(‘filename’,‘UTF-8’)} |
stringinsert | 字符插入 用法:stringInsert(字符串,插入字符串,插入位置) |
stringreverse | 字符倒查 用法: stringreverse(字符串,被搜字符串,结束位置) 返回:找到,返回>=0位置;否则返回-1 |
writetextfile | 写入文本文件 用法:wirtetextfile(文件名,内容,字符集) |
createdir | 创建目录 用法:createdir(‘c:\temp’) |
httpget | 发起http get请求 例子:httpget(url) |
httppost | 发起一个http post 请求例子:httppost(url,postDataJson),参数postDataJson为body的,格式为json |
Demo例子工程下载
这个demo是上面的例子导出,开发者可以导入平台,练手。
导入操作如下:
这个工具理论上可以生成任意平台代码(只要是文本格式的),如果你觉得配置模版麻烦,可以联系我(lita2lz)帮您配置! 祝您使用愉快!