代码生成怎么实现?

329 阅读8分钟

代码生成怎么实现?

咱们在一些开源项目或者公司的产品里应该见过代码生成,今天就来剥一剥这里边的代码,看看是咋实现的。

模板可以用apache的Velocity。

Velocity官方文档

说明一下,我也是一边撸代码一边总结的,有些地方只大概体现一下流程。不一定能跑起来~~~ 需要原代码的可以私下找我。

大概流程

image-20221229145452891.png

建个业务表

接下来的操作就是以这个业务表为基础生成增删改查的代码

test 业务表
column_namedata_typecharacter_maximum_lengthcolumn_keyextrais_nullable
idintPRIauto_incrementNO
namevarchar255YES
genderchar255YES

导入业务表信息

需要导入的业务表信息包括两部分内容:表的信息、表字段信息,往哪里导呢?往存放表信息的表里导。一来是存到表里咱们之后就可以多次生成,二来是把这两步解耦,所以咱们需要两个表。

gen_table 表信息
字段名数据类型字符长度是否允许非空是否自增注释
table_idbigintNO1编号
table_namevarchar200YES0表名称
table_commentvarchar500YES0表描述
sub_table_namevarchar64YES0关联子表的表名
sub_table_fk_namevarchar64YES0子表关联的外键名
class_namevarchar100YES0实体类名称
tpl_categoryvarchar200YES0使用的模板(crud单表操作 tree树表操作)
package_namevarchar100YES0生成包路径
module_namevarchar30YES0生成模块名
business_namevarchar30YES0生成业务名
function_namevarchar50YES0生成功能名
function_authorvarchar50YES0生成功能作者
gen_typechar1YES0生成代码方式(0zip压缩包 1自定义路径)
gen_pathvarchar200YES0生成路径(不填默认项目路径)
optionsvarchar1000YES0其它生成选项
create_byvarchar64YES0创建者
create_timedatetimeYES0创建时间
update_byvarchar64YES0更新者
update_timedatetimeYES0更新时间
gen_table_column 表字段信息
字段名数据类型字符长度是否允许非空是否自增注释
column_idbigintNO1编号
table_idvarchar64YES0归属表编号
column_namevarchar200YES0列名称
column_commentvarchar500YES0列描述
column_typevarchar100YES0列类型
java_typevarchar500YES0JAVA类型
java_fieldvarchar200YES0JAVA字段名
is_pkchar1YES0是否主键(1是)
is_incrementchar1YES0是否自增(1是)
is_requiredchar1YES0是否必填(1是)
is_insertchar1YES0是否为插入字段(1是)
is_editchar1YES0是否编辑字段(1是)
is_listchar1YES0是否列表字段(1是)
is_querychar1YES0是否查询字段(1是)
query_typevarchar200YES0查询方式(等于、不等于、大于、小于、范围)
html_typevarchar200YES0显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)
dict_typevarchar200YES0字典类型
sortintYES0排序
create_byvarchar64YES0创建者
create_timedatetimeYES0创建时间
update_byvarchar64YES0更新者
update_timedatetimeYES0更新时间

导入代码

接口

/**
 * 导入接口
 */
@PostMapping("/importTable")
public AjaxResult importTableSave(String tables)
{
    String[] tableNames = tables.split(",");
    // 查询表信息
    List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames);
    genTableService.importGenTable(tableList);
    return success();
}

业务层

/**
  * 查询据库列表
  */
public List<GenTable> selectDbTableListByNames(String[] tableNames);
​
/**
 * 导入表结构
 */
public void importGenTable(List<GenTable> tableList);
/**
 * 查询据库列表
 */
@Override
public List<GenTable> selectDbTableListByNames(String[] tableNames)
{
    return genTableMapper.selectDbTableListByNames(tableNames);
}
​
/**
 * 导入表结构
 */
@Override
@Transactional
public void importGenTable(List<GenTable> tableList)
{
    try
    {
        for (GenTable table : tableList)
        {
            String tableName = table.getTableName();
            genTable.setClassName(convertClassName(genTable.getTableName()));
            genTable.setPackageName(GenConfig.getPackageName());
            genTable.setModuleName(getModuleName(GenConfig.getPackageName()));
            genTable.setBusinessName(getBusinessName(genTable.getTableName()));
            genTable.setFunctionName(replaceText(genTable.getTableComment()));
            genTable.setFunctionAuthor(GenConfig.getAuthor());
            int row = genTableMapper.insertGenTable(table);
            if (row > 0)
            {
                // 保存列信息
                List<GenTableColumn> genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName);
                for (GenTableColumn column : genTableColumns)
                {
                    GenUtils.initColumnField(column, table);
                    genTableColumnMapper.insertGenTableColumn(column);
                }
            }
        }
    }
    catch (Exception e)
    {
        throw new ServiceException("导入失败:" + e.getMessage());
    }
}

GenTableMapper

public List<GenTable> selectDbTableListByNames(String[] tableNames);
​
/**
 * 新增业务
 */
public int insertGenTable(GenTable genTable);
<select id="selectDbTableListByNames" parameterType="String" resultMap="GenTableResult">
    SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark,
    c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
    FROM gen_table t
    LEFT JOIN gen_table_column c ON t.table_id = c.table_id
    where t.table_name = #{tableName} order by c.sort
