CC 咖啡猫的工作空间 Coding Space

代码生成器实践

代码生成器通过模板批量生成重复代码,提升开发效率,保证代码一致性。


1. 常见方案对比

方案 特点 适用场景
MyBatis-Plus Generator 官方出品、生成 Mapper/Entity/Service 单表 CRUD
SpringBlade 生成完整前后端代码 微服务
Ruoyi 若依框架配套、Vue+Java 管理系统
OpenAPI Generator 根据 API 文档生成客户端 API 消费方
Lombok+MapStruct 减少模板代码 DTO/VO 转换
自定义生成器 按项目需求定制 特殊项目

2. MyBatis-Plus Generator

2.1 Maven 依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.5</version>
</dependency>
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.32</version>
</dependency>

2.2 生成配置

public class GeneratorApp {

    public static void main(String[] args) {
        FastAutoGenerator.create(
                "jdbc:mysql://localhost:3306/test?useSSL=false",
                "root",
                "password"
            )
            .globalConfig(builder -> {
                builder.author("generator")        // 作者
                       .outputDir("src/main/java") // 输出路径
                       .disableOpenDir();          // 生成后不打开目录
            })
            .packageConfig(builder -> {
                builder.parent("com.example")      // 父包名
                       .moduleName("gen")          // 模块名
                       .entity("entity")           // 实体类包名
                       .service("service")
                       .serviceImpl("service.impl")
                       .mapper("mapper")
                       .xml("mapper.xml");
            })
            .strategyConfig(builder -> {
                builder.addInclude("sys_user")    // 表名
                       .addTablePrefix("sys_")    // 过滤表前缀
                       // Entity 配置
                       .entityBuilder()
                       .enableLombok()
                       .enableTableFieldAnnotation()
                       .logicDeleteColumnName("deleted")
                       // Controller 配置
                       .controllerBuilder()
                       .enableRestStyle()
                       .enableHyphenStyle()
                       // Service 配置
                       .serviceBuilder()
                       .formatServiceFileName("%sService")
                       .formatServiceImplFileName("%sServiceImpl");
            })
            .templateEngine(new FreemarkerTemplateEngine())
            .execute();
    }
}

3. 自定义代码生成器

3.1 项目结构

generator/
├── src/main/java/
│   └── com/example/generator/
│       ├── GeneratorApplication.java
│       ├── GeneratorController.java
│       ├── GeneratorService.java
│       └── util/
│           └── TableMetaUtil.java
├── src/main/resources/
│   └── templates/                    # FreeMarker 模板
│       ├── entity.ftl
│       ├── mapper.ftl
│       ├── service.ftl
│       ├── serviceImpl.ftl
│       └── controller.ftl
└── generator.yml                     # 生成配置

3.2 表元数据获取

@Service
public class TableMetaUtil {

    @Autowired
    private DataSource dataSource;

    public TableMeta getTableMeta(String tableName) {
        TableMeta meta = new TableMeta();
        meta.setTableName(tableName);

        // 获取列信息
        try (Connection conn = dataSource.getConnection()) {
            DatabaseMetaData dbMeta = conn.getMetaData();
            try (ResultSet columns = dbMeta.getColumns(null, null, tableName, null)) {
                List<ColumnMeta> columns = new ArrayList<>();
                while (columns.next()) {
                    ColumnMeta col = new ColumnMeta();
                    col.setColumnName(columns.getString("COLUMN_NAME"));
                    col.setColumnType(columns.getString("TYPE_NAME"));
                    col.setComment(columns.getString("REMARKS"));
                    col.setNullable("YES".equals(columns.getString("IS_NULLABLE")));
                    columns.add(col);
                }
                meta.setColumns(columns);
            }
        }
        return meta;
    }
}

3.3 生成服务

@Service
public class GeneratorService {

    public void generate(GeneratorRequest request) {
        // 1. 获取表元数据
        TableMeta tableMeta = tableMetaUtil.getTableMeta(request.getTableName());

        // 2. 准备数据
        Map<String, Object> data = prepareData(tableMeta, request);

        // 3. 生成各层代码
        generateFile("templates/entity.ftl", data,
            request.getBasePackage(), "entity", tableMeta.getEntityName() + ".java");

        generateFile("templates/mapper.ftl", data,
            request.getBasePackage(), "mapper", tableMeta.getEntityName() + "Mapper.java");

        generateFile("templates/service.ftl", data,
            request.getBasePackage(), "service", tableMeta.getEntityName() + "Service.java");

        generateFile("templates/serviceImpl.ftl", data,
            request.getBasePackage(), "service/impl",
            tableMeta.getEntityName() + "ServiceImpl.java");

        generateFile("templates/controller.ftl", data,
            request.getBasePackage(), "controller",
            tableMeta.getEntityName() + "Controller.java");
    }

    private void generateFile(String template, Map<String, Object> data,
                              String basePackage, String subPackage, String fileName) {
        try {
            Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
            cfg.setClassLoaderForTemplateLoading(
                getClass().getClassLoader(), "/templates");

            Template tp = cfg.getTemplate(template);
            Path outputPath = Paths.get(
                request.getOutputDir(),
                basePackage.replace('.', '/'),
                subPackage,
                fileName
            );
            Files.createDirectories(outputPath.getParent());

            try (Writer writer = new FileWriter(outputPath.toFile())) {
                tp.process(data, writer);
            }
        } catch (Exception e) {
            throw new RuntimeException("生成文件失败", e);
        }
    }
}

3.4 Entity 模板示例

package ${basePackage}.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * ${tableMeta.tableComment}
 * @author ${author}
 */
@Data
@TableName("${tableMeta.tableName}")
public class ${tableMeta.entityName} implements Serializable {

<#list tableMeta.columns as column>
    <#if column.columnName == "id">
    @TableId(type = IdType.ASSIGN_ID)
    </#if>
    /**
     * ${column.columnComment}
     */
    <#if column.columnType == "DATETIME" || column.columnType == "TIMESTAMP">
    private LocalDateTime ${column.fieldName};
    <#elseif column.columnType == "BIGINT">
    private Long ${column.fieldName};
    <#elseif column.columnType == "INT">
    private Integer ${column.fieldName};
    <#elseif column.columnType == "VARCHAR">
    private String ${column.fieldName};
    <#else>
    private Object ${column.fieldName};
    </#if>
</#list>
}

4. OpenAPI 生成客户端

# openapi.yaml
openapi: 3.0.0
info:
  title: 用户API
paths:
  /users/{id}:
    get:
      operationId: getUser
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
# 使用 openapi-generator 生成 Java 客户端
openapi-generator generate \
  -i openapi.yaml \
  -g java \
  -o ./generated-client \
  --api-package=com.example.api \
  --model-package=com.example.model

# 生成 TypeScript
openapi-generator generate \
  -i openapi.yaml \
  -g typescript-fetch \
  -o ./generated-client

5. 注意事项

  1. 生成后必检:生成的代码必须 review,不能直接用于生产
  2. 模板版本管理:模板也是代码,进 Git 管理
  3. 命名规范:生成时注意类名、表名符合项目命名规范
  4. 防止覆盖:已修改的文件不要被重新生成覆盖
  5. 字段映射:数据库类型到 Java 类型要完整映射(时间类型、枚举等)

6. 推荐工作流

1. 设计数据库表(ER图)
     ↓
2. 运行代码生成器
     ↓
3. 生成基础 CRUD 代码
     ↓
4. 补充业务逻辑(生成代码只做骨架)
     ↓
5. 添加 Service 层复杂逻辑
     ↓
6. 编写单元测试

最后更新:2026/05/11