现在大多数的项目都是分多层结构
,经典的如遵循MVC规范(Model-View-Controller),如果按照DDD领域模型设计可能会划分更多层,每一层需要创建多个类似模式的类文件和内容,包括类名、注释、属性、方法等,基于数据模型生成代码的插件和工具有很多,比如mybatis-plus、mybatis-generator、mybatisx等,当然市面上也有许多代码自动生成的产品,这些代码生成方式大部分都是生成固定文件结构,但是大多公司一般有自己的一套框架结构或者重新设计的分层结构,这个时候就需要自定义代码生成文件格式,包括生成文件位置和内容格式,今天为大家带来基于mybatis-generator
插件的方式实现自定义代码结构,更好的适配现有代码工程。
基础配置
基于插件基本功能生成的代码,包括Do.java
、Example.java
、Mapper.java
、Mapper.xml
文件,其中生成内容没有对应的注释信息,或者配置生成的配置信息不够人性化,基本无用。
插件配置
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.4.2</version>
<dependencies>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.4.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
</dependencies>
<configuration>
<!-- 输出详细信息 -->
<verbose>true</verbose>
<!-- 覆盖生成的类文件 -->
<overwrite>true</overwrite>
<!-- 定义配置文件 -->
<configurationFile>${basedir}/src/main/resources/generator-configuration.xml</configurationFile>
</configuration>
</plugin>
</plugins>
配置Maven插件
,可以比较容易的触发生成代码。
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!-- 设置生成的文件适用于哪个Mybatis 版本 -->
<context id="default" targetRuntime="MyBatis3">
<!-- 生成RowBounds方法 -->
<plugin type="org.mybatis.generator.plugins.RowBoundsPlugin"/>
<!-- 覆盖XML Mapper文件,POM文件中配置的是Class文件覆盖 -->
<plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"/>
<!-- 旨在创建class时,对注释进行控制 -->
<commentGenerator>
<property name="suppressDate" value="true"/>
<!-- 是否去除自动生成的注释 true:是 false:否 -->
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!-- jdbc的数据库连接 -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/code_gen?useSSL=false"
userId="root"
password="123456"/>
<!-- 非必须,类型处理器,在数据库类型和java类型之间的转换控制,默认情况下数据库中的decimal,bigint在Java对应是sql下的BigDecimal类 -->
<javaTypeResolver>
<!-- 使用常用的基本类型代替sql包下的引用类型 -->
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!-- 生成的实体类所在的包 -->
<javaModelGenerator targetPackage="com.example.codegen.domain" targetProject="src/main/java">
<!-- 是否允许子包 -->
<property name="enableSubPackages" value="true"/>
<!-- 是否清理从数据库中查询出的字符串左右两边的空白字符 -->
<property name="trimStrings" value="false"/>
</javaModelGenerator>
<!-- 生成的mapper xml文件的包和位置 -->
<sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
<!-- 针对数据库的一个配置,是否把schema作为字包名 -->
<property name="enableSubPackages" value="false"/>
</sqlMapGenerator>
<!-- 生成的mapper class文件的包和位置 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.example.codegen.mapper" targetProject="src/main/java">
<property name="enableSubPackages" value="false"/>
</javaClientGenerator>
<!-- 表配置 -->
<table tableName="system_users" domainObjectName="SystemUsersDo"/>
</context>
</generatorConfiguration>
配置注释信息写的比较清楚,这里给出部分配置信息,能够满足大部分需求,如有特殊需求可以参考 官方文档。
效果展示
生成代码总览
, 具体生成内容介于文章内容就不做展示,大家可以实际配置生成。
准备工作
进入正题配置我们的自定义插件配置,基于MybatisGenerator
提供的上下文信息可以方便的获取到元数据
信息,自定义插件有两种解决方案:
- 在项目工程中定义插件,执行生成代码可以动态加载对应的插件;
- 基于MybatisGenerator核心包修改,
不侵入项目工程
,可以方便的分享给其他人使用;
当然,本篇文章主要介绍基于MybatisGenerator核心包修改的方式,简单在GitHub上边搜索mybatis-generator-core发现魔改项目还挺多,接下来实际进行操作。
拉取核心包
可以从GitHub直接拉取魔改的仓库,或者直接下载源码解压
,我这里直接从仓库中找到源码并解压,mybatis-generator-core-1.4.2-sources。
导入工程
- 将源码包移动到
/src/main/java
中; - 从/META-INF/maven/org.mybatis.generator/mybatis-generator-core中将pom.xml文件
复制
一份出来放到根目录下并删除部分内容; 修改坐标
,groupId或者artifactId都是可以的;
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.local.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.0.0</version>
<name>mybatis-generator-core</name>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.6</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.20.0</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.10.13</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.9.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.2</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.24.2</version>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.7.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.javaparser</groupId>
<artifactId>javaparser-core</artifactId>
<version>3.25.0</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>1.8.10</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
其中依赖版本从parent
获取,配置完成后移除parent,配置完成后注意执行mvn clean install
操作,将包install到仓库中
。
工程依赖自定义插件
依赖自定义的工程包,groupId和artifactId可以自行修改,我这里未变更,仅修改版本号
为1.0.0,执行生成代码查看效果,我这里执行无任何变化,需要注意的是一定需要修改坐标,不然可能遇到执行出现null的情况,目测是插件执行过程中验证了某些信息。
自定义插件
说了一堆前置条件,终于到了文章的重点,自定义插件逻辑实现,本篇文章主要介绍定制化Mapper.java
、实体注释优化、Service.java生成逻辑,可以参考生成更多内容,如Controller.java、Manager.java等等,生成逻辑都是相似的几乎可以CV CV CV,一个分层结构插件可能存在比较多的上下文,为了方便按照层拆分插件数量。
- JavaMapperPlugin插件:主要
定制化基础插件
功能生成的Do.java、Mapper.java文件; - ServicePluginc插件:生成
Model.java
、Convertor.java
、Service.java
、ServiceImpl.java
文件;
添加配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!-- 设置生成的文件适用于哪个Mybatis 版本 -->
<context id="default" targetRuntime="MyBatis3">
<!-- 自定义全局属性,根据实际情况进行修改 -->
<property name="author" value="reboot"/>
<property name="baseProject" value="/Users/xx/Codes/IdeaProjects/Demos/code-gen"/>
<!-- 生成RowBounds方法 -->
<plugin type="org.mybatis.generator.plugins.RowBoundsPlugin"/>
<!-- 覆盖XML Mapper文件,POM文件中配置的是Class文件覆盖 -->
<plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"/>
<!-- 自定义插件,Mapper生成、扩展添加注释、使用通用接口 -->
<plugin type="org.mybatis.generator.plugins.custom.JavaMapperPlugin"/>
<!-- 自定义插件,Service生成 -->
<plugin type="org.mybatis.generator.plugins.custom.ServicePlugin"/>
<!-- 旨在创建class时,对注释进行控制 -->
<commentGenerator>
<property name="suppressDate" value="true"/>
<!-- 是否去除自动生成的注释 true:是 false:否 -->
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!-- jdbc的数据库连接 -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/code_gen?useSSL=false"
userId="root"
password="123456"/>
<!-- 非必须,类型处理器,在数据库类型和java类型之间的转换控制,默认情况下数据库中的decimal,bigint在Java对应是sql下的BigDecimal类 -->
<javaTypeResolver>
<!-- 使用常用的基本类型代替sql包下的引用类型 -->
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!-- 生成的实体类所在的包 -->
<javaModelGenerator targetPackage="com.example.codegen.domain" targetProject="src/main/java">
<!-- 是否允许子包 -->
<property name="enableSubPackages" value="true"/>
<!-- 是否清理从数据库中查询出的字符串左右两边的空白字符 -->
<property name="trimStrings" value="false"/>
</javaModelGenerator>
<!-- 生成的mapper xml文件的包和位置 -->
<sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
<!-- 针对数据库的一个配置,是否把schema作为字包名 -->
<property name="enableSubPackages" value="false"/>
</sqlMapGenerator>
<!-- 生成的mapper class文件的包和位置 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.example.codegen.mapper" targetProject="src/main/java">
<property name="enableSubPackages" value="false"/>
</javaClientGenerator>
<!-- 表配置 -->
<table tableName="system_users" domainObjectName="SystemUsersDo">
<property name="comment" value="系统用户"/>
<generatedKey column="id" sqlStatement="Mysql" identity="true"/>
</table>
</context>
</generatorConfiguration>
修改点如下:
<!-- 自定义全局属性,根据实际情况进行修改 -->
<property name="author" value="reboot"/>
<property name="baseProject" value="/Users/xx/Codes/IdeaProjects/Demos/code-gen"/>
<!-- 自定义插件,Mapper生成、扩展添加注释、使用通用接口 -->
<plugin type="org.mybatis.generator.plugins.custom.JavaMapperPlugin"/>
<!-- 自定义插件,Service生成 -->
<plugin type="org.mybatis.generator.plugins.custom.ServicePlugin"/>
<!-- 表配置,其中comment属性为自定义添加 -->
<table tableName="system_users" domainObjectName="SystemUsersDo">
<property name="comment" value="系统用户"/>
<generatedKey column="id" sqlStatement="Mysql" identity="true"/>
</table>
JavaMapperPlugin插件
package org.mybatis.generator.plugins.custom;
import org.mybatis.generator.api.GeneratedJavaFile;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.DefaultJavaFormatter;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.JavaVisibility;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import java.util.ArrayList;
import java.util.List;
/**
* JavaMapper插件
*
* @author reboot
*/
public class JavaMapperPlugin extends PluginAdapter {
private List<String> warnings;
private String tableComment;
private String author;
private String baseProject;
private static final String MAPPER_BASE = "/src/main/java";
private static final String MAPPER_PACKAGE = "com.example.codegen.mapper";
@Override
public boolean validate(List<String> warnings) {
this.warnings = warnings;
return true;
}
@Override
public List<GeneratedJavaFile> contextGenerateAdditionalJavaFiles(IntrospectedTable introspectedTable) {
tableComment = introspectedTable.getTableConfigurationProperty("comment");
author = introspectedTable.getContext().getProperty("author");
baseProject = introspectedTable.getContext().getProperty("baseProject");
List<GeneratedJavaFile> javaFiles = new ArrayList<>();
// DO
javaFiles.add(generateDoFile(introspectedTable));
// Mapper
javaFiles.add(generateMapperFile(introspectedTable));
return javaFiles;
}
/**
* 生成DO文件
*
* @param introspectedTable 表信息
* @return {@link GeneratedJavaFile}
*/
private GeneratedJavaFile generateDoFile(IntrospectedTable introspectedTable) {
String fullDoName = introspectedTable.getBaseRecordType();
String doName = PluginUtils.getClassName(fullDoName);
PluginUtils.fileExistCheck(fullDoName, baseProject + MAPPER_BASE, warnings);
TopLevelClass doTopLevelClass = PluginUtils.generateEntity(introspectedTable, this.context, fullDoName,
PluginUtils.classDoc(tableComment + "数据模型", doName, author));
return new GeneratedJavaFile(doTopLevelClass, baseProject + MAPPER_BASE, new DefaultJavaFormatter());
}
/**
* 生成Mapper文件
*
* @param introspectedTable 表信息
* @return {@link GeneratedJavaFile}
*/
private GeneratedJavaFile generateMapperFile(IntrospectedTable introspectedTable) {
String fullDoName = introspectedTable.getBaseRecordType();
String doName = PluginUtils.getClassName(fullDoName);
String mapperName = doName + "Mapper";
String fullMapperName = MAPPER_PACKAGE + "." + mapperName;
String exampleName = doName + "Example";
String fullExampleName = fullDoName + "Example";
PluginUtils.fileExistCheck(fullMapperName, baseProject + MAPPER_BASE, warnings);
Interface baseMapperInterface = new Interface(new FullyQualifiedJavaType(fullMapperName));
baseMapperInterface.addFileCommentLine(PluginUtils.fileComment());
baseMapperInterface.setVisibility(JavaVisibility.PUBLIC);
baseMapperInterface.addJavaDocLine(PluginUtils.classDoc(tableComment + "Mapper", mapperName, author));
baseMapperInterface.addSuperInterface(new FullyQualifiedJavaType("BaseMapper<" + doName + "," + exampleName + ">"));
baseMapperInterface.addImportedType(new FullyQualifiedJavaType("com.example.codegen.mapper.BaseMapper"));
baseMapperInterface.addImportedType(new FullyQualifiedJavaType(fullDoName));
baseMapperInterface.addImportedType(new FullyQualifiedJavaType(fullExampleName));
return new GeneratedJavaFile(baseMapperInterface, baseProject + MAPPER_BASE, new DefaultJavaFormatter());
}
}
ServicePluginc插件
package org.mybatis.generator.plugins.custom;
import org.mybatis.generator.api.GeneratedJavaFile;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.DefaultJavaFormatter;
import org.mybatis.generator.api.dom.java.Field;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.JavaVisibility;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import org.mybatis.generator.internal.util.JavaBeansUtil;
import java.util.ArrayList;
import java.util.List;
/**
* Service插件
*
* @author reboot
*/
public class ServicePlugin extends PluginAdapter {
private List<String> warnings;
private String tableComment;
private String author;
private String baseProject;
private static final String SERVICE_BASE = "/src/main/java";
private static final String SERVICE_PACKAGE = "com.example.codegen.service";
private static final String MAPPER_PACKAGE = "com.example.codegen.mapper";
@Override
public boolean validate(List<String> warnings) {
this.warnings = warnings;
return true;
}
@Override
public List<GeneratedJavaFile> contextGenerateAdditionalJavaFiles(IntrospectedTable introspectedTable) {
tableComment = introspectedTable.getTableConfigurationProperty("comment");
author = introspectedTable.getContext().getProperty("author");
baseProject = introspectedTable.getContext().getProperty("baseProject");
List<GeneratedJavaFile> javaFiles = new ArrayList<>();
// Service
javaFiles.add(generateServiceFile(introspectedTable));
return javaFiles;
}
/**
* 生成Service文件
*
* @param introspectedTable 表信息
* @return {@link GeneratedJavaFile}
*/
private GeneratedJavaFile generateServiceFile(IntrospectedTable introspectedTable) {
String fullDoName = introspectedTable.getBaseRecordType();
String doName = PluginUtils.getClassName(fullDoName);
String serviceName = doName.replace("Do", "Service");
String fullServiceName = SERVICE_PACKAGE + "." + serviceName;
String mapperName = doName + "Mapper";
String fullMapperName = MAPPER_PACKAGE + "." + mapperName;
PluginUtils.fileExistCheck(fullServiceName, baseProject + SERVICE_PACKAGE, warnings);
TopLevelClass serviceTopLevelClass = new TopLevelClass(fullServiceName);
serviceTopLevelClass.addFileCommentLine(PluginUtils.fileComment());
serviceTopLevelClass.setVisibility(JavaVisibility.PUBLIC);
serviceTopLevelClass.addJavaDocLine(PluginUtils.classDoc(tableComment + "领域服务实现", serviceName, author));
serviceTopLevelClass.addAnnotation("@Component");
Field mapperField = new Field(JavaBeansUtil.getValidPropertyName(mapperName), new FullyQualifiedJavaType(mapperName));
mapperField.setVisibility(JavaVisibility.PUBLIC);
mapperField.addAnnotation("@Autowired");
serviceTopLevelClass.addField(mapperField);
serviceTopLevelClass.addImportedType("org.springframework.stereotype.Component");
serviceTopLevelClass.addImportedType("org.springframework.beans.factory.annotation.Autowired");
serviceTopLevelClass.addImportedType(fullMapperName);
return new GeneratedJavaFile(serviceTopLevelClass, baseProject + SERVICE_BASE, new DefaultJavaFormatter());
}
}
抽取通用工具
package org.mybatis.generator.plugins.custom;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.dom.java.Field;
import org.mybatis.generator.api.dom.java.JavaVisibility;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import org.mybatis.generator.config.Context;
import org.mybatis.generator.internal.util.JavaBeansUtil;
import java.io.File;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
* 插件工具类
*
* @author reboot
*/
public class PluginUtils {
/**
* 文件存在性检查
*
* @param fullName 全名
* @param basePath 基本路径
* @param warnings 警告
*/
public static void fileExistCheck(String fullName, String basePath, List<String> warnings) {
String path = String.join(File.separator, basePath, fullName.replace(".", File.separator), ".java");
if (new File(path).exists()) {
warnings.add("java file" + fullName + " already exists in " + basePath + " and will be overridden.");
}
}
/**
* 获取类名
*
* @param recordType 记录类型
* @return {@link String}
*/
public static String getClassName(String recordType) {
return recordType.substring(recordType.lastIndexOf(".") + 1);
}
/**
* 文件注释,自行实现
*
* @return {@link String}
*/
public static String fileComment() {
return "/* 文件头 2023 */";
}
/**
* 类注释
*
* @param remark 备注
* @param className 类名
* @param author 作者
* @return {@link String}
*/
public static String classDoc(String remark, String className, String author) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm");
StringBuilder javaDoc = new StringBuilder();
javaDoc.append("/**\n");
javaDoc.append(" * " + remark + "\n");
javaDoc.append(" *\n");
javaDoc.append(" * @author " + author + "\n");
javaDoc.append(" * @version " + className + ".java, v0.1 " + LocalDateTime.now().format(dateTimeFormatter) + " " + author + "\n");
javaDoc.append(" */");
return javaDoc.toString();
}
/**
* 属性注释
*
* @param remark 备注
* @return {@link String}
*/
public static String fieldDoc(String remark) {
StringBuilder fieldDoc = new StringBuilder();
fieldDoc.append("/**\n");
fieldDoc.append("\t * " + remark);
fieldDoc.append("\n");
fieldDoc.append("\t */");
return fieldDoc.toString();
}
/**
* 生成实体
*
* @param introspectedTable 表信息
* @param context 上下文
* @param fullName 全限定名
* @param javaDoc java注释
* @return {@link TopLevelClass}
*/
public static TopLevelClass generateEntity(IntrospectedTable introspectedTable, Context context, String fullName, String javaDoc) {
TopLevelClass topLevelClass = new TopLevelClass(fullName);
topLevelClass.addFileCommentLine(fileComment());
topLevelClass.setVisibility(JavaVisibility.PUBLIC);
topLevelClass.addAnnotation("@Data");
topLevelClass.addImportedType("lombok.Data");
topLevelClass.addJavaDocLine(javaDoc);
for (IntrospectedColumn introspectedColumn : introspectedTable.getAllColumns()) {
Field field = JavaBeansUtil.getJavaBeansField(introspectedColumn, context, introspectedTable);
field.addJavaDocLine(fieldDoc(introspectedColumn.getRemarks()));
topLevelClass.addField(field);
topLevelClass.addImportedType(field.getType());
}
return topLevelClass;
}
}
验证效果
SystemUsersDo.java
SystemUsersDoMapper.java
SystemUsersService.java
Service这里仅仅举个例子,我写的插件生成内容比较简单,可以根据实际情况生成。
总结
虽说不建议重复造轮子,但是轮子没有那么好,就只好基于轮子造轮子
了,上边的写法可能不是最好的,但是能满足项目工程总体框架结构,一般的后台系统生成基本的CRUD功能
还是非常方便的,基于这种方式几乎项目中相同模式
的代码都可以生成,不要让基础的功能浪费大量时间。