</select>
​
<insert id="insertGenTable" parameterType="GenTable" useGeneratedKeys="true" keyProperty="tableId">
    insert into gen_table (
    <if test="tableName != null">table_name,</if>
    <if test="tableComment != null and tableComment != ''">table_comment,</if>
    <if test="className != null and className != ''">class_name,</if>
    <if test="tplCategory != null and tplCategory != ''">tpl_category,</if>
    <if test="packageName != null and packageName != ''">package_name,</if>
    <if test="moduleName != null and moduleName != ''">module_name,</if>
    <if test="businessName != null and businessName != ''">business_name,</if>
    <if test="functionName != null and functionName != ''">function_name,</if>
    <if test="functionAuthor != null and functionAuthor != ''">function_author,</if>
    <if test="genType != null and genType != ''">gen_type,</if>
    <if test="genPath != null and genPath != ''">gen_path,</if>
    <if test="remark != null and remark != ''">remark,</if>
    <if test="createBy != null and createBy != ''">create_by,</if>
    create_time
    )values(
    <if test="tableName != null">#{tableName},</if>
    <if test="tableComment != null and tableComment != ''">#{tableComment},</if>
    <if test="className != null and className != ''">#{className},</if>
    <if test="tplCategory != null and tplCategory != ''">#{tplCategory},</if>
    <if test="packageName != null and packageName != ''">#{packageName},</if>
    <if test="moduleName != null and moduleName != ''">#{moduleName},</if>
    <if test="businessName != null and businessName != ''">#{businessName},</if>
    <if test="functionName != null and functionName != ''">#{functionName},</if>
    <if test="functionAuthor != null and functionAuthor != ''">#{functionAuthor},</if>
    <if test="genType != null and genType != ''">#{genType},</if>
    <if test="genPath != null and genPath != ''">#{genPath},</if>
    <if test="remark != null and remark != ''">#{remark},</if>
    <if test="createBy != null and createBy != ''">#{createBy},</if>
    sysdate()
    )
</insert>

GenTableColumnMapper

/**
 * 新增业务字段
 */
public int insertGenTableColumn(GenTableColumn genTableColumn);
<insert id="insertGenTableColumn" parameterType="GenTableColumn" useGeneratedKeys="true" keyProperty="columnId">
    insert into gen_table_column (
    <if test="tableId != null and tableId != ''">table_id,</if>
    <if test="columnName != null and columnName != ''">column_name,</if>
    <if test="columnComment != null and columnComment != ''">column_comment,</if>
    <if test="columnType != null and columnType != ''">column_type,</if>
    <if test="javaType != null and javaType != ''">java_type,</if>
    <if test="javaField != null  and javaField != ''">java_field,</if>
    <if test="isPk != null and isPk != ''">is_pk,</if>
    <if test="isIncrement != null and isIncrement != ''">is_increment,</if>
    <if test="isRequired != null and isRequired != ''">is_required,</if>
    <if test="isInsert != null and isInsert != ''">is_insert,</if>
    <if test="isEdit != null and isEdit != ''">is_edit,</if>
    <if test="isList != null and isList != ''">is_list,</if>
    <if test="isQuery != null and isQuery != ''">is_query,</if>
    <if test="queryType != null and queryType != ''">query_type,</if>
    <if test="htmlType != null and htmlType != ''">html_type,</if>
    <if test="dictType != null and dictType != ''">dict_type,</if>
    <if test="sort != null">sort,</if>
    <if test="createBy != null and createBy != ''">create_by,</if>
    create_time
    )values(
    <if test="tableId != null and tableId != ''">#{tableId},</if>
    <if test="columnName != null and columnName != ''">#{columnName},</if>
    <if test="columnComment != null and columnComment != ''">#{columnComment},</if>
    <if test="columnType != null and columnType != ''">#{columnType},</if>
    <if test="javaType != null and javaType != ''">#{javaType},</if>
    <if test="javaField != null and javaField != ''">#{javaField},</if>
    <if test="isPk != null and isPk != ''">#{isPk},</if>
    <if test="isIncrement != null and isIncrement != ''">#{isIncrement},</if>
    <if test="isRequired != null and isRequired != ''">#{isRequired},</if>
    <if test="isInsert != null and isInsert != ''">#{isInsert},</if>
    <if test="isEdit != null and isEdit != ''">#{isEdit},</if>
    <if test="isList != null and isList != ''">#{isList},</if>
    <if test="isQuery != null and isQuery != ''">#{isQuery},</if>
    <if test="queryType != null and queryType != ''">#{queryType},</if>
    <if test="htmlType != null and htmlType != ''">#{htmlType},</if>
    <if test="dictType != null and dictType != ''">#{dictType},</if>
    <if test="sort != null">#{sort},</if>
    <if test="createBy != null and createBy != ''">#{createBy},</if>
    sysdate()
    )
</insert>

生成代码

/**
 * 生成代码接口
 */
@GetMapping("/batchGenCode")
public void batchGenCode(HttpServletResponse response, String tables) throws IOException
{
    String[] tableNames = tables.split(",");
    byte[] data = genTableService.downloadCode(tableNames);
    response.reset();
    response.addHeader("Access-Control-Allow-Origin", "*");
    response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
    response.setHeader("Content-Disposition", "attachment; filename="learn.zip"");
    response.addHeader("Content-Length", "" + data.length);
    response.setContentType("application/octet-stream; charset=UTF-8");
    IOUtils.write(data, response.getOutputStream());
}
/**
 * 生成代码
 */
public byte[] downloadCode(String[] tableNames);
/**
 * 生成代码
 */
@Override
public byte[] downloadCode(String[] tableNames)
{
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    ZipOutputStream zip = new ZipOutputStream(outputStream);
    for (String tableName : tableNames)
    {
        // 查询表信息
        GenTable table = genTableMapper.selectGenTableByName(tableName);
        
        // 设置主子表信息
        String subTableName = table.getSubTableName();
        if (StringUtils.isNotEmpty(subTableName))
        {
            table.setSubTable(genTableMapper.selectGenTableByName(subTableName));
        }
        
        // 设置主键列信息
        for (GenTableColumn column : table.getColumns())
        {
            if (column.isPk())
            {
                table.setPkColumn(column);
                break;
            }
        }
        if (StringUtils.isNull(table.getPkColumn()))
        {
            table.setPkColumn(table.getColumns().get(0));
        }
        if (GenConstants.TPL_SUB.equals(table.getTplCategory()))
        {
            for (GenTableColumn column : table.getSubTable().getColumns())
            {
                if (column.isPk())
                {
                    table.getSubTable().setPkColumn(column);
                    break;
                }
            }
            if (StringUtils.isNull(table.getSubTable().getPkColumn()))
            {
                table.getSubTable().setPkColumn(table.getSubTable().getColumns().get(0));
            }
        }
        
        //初始化vm方法
        Properties p = new Properties();
        try
        {
            // 加载classpath目录下的vm文件
            p.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
            // 定义字符集
            p.setProperty(Velocity.INPUT_ENCODING, Constants.UTF8);
            // 初始化Velocity引擎,指定配置Properties
            Velocity.init(p);
        }
        catch (Exception e)
        {
            throw new RuntimeException(e);
        }
        
        //设置模板变量信息
        String moduleName = genTable.getModuleName();
        String businessName = genTable.getBusinessName();
        String packageName = genTable.getPackageName();
        String tplCategory = genTable.getTplCategory();
        String functionName = genTable.getFunctionName();
        VelocityContext velocityContext = new VelocityContext();
        velocityContext.put("tplCategory", genTable.getTplCategory());
        velocityContext.put("tableName", genTable.getTableName());
        velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】");
        velocityContext.put("ClassName", genTable.getClassName());
        velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName()));
        velocityContext.put("moduleName", genTable.getModuleName());
        velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName()));
        velocityContext.put("businessName", genTable.getBusinessName());
        velocityContext.put("basePackage", getPackagePrefix(packageName));
        velocityContext.put("packageName", packageName);
        velocityContext.put("author", genTable.getFunctionAuthor());
        velocityContext.put("datetime", DateUtils.getDate());
        velocityContext.put("pkColumn", genTable.getPkColumn());
        velocityContext.put("importList", getImportList(genTable));
        velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName));
        velocityContext.put("columns", genTable.getColumns());
        velocityContext.put("table", genTable);
        velocityContext.put("dicts", getDicts(genTable));
        setMenuVelocityContext(velocityContext, genTable);
        if (GenConstants.TPL_TREE.equals(tplCategory))
        {
            setTreeVelocityContext(velocityContext, genTable);
        }
        if (GenConstants.TPL_SUB.equals(tplCategory))
        {
            setSubVelocityContext(velocityContext, genTable);
        }
        
        // 获取模板列表
        List<String> templates = new ArrayList<String>();
        templates.add("vm/java/domain.java.vm");
        templates.add("vm/java/mapper.java.vm");
        templates.add("vm/java/service.java.vm");
        templates.add("vm/java/serviceImpl.java.vm");
        templates.add("vm/java/controller.java.vm");
        templates.add("vm/xml/mapper.xml.vm");
        templates.add("vm/sql/sql.vm");
        templates.add("vm/js/api.js.vm");
        templates.add("vm/vue/index.vue.vm");
        for (String template : templates)
        {
            // 渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(template, Constants.UTF8);
            tpl.merge(context, sw);
            try
            {
                // 添加到zip
                zip.putNextEntry(new ZipEntry(getFileName(template, table)));
                IOUtils.write(sw.toString(), zip, Constants.UTF8);
                IOUtils.closeQuietly(sw);
                zip.flush();
                zip.closeEntry();
            }
            catch (IOException e)
            {
                log.error("渲染模板失败,表名:" + table.getTableName(), e);
            }
        }
    }
    IOUtils.closeQuietly(zip);
    return outputStream.toByteArray();
}
​
/**
 * 获取文件名
 */
public static String getFileName(String template, GenTable genTable)
{
    // 文件名称
    String fileName = "";
    // 包路径
    String packageName = genTable.getPackageName();
    // 模块名
    String moduleName = genTable.getModuleName();
    // 大写类名
    String className = genTable.getClassName();
    // 业务名称
    String businessName = genTable.getBusinessName();
​
    String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/");
    String mybatisPath = MYBATIS_PATH + "/" + moduleName;
    String vuePath = "vue";
​
    if (template.contains("domain.java.vm"))
    {
        fileName = StringUtils.format("{}/domain/{}.java", javaPath, className);
    }
    if (template.contains("sub-domain.java.vm") && StringUtils.equals(GenConstants.TPL_SUB, genTable.getTplCategory()))
    {
        fileName = StringUtils.format("{}/domain/{}.java", javaPath, genTable.getSubTable().getClassName());
    }
    else if (template.contains("mapper.java.vm"))
    {
        fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className);
    }
    else if (template.contains("service.java.vm"))
    {
        fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className);
    }
    else if (template.contains("serviceImpl.java.vm"))
    {
        fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className);
    }
    else if (template.contains("controller.java.vm"))
    {
        fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className);
    }
    else if (template.contains("mapper.xml.vm"))
    {
        fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className);
    }
    else if (template.contains("sql.vm"))
    {
        fileName = businessName + "Menu.sql";
    }
    else if (template.contains("api.js.vm"))
    {
        fileName = StringUtils.format("{}/api/{}/{}.js", vuePath, moduleName, businessName);
    }
    else if (template.contains("index.vue.vm"))
    {
        fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
    }
    else if (template.contains("index-tree.vue.vm"))
    {
        fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName);
    }
    return fileName;
}
/**
 * 查询表名称业务信息
 */
public GenTable selectGenTableByName(String tableName);
<select id="selectGenTableByName" parameterType="String" resultMap="GenTableResult">
    SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark,
    c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
    FROM gen_table t
    LEFT JOIN gen_table_column c ON t.table_id = c.table_id
    where t.table_name = #{tableName} order by c.sort
</select>
​
<select id="selectGenTableByName" parameterType="String" resultMap="GenTableResult">
    SELECT t.table_id, t.table_name, t.table_comment, t.sub_table_name, t.sub_table_fk_name, t.class_name, t.tpl_category, t.package_name, t.module_name, t.business_name, t.function_name, t.function_author, t.gen_type, t.gen_path, t.options, t.remark,
    c.column_id, c.column_name, c.column_comment, c.column_type, c.java_type, c.java_field, c.is_pk, c.is_increment, c.is_required, c.is_insert, c.is_edit, c.is_list, c.is_query, c.query_type, c.html_type, c.dict_type, c.sort
    FROM gen_table t
    LEFT JOIN gen_table_column c ON t.table_id = c.table_id
    where t.table_name = #{tableName} order by c.sort
</select>

IOUtils依赖

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
</dependency>

两个实体

@Data
public class GenTable extends BaseEntity
{
    private static final long serialVersionUID = 1L;
​
    /** 编号 */
    private Long tableId;
​
    /** 表名称 */
    @NotBlank(message = "表名称不能为空")
    private String tableName;
​
    /** 表描述 */
    @NotBlank(message = "表描述不能为空")
    private String tableComment;
​
    /** 关联父表的表名 */
    private String subTableName;
​
    /** 本表关联父表的外键名 */
    private String subTableFkName;
​
    /** 实体类名称(首字母大写) */
    @NotBlank(message = "实体类名称不能为空")
    private String className;
​
    /** 使用的模板(crud单表操作 tree树表操作 sub主子表操作) */
    private String tplCategory;
​
    /** 生成包路径 */
    @NotBlank(message = "生成包路径不能为空")
    private String packageName;
​
    /** 生成模块名 */
    @NotBlank(message = "生成模块名不能为空")
    private String moduleName;
​
    /** 生成业务名 */
    @NotBlank(message = "生成业务名不能为空")
    private String businessName;
​
    /** 生成功能名 */
    @NotBlank(message = "生成功能名不能为空")
    private String functionName;
​
    /** 生成作者 */
    @NotBlank(message = "作者不能为空")
    private String functionAuthor;
​
    /** 生成代码方式(0zip压缩包 1自定义路径) */
    private String genType;
​
    /** 生成路径(不填默认项目路径) */
    private String genPath;
​
    /** 主键信息 */
    private GenTableColumn pkColumn;
​
    /** 子表信息 */
    private GenTable subTable;
​
    /** 表列信息 */
    @Valid
    private List<GenTableColumn> columns;
​
    /** 其它生成选项 */
    private String options;
​
    /** 树编码字段 */
    private String treeCode;
​
    /** 树父编码字段 */
    private String treeParentCode;
​
    /** 树名称字段 */
    private String treeName;
​
    /** 上级菜单ID字段 */
    private String parentMenuId;
​
    /** 上级菜单名称字段 */
    private String parentMenuName;
​
}
@Data
public class GenTableColumn extends BaseEntity
{
    private static final long serialVersionUID = 1L;
​
    /** 编号 */
    private Long columnId;
​
    /** 归属表编号 */
    private Long tableId;
​
    /** 列名称 */
    private String columnName;
​
    /** 列描述 */
    private String columnComment;
​
    /** 列类型 */
    private String columnType;
​
    /** JAVA类型 */
    private String javaType;
​
    /** JAVA字段名 */
    @NotBlank(message = "Java属性不能为空")
    private String javaField;
​
    /** 是否主键(1是) */
    private String isPk;
​
    /** 是否自增(1是) */
    private String isIncrement;
​
    /** 是否必填(1是) */
    private String isRequired;
​
    /** 是否为插入字段(1是) */
    private String isInsert;
​
    /** 是否编辑字段(1是) */
    private String isEdit;
​
    /** 是否列表字段(1是) */
    private String isList;
​
    /** 是否查询字段(1是) */
    private String isQuery;
​
    /** 查询方式(EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围) */
    private String queryType;
​
    /** 显示类型(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、image图片上传控件、upload文件上传控件、editor富文本控件) */
    private String htmlType;
​
    /** 字典类型 */
    private String dictType;
​
    /** 排序 */
    private Integer sort;
}

上个模板看看,这个语法跟freemarker、jsp、thymleaf挺像,感觉几种模板的语法都差不多,大同小异,改天总结一下。

controller

package ${packageName}.controller;
​
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.learn.common.annotation.Log;
import com.learn.common.core.controller.BaseController;
import com.learn.common.core.domain.AjaxResult;
import com.learn.common.enums.BusinessType;
import ${packageName}.domain.${ClassName};
import ${packageName}.service.I${ClassName}Service;
import com.learn.common.utils.poi.ExcelUtil;
#if($table.crud || $table.sub)
import com.learn.common.core.page.TableDataInfo;
#elseif($table.tree)
#end
​
/**
 * ${functionName}Controller
 *
 * @author ${author}
 * @date ${datetime}
 */
@RestController
@RequestMapping("/${moduleName}/${businessName}")
public class ${ClassName}Controller extends BaseController
{
    @Autowired
    private I${ClassName}Service ${className}Service;
​
    /**
     * 查询${functionName}列表
     */
    @GetMapping("/list")
#if($table.crud || $table.sub)
    public TableDataInfo list(${ClassName} ${className})
    {
        startPage();
        List<${ClassName}> list = ${className}Service.select${ClassName}List(${className});
        return getDataTable(list);
    }
#elseif($table.tree)
    public AjaxResult list(${ClassName} ${className})
    {
        List<${ClassName}> list = ${className}Service.select${ClassName}List(${className});
        return success(list);
    }
#end
​
    /**
     * 导出${functionName}列表
     */
    @PostMapping("/export")
    public void export(HttpServletResponse response, ${ClassName} ${className})
    {
        List<${ClassName}> list = ${className}Service.select${ClassName}List(${className});
        ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class);
        util.exportExcel(response, list, "${functionName}数据");
    }
​
    /**
     * 获取${functionName}详细信息
     */
    @GetMapping(value = "/{${pkColumn.javaField}}")
    public AjaxResult getInfo(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField})
    {
        return success(${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}));
    }
​
    /**
     * 新增${functionName}
     */
    @PostMapping
    public AjaxResult add(@RequestBody ${ClassName} ${className})
    {
        return toAjax(${className}Service.insert${ClassName}(${className}));
    }
​
    /**
     * 修改${functionName}
     */
    @PutMapping
    public AjaxResult edit(@RequestBody ${ClassName} ${className})
    {
        return toAjax(${className}Service.update${ClassName}(${className}));
    }
​
    /**
     * 删除${functionName}
     */
    @DeleteMapping("/{${pkColumn.javaField}s}")
    public AjaxResult remove(@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s)
    {
        return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s));
    }
}
​

vue

<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
#foreach($column in $columns)
#if($column.query)
#set($dictType=$column.dictType)
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#set($parentheseIndex=$column.columnComment.indexOf("("))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if($column.htmlType == "input")
      <el-form-item label="${comment}" prop="${column.javaField}">
        <el-input
          v-model="queryParams.${column.javaField}"
          placeholder="请输入${comment}"
          clearable
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && "" != $dictType)
      <el-form-item label="${comment}" prop="${column.javaField}">
        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
          <el-option
            v-for="dict in dict.type.${dictType}"
            :key="dict.value"
            :label="dict.label"
            :value="dict.value"
          />
        </el-select>
      </el-form-item>
#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $dictType)
      <el-form-item label="${comment}" prop="${column.javaField}">
        <el-select v-model="queryParams.${column.javaField}" placeholder="请选择${comment}" clearable>
          <el-option label="请选择字典生成" value="" />
        </el-select>
      </el-form-item>
#elseif($column.htmlType == "datetime" && $column.queryType != "BETWEEN")
      <el-form-item label="${comment}" prop="${column.javaField}">
        <el-date-picker clearable
          v-model="queryParams.${column.javaField}"
          type="date"
          value-format="yyyy-MM-dd"
          placeholder="请选择${comment}">
        </el-date-picker>
      </el-form-item>
#elseif($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
      <el-form-item label="${comment}">
        <el-date-picker
          v-model="daterange${AttrName}"
          style="width: 240px"
          value-format="yyyy-MM-dd"
          type="daterange"
          range-separator="-"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
        ></el-date-picker>
      </el-form-item>
#end
#end
#end
      <el-form-item>
        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>
​
    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button
          type="primary"
          plain
          icon="el-icon-plus"
          size="mini"
          @click="handleAdd"
          v-hasPermi="['${moduleName}:${businessName}:add']"
        >新增</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="success"
          plain
          icon="el-icon-edit"
          size="mini"
          :disabled="single"
          @click="handleUpdate"
          v-hasPermi="['${moduleName}:${businessName}:edit']"
        >修改</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="danger"
          plain
          icon="el-icon-delete"
          size="mini"
          :disabled="multiple"
          @click="handleDelete"
          v-hasPermi="['${moduleName}:${businessName}:remove']"
        >删除</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="warning"
          plain
          icon="el-icon-download"
          size="mini"
          @click="handleExport"
          v-hasPermi="['${moduleName}:${businessName}:export']"
        >导出</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
    </el-row>
​
    <el-table v-loading="loading" :data="${businessName}List" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
#foreach($column in $columns)
#set($javaField=$column.javaField)
#set($parentheseIndex=$column.columnComment.indexOf("("))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if($column.pk)
      <el-table-column label="${comment}" align="center" prop="${javaField}" />
#elseif($column.list && $column.htmlType == "datetime")
      <el-table-column label="${comment}" align="center" prop="${javaField}" width="180">
        <template slot-scope="scope">
          <span>{{ parseTime(scope.row.${javaField}, '{y}-{m}-{d}') }}</span>
        </template>
      </el-table-column>
#elseif($column.list && $column.htmlType == "imageUpload")
      <el-table-column label="${comment}" align="center" prop="${javaField}" width="100">
        <template slot-scope="scope">
          <image-preview :src="scope.row.${javaField}" :width="50" :height="50"/>
        </template>
      </el-table-column>
#elseif($column.list && "" != $column.dictType)
      <el-table-column label="${comment}" align="center" prop="${javaField}">
        <template slot-scope="scope">
#if($column.htmlType == "checkbox")
          <dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField} ? scope.row.${javaField}.split(',') : []"/>
#else
          <dict-tag :options="dict.type.${column.dictType}" :value="scope.row.${javaField}"/>
#end
        </template>
      </el-table-column>
#elseif($column.list && "" != $javaField)
      <el-table-column label="${comment}" align="center" prop="${javaField}" />
#end
#end
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template slot-scope="scope">
          <el-button
            size="mini"
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-hasPermi="['${moduleName}:${businessName}:edit']"
          >修改</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['${moduleName}:${businessName}:remove']"
          >删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    
    <pagination
      v-show="total>0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getList"
    />
​
    <!-- 添加或修改${functionName}对话框 -->
    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
#foreach($column in $columns)
#set($field=$column.javaField)
#if($column.insert && !$column.pk)
#if(($column.usableColumn) || (!$column.superColumn))
#set($parentheseIndex=$column.columnComment.indexOf("("))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#set($dictType=$column.dictType)
#if($column.htmlType == "input")
        <el-form-item label="${comment}" prop="${field}">
          <el-input v-model="form.${field}" placeholder="请输入${comment}" />
        </el-form-item>
#elseif($column.htmlType == "imageUpload")
        <el-form-item label="${comment}">
          <image-upload v-model="form.${field}"/>
        </el-form-item>
#elseif($column.htmlType == "fileUpload")
        <el-form-item label="${comment}">
          <file-upload v-model="form.${field}"/>
        </el-form-item>
#elseif($column.htmlType == "editor")
        <el-form-item label="${comment}">
          <editor v-model="form.${field}" :min-height="192"/>
        </el-form-item>
#elseif($column.htmlType == "select" && "" != $dictType)
        <el-form-item label="${comment}" prop="${field}">
          <el-select v-model="form.${field}" placeholder="请选择${comment}">
            <el-option
              v-for="dict in dict.type.${dictType}"
              :key="dict.value"
              :label="dict.label"
              #if($column.javaType == "Integer" || $column.javaType == "Long"):value="parseInt(dict.value)"#else:value="dict.value"#end
​
            ></el-option>
          </el-select>
        </el-form-item>
#elseif($column.htmlType == "select" && $dictType)
        <el-form-item label="${comment}" prop="${field}">
          <el-select v-model="form.${field}" placeholder="请选择${comment}">
            <el-option label="请选择字典生成" value="" />
          </el-select>
        </el-form-item>
#elseif($column.htmlType == "checkbox" && "" != $dictType)
        <el-form-item label="${comment}">
          <el-checkbox-group v-model="form.${field}">
            <el-checkbox
              v-for="dict in dict.type.${dictType}"
              :key="dict.value"
              :label="dict.value">
              {{dict.label}}
            </el-checkbox>
          </el-checkbox-group>
        </el-form-item>
#elseif($column.htmlType == "checkbox" && $dictType)
        <el-form-item label="${comment}">
          <el-checkbox-group v-model="form.${field}">
            <el-checkbox>请选择字典生成</el-checkbox>
          </el-checkbox-group>
        </el-form-item>
#elseif($column.htmlType == "radio" && "" != $dictType)
        <el-form-item label="${comment}">
          <el-radio-group v-model="form.${field}">
            <el-radio
              v-for="dict in dict.type.${dictType}"
              :key="dict.value"
              #if($column.javaType == "Integer" || $column.javaType == "Long"):label="parseInt(dict.value)"#else:label="dict.value"#end
​
            >{{dict.label}}</el-radio>
          </el-radio-group>
        </el-form-item>
#elseif($column.htmlType == "radio" && $dictType)
        <el-form-item label="${comment}">
          <el-radio-group v-model="form.${field}">
            <el-radio label="1">请选择字典生成</el-radio>
          </el-radio-group>
        </el-form-item>
#elseif($column.htmlType == "datetime")
        <el-form-item label="${comment}" prop="${field}">
          <el-date-picker clearable
            v-model="form.${field}"
            type="date"
            value-format="yyyy-MM-dd"
            placeholder="请选择${comment}">
          </el-date-picker>
        </el-form-item>
#elseif($column.htmlType == "textarea")
        <el-form-item label="${comment}" prop="${field}">
          <el-input v-model="form.${field}" type="textarea" placeholder="请输入内容" />
        </el-form-item>
#end
#end
#end
#end
#if($table.sub)
        <el-divider content-position="center">${subTable.functionName}信息</el-divider>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd${subClassName}">添加</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" icon="el-icon-delete" size="mini" @click="handleDelete${subClassName}">删除</el-button>
          </el-col>
        </el-row>
        <el-table :data="${subclassName}List" :row-class-name="row${subClassName}Index" @selection-change="handle${subClassName}SelectionChange" ref="${subclassName}">
          <el-table-column type="selection" width="50" align="center" />
          <el-table-column label="序号" align="center" prop="index" width="50"/>
#foreach($column in $subTable.columns)
#set($javaField=$column.javaField)
#set($parentheseIndex=$column.columnComment.indexOf("("))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if($column.pk || $javaField == ${subTableFkclassName})
#elseif($column.list && $column.htmlType == "input")
          <el-table-column label="$comment" prop="${javaField}" width="150">
            <template slot-scope="scope">
              <el-input v-model="scope.row.$javaField" placeholder="请输入$comment" />
            </template>
          </el-table-column>
#elseif($column.list && $column.htmlType == "datetime")
          <el-table-column label="$comment" prop="${javaField}" width="240">
            <template slot-scope="scope">
              <el-date-picker clearable v-model="scope.row.$javaField" type="date" value-format="yyyy-MM-dd" placeholder="请选择$comment" />
            </template>
          </el-table-column>
#elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") && "" != $column.dictType)
          <el-table-column label="$comment" prop="${javaField}" width="150">
            <template slot-scope="scope">
              <el-select v-model="scope.row.$javaField" placeholder="请选择$comment">
                <el-option
                  v-for="dict in dict.type.$column.dictType"
                  :key="dict.value"
                  :label="dict.label"
                  :value="dict.value"
                ></el-option>
              </el-select>
            </template>
          </el-table-column>
#elseif($column.list && ($column.htmlType == "select" || $column.htmlType == "radio") && "" == $column.dictType)
          <el-table-column label="$comment" prop="${javaField}" width="150">
            <template slot-scope="scope">
              <el-select v-model="scope.row.$javaField" placeholder="请选择$comment">
                <el-option label="请选择字典生成" value="" />
              </el-select>
            </template>
          </el-table-column>
#end
#end
        </el-table>
#end
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitForm">确 定</el-button>
        <el-button @click="cancel">取 消</el-button>
      </div>
    </el-dialog>
  </div>
</template>
​
<script>
import { list${BusinessName}, get${BusinessName}, del${BusinessName}, add${BusinessName}, update${BusinessName} } from "@/api/${moduleName}/${businessName}";
​
export default {
  name: "${BusinessName}",
#if(${dicts} != '')
  dicts: [${dicts}],
#end
  data() {
    return {
      // 遮罩层
      loading: true,
      // 选中数组
      ids: [],
#if($table.sub)
      // 子表选中数据
      checked${subClassName}: [],
#end
      // 非单个禁用
      single: true,
      // 非多个禁用
      multiple: true,
      // 显示搜索条件
      showSearch: true,
      // 总条数
      total: 0,
      // ${functionName}表格数据
      ${businessName}List: [],
#if($table.sub)
      // ${subTable.functionName}表格数据
      ${subclassName}List: [],
#end
      // 弹出层标题
      title: "",
      // 是否显示弹出层
      open: false,
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
      // $comment时间范围
      daterange${AttrName}: [],
#end
#end
      // 查询参数
      queryParams: {
        pageNum: 1,
        pageSize: 10,
#foreach ($column in $columns)
#if($column.query)
        $column.javaField: null#if($foreach.count != $columns.size()),#end
#end
#end
      },
      // 表单参数
      form: {},
      // 表单校验
      rules: {
#foreach ($column in $columns)
#if($column.required)
#set($parentheseIndex=$column.columnComment.indexOf("("))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
        $column.javaField: [
          { required: true, message: "$comment不能为空", trigger: #if($column.htmlType == "select")"change"#else"blur"#end }
        ]#if($foreach.count != $columns.size()),#end
#end
#end
      }
    };
  },
  created() {
    this.getList();
  },
  methods: {
    /** 查询${functionName}列表 */
    getList() {
      this.loading = true;
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
      this.queryParams.params = {};
#break
#end
#end
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
      if (null != this.daterange${AttrName} && '' != this.daterange${AttrName}) {
        this.queryParams.params["begin${AttrName}"] = this.daterange${AttrName}[0];
        this.queryParams.params["end${AttrName}"] = this.daterange${AttrName}[1];
      }
#end
#end
      list${BusinessName}(this.queryParams).then(response => {
        this.${businessName}List = response.rows;
        this.total = response.total;
        this.loading = false;
      });
    },
    // 取消按钮
    cancel() {
      this.open = false;
      this.reset();
    },
    // 表单重置
    reset() {
      this.form = {
#foreach ($column in $columns)
#if($column.htmlType == "radio")
        $column.javaField: #if($column.javaType == "Integer" || $column.javaType == "Long")0#else"0"#end#if($foreach.count != $columns.size()),#end
#elseif($column.htmlType == "checkbox")
        $column.javaField: []#if($foreach.count != $columns.size()),#end
#else
        $column.javaField: null#if($foreach.count != $columns.size()),#end
#end
#end
      };
#if($table.sub)
      this.${subclassName}List = [];
#end
      this.resetForm("form");
    },
    /** 搜索按钮操作 */
    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getList();
    },
    /** 重置按钮操作 */
    resetQuery() {
#foreach ($column in $columns)
#if($column.htmlType == "datetime" && $column.queryType == "BETWEEN")
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
      this.daterange${AttrName} = [];
#end
#end
      this.resetForm("queryForm");
      this.handleQuery();
    },
    // 多选框选中数据
    handleSelectionChange(selection) {
      this.ids = selection.map(item => item.${pkColumn.javaField})
      this.single = selection.length!==1
      this.multiple = !selection.length
    },
    /** 新增按钮操作 */
    handleAdd() {
      this.reset();
      this.open = true;
      this.title = "添加${functionName}";
    },
    /** 修改按钮操作 */
    handleUpdate(row) {
      this.reset();
      const ${pkColumn.javaField} = row.${pkColumn.javaField} || this.ids
      get${BusinessName}(${pkColumn.javaField}).then(response => {
        this.form = response.data;
#foreach ($column in $columns)
#if($column.htmlType == "checkbox")
        this.form.$column.javaField = this.form.${column.javaField}.split(",");
#end
#end
#if($table.sub)
        this.${subclassName}List = response.data.${subclassName}List;
#end
        this.open = true;
        this.title = "修改${functionName}";
      });
    },
    /** 提交按钮 */
    submitForm() {
      this.#[[$]]#refs["form"].validate(valid => {
        if (valid) {
#foreach ($column in $columns)
#if($column.htmlType == "checkbox")
          this.form.$column.javaField = this.form.${column.javaField}.join(",");
#end
#end
#if($table.sub)
          this.form.${subclassName}List = this.${subclassName}List;
#end
          if (this.form.${pkColumn.javaField} != null) {
            update${BusinessName}(this.form).then(response => {
              this.#[[$modal]]#.msgSuccess("修改成功");
              this.open = false;
              this.getList();
            });
          } else {
            add${BusinessName}(this.form).then(response => {
              this.#[[$modal]]#.msgSuccess("新增成功");
              this.open = false;
              this.getList();
            });
          }
        }
      });
    },
    /** 删除按钮操作 */
    handleDelete(row) {
      const ${pkColumn.javaField}s = row.${pkColumn.javaField} || this.ids;
      this.#[[$modal]]#.confirm('是否确认删除${functionName}编号为"' + ${pkColumn.javaField}s + '"的数据项?').then(function() {
        return del${BusinessName}(${pkColumn.javaField}s);
      }).then(() => {
        this.getList();
        this.#[[$modal]]#.msgSuccess("删除成功");
      }).catch(() => {});
    },
#if($table.sub)
    /** ${subTable.functionName}序号 */
    row${subClassName}Index({ row, rowIndex }) {
      row.index = rowIndex + 1;
    },
    /** ${subTable.functionName}添加按钮操作 */
    handleAdd${subClassName}() {
      let obj = {};
#foreach($column in $subTable.columns)
#if($column.pk || $column.javaField == ${subTableFkclassName})
#elseif($column.list && "" != $javaField)
      obj.$column.javaField = "";
#end
#end
      this.${subclassName}List.push(obj);
    },
    /** ${subTable.functionName}删除按钮操作 */
    handleDelete${subClassName}() {
      if (this.checked${subClassName}.length == 0) {
        this.#[[$modal]]#.msgError("请先选择要删除的${subTable.functionName}数据");
      } else {
        const ${subclassName}List = this.${subclassName}List;
        const checked${subClassName} = this.checked${subClassName};
        this.${subclassName}List = ${subclassName}List.filter(function(item) {
          return checked${subClassName}.indexOf(item.index) == -1
        });
      }
    },
    /** 复选框选中数据 */
    handle${subClassName}SelectionChange(selection) {
      this.checked${subClassName} = selection.map(item => item.index)
    },
#end
    /** 导出按钮操作 */
    handleExport() {
      this.download('${moduleName}/${businessName}/export', {
        ...this.queryParams
      }, `${businessName}_#[[${new Date().getTime()}]]#.xlsx`)
    }
  }
};
</script>
​