Java 1.8 mabtis-plu 代码生成工具


package com.tianShu.hr.utils;


import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import lombok.experimental.Accessors;

import java.sql.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

/**
 * MyBatis Plus 完整代码生成器 v9.0 - 达梦数据库版
 * Entity、VO、DTO 字段完全一致
 * 支持单主键和复合主键
 * 直接运行 main 方法即可
 *
 * @author chenLiTao
 */
public class CodeGeneratorDm {

    // ==================== 数据库配置 ====================
    private static final String DB_URL = "jdbc:dm://127.0.0.1:5236/hr_center?" +
            "useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&" +
            "serverTimezone=Asia/Shanghai&useSSL=false";
    private static final String DB_USERNAME = "SYSDBA";
    private static final String DB_PASSWORD = "SYSDBA";
    private static final String DB_NAME = "HR_CENTER";  // 达梦数据库名称为大写
    private static final String DB_SCHEMA = "HR_CENTER"; // 达梦模式名

    // ==================== 基础配置 ====================
    private static final String OUTPUT_DIR = System.getProperty("user.dir") + "/generated-code";
    private static final String AUTHOR = "chenLiTao";
    private static final String BASE_PACKAGE = "com.tianShu.hr";
    private static final String MODULE_NAME = "";
    private static final String TABLE_NAMES = "";
    private static final String[] TABLE_PREFIX = {"t_", "sys_", "tb_"};

    // ==================== 包路径配置 ====================
    private static final String ENTITY_PACKAGE = "entity";
    private static final String VO_PACKAGE = "param.vo";
    private static final String DTO_PACKAGE = "param.dto";
    private static final String MAPPER_PACKAGE = "mapper";
    private static final String SERVICE_PACKAGE = "service";
    private static final String SERVICE_IMPL_PACKAGE = "service.impl";
    private static final String CONTROLLER_PACKAGE = "controller";
    private static final String COMMON_PACKAGE = "common";
    private static final String MAPPER_XML_DIR = "mapper";

    // ==================== 类名后缀配置 ====================
    private static final String ENTITY_SUFFIX = "";
    private static final String VO_SUFFIX = "VO";
    private static final String DTO_SUFFIX = "DTO";
    private static final String MAPPER_SUFFIX = "Mapper";
    private static final String SERVICE_SUFFIX = "Service";
    private static final String SERVICE_IMPL_SUFFIX = "ServiceImpl";
    private static final String CONTROLLER_SUFFIX = "Controller";

    // ==================== Controller 路径配置 ====================
    private static final String CONTROLLER_BASE_PATH = "";
    private static final boolean INCLUDE_MODULE_IN_PATH = true;
    private static final boolean USE_TABLE_NAME_AS_PATH = false;

    // ==================== 生成开关 ====================
    private static final boolean GEN_ENTITY = true;
    private static final boolean GEN_VO = true;
    private static final boolean GEN_DTO = true;
    private static final boolean GEN_MAPPER = true;
    private static final boolean GEN_MAPPER_XML = true;
    private static final boolean GEN_SERVICE = true;
    private static final boolean GEN_SERVICE_IMPL = true;
    private static final boolean GEN_CONTROLLER = true;
    private static final boolean GEN_RESULT = true;
    private static final boolean GEN_PAGE_DTO = true;

    // ==================== Result配置 ====================
    private static final String RESULT_CLASS = BASE_PACKAGE + "." + COMMON_PACKAGE + ".Result";
    private static final String RESULT_CLASS_NAME = "Result";

    // ==================== 逻辑删除/创建更新字段 ====================
    private static final String[] LOGIC_DELETE_FIELDS = {"is_deleted", "deleted", "del_flag", "is_del"};
    private static final String[] CREATE_UPDATE_FIELDS = {"create_time", "update_time", "created_at",
            "updated_at", "create_date", "update_date", "gmt_create", "gmt_modified",
            "create_user", "update_user", "creater", "updater"};

    // ==================== 批量插入配置 ====================
    private static final int BATCH_SIZE = 500;

    // ==================== main ====================
    public static void main(String[] args) {
        System.out.println("\n============================================");
        System.out.println("   MyBatis Plus 代码生成器 v9.0 - 达梦数据库版");
        System.out.println("   支持单主键/复合主键");
        System.out.println("============================================\n");
        System.out.println("数据库: " + DB_NAME);
        System.out.println("输出目录: " + OUTPUT_DIR);
        System.out.println("基础包名: " + BASE_PACKAGE);
        System.out.println("模块名: " + MODULE_NAME);
        System.out.println("Controller路径: " + getControllerPath("{table}"));
        System.out.println("生成表: " + (StrUtil.isBlank(TABLE_NAMES) ? "全部表" : TABLE_NAMES));
        System.out.println();

        try {
            testConnection();
            List<TableInfo> tables = resolveTables();
            if (tables.isEmpty()) {
                System.out.println("没有找到任何有效的表!");
                return;
            }

            FileUtil.del(OUTPUT_DIR);
            System.out.println("已清理旧输出目录\n");

            if (GEN_RESULT) {
                generateResult();
            }
            if (GEN_PAGE_DTO) {
                generatePageDTO();
            }

            int successCount = 0;
            for (TableInfo table : tables) {
                try {
                    generateAll(table);
                    successCount++;
                } catch (Exception e) {
                    System.err.println("❌ 生成表 [" + table.getTableName() + "] 失败: " + e.getMessage());
                    e.printStackTrace();
                }
            }

            System.out.println("\n============================================");
            System.out.println("  ✅ 代码生成完成!成功: " + successCount + " 张表");
            System.out.println("  输出目录: " + OUTPUT_DIR);
            System.out.println("============================================");
        } catch (Exception e) {
            System.err.println("\n❌ 代码生成失败: " + e.getMessage());
            System.err.println("请检查: 1.达梦数据库是否启动 2.数据库'" + DB_NAME + "'是否存在 3.用户名密码是否正确");
            e.printStackTrace();
        }
    }

    // ==================== 数据库 ====================
    private static void testConnection() throws SQLException {
        System.out.print("测试数据库连接... ");
        try (Connection conn = getConnection()) {
            DatabaseMetaData metaData = conn.getMetaData();
            System.out.println("✅ 成功 (达梦数据库 " + metaData.getDatabaseProductVersion() + ")");
        }
    }

    private static Connection getConnection() throws SQLException {
        try {
            Class.forName("dm.jdbc.driver.DmDriver");
        } catch (ClassNotFoundException e) {
            throw new SQLException("达梦数据库驱动未找到,请确保已添加DmJdbcDriver依赖", e);
        }
        return DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD);
    }

    private static List<TableInfo> resolveTables() throws SQLException {
        if (StrUtil.isBlank(TABLE_NAMES)) {
            return getAllTables();
        }
        List<TableInfo> tables = new ArrayList<>();
        List<TableInfo> allTables = getAllTables();
        for (String name : TABLE_NAMES.split(",")) {
            String trimmed = name.trim();
            if (StrUtil.isBlank(trimmed)) {
                continue;
            }
            TableInfo table = allTables.stream()
                    .filter(t -> t.getTableName().equalsIgnoreCase(trimmed))
                    .findFirst()
                    .orElse(null);
            if (table != null) {
                tables.add(table);
            } else {
                System.out.println("⚠ 表 [" + trimmed + "] 不存在");
            }
        }
        return tables;
    }

    private static List<TableInfo> getAllTables() throws SQLException {
        List<TableInfo> tables = new ArrayList<>();
        // 达梦数据库查询表信息
        String sql = "SELECT TABLE_NAME, COMMENTS AS TABLE_COMMENT " +
                "FROM ALL_TAB_COMMENTS " +
                "WHERE OWNER = ? AND TABLE_TYPE = 'TABLE'";
        try (Connection conn = getConnection();
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setString(1, DB_SCHEMA);
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    TableInfo t = new TableInfo();
                    t.setTableName(rs.getString("TABLE_NAME"));
                    t.setTableComment(rs.getString("TABLE_COMMENT"));
                    t.setClassName(tableNameToClassName(t.getTableName()));
                    t.setVarName(tableNameToVarName(t.getTableName()));
                    t.setColumns(getTableColumns(t.getTableName()));
                    tables.add(t);
                }
            }
        }
        return tables;
    }

    private static List<ColumnInfo> getTableColumns(String tableName) throws SQLException {
        List<ColumnInfo> columns = new ArrayList<>();
        // 达梦数据库查询列信息
        String sql = "SELECT " +
                "C.COLUMN_NAME, " +
                "C.DATA_TYPE, " +
                "CM.COMMENTS AS COLUMN_COMMENT, " +
                "C.NULLABLE, " +
                "C.DATA_DEFAULT AS COLUMN_DEFAULT, " +
                "C.DATA_LENGTH AS CHARACTER_MAXIMUM_LENGTH, " +
                "CASE WHEN PK.COLUMN_NAME IS NOT NULL THEN 'PRI' ELSE '' END AS COLUMN_KEY " +
                "FROM ALL_TAB_COLUMNS C " +
                "LEFT JOIN ALL_COL_COMMENTS CM ON C.OWNER = CM.OWNER AND C.TABLE_NAME = CM.TABLE_NAME AND C.COLUMN_NAME = CM.COLUMN_NAME " +
                "LEFT JOIN ( " +
                "    SELECT CC.OWNER, CC.TABLE_NAME, CC.COLUMN_NAME " +
                "    FROM ALL_CONS_COLUMNS CC " +
                "    JOIN ALL_CONSTRAINTS C ON CC.OWNER = C.OWNER AND CC.CONSTRAINT_NAME = C.CONSTRAINT_NAME " +
                "    WHERE C.CONSTRAINT_TYPE = 'P' " +
                ") PK ON C.OWNER = PK.OWNER AND C.TABLE_NAME = PK.TABLE_NAME AND C.COLUMN_NAME = PK.COLUMN_NAME " +
                "WHERE C.OWNER = ? AND C.TABLE_NAME = ? " +
                "ORDER BY C.COLUMN_ID";
        try (Connection conn = getConnection();
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setString(1, DB_SCHEMA);
            ps.setString(2, tableName);
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    ColumnInfo col = new ColumnInfo();
                    String colName = rs.getString("COLUMN_NAME");
                    col.setColumnName(colName);
                    col.setDataType(rs.getString("DATA_TYPE"));
                    col.setColumnComment(rs.getString("COLUMN_COMMENT"));
                    col.setNullable("Y".equals(rs.getString("NULLABLE")));
                    col.setColumnDefault(rs.getString("COLUMN_DEFAULT"));
                    col.setMaxLength(rs.getInt("CHARACTER_MAXIMUM_LENGTH"));
                    col.setPrimaryKey("PRI".equals(rs.getString("COLUMN_KEY")));
                    col.setJavaType(dmDbTypeToJavaType(col.getDataType(), col.getMaxLength()));
                    col.setJavaFieldName(StrUtil.toCamelCase(colName.toLowerCase()));
                    columns.add(col);
                }
            }
        }
        return columns;
    }

    // ==================== 代码生成入口 ====================
    private static void generateAll(TableInfo table) {
        System.out.println("\n▶ 生成表 [" + table.getTableName() + "] (" + getTableComment(table) + ")");
        PrimaryKeyInfo pkInfo = getPrimaryKeyInfo(table);
        if (pkInfo.isComposite()) {
            System.out.println("  复合主键: " + pkInfo.getColumnNames());
        } else {
            System.out.println("  主键: " + pkInfo.getPrimaryKeyField());
        }
        if (table.getColumns() == null || table.getColumns().isEmpty()) {
            System.out.println("  ⚠ 无列信息,跳过");
            return;
        }
        if (GEN_ENTITY) {
            generateEntity(table);
        }
        if (GEN_VO) {
            generateVO(table);
        }
        if (GEN_DTO) {
            generateDTO(table);
        }
        if (GEN_MAPPER) {
            generateMapper(table);
        }
        if (GEN_MAPPER_XML) {
            generateMapperXml(table);
        }
        if (GEN_SERVICE) {
            generateService(table);
        }
        if (GEN_SERVICE_IMPL) {
            generateServiceImpl(table);
        }
        if (GEN_CONTROLLER) {
            generateController(table);
        }
        System.out.println("  ✔ 完成");
    }

    // ==================== Result ====================
    private static void generateResult() {
        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(getFullPackage(COMMON_PACKAGE)).append(";\n\n");
        sb.append("import io.swagger.annotations.ApiModel;\n");
        sb.append("import io.swagger.annotations.ApiModelProperty;\n");
        sb.append("import lombok.AllArgsConstructor;\n");
        sb.append("import lombok.Data;\n");
        sb.append("import lombok.NoArgsConstructor;\n\n");
        sb.append("@Data\n");
        sb.append("@NoArgsConstructor\n");
        sb.append("@AllArgsConstructor\n");
        sb.append("@ApiModel(\"统一返回结果\")\n");
        sb.append("public class Result<T> {\n\n");
        sb.append("    @ApiModelProperty(\"状态码\")\n");
        sb.append("    private Integer code;\n\n");
        sb.append("    @ApiModelProperty(\"消息\")\n");
        sb.append("    private String message;\n\n");
        sb.append("    @ApiModelProperty(\"数据\")\n");
        sb.append("    private T data;\n\n");
        sb.append("    @ApiModelProperty(\"时间戳\")\n");
        sb.append("    private Long timestamp;\n\n");
        sb.append("    public static <T> Result<T> success() {\n");
        sb.append("        return new Result<>(200, \"操作成功\", null, System.currentTimeMillis());\n");
        sb.append("    }\n\n");
        sb.append("    public static <T> Result<T> success(T data) {\n");
        sb.append("        return new Result<>(200, \"操作成功\", data, System.currentTimeMillis());\n");
        sb.append("    }\n\n");
        sb.append("    public static <T> Result<T> success(String msg, T data) {\n");
        sb.append("        return new Result<>(200, msg, data, System.currentTimeMillis());\n");
        sb.append("    }\n\n");
        sb.append("    public static <T> Result<T> error() {\n");
        sb.append("        return new Result<>(500, \"操作失败\", null, System.currentTimeMillis());\n");
        sb.append("    }\n\n");
        sb.append("    public static <T> Result<T> error(String msg) {\n");
        sb.append("        return new Result<>(500, msg, null, System.currentTimeMillis());\n");
        sb.append("    }\n\n");
        sb.append("    public static <T> Result<T> error(Integer code, String msg) {\n");
        sb.append("        return new Result<>(code, msg, null, System.currentTimeMillis());\n");
        sb.append("    }\n");
        sb.append("}\n");
        writeCommonFile(COMMON_PACKAGE, "Result.java", sb.toString());
    }

    // ==================== PageDTO ====================
    private static void generatePageDTO() {
        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(getFullPackage(DTO_PACKAGE)).append(";\n\n");
        sb.append("import io.swagger.annotations.ApiModel;\n");
        sb.append("import io.swagger.annotations.ApiModelProperty;\n");
        sb.append("import lombok.Data;\n\n");
        sb.append("@Data\n");
        sb.append("@ApiModel(\"分页请求基类\")\n");
        sb.append("public class PageDTO {\n\n");
        sb.append("    @ApiModelProperty(value = \"当前页\", example = \"1\")\n");
        sb.append("    private Long pageNum = 1L;\n\n");
        sb.append("    @ApiModelProperty(value = \"每页大小\", example = \"10\")\n");
        sb.append("    private Long pageSize = 10L;\n\n");
        sb.append("    @ApiModelProperty(value = \"排序字段\", example = \"id\")\n");
        sb.append("    private String orderBy;\n\n");
        sb.append("    @ApiModelProperty(value = \"是否升序\", example = \"false\")\n");
        sb.append("    private Boolean isAsc = false;\n");
        sb.append("}\n");
        writeCommonFile(DTO_PACKAGE, "PageDTO.java", sb.toString());
    }

    // ==================== Entity ====================
    private static void generateEntity(TableInfo table) {
        String className = table.getClassName() + ENTITY_SUFFIX;
        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(getFullPackage(ENTITY_PACKAGE)).append(";\n\n");
        sb.append("import com.baomidou.mybatisplus.annotation.*;\n");
        sb.append("import io.swagger.annotations.ApiModel;\n");
        sb.append("import io.swagger.annotations.ApiModelProperty;\n");
        sb.append("import lombok.AllArgsConstructor;\n");
        sb.append("import lombok.Builder;\n");
        sb.append("import lombok.Data;\n");
        sb.append("import lombok.NoArgsConstructor;\n");
        sb.append("import lombok.EqualsAndHashCode;\n");
        sb.append("import lombok.experimental.Accessors;\n");

        boolean hasDateTime = false;
        boolean hasBigDecimal = false;
        for (ColumnInfo col : table.getColumns()) {
            if ("LocalDateTime".equals(col.getJavaType())) {
                hasDateTime = true;
            }
            if ("BigDecimal".equals(col.getJavaType())) {
                hasBigDecimal = true;
            }
        }
        if (hasDateTime) {
            sb.append("import java.time.LocalDateTime;\n");
            sb.append("import com.fasterxml.jackson.annotation.JsonFormat;\n");
        }
        if (hasBigDecimal) {
            sb.append("import java.math.BigDecimal;\n");
        }
        sb.append("\n/**\n");
        sb.append(" * ").append(getTableComment(table)).append("实体类\n");
        sb.append(" *\n");
        sb.append(" * @author ").append(AUTHOR).append("\n");
        sb.append(" */\n");
        sb.append("@Data\n");
        sb.append("@Builder\n");
        sb.append("@NoArgsConstructor\n");
        sb.append("@AllArgsConstructor\n");
        sb.append("@EqualsAndHashCode(callSuper = false)\n");
        sb.append("@Accessors(chain = true)\n");
        sb.append("@TableName(\"").append(table.getTableName()).append("\")\n");
        sb.append("@ApiModel(value = \"").append(className).append("\", description = \"").append(getTableComment(table)).append("\")\n");
        sb.append("public class ").append(className).append(" {\n\n");

        PrimaryKeyInfo pkInfo = getPrimaryKeyInfo(table);

        for (ColumnInfo col : table.getColumns()) {
            String comment = StrUtil.isNotBlank(col.getColumnComment()) ? col.getColumnComment() : col.getJavaFieldName();
            sb.append("    @ApiModelProperty(value = \"").append(comment).append("\"");
            String example = getExampleValue(col);
            if (example != null) {
                sb.append(", example = \"").append(example).append("\"");
            }
            if (!col.isNullable() && !col.isPrimaryKey()) {
                sb.append(", required = true");
            }
            sb.append(")\n");

            if (col.isPrimaryKey()) {
                if (!pkInfo.isComposite()) {
                    String pkType = col.getJavaType();
                    if ("String".equals(pkType)) {
                        sb.append("    @TableId(value = \"").append(col.getColumnName()).append("\", type = IdType.ASSIGN_ID)\n");
                    } else if ("Long".equals(pkType) || "Integer".equals(pkType)) {
                        sb.append("    @TableId(value = \"").append(col.getColumnName()).append("\", type = IdType.AUTO)\n");
                    } else {
                        sb.append("    @TableId(value = \"").append(col.getColumnName()).append("\", type = IdType.ASSIGN_ID)\n");
                    }
                } else {
                    sb.append("    @TableId(value = \"").append(col.getColumnName()).append("\")\n");
                }
            } else if (isLogicDeleteField(col.getColumnName())) {
                sb.append("    @TableLogic\n");
                sb.append("    @TableField(\"").append(col.getColumnName()).append("\")\n");
            } else {
                sb.append("    @TableField(\"").append(col.getColumnName()).append("\")\n");
            }

            if ("LocalDateTime".equals(col.getJavaType())) {
                sb.append("    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\", timezone = \"GMT+8\")\n");
            }
            sb.append("    private ").append(col.getJavaType()).append(" ").append(col.getJavaFieldName()).append(";\n\n");
        }
        sb.append("}\n");
        writeFile(ENTITY_PACKAGE, className + ".java", sb.toString());
    }

    // ==================== VO ====================
    private static void generateVO(TableInfo table) {
        String className = table.getClassName() + VO_SUFFIX;
        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(getFullPackage(VO_PACKAGE)).append(";\n\n");
        sb.append("import com.fasterxml.jackson.annotation.JsonFormat;\n");
        sb.append("import io.swagger.annotations.ApiModel;\n");
        sb.append("import io.swagger.annotations.ApiModelProperty;\n");
        sb.append("import lombok.AllArgsConstructor;\n");
        sb.append("import lombok.Builder;\n");
        sb.append("import lombok.Data;\n");
        sb.append("import lombok.NoArgsConstructor;\n\n");

        boolean hasDateTime = false;
        boolean hasBigDecimal = false;
        for (ColumnInfo col : table.getColumns()) {
            if ("LocalDateTime".equals(col.getJavaType())) {
                hasDateTime = true;
            }
            if ("BigDecimal".equals(col.getJavaType())) {
                hasBigDecimal = true;
            }
        }
        if (hasDateTime) {
            sb.append("import java.time.LocalDateTime;\n");
        }
        if (hasBigDecimal) {
            sb.append("import java.math.BigDecimal;\n");
        }
        sb.append("\n/**\n");
        sb.append(" * ").append(getTableComment(table)).append("视图对象\n");
        sb.append(" *\n");
        sb.append(" * @author ").append(AUTHOR).append("\n");
        sb.append(" */\n");
        sb.append("@Data\n");
        sb.append("@Builder\n");
        sb.append("@NoArgsConstructor\n");
        sb.append("@AllArgsConstructor\n");
        sb.append("@ApiModel(value = \"").append(className).append("\", description = \"").append(getTableComment(table)).append("视图对象\")\n");
        sb.append("public class ").append(className).append(" {\n\n");

        for (ColumnInfo col : table.getColumns()) {
            String comment = StrUtil.isNotBlank(col.getColumnComment()) ? col.getColumnComment() : col.getJavaFieldName();
            sb.append("    @ApiModelProperty(value = \"").append(comment).append("\"");
            String example = getExampleValue(col);
            if (example != null) {
                sb.append(", example = \"").append(example).append("\"");
            }
            sb.append(")\n");
            if ("LocalDateTime".equals(col.getJavaType())) {
                sb.append("    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\", timezone = \"GMT+8\")\n");
            }
            sb.append("    private ").append(col.getJavaType()).append(" ").append(col.getJavaFieldName()).append(";\n\n");
        }
        sb.append("}\n");
        writeFile(VO_PACKAGE, className + ".java", sb.toString());
    }

    // ==================== DTO ====================
    private static void generateDTO(TableInfo table) {
        String className = table.getClassName() + DTO_SUFFIX;
        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(getFullPackage(DTO_PACKAGE)).append(";\n\n");
        sb.append("import com.fasterxml.jackson.annotation.JsonFormat;\n");
        sb.append("import io.swagger.annotations.ApiModel;\n");
        sb.append("import io.swagger.annotations.ApiModelProperty;\n");
        sb.append("import lombok.Data;\n");
        sb.append("import lombok.EqualsAndHashCode;\n\n");

        boolean hasDateTime = false;
        boolean hasBigDecimal = false;
        for (ColumnInfo col : table.getColumns()) {
            if ("LocalDateTime".equals(col.getJavaType())) {
                hasDateTime = true;
            }
            if ("BigDecimal".equals(col.getJavaType())) {
                hasBigDecimal = true;
            }
        }
        if (hasDateTime) {
            sb.append("import java.time.LocalDateTime;\n");
        }
        if (hasBigDecimal) {
            sb.append("import java.math.BigDecimal;\n");
        }
        if (hasDateTime || hasBigDecimal) {
            sb.append("\n");
        }

        sb.append("/**\n");
        sb.append(" * ").append(getTableComment(table)).append("DTO\n");
        sb.append(" * - 新增时主键为空(复合主键需要手动传入)\n");
        sb.append(" * - 修改时主键必传\n");
        sb.append(" * - 查询时传条件字段\n");
        sb.append(" *\n");
        sb.append(" * @author ").append(AUTHOR).append("\n");
        sb.append(" */\n");
        sb.append("@Data\n");
        sb.append("@EqualsAndHashCode(callSuper = true)\n");
        sb.append("@ApiModel(value = \"").append(className).append("\", description = \"").append(getTableComment(table)).append("请求DTO\")\n");
        sb.append("public class ").append(className).append(" extends PageDTO {\n\n");

        for (ColumnInfo col : table.getColumns()) {
            String comment = StrUtil.isNotBlank(col.getColumnComment()) ? col.getColumnComment() : col.getJavaFieldName();
            sb.append("    @ApiModelProperty(value = \"").append(comment).append("\"");
            if (col.isPrimaryKey()) {
                sb.append(", notes = \"修改时必填\"");
            }
            String example = getExampleValue(col);
            if (example != null) {
                sb.append(", example = \"").append(example).append("\"");
            }
            if (!col.isNullable() && !col.isPrimaryKey()) {
                sb.append(", required = true");
            }
            sb.append(")\n");
            if ("LocalDateTime".equals(col.getJavaType())) {
                sb.append("    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\", timezone = \"GMT+8\")\n");
            }
            sb.append("    private ").append(col.getJavaType()).append(" ").append(col.getJavaFieldName()).append(";\n\n");
        }
        sb.append("}\n");
        writeFile(DTO_PACKAGE, className + ".java", sb.toString());
    }

    // ==================== Mapper ====================
    private static void generateMapper(TableInfo table) {
        String className = table.getClassName() + MAPPER_SUFFIX;
        String entityClass = table.getClassName() + ENTITY_SUFFIX;
        String voClass = table.getClassName() + VO_SUFFIX;
        String dtoClass = table.getClassName() + DTO_SUFFIX;

        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(getFullPackage(MAPPER_PACKAGE)).append(";\n\n");
        sb.append("import com.baomidou.mybatisplus.core.mapper.BaseMapper;\n");
        sb.append("import com.baomidou.mybatisplus.core.metadata.IPage;\n");
        sb.append("import com.baomidou.mybatisplus.extension.plugins.pagination.Page;\n");
        sb.append("import ").append(getFullClassName(ENTITY_PACKAGE, entityClass)).append(";\n");
        sb.append("import ").append(getFullClassName(VO_PACKAGE, voClass)).append(";\n");
        sb.append("import ").append(getFullClassName(DTO_PACKAGE, dtoClass)).append(";\n");
        sb.append("import org.apache.ibatis.annotations.Mapper;\n");
        sb.append("import org.apache.ibatis.annotations.Param;\n\n");
        sb.append("import java.util.List;\n\n");
        sb.append("@Mapper\n");
        sb.append("public interface ").append(className).append(" extends BaseMapper<").append(entityClass).append("> {\n\n");
        sb.append("    /**\n");
        sb.append("     * 分页查询VO\n");
        sb.append("     *\n");
        sb.append("     * @param page 分页对象\n");
        sb.append("     * @param query 查询条件\n");
        sb.append("     * @return 分页结果\n");
        sb.append("     */\n");
        sb.append("    IPage<").append(voClass).append("> selectPageVO(Page<").append(voClass).append("> page, @Param(\"query\") ").append(dtoClass).append(" query);\n\n");
        sb.append("    /**\n");
        sb.append("     * 查询列表VO\n");
        sb.append("     *\n");
        sb.append("     * @param query 查询条件\n");
        sb.append("     * @return 列表结果\n");
        sb.append("     */\n");
        sb.append("    List<").append(voClass).append("> selectListVO(@Param(\"query\") ").append(dtoClass).append(" query);\n\n");
        sb.append("    /**\n");
        sb.append("     * 批量插入\n");
        sb.append("     *\n");
        sb.append("     * @param list 实体列表\n");
        sb.append("     * @return 影响行数\n");
        sb.append("     */\n");
        sb.append("    int insertBatch(@Param(\"list\") List<").append(entityClass).append("> list);\n\n");
        sb.append("}\n");
        writeFile(MAPPER_PACKAGE, className + ".java", sb.toString());
    }

    // ==================== Mapper XML ====================
    private static void generateMapperXml(TableInfo table) {
        String mapperClass = table.getClassName() + MAPPER_SUFFIX;
        String voClass = table.getClassName() + VO_SUFFIX;

        StringBuilder sb = new StringBuilder();
        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
        sb.append("<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n");
        sb.append("<mapper namespace=\"").append(getFullPackage(MAPPER_PACKAGE)).append(".").append(mapperClass).append("\">\n\n");

        // 基础字段列表
        sb.append("    <sql id=\"Base_Column_List\">\n");
        sb.append("        ");
        List<String> cols = new ArrayList<>();
        for (ColumnInfo col : table.getColumns()) {
            // 达梦数据库列名默认大写,使用双引号包裹
            cols.add("\"" + col.getColumnName() + "\"");
        }
        sb.append(String.join(", ", cols)).append("\n");
        sb.append("    </sql>\n\n");

        // 通用查询条件
        sb.append("    <sql id=\"base\">\n");
        sb.append("        <where>\n");

        for (ColumnInfo col : table.getColumns()) {
            if (isLogicDeleteField(col.getColumnName())) {
                sb.append("            AND \"").append(col.getColumnName()).append("\" = 0\n");
                break;
            }
        }

        for (ColumnInfo col : table.getColumns()) {
            if (isLogicDeleteField(col.getColumnName()) || isCreateUpdateField(col.getColumnName())) {
                continue;
            }
            if ("String".equals(col.getJavaType())) {
                sb.append("            <if test=\"query.").append(col.getJavaFieldName()).append(" != null and query.").append(col.getJavaFieldName()).append(" != ''\">\n");
                // 达梦数据库使用 CONCAT 函数进行模糊查询
                sb.append("                AND \"").append(col.getColumnName()).append("\" LIKE CONCAT('%', #{query.").append(col.getJavaFieldName()).append("}, '%')\n");
                sb.append("            </if>\n");
            } else {
                sb.append("            <if test=\"query.").append(col.getJavaFieldName()).append(" != null\">\n");
                sb.append("                AND \"").append(col.getColumnName()).append("\" = #{query.").append(col.getJavaFieldName()).append("}\n");
                sb.append("            </if>\n");
            }
        }

        sb.append("            <if test=\"query.orderBy != null and query.orderBy != ''\">\n");
        sb.append("                ORDER BY \"${\"query.orderBy\"}\"\n");
        sb.append("                <if test=\"query.isAsc != null and query.isAsc\">\n");
        sb.append("                    ASC\n");
        sb.append("                </if>\n");
        sb.append("                <if test=\"query.isAsc != null and !query.isAsc\">\n");
        sb.append("                    DESC\n");
        sb.append("                </if>\n");
        sb.append("            </if>\n");
        sb.append("        </where>\n");
        sb.append("    </sql>\n\n");

        sb.append("    <resultMap id=\"VOMap\" type=\"").append(getFullClassName(VO_PACKAGE, voClass)).append("\">\n");
        for (ColumnInfo col : table.getColumns()) {
            if (col.isPrimaryKey()) {
                sb.append("        <id column=\"").append(col.getColumnName()).append("\" property=\"").append(col.getJavaFieldName()).append("\"/>\n");
            }
        }
        for (ColumnInfo col : table.getColumns()) {
            if (!col.isPrimaryKey()) {
                sb.append("        <result column=\"").append(col.getColumnName()).append("\" property=\"").append(col.getJavaFieldName()).append("\"/>\n");
            }
        }
        sb.append("    </resultMap>\n\n");

        // 分页查询
        sb.append("    <select id=\"selectPageVO\" resultMap=\"VOMap\">\n");
        sb.append("        SELECT <include refid=\"Base_Column_List\"/>\n");
        sb.append("        FROM \"").append(DB_SCHEMA).append("\".\"").append(table.getTableName()).append("\"\n");
        sb.append("        <include refid=\"base\"/>\n");
        sb.append("    </select>\n\n");

        // 列表查询
        sb.append("    <select id=\"selectListVO\" resultMap=\"VOMap\">\n");
        sb.append("        SELECT <include refid=\"Base_Column_List\"/>\n");
        sb.append("        FROM \"").append(DB_SCHEMA).append("\".\"").append(table.getTableName()).append("\"\n");
        sb.append("        <include refid=\"base\"/>\n");
        sb.append("    </select>\n\n");

        // 批量插入 - 达梦数据库批量插入语法
        sb.append("    <insert id=\"insertBatch\">\n");
        sb.append("        INSERT INTO \"").append(DB_SCHEMA).append("\".\"").append(table.getTableName()).append("\" (\n");
        sb.append("        ");
        List<String> insertCols = new ArrayList<>();
        for (ColumnInfo col : table.getColumns()) {
            insertCols.add("\"" + col.getColumnName() + "\"");
        }
        sb.append(String.join(", ", insertCols)).append("\n");
        sb.append("        )\n");
        sb.append("        <foreach collection=\"list\" item=\"item\" separator=\"UNION ALL\">\n");
        sb.append("            SELECT\n");
        sb.append("            ");
        List<String> insertVals = new ArrayList<>();
        for (ColumnInfo col : table.getColumns()) {
            insertVals.add("#{item." + col.getJavaFieldName() + "} AS \"" + col.getColumnName() + "\"");
        }
        sb.append(String.join(", ", insertVals)).append("\n");
        sb.append("            FROM DUAL\n");
        sb.append("        </foreach>\n");
        sb.append("    </insert>\n\n");
        sb.append("</mapper>\n");
        writeXmlFile(MAPPER_XML_DIR, mapperClass + ".xml", sb.toString());
    }

    // ==================== Service ====================
    private static void generateService(TableInfo table) {
        String className = table.getClassName() + SERVICE_SUFFIX;
        String entityClass = table.getClassName() + ENTITY_SUFFIX;
        String voClass = table.getClassName() + VO_SUFFIX;
        String dtoClass = table.getClassName() + DTO_SUFFIX;

        PrimaryKeyInfo pkInfo = getPrimaryKeyInfo(table);
        String pkType = getPrimaryKeyType(table);
        String pkField = getPrimaryKeyFieldName(table);

        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(getFullPackage(SERVICE_PACKAGE)).append(";\n\n");
        sb.append("import com.baomidou.mybatisplus.core.metadata.IPage;\n");
        sb.append("import com.baomidou.mybatisplus.extension.service.IService;\n");
        sb.append("import ").append(getFullClassName(ENTITY_PACKAGE, entityClass)).append(";\n");
        sb.append("import ").append(getFullClassName(VO_PACKAGE, voClass)).append(";\n");
        sb.append("import ").append(getFullClassName(DTO_PACKAGE, dtoClass)).append(";\n\n");
        sb.append("import java.util.List;\n");
        if (pkInfo.isComposite()) {
            sb.append("import java.util.Map;\n");
        }
        sb.append("\n");

        sb.append("/**\n");
        sb.append(" * ").append(getTableComment(table)).append("服务接口\n");
        sb.append(" *\n");
        sb.append(" * @author ").append(AUTHOR).append("\n");
        sb.append(" */\n");
        sb.append("public interface ").append(className).append(" extends IService<").append(entityClass).append("> {\n\n");

        sb.append("    /**\n");
        sb.append("     * 分页查询VO\n");
        sb.append("     *\n");
        sb.append("     * @param dto 查询条件\n");
        sb.append("     * @return 分页结果\n");
        sb.append("     */\n");
        sb.append("    IPage<").append(voClass).append("> pageVO(").append(dtoClass).append(" dto);\n\n");

        sb.append("    /**\n");
        sb.append("     * 查询列表VO\n");
        sb.append("     *\n");
        sb.append("     * @param dto 查询条件\n");
        sb.append("     * @return 列表结果\n");
        sb.append("     */\n");
        sb.append("    List<").append(voClass).append("> listVO(").append(dtoClass).append(" dto);\n\n");

        if (pkInfo.isComposite()) {
            sb.append("    /**\n");
            sb.append("     * 根据主键查询VO(复合主键)\n");
            sb.append("     *\n");
            sb.append("     * @param pkMap 主键Map,key为字段名,value为字段值\n");
            sb.append("     * @return VO对象\n");
            sb.append("     */\n");
            sb.append("    ").append(voClass).append(" getVOById(Map<String, Object> pkMap);\n\n");
        } else {
            sb.append("    /**\n");
            sb.append("     * 根据主键查询VO\n");
            sb.append("     *\n");
            sb.append("     * @param ").append(pkField).append(" 主键ID\n");
            sb.append("     * @return VO对象\n");
            sb.append("     */\n");
            sb.append("    ").append(voClass).append(" getVOById(").append(pkType).append(" ").append(pkField).append(");\n\n");
        }

        sb.append("    /**\n");
        sb.append("     * 新增\n");
        sb.append("     *\n");
        sb.append("     * @param dto DTO对象\n");
        sb.append("     * @return 是否成功\n");
        sb.append("     */\n");
        sb.append("    Boolean add(").append(dtoClass).append(" dto);\n\n");

        sb.append("    /**\n");
        sb.append("     * 批量新增\n");
        sb.append("     *\n");
        sb.append("     * @param dtoList DTO列表\n");
        sb.append("     * @return 是否成功\n");
        sb.append("     */\n");
        sb.append("    Boolean batchAdd(List<").append(dtoClass).append("> dtoList);\n\n");

        sb.append("    /**\n");
        sb.append("     * 修改\n");
        sb.append("     *\n");
        sb.append("     * @param dto DTO对象\n");
        sb.append("     * @return 是否成功\n");
        sb.append("     */\n");
        sb.append("    Boolean update(").append(dtoClass).append(" dto);\n\n");

        if (pkInfo.isComposite()) {
            sb.append("    /**\n");
            sb.append("     * 根据主键删除(复合主键)\n");
            sb.append("     *\n");
            sb.append("     * @param pkMap 主键Map,key为字段名,value为字段值\n");
            sb.append("     * @return 是否成功\n");
            sb.append("     */\n");
            sb.append("    Boolean deleteById(Map<String, Object> pkMap);\n\n");
        } else {
            sb.append("    /**\n");
            sb.append("     * 根据主键删除\n");
            sb.append("     *\n");
            sb.append("     * @param ").append(pkField).append(" 主键ID\n");
            sb.append("     * @return 是否成功\n");
            sb.append("     */\n");
            sb.append("    Boolean deleteById(").append(pkType).append(" ").append(pkField).append(");\n\n");
        }

        sb.append("}\n");
        writeFile(SERVICE_PACKAGE, className + ".java", sb.toString());
    }

    // ==================== ServiceImpl ====================
    private static void generateServiceImpl(TableInfo table) {
        String className = table.getClassName() + SERVICE_IMPL_SUFFIX;
        String serviceClass = table.getClassName() + SERVICE_SUFFIX;
        String mapperClass = table.getClassName() + MAPPER_SUFFIX;
        String entityClass = table.getClassName() + ENTITY_SUFFIX;
        String voClass = table.getClassName() + VO_SUFFIX;
        String dtoClass = table.getClassName() + DTO_SUFFIX;

        PrimaryKeyInfo pkInfo = getPrimaryKeyInfo(table);
        String pkType = getPrimaryKeyType(table);
        String pkField = getPrimaryKeyFieldName(table);

        String logicDeleteField = null;
        for (ColumnInfo col : table.getColumns()) {
            if (isLogicDeleteField(col.getColumnName())) {
                logicDeleteField = col.getJavaFieldName();
                break;
            }
        }

        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(getFullPackage(SERVICE_IMPL_PACKAGE)).append(";\n\n");
        sb.append("import cn.hutool.core.bean.BeanUtil;\n");
        sb.append("import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\n");
        sb.append("import com.baomidou.mybatisplus.core.metadata.IPage;\n");
        sb.append("import com.baomidou.mybatisplus.core.toolkit.IdWorker;\n");
        sb.append("import com.baomidou.mybatisplus.extension.plugins.pagination.Page;\n");
        sb.append("import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\n");
        sb.append("import ").append(getFullClassName(ENTITY_PACKAGE, entityClass)).append(";\n");
        sb.append("import ").append(getFullClassName(MAPPER_PACKAGE, mapperClass)).append(";\n");
        sb.append("import ").append(getFullClassName(VO_PACKAGE, voClass)).append(";\n");
        sb.append("import ").append(getFullClassName(DTO_PACKAGE, dtoClass)).append(";\n");
        sb.append("import ").append(getFullClassName(SERVICE_PACKAGE, serviceClass)).append(";\n");
        sb.append("import lombok.extern.slf4j.Slf4j;\n");
        sb.append("import org.springframework.stereotype.Service;\n");
        sb.append("import org.springframework.transaction.annotation.Transactional;\n\n");
        sb.append("import java.util.List;\n");
        if (pkInfo.isComposite()) {
            sb.append("import java.util.Map;\n");
        }
        sb.append("import java.util.stream.Collectors;\n\n");

        sb.append("/**\n");
        sb.append(" * ").append(getTableComment(table)).append("服务实现类\n");
        sb.append(" *\n");
        sb.append(" * @author ").append(AUTHOR).append("\n");
        sb.append(" */\n");
        sb.append("@Slf4j\n");
        sb.append("@Service\n");
        sb.append("public class ").append(className).append(" extends ServiceImpl<").append(mapperClass).append(", ").append(entityClass).append("> implements ").append(serviceClass).append(" {\n\n");
        sb.append("    private static final int BATCH_SIZE = ").append(BATCH_SIZE).append(";\n\n");

        // pageVO
        sb.append("    @Override\n");
        sb.append("    public IPage<").append(voClass).append("> pageVO(").append(dtoClass).append(" dto) {\n");
        sb.append("        Page<").append(voClass).append("> page = new Page<>(dto.getPageNum(), dto.getPageSize());\n");
        sb.append("        return baseMapper.selectPageVO(page, dto);\n");
        sb.append("    }\n\n");

        // listVO
        sb.append("    @Override\n");
        sb.append("    public List<").append(voClass).append("> listVO(").append(dtoClass).append(" dto) {\n");
        sb.append("        return baseMapper.selectListVO(dto);\n");
        sb.append("    }\n\n");

        // getVOById
        if (pkInfo.isComposite()) {
            sb.append("    @Override\n");
            sb.append("    public ").append(voClass).append(" getVOById(Map<String, Object> pkMap) {\n");
            sb.append("        QueryWrapper<").append(entityClass).append("> wrapper = new QueryWrapper<>();\n");
            for (ColumnInfo col : pkInfo.getColumns()) {
                sb.append("        wrapper.eq(\"").append(col.getColumnName()).append("\", pkMap.get(\"").append(col.getJavaFieldName()).append("\"));\n");
            }
            sb.append("        ").append(entityClass).append(" entity = getOne(wrapper);\n");
            sb.append("        if (entity == null) {\n");
            sb.append("            return null;\n");
            sb.append("        }\n");
            sb.append("        return BeanUtil.copyProperties(entity, ").append(voClass).append(".class);\n");
            sb.append("    }\n\n");
        } else {
            sb.append("    @Override\n");
            sb.append("    public ").append(voClass).append(" getVOById(").append(pkType).append(" ").append(pkField).append(") {\n");
            sb.append("        ").append(entityClass).append(" entity = getById(").append(pkField).append(");\n");
            sb.append("        if (entity == null) {\n");
            sb.append("            return null;\n");
            sb.append("        }\n");
            sb.append("        return BeanUtil.copyProperties(entity, ").append(voClass).append(".class);\n");
            sb.append("    }\n\n");
        }

        // add
        sb.append("    @Override\n");
        sb.append("    @Transactional(rollbackFor = Exception.class)\n");
        sb.append("    public Boolean add(").append(dtoClass).append(" dto) {\n");
        sb.append("        ").append(entityClass).append(" entity = BeanUtil.copyProperties(dto, ").append(entityClass).append(".class);\n");
        if (!pkInfo.isComposite() && !pkInfo.getColumns().isEmpty()) {
            ColumnInfo pkCol = pkInfo.getColumns().get(0);
            String fieldName = pkCol.getJavaFieldName();
            String pkTypeCol = pkCol.getJavaType();
            if ("String".equals(pkTypeCol)) {
                sb.append("        entity.set").append(StrUtil.upperFirst(fieldName)).append("(IdWorker.getIdStr());\n");
            }
        }
        if (logicDeleteField != null) {
            sb.append("        entity.set").append(StrUtil.upperFirst(logicDeleteField)).append("(0);\n");
        }
        sb.append("        return save(entity);\n");
        sb.append("    }\n\n");

        // batchAdd
        sb.append("    @Override\n");
        sb.append("    @Transactional(rollbackFor = Exception.class)\n");
        sb.append("    public Boolean batchAdd(List<").append(dtoClass).append("> dtoList) {\n");
        sb.append("        if (dtoList == null || dtoList.isEmpty()) {\n");
        sb.append("            return true;\n");
        sb.append("        }\n");
        sb.append("        List<").append(entityClass).append("> list = dtoList.stream()\n");
        sb.append("                .map(d -> {\n");
        sb.append("                    ").append(entityClass).append(" entity = BeanUtil.copyProperties(d, ").append(entityClass).append(".class);\n");
        if (!pkInfo.isComposite() && !pkInfo.getColumns().isEmpty()) {
            ColumnInfo pkCol = pkInfo.getColumns().get(0);
            String fieldName = pkCol.getJavaFieldName();
            String pkTypeCol = pkCol.getJavaType();
            if ("String".equals(pkTypeCol)) {
                sb.append("                    entity.set").append(StrUtil.upperFirst(fieldName)).append("(IdWorker.getIdStr());\n");
            }
        }
        if (logicDeleteField != null) {
            sb.append("                    entity.set").append(StrUtil.upperFirst(logicDeleteField)).append("(0);\n");
        }
        sb.append("                    return entity;\n");
        sb.append("                })\n");
        sb.append("                .collect(Collectors.toList());\n");
        sb.append("        return insertBatch(list);\n");
        sb.append("    }\n\n");

        // insertBatch
        sb.append("    private Boolean insertBatch(List<").append(entityClass).append("> list) {\n");
        sb.append("        int total = list.size();\n");
        sb.append("        int inserted = 0;\n");
        sb.append("        for (int i = 0; i < total; i += BATCH_SIZE) {\n");
        sb.append("            int end = Math.min(i + BATCH_SIZE, total);\n");
        sb.append("            List<").append(entityClass).append("> subList = list.subList(i, end);\n");
        sb.append("            inserted += baseMapper.insertBatch(subList);\n");
        sb.append("        }\n");
        sb.append("        return inserted == total;\n");
        sb.append("    }\n\n");

        // update
        sb.append("    @Override\n");
        sb.append("    @Transactional(rollbackFor = Exception.class)\n");
        sb.append("    public Boolean update(").append(dtoClass).append(" dto) {\n");
        if (pkInfo.isComposite()) {
            for (ColumnInfo col : pkInfo.getColumns()) {
                String fieldName = col.getJavaFieldName();
                sb.append("        if (dto.get").append(StrUtil.upperFirst(fieldName)).append("() == null) {\n");
                sb.append("            throw new IllegalArgumentException(\"更新时主键字段 ").append(fieldName).append(" 不能为空\");\n");
                sb.append("        }\n");
            }
        } else if (!pkInfo.getColumns().isEmpty()) {
            String fieldName = pkInfo.getColumns().get(0).getJavaFieldName();
            sb.append("        if (dto.get").append(StrUtil.upperFirst(fieldName)).append("() == null) {\n");
            sb.append("            throw new IllegalArgumentException(\"更新时主键不能为空\");\n");
            sb.append("        }\n");
        }
        sb.append("        return updateById(BeanUtil.copyProperties(dto, ").append(entityClass).append(".class));\n");
        sb.append("    }\n\n");

        // deleteById
        if (pkInfo.isComposite()) {
            sb.append("    @Override\n");
            sb.append("    @Transactional(rollbackFor = Exception.class)\n");
            sb.append("    public Boolean deleteById(Map<String, Object> pkMap) {\n");
            sb.append("        QueryWrapper<").append(entityClass).append("> wrapper = new QueryWrapper<>();\n");
            for (ColumnInfo col : pkInfo.getColumns()) {
                sb.append("        wrapper.eq(\"").append(col.getColumnName()).append("\", pkMap.get(\"").append(col.getJavaFieldName()).append("\"));\n");
            }
            if (logicDeleteField != null) {
                sb.append("        ").append(entityClass).append(" entity = new ").append(entityClass).append("();\n");
                sb.append("        entity.set").append(StrUtil.upperFirst(logicDeleteField)).append("(1);\n");
                sb.append("        return update(entity, wrapper);\n");
            } else {
                sb.append("        return remove(wrapper);\n");
            }
            sb.append("    }\n\n");
        } else {
            sb.append("    @Override\n");
            sb.append("    @Transactional(rollbackFor = Exception.class)\n");
            sb.append("    public Boolean deleteById(").append(pkType).append(" ").append(pkField).append(") {\n");
            if (logicDeleteField != null) {
                sb.append("        ").append(entityClass).append(" entity = new ").append(entityClass).append("();\n");
                String fieldName = pkInfo.getColumns().isEmpty() ? "id" : pkInfo.getColumns().get(0).getJavaFieldName();
                sb.append("        entity.set").append(StrUtil.upperFirst(fieldName)).append("(").append(pkField).append(");\n");
                sb.append("        entity.set").append(StrUtil.upperFirst(logicDeleteField)).append("(1);\n");
                sb.append("        return updateById(entity);\n");
            } else {
                sb.append("        return removeById(").append(pkField).append(");\n");
            }
            sb.append("    }\n\n");
        }

        sb.append("}\n");
        writeFile(SERVICE_IMPL_PACKAGE, className + ".java", sb.toString());
    }

    // ==================== Controller ====================
    private static void generateController(TableInfo table) {
        String className = table.getClassName() + CONTROLLER_SUFFIX;
        String serviceClass = table.getClassName() + SERVICE_SUFFIX;
        String voClass = table.getClassName() + VO_SUFFIX;
        String dtoClass = table.getClassName() + DTO_SUFFIX;

        PrimaryKeyInfo pkInfo = getPrimaryKeyInfo(table);
        String pkType = getPrimaryKeyType(table);
        String pkField = getPrimaryKeyFieldName(table);
        String path = getControllerPath(getTablePath(table));

        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(getFullPackage(CONTROLLER_PACKAGE)).append(";\n\n");
        sb.append("import io.swagger.annotations.Api;\n");
        sb.append("import io.swagger.annotations.ApiOperation;\n");
        sb.append("import io.swagger.annotations.ApiParam;\n");
        sb.append("import ").append(RESULT_CLASS).append(";\n");
        sb.append("import com.baomidou.mybatisplus.core.metadata.IPage;\n");
        sb.append("import ").append(getFullClassName(VO_PACKAGE, voClass)).append(";\n");
        sb.append("import ").append(getFullClassName(DTO_PACKAGE, dtoClass)).append(";\n");
        sb.append("import ").append(getFullClassName(SERVICE_PACKAGE, serviceClass)).append(";\n");
        sb.append("import lombok.RequiredArgsConstructor;\n");
        sb.append("import lombok.extern.slf4j.Slf4j;\n");
        sb.append("import org.springframework.web.bind.annotation.*;\n\n");
        sb.append("import javax.validation.Valid;\n");
        sb.append("import java.util.List;\n");
        if (pkInfo.isComposite()) {
            sb.append("import java.util.Map;\n");
        }
        sb.append("\n");

        sb.append("/**\n");
        sb.append(" * ").append(getTableComment(table)).append("控制器\n");
        sb.append(" *\n");
        sb.append(" * @author ").append(AUTHOR).append("\n");
        sb.append(" */\n");
        sb.append("@Slf4j\n");
        sb.append("@RestController\n");
        sb.append("@RequiredArgsConstructor\n");
        sb.append("@RequestMapping(\"").append(path).append("\")\n");
        sb.append("@Api(tags = \"").append(getTableComment(table)).append("管理\")\n");
        sb.append("public class ").append(className).append(" {\n\n");
        sb.append("    private final ").append(serviceClass).append(" ").append(table.getVarName()).append("Service;\n\n");

        // page
        sb.append("    @PostMapping(\"/page\")\n");
        sb.append("    @ApiOperation(\"条件分页查询\")\n");
        sb.append("    public Result<IPage<").append(voClass).append(">> page(@RequestBody(required = false) @Valid ").append(dtoClass).append(" dto) {\n");
        sb.append("        dto = dto == null ? new ").append(dtoClass).append("() : dto;\n");
        sb.append("        return Result.success(").append(table.getVarName()).append("Service.pageVO(dto));\n");
        sb.append("    }\n\n");

        // list
        sb.append("    @PostMapping(\"/list\")\n");
        sb.append("    @ApiOperation(\"条件查询所有\")\n");
        sb.append("    public Result<List<").append(voClass).append(">> list(@RequestBody(required = false) ").append(dtoClass).append(" dto) {\n");
        sb.append("        dto = dto == null ? new ").append(dtoClass).append("() : dto;\n");
        sb.append("        return Result.success(").append(table.getVarName()).append("Service.listVO(dto));\n");
        sb.append("    }\n\n");

        // getById
        if (pkInfo.isComposite()) {
            sb.append("    @PostMapping(\"/detail\")\n");
            sb.append("    @ApiOperation(\"根据主键查询(复合主键)\")\n");
            sb.append("    public Result<").append(voClass).append("> getById(@RequestBody Map<String, Object> pkMap) {\n");
            sb.append("        return Result.success(").append(table.getVarName()).append("Service.getVOById(pkMap));\n");
            sb.append("    }\n\n");
        } else {
            sb.append("    @GetMapping(\"/{").append(pkField).append("}\")\n");
            sb.append("    @ApiOperation(\"根据主键查询\")\n");
            sb.append("    public Result<").append(voClass).append("> getById(@ApiParam(\"主键ID\") @PathVariable ").append(pkType).append(" ").append(pkField).append(") {\n");
            sb.append("        return Result.success(").append(table.getVarName()).append("Service.getVOById(").append(pkField).append("));\n");
            sb.append("    }\n\n");
        }

        // add
        sb.append("    @PostMapping\n");
        sb.append("    @ApiOperation(\"新增\")\n");
        sb.append("    public Result<Boolean> add(@RequestBody @Valid ").append(dtoClass).append(" dto) {\n");
        sb.append("        return Result.success(\"新增成功\", ").append(table.getVarName()).append("Service.add(dto));\n");
        sb.append("    }\n\n");

        // batchAdd
        sb.append("    @PostMapping(\"/batch\")\n");
        sb.append("    @ApiOperation(\"批量新增\")\n");
        sb.append("    public Result<Boolean> batchAdd(@RequestBody @Valid List<").append(dtoClass).append("> dtoList) {\n");
        sb.append("        return Result.success(\"批量新增成功\", ").append(table.getVarName()).append("Service.batchAdd(dtoList));\n");
        sb.append("    }\n\n");

        // update
        sb.append("    @PutMapping\n");
        sb.append("    @PostMapping(\"/update\")\n");
        sb.append("    @ApiOperation(\"修改\")\n");
        sb.append("    public Result<Boolean> update(@RequestBody @Valid ").append(dtoClass).append(" dto) {\n");
        sb.append("        return Result.success(\"修改成功\", ").append(table.getVarName()).append("Service.update(dto));\n");
        sb.append("    }\n\n");

        // delete
        if (pkInfo.isComposite()) {
            sb.append("    @DeleteMapping\n");
            sb.append("    @ApiOperation(\"根据主键删除(复合主键)\")\n");
            sb.append("    public Result<Boolean> delete(@RequestBody Map<String, Object> pkMap) {\n");
            sb.append("        return Result.success(\"删除成功\", ").append(table.getVarName()).append("Service.deleteById(pkMap));\n");
            sb.append("    }\n\n");
        } else {
            sb.append("    @DeleteMapping(\"/{").append(pkField).append("}\")\n");
            sb.append("    @ApiOperation(\"根据主键删除\")\n");
            sb.append("    public Result<Boolean> delete(@ApiParam(\"主键ID\") @PathVariable ").append(pkType).append(" ").append(pkField).append(") {\n");
            sb.append("        return Result.success(\"删除成功\", ").append(table.getVarName()).append("Service.deleteById(").append(pkField).append("));\n");
            sb.append("    }\n\n");
        }

        sb.append("}\n");
        writeFile(CONTROLLER_PACKAGE, className + ".java", sb.toString());
    }

    // ==================== 文件写入 ====================
    private static void writeFile(String pkgDir, String fileName, String content) {
        String pkgPath = pkgDir.replace(".", "/");
        String path = String.format("%s/src/main/java/%s/%s/%s",
                OUTPUT_DIR,
                BASE_PACKAGE.replace(".", "/"),
                pkgPath,
                fileName);
        FileUtil.writeUtf8String(content, path);
    }

    private static void writeXmlFile(String xmlDir, String fileName, String content) {
        String path = String.format("%s/src/main/resources/%s/%s", OUTPUT_DIR, xmlDir, fileName);
        FileUtil.writeUtf8String(content, path);
    }

    private static void writeCommonFile(String pkgDir, String fileName, String content) {
        String pkgPath = pkgDir.replace(".", "/");
        String path = String.format("%s/src/main/java/%s/%s/%s",
                OUTPUT_DIR,
                BASE_PACKAGE.replace(".", "/"),
                pkgPath,
                fileName);
        FileUtil.writeUtf8String(content, path);
        System.out.println("  ✔ " + fileName);
    }

    // ==================== 工具方法 ====================
    private static String getControllerPath(String tablePath) {
        StringBuilder sb = new StringBuilder(CONTROLLER_BASE_PATH);
        if (INCLUDE_MODULE_IN_PATH && StrUtil.isNotBlank(MODULE_NAME)) {
            sb.append("/").append(MODULE_NAME);
        }
        if (StrUtil.isNotBlank(tablePath)) {
            sb.append("/").append(tablePath);
        }
        return sb.toString();
    }

    private static String getTablePath(TableInfo table) {
        return USE_TABLE_NAME_AS_PATH ? table.getTableName().toLowerCase() : table.getVarName();
    }

    private static String getFullPackage(String pkg) {
        return BASE_PACKAGE + "." + pkg;
    }

    private static String getFullClassName(String pkg, String className) {
        return getFullPackage(pkg) + "." + className;
    }

    private static String tableNameToClassName(String tableName) {
        String name = tableName.toLowerCase();
        for (String p : TABLE_PREFIX) {
            if (name.startsWith(p)) {
                name = name.substring(p.length());
                break;
            }
        }
        return StrUtil.upperFirst(StrUtil.toCamelCase(name));
    }

    private static String tableNameToVarName(String tableName) {
        String name = tableName.toLowerCase();
        for (String p : TABLE_PREFIX) {
            if (name.startsWith(p)) {
                name = name.substring(p.length());
                break;
            }
        }
        return StrUtil.toCamelCase(name);
    }

    /**
     * 达梦数据库类型映射到Java类型
     */
    private static String dmDbTypeToJavaType(String dbType, Integer maxLength) {
        if (dbType == null) {
            return "String";
        }
        String upperType = dbType.toUpperCase().trim();
        switch (upperType) {
            case "INT":
            case "INTEGER":
            case "TINYINT":
            case "SMALLINT":
            case "MEDIUMINT":
            case "PLS_INTEGER":
                return "Integer";
            case "BIGINT":
                return "Long";
            case "FLOAT":
            case "REAL":
                return "Float";
            case "DOUBLE":
            case "DOUBLE PRECISION":
            case "BINARY_DOUBLE":
                return "Double";
            case "DECIMAL":
            case "DEC":
            case "NUMERIC":
            case "NUMBER":
                // 达梦的NUMBER类型根据精度判断
                if (maxLength != null && maxLength > 0) {
                    return "BigDecimal";
                }
                return "Long";
            case "DATE":
            case "DATETIME":
            case "TIMESTAMP":
            case "TIME":
                return "LocalDateTime";
            case "BIT":
            case "BOOLEAN":
                return "Boolean";
            case "CHAR":
            case "CHARACTER":
            case "VARCHAR":
            case "VARCHAR2":
            case "NVARCHAR":
            case "NVARCHAR2":
            case "LONGVARCHAR":
            case "TEXT":
            case "CLOB":
            case "LONG":
                return "String";
            case "BLOB":
            case "BINARY":
            case "VARBINARY":
            case "IMAGE":
            case "BFILE":
            case "RAW":
                return "byte[]";
            default:
                return "String";
        }
    }

    private static String getExampleValue(ColumnInfo col) {
        String name = col.getJavaFieldName().toLowerCase();
        switch (col.getJavaType()) {
            case "String":
                if (name.contains("mail") || name.contains("email")) {
                    return "example@email.com";
                }
                if (name.contains("phone") || name.contains("mobile")) {
                    return "13800138000";
                }
                return "示例" + col.getJavaFieldName();
            case "Integer":
                return name.contains("age") ? "18" : "1";
            case "Long":
                return "1";
            case "Double":
            case "Float":
                return "1.0";
            case "BigDecimal":
                return "0.00";
            case "Boolean":
                return "true";
            case "LocalDateTime":
                return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            default:
                return null;
        }
    }

    private static boolean isLogicDeleteField(String columnName) {
        for (String f : LOGIC_DELETE_FIELDS) {
            if (f.equalsIgnoreCase(columnName)) {
                return true;
            }
        }
        return false;
    }

    private static boolean isCreateUpdateField(String columnName) {
        for (String f : CREATE_UPDATE_FIELDS) {
            if (f.equalsIgnoreCase(columnName)) {
                return true;
            }
        }
        return false;
    }

    private static String getTableComment(TableInfo table) {
        return StrUtil.isNotBlank(table.getTableComment()) ? table.getTableComment() : table.getClassName();
    }

    // ==================== 主键信息类 ====================
    private static class PrimaryKeyInfo {
        private final List<ColumnInfo> columns = new ArrayList<>();
        private boolean composite;

        public List<ColumnInfo> getColumns() {
            return columns;
        }

        public boolean isComposite() {
            return composite;
        }

        public void setComposite(boolean composite) {
            this.composite = composite;
        }

        public String getPrimaryKeyField() {
            if (columns.isEmpty()) {
                return "id";
            }
            return columns.get(0).getJavaFieldName();
        }

        public String getPrimaryKeyType() {
            if (columns.isEmpty()) {
                return "Long";
            }
            if (composite) {
                return "String";
            }
            return columns.get(0).getJavaType();
        }

        public String getColumnNames() {
            if (columns.isEmpty()) {
                return "id";
            }
            List<String> names = new ArrayList<>();
            for (ColumnInfo col : columns) {
                names.add(col.getColumnName());
            }
            return String.join(", ", names);
        }
    }

    // ==================== 主键辅助方法 ====================
    private static PrimaryKeyInfo getPrimaryKeyInfo(TableInfo table) {
        PrimaryKeyInfo pkInfo = new PrimaryKeyInfo();
        if (table.getColumns() == null) {
            return pkInfo;
        }
        for (ColumnInfo col : table.getColumns()) {
            if (col.isPrimaryKey()) {
                pkInfo.getColumns().add(col);
            }
        }
        pkInfo.setComposite(pkInfo.getColumns().size() > 1);
        return pkInfo;
    }

    private static String getPrimaryKeyType(TableInfo table) {
        PrimaryKeyInfo pkInfo = getPrimaryKeyInfo(table);
        if (pkInfo.getColumns().isEmpty()) {
            return "Long";
        }
        if (pkInfo.isComposite()) {
            return "String";
        }
        return pkInfo.getColumns().get(0).getJavaType();
    }

    private static String getPrimaryKeyFieldName(TableInfo table) {
        PrimaryKeyInfo pkInfo = getPrimaryKeyInfo(table);
        if (pkInfo.getColumns().isEmpty()) {
            return "id";
        }
        if (pkInfo.isComposite()) {
            return "id";
        }
        return pkInfo.getColumns().get(0).getJavaFieldName();
    }

    // ==================== 内部类 ====================
    @Data
    @Accessors(chain = true)
    private static class TableInfo {
        private String tableName;
        private String tableComment;
        private String className;
        private String varName;
        private List<ColumnInfo> columns;
    }

    @Data
    @Accessors(chain = true)
    private static class ColumnInfo {
        private String columnName;
        private String dataType;
        private String columnComment;
        private boolean nullable;
        private String columnDefault;
        private Integer maxLength;
        private boolean primaryKey;
        private String javaType;
        private String javaFieldName;
    }
}

 

package com.tianshu.hr.utills;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Data;
import lombok.experimental.Accessors;

import java.sql.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

/**
 * MyBatis Plus 完整代码生成器 v9.0
 * Entity、VO、DTO 字段完全一致
 * 支持单主键和复合主键
 * 直接运行 main 方法即可
 *
 * @author chenLiTao
 */
public class CodeGenerator {

    // ==================== 数据库配置 ====================
    private static final String DB_URL = "jdbc:mysql://127.0.0.1:3306/hr_center?" +
            "useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&" +
            "serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true";
    private static final String DB_USERNAME = "root";
    private static final String DB_PASSWORD = "root";
    private static final String DB_NAME = "hr_center";

    // ==================== 基础配置 ====================
    private static final String OUTPUT_DIR = System.getProperty("user.dir") + "/generated-code";
    private static final String AUTHOR = "chenLiTao";
    private static final String BASE_PACKAGE = "com.tianShu.hr";
    private static final String MODULE_NAME = "";
    private static final String TABLE_NAMES = "";
    private static final String[] TABLE_PREFIX = {"t_", "sys_", "tb_"};

    // ==================== 包路径配置 ====================
    private static final String ENTITY_PACKAGE = "entity";
    private static final String VO_PACKAGE = "param.vo";
    private static final String DTO_PACKAGE = "param.dto";
    private static final String MAPPER_PACKAGE = "mapper";
    private static final String SERVICE_PACKAGE = "service";
    private static final String SERVICE_IMPL_PACKAGE = "service.impl";
    private static final String CONTROLLER_PACKAGE = "controller";
    private static final String COMMON_PACKAGE = "common";
    private static final String MAPPER_XML_DIR = "mapper";

    // ==================== 类名后缀配置 ====================
    private static final String ENTITY_SUFFIX = "";
    private static final String VO_SUFFIX = "VO";
    private static final String DTO_SUFFIX = "DTO";
    private static final String MAPPER_SUFFIX = "Mapper";
    private static final String SERVICE_SUFFIX = "Service";
    private static final String SERVICE_IMPL_SUFFIX = "ServiceImpl";
    private static final String CONTROLLER_SUFFIX = "Controller";

    // ==================== Controller 路径配置 ====================
    private static final String CONTROLLER_BASE_PATH = "";
    private static final boolean INCLUDE_MODULE_IN_PATH = true;
    private static final boolean USE_TABLE_NAME_AS_PATH = false;

    // ==================== 生成开关 ====================
    private static final boolean GEN_ENTITY = true;
    private static final boolean GEN_VO = true;
    private static final boolean GEN_DTO = true;
    private static final boolean GEN_MAPPER = true;
    private static final boolean GEN_MAPPER_XML = true;
    private static final boolean GEN_SERVICE = true;
    private static final boolean GEN_SERVICE_IMPL = true;
    private static final boolean GEN_CONTROLLER = true;
    private static final boolean GEN_RESULT = true;
    private static final boolean GEN_PAGE_DTO = true;

    // ==================== Result配置 ====================
    private static final String RESULT_CLASS = BASE_PACKAGE + "." + COMMON_PACKAGE + ".Result";
    private static final String RESULT_CLASS_NAME = "Result";

    // ==================== 逻辑删除/创建更新字段 ====================
    private static final String[] LOGIC_DELETE_FIELDS = {"is_deleted", "deleted", "del_flag", "is_del"};
    private static final String[] CREATE_UPDATE_FIELDS = {"create_time", "update_time", "created_at",
            "updated_at", "create_date", "update_date", "gmt_create", "gmt_modified",
            "create_user", "update_user", "creater", "updater"};

    // ==================== 批量插入配置 ====================
    private static final int BATCH_SIZE = 500;

    // ==================== main ====================
    public static void main(String[] args) {
        System.out.println("\n============================================");
        System.out.println("   MyBatis Plus 代码生成器 v9.0");
        System.out.println("   支持单主键/复合主键");
        System.out.println("============================================\n");
        System.out.println("数据库: " + DB_NAME);
        System.out.println("输出目录: " + OUTPUT_DIR);
        System.out.println("基础包名: " + BASE_PACKAGE);
        System.out.println("模块名: " + MODULE_NAME);
        System.out.println("Controller路径: " + getControllerPath("{table}"));
        System.out.println("生成表: " + (StrUtil.isBlank(TABLE_NAMES) ? "全部表" : TABLE_NAMES));
        System.out.println();

        try {
            testConnection();
            List<TableInfo> tables = resolveTables();
            if (tables.isEmpty()) {
                System.out.println("没有找到任何有效的表!");
                return;
            }

            FileUtil.del(OUTPUT_DIR);
            System.out.println("已清理旧输出目录\n");

            if (GEN_RESULT) {
                generateResult();
            }
            if (GEN_PAGE_DTO) {
                generatePageDTO();
            }

            int successCount = 0;
            for (TableInfo table : tables) {
                try {
                    generateAll(table);
                    successCount++;
                } catch (Exception e) {
                    System.err.println("❌ 生成表 [" + table.getTableName() + "] 失败: " + e.getMessage());
                    e.printStackTrace();
                }
            }

            System.out.println("\n============================================");
            System.out.println("  ✅ 代码生成完成!成功: " + successCount + " 张表");
            System.out.println("  输出目录: " + OUTPUT_DIR);
            System.out.println("============================================");
        } catch (Exception e) {
            System.err.println("\n❌ 代码生成失败: " + e.getMessage());
            System.err.println("请检查: 1.MySQL是否启动 2.数据库'" + DB_NAME + "'是否存在 3.用户名密码是否正确");
            e.printStackTrace();
        }
    }

    // ==================== 数据库 ====================
    private static void testConnection() throws SQLException {
        System.out.print("测试数据库连接... ");
        try (Connection conn = getConnection()) {
            System.out.println("✅ 成功 (MySQL " + conn.getMetaData().getDatabaseProductVersion() + ")");
        }
    }

    private static Connection getConnection() throws SQLException {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            throw new SQLException("MySQL驱动未找到", e);
        }
        return DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD);
    }

    private static List<TableInfo> resolveTables() throws SQLException {
        if (StrUtil.isBlank(TABLE_NAMES)) {
            return getAllTables();
        }
        List<TableInfo> tables = new ArrayList<>();
        List<TableInfo> allTables = getAllTables();
        for (String name : TABLE_NAMES.split(",")) {
            String trimmed = name.trim();
            if (StrUtil.isBlank(trimmed)) {
                continue;
            }
            TableInfo table = allTables.stream()
                    .filter(t -> t.getTableName().equalsIgnoreCase(trimmed))
                    .findFirst()
                    .orElse(null);
            if (table != null) {
                tables.add(table);
            } else {
                System.out.println("⚠ 表 [" + trimmed + "] 不存在");
            }
        }
        return tables;
    }

    private static List<TableInfo> getAllTables() throws SQLException {
        List<TableInfo> tables = new ArrayList<>();
        String sql = "SELECT TABLE_NAME, TABLE_COMMENT FROM information_schema.TABLES " +
                "WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = 'BASE TABLE'";
        try (Connection conn = getConnection();
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setString(1, DB_NAME);
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    TableInfo t = new TableInfo();
                    t.setTableName(rs.getString("TABLE_NAME"));
                    t.setTableComment(rs.getString("TABLE_COMMENT"));
                    t.setClassName(tableNameToClassName(t.getTableName()));
                    t.setVarName(tableNameToVarName(t.getTableName()));
                    t.setColumns(getTableColumns(t.getTableName()));
                    tables.add(t);
                }
            }
        }
        return tables;
    }

    private static List<ColumnInfo> getTableColumns(String tableName) throws SQLException {
        List<ColumnInfo> columns = new ArrayList<>();
        String sql = "SELECT COLUMN_NAME, DATA_TYPE, COLUMN_COMMENT, IS_NULLABLE, " +
                "COLUMN_DEFAULT, CHARACTER_MAXIMUM_LENGTH, COLUMN_KEY " +
                "FROM information_schema.COLUMNS " +
                "WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? " +
                "ORDER BY ORDINAL_POSITION";
        try (Connection conn = getConnection();
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setString(1, DB_NAME);
            ps.setString(2, tableName);
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    ColumnInfo col = new ColumnInfo();
                    String colName = rs.getString("COLUMN_NAME");
                    col.setColumnName(colName);
                    col.setDataType(rs.getString("DATA_TYPE"));
                    col.setColumnComment(rs.getString("COLUMN_COMMENT"));
                    col.setNullable("YES".equals(rs.getString("IS_NULLABLE")));
                    col.setColumnDefault(rs.getString("COLUMN_DEFAULT"));
                    col.setMaxLength(rs.getInt("CHARACTER_MAXIMUM_LENGTH"));
                    col.setPrimaryKey("PRI".equals(rs.getString("COLUMN_KEY")));
                    col.setJavaType(dbTypeToJavaType(col.getDataType()));
                    col.setJavaFieldName(StrUtil.toCamelCase(colName.toLowerCase()));
                    columns.add(col);
                }
            }
        }
        return columns;
    }

    // ==================== 代码生成入口 ====================
    private static void generateAll(TableInfo table) {
        System.out.println("\n▶ 生成表 [" + table.getTableName() + "] (" + getTableComment(table) + ")");
        PrimaryKeyInfo pkInfo = getPrimaryKeyInfo(table);
        if (pkInfo.isComposite()) {
            System.out.println("  复合主键: " + pkInfo.getColumnNames());
        } else {
            System.out.println("  主键: " + pkInfo.getPrimaryKeyField());
        }
        if (table.getColumns() == null || table.getColumns().isEmpty()) {
            System.out.println("  ⚠ 无列信息,跳过");
            return;
        }
        if (GEN_ENTITY) {
            generateEntity(table);
        }
        if (GEN_VO) {
            generateVO(table);
        }
        if (GEN_DTO) {
            generateDTO(table);
        }
        if (GEN_MAPPER) {
            generateMapper(table);
        }
        if (GEN_MAPPER_XML) {
            generateMapperXml(table);
        }
        if (GEN_SERVICE) {
            generateService(table);
        }
        if (GEN_SERVICE_IMPL) {
            generateServiceImpl(table);
        }
        if (GEN_CONTROLLER) {
            generateController(table);
        }
        System.out.println("  ✔ 完成");
    }

    // ==================== Result ====================
    private static void generateResult() {
        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(getFullPackage(COMMON_PACKAGE)).append(";\n\n");
        sb.append("import io.swagger.annotations.ApiModel;\n");
        sb.append("import io.swagger.annotations.ApiModelProperty;\n");
        sb.append("import lombok.AllArgsConstructor;\n");
        sb.append("import lombok.Data;\n");
        sb.append("import lombok.NoArgsConstructor;\n\n");
        sb.append("@Data\n");
        sb.append("@NoArgsConstructor\n");
        sb.append("@AllArgsConstructor\n");
        sb.append("@ApiModel(\"统一返回结果\")\n");
        sb.append("public class Result<T> {\n\n");
        sb.append("    @ApiModelProperty(\"状态码\")\n");
        sb.append("    private Integer code;\n\n");
        sb.append("    @ApiModelProperty(\"消息\")\n");
        sb.append("    private String message;\n\n");
        sb.append("    @ApiModelProperty(\"数据\")\n");
        sb.append("    private T data;\n\n");
        sb.append("    @ApiModelProperty(\"时间戳\")\n");
        sb.append("    private Long timestamp;\n\n");
        sb.append("    public static <T> Result<T> success() {\n");
        sb.append("        return new Result<>(200, \"操作成功\", null, System.currentTimeMillis());\n");
        sb.append("    }\n\n");
        sb.append("    public static <T> Result<T> success(T data) {\n");
        sb.append("        return new Result<>(200, \"操作成功\", data, System.currentTimeMillis());\n");
        sb.append("    }\n\n");
        sb.append("    public static <T> Result<T> success(String msg, T data) {\n");
        sb.append("        return new Result<>(200, msg, data, System.currentTimeMillis());\n");
        sb.append("    }\n\n");
        sb.append("    public static <T> Result<T> error() {\n");
        sb.append("        return new Result<>(500, \"操作失败\", null, System.currentTimeMillis());\n");
        sb.append("    }\n\n");
        sb.append("    public static <T> Result<T> error(String msg) {\n");
        sb.append("        return new Result<>(500, msg, null, System.currentTimeMillis());\n");
        sb.append("    }\n\n");
        sb.append("    public static <T> Result<T> error(Integer code, String msg) {\n");
        sb.append("        return new Result<>(code, msg, null, System.currentTimeMillis());\n");
        sb.append("    }\n");
        sb.append("}\n");
        writeCommonFile(COMMON_PACKAGE, "Result.java", sb.toString());
    }

    // ==================== PageDTO ====================
    private static void generatePageDTO() {
        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(getFullPackage(DTO_PACKAGE)).append(";\n\n");
        sb.append("import io.swagger.annotations.ApiModel;\n");
        sb.append("import io.swagger.annotations.ApiModelProperty;\n");
        sb.append("import lombok.Data;\n\n");
        sb.append("@Data\n");
        sb.append("@ApiModel(\"分页请求基类\")\n");
        sb.append("public class PageDTO {\n\n");
        sb.append("    @ApiModelProperty(value = \"当前页\", example = \"1\")\n");
        sb.append("    private Long pageNum = 1L;\n\n");
        sb.append("    @ApiModelProperty(value = \"每页大小\", example = \"10\")\n");
        sb.append("    private Long pageSize = 10L;\n\n");
        sb.append("    @ApiModelProperty(value = \"排序字段\", example = \"id\")\n");
        sb.append("    private String orderBy;\n\n");
        sb.append("    @ApiModelProperty(value = \"是否升序\", example = \"false\")\n");
        sb.append("    private Boolean isAsc = false;\n");
        sb.append("}\n");
        writeCommonFile(DTO_PACKAGE, "PageDTO.java", sb.toString());
    }

    // ==================== Entity ====================
    private static void generateEntity(TableInfo table) {
        String className = table.getClassName() + ENTITY_SUFFIX;
        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(getFullPackage(ENTITY_PACKAGE)).append(";\n\n");
        sb.append("import com.baomidou.mybatisplus.annotation.*;\n");
        sb.append("import io.swagger.annotations.ApiModel;\n");
        sb.append("import io.swagger.annotations.ApiModelProperty;\n");
        sb.append("import lombok.AllArgsConstructor;\n");
        sb.append("import lombok.Builder;\n");
        sb.append("import lombok.Data;\n");
        sb.append("import lombok.NoArgsConstructor;\n");
        sb.append("import lombok.EqualsAndHashCode;\n");
        sb.append("import lombok.experimental.Accessors;\n");

        boolean hasDateTime = false;
        boolean hasBigDecimal = false;
        for (ColumnInfo col : table.getColumns()) {
            if ("LocalDateTime".equals(col.getJavaType())) {
                hasDateTime = true;
            }
            if ("BigDecimal".equals(col.getJavaType())) {
                hasBigDecimal = true;
            }
        }
        if (hasDateTime) {
            sb.append("import java.time.LocalDateTime;\n");
            sb.append("import com.fasterxml.jackson.annotation.JsonFormat;\n");
        }
        if (hasBigDecimal) {
            sb.append("import java.math.BigDecimal;\n");
        }
        sb.append("\n/**\n");
        sb.append(" * ").append(getTableComment(table)).append("实体类\n");
        sb.append(" *\n");
        sb.append(" * @author ").append(AUTHOR).append("\n");
        sb.append(" */\n");
        sb.append("@Data\n");
        sb.append("@Builder\n");
        sb.append("@NoArgsConstructor\n");
        sb.append("@AllArgsConstructor\n");
        sb.append("@EqualsAndHashCode(callSuper = false)\n");
        sb.append("@Accessors(chain = true)\n");
        sb.append("@TableName(\"").append(table.getTableName()).append("\")\n");
        sb.append("@ApiModel(value = \"").append(className).append("\", description = \"").append(getTableComment(table)).append("\")\n");
        sb.append("public class ").append(className).append(" {\n\n");

        PrimaryKeyInfo pkInfo = getPrimaryKeyInfo(table);

        for (ColumnInfo col : table.getColumns()) {
            String comment = StrUtil.isNotBlank(col.getColumnComment()) ? col.getColumnComment() : col.getJavaFieldName();
            sb.append("    @ApiModelProperty(value = \"").append(comment).append("\"");
            String example = getExampleValue(col);
            if (example != null) {
                sb.append(", example = \"").append(example).append("\"");
            }
            if (!col.isNullable() && !col.isPrimaryKey()) {
                sb.append(", required = true");
            }
            sb.append(")\n");

            if (col.isPrimaryKey()) {
                // 修改原因:根据主键类型决定ID生成策略,而非仅判断字段名是否包含"id"
                // 修改前:只判断字段名包含id才使用雪花算法
                // 修改后:根据主键Java类型决定策略 - String用雪花算法,Long/Integer用自增
                if (!pkInfo.isComposite()) {
                    String pkType = col.getJavaType();
                    if ("String".equals(pkType)) {
                        // String类型主键:使用雪花算法生成字符串ID
                        sb.append("    @TableId(value = \"").append(col.getColumnName()).append("\", type = IdType.ASSIGN_ID)\n");
                    } else if ("Long".equals(pkType) || "Integer".equals(pkType)) {
                        // Long/Integer类型主键:使用数据库自增
                        sb.append("    @TableId(value = \"").append(col.getColumnName()).append("\", type = IdType.AUTO)\n");
                    } else {
                        // 其他类型:使用ASSIGN_ID
                        sb.append("    @TableId(value = \"").append(col.getColumnName()).append("\", type = IdType.ASSIGN_ID)\n");
                    }
                } else {
                    // 复合主键不自动生成
                    sb.append("    @TableId(value = \"").append(col.getColumnName()).append("\")\n");
                }
            } else if (isLogicDeleteField(col.getColumnName())) {
                sb.append("    @TableLogic\n");
                sb.append("    @TableField(\"").append(col.getColumnName()).append("\")\n");
            } else {
                sb.append("    @TableField(\"").append(col.getColumnName()).append("\")\n");
            }

            if ("LocalDateTime".equals(col.getJavaType())) {
                sb.append("    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\", timezone = \"GMT+8\")\n");
            }
            sb.append("    private ").append(col.getJavaType()).append(" ").append(col.getJavaFieldName()).append(";\n\n");
        }
        sb.append("}\n");
        writeFile(ENTITY_PACKAGE, className + ".java", sb.toString());
    }

    // ==================== VO ====================
    private static void generateVO(TableInfo table) {
        String className = table.getClassName() + VO_SUFFIX;
        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(getFullPackage(VO_PACKAGE)).append(";\n\n");
        sb.append("import com.fasterxml.jackson.annotation.JsonFormat;\n");
        sb.append("import io.swagger.annotations.ApiModel;\n");
        sb.append("import io.swagger.annotations.ApiModelProperty;\n");
        sb.append("import lombok.AllArgsConstructor;\n");
        sb.append("import lombok.Builder;\n");
        sb.append("import lombok.Data;\n");
        sb.append("import lombok.NoArgsConstructor;\n\n");

        boolean hasDateTime = false;
        boolean hasBigDecimal = false;
        for (ColumnInfo col : table.getColumns()) {
            if ("LocalDateTime".equals(col.getJavaType())) {
                hasDateTime = true;
            }
            if ("BigDecimal".equals(col.getJavaType())) {
                hasBigDecimal = true;
            }
        }
        if (hasDateTime) {
            sb.append("import java.time.LocalDateTime;\n");
        }
        if (hasBigDecimal) {
            sb.append("import java.math.BigDecimal;\n");
        }
        sb.append("\n/**\n");
        sb.append(" * ").append(getTableComment(table)).append("视图对象\n");
        sb.append(" *\n");
        sb.append(" * @author ").append(AUTHOR).append("\n");
        sb.append(" */\n");
        sb.append("@Data\n");
        sb.append("@Builder\n");
        sb.append("@NoArgsConstructor\n");
        sb.append("@AllArgsConstructor\n");
        sb.append("@ApiModel(value = \"").append(className).append("\", description = \"").append(getTableComment(table)).append("视图对象\")\n");
        sb.append("public class ").append(className).append(" {\n\n");

        for (ColumnInfo col : table.getColumns()) {
            String comment = StrUtil.isNotBlank(col.getColumnComment()) ? col.getColumnComment() : col.getJavaFieldName();
            sb.append("    @ApiModelProperty(value = \"").append(comment).append("\"");
            String example = getExampleValue(col);
            if (example != null) {
                sb.append(", example = \"").append(example).append("\"");
            }
            sb.append(")\n");
            // 修改原因:VO中LocalDateTime字段需要添加@JsonFormat注解,确保返回给前端的日期格式统一
            // 修改前:未添加@JsonFormat,返回的日期格式可能为数组或ISO格式
            // 修改后:添加@JsonFormat指定格式和时区,统一返回 yyyy-MM-dd HH:mm:ss
            if ("LocalDateTime".equals(col.getJavaType())) {
                sb.append("    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\", timezone = \"GMT+8\")\n");
            }
            sb.append("    private ").append(col.getJavaType()).append(" ").append(col.getJavaFieldName()).append(";\n\n");
        }
        sb.append("}\n");
        writeFile(VO_PACKAGE, className + ".java", sb.toString());
    }
    // ==================== DTO ====================
    private static void generateDTO(TableInfo table) {
        String className = table.getClassName() + DTO_SUFFIX;
        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(getFullPackage(DTO_PACKAGE)).append(";\n\n");
        sb.append("import com.fasterxml.jackson.annotation.JsonFormat;\n");
        sb.append("import io.swagger.annotations.ApiModel;\n");
        sb.append("import io.swagger.annotations.ApiModelProperty;\n");
        sb.append("import lombok.Data;\n");
        sb.append("import lombok.EqualsAndHashCode;\n\n");

        boolean hasDateTime = false;
        boolean hasBigDecimal = false;
        for (ColumnInfo col : table.getColumns()) {
            if ("LocalDateTime".equals(col.getJavaType())) {
                hasDateTime = true;
            }
            if ("BigDecimal".equals(col.getJavaType())) {
                hasBigDecimal = true;
            }
        }
        if (hasDateTime) {
            sb.append("import java.time.LocalDateTime;\n");
        }
        if (hasBigDecimal) {
            sb.append("import java.math.BigDecimal;\n");
        }
        if (hasDateTime || hasBigDecimal) {
            sb.append("\n");
        }

        sb.append("/**\n");
        sb.append(" * ").append(getTableComment(table)).append("DTO\n");
        sb.append(" * - 新增时主键为空(复合主键需要手动传入)\n");
        sb.append(" * - 修改时主键必传\n");
        sb.append(" * - 查询时传条件字段\n");
        sb.append(" *\n");
        sb.append(" * @author ").append(AUTHOR).append("\n");
        sb.append(" */\n");
        sb.append("@Data\n");
        sb.append("@EqualsAndHashCode(callSuper = true)\n");
        sb.append("@ApiModel(value = \"").append(className).append("\", description = \"").append(getTableComment(table)).append("请求DTO\")\n");
        sb.append("public class ").append(className).append(" extends PageDTO {\n\n");

        for (ColumnInfo col : table.getColumns()) {
            String comment = StrUtil.isNotBlank(col.getColumnComment()) ? col.getColumnComment() : col.getJavaFieldName();
            sb.append("    @ApiModelProperty(value = \"").append(comment).append("\"");
            if (col.isPrimaryKey()) {
                sb.append(", notes = \"修改时必填\"");
            }
            String example = getExampleValue(col);
            if (example != null) {
                sb.append(", example = \"").append(example).append("\"");
            }
            if (!col.isNullable() && !col.isPrimaryKey()) {
                sb.append(", required = true");
            }
            sb.append(")\n");
            // 修改原因:DTO中LocalDateTime字段需要添加@JsonFormat注解,支持前端传入 yyyy-MM-dd HH:mm:ss 格式
            // 修改前:未添加@JsonFormat,无法解析带空格的日期字符串
            // 修改后:添加@JsonFormat指定格式和时区
            if ("LocalDateTime".equals(col.getJavaType())) {
                sb.append("    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\", timezone = \"GMT+8\")\n");
            }
            sb.append("    private ").append(col.getJavaType()).append(" ").append(col.getJavaFieldName()).append(";\n\n");
        }
        sb.append("}\n");
        writeFile(DTO_PACKAGE, className + ".java", sb.toString());
    }

    // ==================== Mapper ====================
    private static void generateMapper(TableInfo table) {
        String className = table.getClassName() + MAPPER_SUFFIX;
        String entityClass = table.getClassName() + ENTITY_SUFFIX;
        String voClass = table.getClassName() + VO_SUFFIX;
        String dtoClass = table.getClassName() + DTO_SUFFIX;

        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(getFullPackage(MAPPER_PACKAGE)).append(";\n\n");
        sb.append("import com.baomidou.mybatisplus.core.mapper.BaseMapper;\n");
        sb.append("import com.baomidou.mybatisplus.core.metadata.IPage;\n");
        sb.append("import com.baomidou.mybatisplus.extension.plugins.pagination.Page;\n");
        sb.append("import ").append(getFullClassName(ENTITY_PACKAGE, entityClass)).append(";\n");
        sb.append("import ").append(getFullClassName(VO_PACKAGE, voClass)).append(";\n");
        sb.append("import ").append(getFullClassName(DTO_PACKAGE, dtoClass)).append(";\n");
        sb.append("import org.apache.ibatis.annotations.Mapper;\n");
        sb.append("import org.apache.ibatis.annotations.Param;\n\n");
        sb.append("import java.util.List;\n\n");
        sb.append("@Mapper\n");
        sb.append("public interface ").append(className).append(" extends BaseMapper<").append(entityClass).append("> {\n\n");
        sb.append("    /**\n");
        sb.append("     * 分页查询VO\n");
        sb.append("     *\n");
        sb.append("     * @param page 分页对象\n");
        sb.append("     * @param query 查询条件\n");
        sb.append("     * @return 分页结果\n");
        sb.append("     */\n");
        sb.append("    IPage<").append(voClass).append("> selectPageVO(Page<").append(voClass).append("> page, @Param(\"query\") ").append(dtoClass).append(" query);\n\n");
        sb.append("    /**\n");
        sb.append("     * 查询列表VO\n");
        sb.append("     *\n");
        sb.append("     * @param query 查询条件\n");
        sb.append("     * @return 列表结果\n");
        sb.append("     */\n");
        sb.append("    List<").append(voClass).append("> selectListVO(@Param(\"query\") ").append(dtoClass).append(" query);\n\n");
        sb.append("    /**\n");
        sb.append("     * 批量插入\n");
        sb.append("     *\n");
        sb.append("     * @param list 实体列表\n");
        sb.append("     * @return 影响行数\n");
        sb.append("     */\n");
        sb.append("    int insertBatch(@Param(\"list\") List<").append(entityClass).append("> list);\n\n");
        sb.append("}\n");
        writeFile(MAPPER_PACKAGE, className + ".java", sb.toString());
    }

    // ==================== Mapper XML ====================
    private static void generateMapperXml(TableInfo table) {
        String mapperClass = table.getClassName() + MAPPER_SUFFIX;
        String voClass = table.getClassName() + VO_SUFFIX;

        StringBuilder sb = new StringBuilder();
        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
        sb.append("<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n");
        sb.append("<mapper namespace=\"").append(getFullPackage(MAPPER_PACKAGE)).append(".").append(mapperClass).append("\">\n\n");

        // 基础字段列表
        sb.append("    <sql id=\"Base_Column_List\">\n");
        sb.append("        ");
        List<String> cols = new ArrayList<>();
        for (ColumnInfo col : table.getColumns()) {
            cols.add(col.getColumnName());
        }
        sb.append(String.join(", ", cols)).append("\n");
        sb.append("    </sql>\n\n");

        // 通用查询条件
        sb.append("    <sql id=\"base\">\n");
        sb.append("        <where>\n");

        // 逻辑删除过滤
        for (ColumnInfo col : table.getColumns()) {
            if (isLogicDeleteField(col.getColumnName())) {
                sb.append("            AND ").append(col.getColumnName()).append(" = 0\n");
                break;
            }
        }

        // 动态查询条件
        for (ColumnInfo col : table.getColumns()) {
            if (isLogicDeleteField(col.getColumnName()) || isCreateUpdateField(col.getColumnName())) {
                continue;
            }
            if ("String".equals(col.getJavaType())) {
                sb.append("            <if test=\"query.").append(col.getJavaFieldName()).append(" != null and query.").append(col.getJavaFieldName()).append(" != ''\">\n");
                sb.append("                AND ").append(col.getColumnName()).append(" LIKE CONCAT('%', #{query.").append(col.getJavaFieldName()).append("}, '%')\n");
                sb.append("            </if>\n");
            } else {
                sb.append("            <if test=\"query.").append(col.getJavaFieldName()).append(" != null\">\n");
                sb.append("                AND ").append(col.getColumnName()).append(" = #{query.").append(col.getJavaFieldName()).append("}\n");
                sb.append("            </if>\n");
            }
        }

        // 修改原因:将choose改为if else,更清晰地处理排序逻辑
        // 修改前:使用choose,当isAsc为null时走otherwise默认DESC
        // 修改后:使用if else,明确处理isAsc为true/false/null三种情况
        sb.append("            <if test=\"query.orderBy != null and query.orderBy != ''\">\n");
        sb.append("                ORDER BY ${query.orderBy}\n");
        sb.append("                <if test=\"query.isAsc != null and query.isAsc\">\n");
        sb.append("                    ASC\n");
        sb.append("                </if>\n");
        sb.append("                <if test=\"query.isAsc != null and !query.isAsc\">\n");
        sb.append("                    DESC\n");
        sb.append("                </if>\n");
        sb.append("                <!-- 当isAsc为null时,默认不排序,由数据库默认顺序决定 -->\n");
        sb.append("            </if>\n");
        sb.append("        </where>\n");
        sb.append("    </sql>\n\n");

        // 修改原因:MyBatis DTD要求resultMap的子元素必须按 id* → result* 顺序
        // 修改前:直接循环所有字段,未区分主键顺序,可能导致result在id前面
        // 修改后:先输出id标签(主键),再输出result标签(非主键),符合DTD规范
        sb.append("    <resultMap id=\"VOMap\" type=\"").append(getFullClassName(VO_PACKAGE, voClass)).append("\">\n");
        // 先输出主键字段(id标签)
        for (ColumnInfo col : table.getColumns()) {
            if (col.isPrimaryKey()) {
                sb.append("        <id column=\"").append(col.getColumnName()).append("\" property=\"").append(col.getJavaFieldName()).append("\"/>\n");
            }
        }
        // 再输出非主键字段(result标签)
        for (ColumnInfo col : table.getColumns()) {
            if (!col.isPrimaryKey()) {
                sb.append("        <result column=\"").append(col.getColumnName()).append("\" property=\"").append(col.getJavaFieldName()).append("\"/>\n");
            }
        }
        sb.append("    </resultMap>\n\n");

        // 分页查询
        sb.append("    <select id=\"selectPageVO\" resultMap=\"VOMap\">\n");
        sb.append("        SELECT <include refid=\"Base_Column_List\"/>\n");
        sb.append("        FROM ").append(table.getTableName()).append("\n");
        sb.append("        <include refid=\"base\"/>\n");
        sb.append("    </select>\n\n");

        // 列表查询
        sb.append("    <select id=\"selectListVO\" resultMap=\"VOMap\">\n");
        sb.append("        SELECT <include refid=\"Base_Column_List\"/>\n");
        sb.append("        FROM ").append(table.getTableName()).append("\n");
        sb.append("        <include refid=\"base\"/>\n");
        sb.append("    </select>\n\n");

        // 批量插入 - 包含所有字段(包括主键)
        sb.append("    <insert id=\"insertBatch\">\n");
        sb.append("        INSERT INTO ").append(table.getTableName()).append(" (\n");
        sb.append("        ");
        List<String> insertCols = new ArrayList<>();
        for (ColumnInfo col : table.getColumns()) {
            insertCols.add(col.getColumnName());
        }
        sb.append(String.join(", ", insertCols)).append("\n");
        sb.append("        ) VALUES\n");
        sb.append("        <foreach collection=\"list\" item=\"item\" separator=\",\">\n");
        sb.append("            (\n");
        sb.append("            ");
        List<String> insertVals = new ArrayList<>();
        for (ColumnInfo col : table.getColumns()) {
            insertVals.add("#{item." + col.getJavaFieldName() + "}");
        }
        sb.append(String.join(", ", insertVals)).append("\n");
        sb.append("            )\n");
        sb.append("        </foreach>\n");
        sb.append("    </insert>\n\n");
        sb.append("</mapper>\n");
        writeXmlFile(MAPPER_XML_DIR, mapperClass + ".xml", sb.toString());
    }

    // ==================== Service ====================
    private static void generateService(TableInfo table) {
        String className = table.getClassName() + SERVICE_SUFFIX;
        String entityClass = table.getClassName() + ENTITY_SUFFIX;
        String voClass = table.getClassName() + VO_SUFFIX;
        String dtoClass = table.getClassName() + DTO_SUFFIX;

        PrimaryKeyInfo pkInfo = getPrimaryKeyInfo(table);
        String pkType = getPrimaryKeyType(table);
        String pkField = getPrimaryKeyFieldName(table);

        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(getFullPackage(SERVICE_PACKAGE)).append(";\n\n");
        sb.append("import com.baomidou.mybatisplus.core.metadata.IPage;\n");
        sb.append("import com.baomidou.mybatisplus.extension.service.IService;\n");
        sb.append("import ").append(getFullClassName(ENTITY_PACKAGE, entityClass)).append(";\n");
        sb.append("import ").append(getFullClassName(VO_PACKAGE, voClass)).append(";\n");
        sb.append("import ").append(getFullClassName(DTO_PACKAGE, dtoClass)).append(";\n\n");
        sb.append("import java.util.List;\n");
        if (pkInfo.isComposite()) {
            sb.append("import java.util.Map;\n");
        }
        sb.append("\n");

        sb.append("/**\n");
        sb.append(" * ").append(getTableComment(table)).append("服务接口\n");
        sb.append(" *\n");
        sb.append(" * @author ").append(AUTHOR).append("\n");
        sb.append(" */\n");
        sb.append("public interface ").append(className).append(" extends IService<").append(entityClass).append("> {\n\n");

        sb.append("    /**\n");
        sb.append("     * 分页查询VO\n");
        sb.append("     *\n");
        sb.append("     * @param dto 查询条件\n");
        sb.append("     * @return 分页结果\n");
        sb.append("     */\n");
        sb.append("    IPage<").append(voClass).append("> pageVO(").append(dtoClass).append(" dto);\n\n");

        sb.append("    /**\n");
        sb.append("     * 查询列表VO\n");
        sb.append("     *\n");
        sb.append("     * @param dto 查询条件\n");
        sb.append("     * @return 列表结果\n");
        sb.append("     */\n");
        sb.append("    List<").append(voClass).append("> listVO(").append(dtoClass).append(" dto);\n\n");

        // 根据主键查询
        if (pkInfo.isComposite()) {
            sb.append("    /**\n");
            sb.append("     * 根据主键查询VO(复合主键)\n");
            sb.append("     *\n");
            sb.append("     * @param pkMap 主键Map,key为字段名,value为字段值\n");
            sb.append("     * @return VO对象\n");
            sb.append("     */\n");
            sb.append("    ").append(voClass).append(" getVOById(Map<String, Object> pkMap);\n\n");
        } else {
            sb.append("    /**\n");
            sb.append("     * 根据主键查询VO\n");
            sb.append("     *\n");
            sb.append("     * @param ").append(pkField).append(" 主键ID\n");
            sb.append("     * @return VO对象\n");
            sb.append("     */\n");
            sb.append("    ").append(voClass).append(" getVOById(").append(pkType).append(" ").append(pkField).append(");\n\n");
        }

        sb.append("    /**\n");
        sb.append("     * 新增\n");
        sb.append("     *\n");
        sb.append("     * @param dto DTO对象\n");
        sb.append("     * @return 是否成功\n");
        sb.append("     */\n");
        sb.append("    Boolean add(").append(dtoClass).append(" dto);\n\n");

        sb.append("    /**\n");
        sb.append("     * 批量新增\n");
        sb.append("     *\n");
        sb.append("     * @param dtoList DTO列表\n");
        sb.append("     * @return 是否成功\n");
        sb.append("     */\n");
        sb.append("    Boolean batchAdd(List<").append(dtoClass).append("> dtoList);\n\n");

        sb.append("    /**\n");
        sb.append("     * 修改\n");
        sb.append("     *\n");
        sb.append("     * @param dto DTO对象\n");
        sb.append("     * @return 是否成功\n");
        sb.append("     */\n");
        sb.append("    Boolean update(").append(dtoClass).append(" dto);\n\n");

        // 根据主键删除
        if (pkInfo.isComposite()) {
            sb.append("    /**\n");
            sb.append("     * 根据主键删除(复合主键)\n");
            sb.append("     *\n");
            sb.append("     * @param pkMap 主键Map,key为字段名,value为字段值\n");
            sb.append("     * @return 是否成功\n");
            sb.append("     */\n");
            sb.append("    Boolean deleteById(Map<String, Object> pkMap);\n\n");
        } else {
            sb.append("    /**\n");
            sb.append("     * 根据主键删除\n");
            sb.append("     *\n");
            sb.append("     * @param ").append(pkField).append(" 主键ID\n");
            sb.append("     * @return 是否成功\n");
            sb.append("     */\n");
            sb.append("    Boolean deleteById(").append(pkType).append(" ").append(pkField).append(");\n\n");
        }

        sb.append("}\n");
        writeFile(SERVICE_PACKAGE, className + ".java", sb.toString());
    }

    // ==================== ServiceImpl ====================
    private static void generateServiceImpl(TableInfo table) {
        String className = table.getClassName() + SERVICE_IMPL_SUFFIX;
        String serviceClass = table.getClassName() + SERVICE_SUFFIX;
        String mapperClass = table.getClassName() + MAPPER_SUFFIX;
        String entityClass = table.getClassName() + ENTITY_SUFFIX;
        String voClass = table.getClassName() + VO_SUFFIX;
        String dtoClass = table.getClassName() + DTO_SUFFIX;

        PrimaryKeyInfo pkInfo = getPrimaryKeyInfo(table);
        String pkType = getPrimaryKeyType(table);
        String pkField = getPrimaryKeyFieldName(table);

        // 查找逻辑删除字段
        String logicDeleteField = null;
        for (ColumnInfo col : table.getColumns()) {
            if (isLogicDeleteField(col.getColumnName())) {
                logicDeleteField = col.getJavaFieldName();
                break;
            }
        }

        // 获取主键字段名列表
        List<String> pkFieldNames = new ArrayList<>();
        for (ColumnInfo col : pkInfo.getColumns()) {
            pkFieldNames.add(col.getJavaFieldName());
        }

        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(getFullPackage(SERVICE_IMPL_PACKAGE)).append(";\n\n");
        sb.append("import cn.hutool.core.bean.BeanUtil;\n");
        sb.append("import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\n");
        sb.append("import com.baomidou.mybatisplus.core.metadata.IPage;\n");
        sb.append("import com.baomidou.mybatisplus.core.toolkit.IdWorker;\n");
        sb.append("import com.baomidou.mybatisplus.extension.plugins.pagination.Page;\n");
        sb.append("import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\n");
        sb.append("import ").append(getFullClassName(ENTITY_PACKAGE, entityClass)).append(";\n");
        sb.append("import ").append(getFullClassName(MAPPER_PACKAGE, mapperClass)).append(";\n");
        sb.append("import ").append(getFullClassName(VO_PACKAGE, voClass)).append(";\n");
        sb.append("import ").append(getFullClassName(DTO_PACKAGE, dtoClass)).append(";\n");
        sb.append("import ").append(getFullClassName(SERVICE_PACKAGE, serviceClass)).append(";\n");
        sb.append("import lombok.extern.slf4j.Slf4j;\n");
        sb.append("import org.springframework.stereotype.Service;\n");
        sb.append("import org.springframework.transaction.annotation.Transactional;\n\n");
        sb.append("import java.util.List;\n");
        if (pkInfo.isComposite()) {
            sb.append("import java.util.Map;\n");
        }
        sb.append("import java.util.stream.Collectors;\n\n");

        sb.append("/**\n");
        sb.append(" * ").append(getTableComment(table)).append("服务实现类\n");
        sb.append(" *\n");
        sb.append(" * @author ").append(AUTHOR).append("\n");
        sb.append(" */\n");
        sb.append("@Slf4j\n");
        sb.append("@Service\n");
        sb.append("public class ").append(className).append(" extends ServiceImpl<").append(mapperClass).append(", ").append(entityClass).append("> implements ").append(serviceClass).append(" {\n\n");
        sb.append("    /**\n");
        sb.append("     * 批量插入分片大小\n");
        sb.append("     */\n");
        sb.append("    private static final int BATCH_SIZE = ").append(BATCH_SIZE).append(";\n\n");

        // pageVO
        sb.append("    @Override\n");
        sb.append("    public IPage<").append(voClass).append("> pageVO(").append(dtoClass).append(" dto) {\n");
        sb.append("        Page<").append(voClass).append("> page = new Page<>(dto.getPageNum(), dto.getPageSize());\n");
        sb.append("        return baseMapper.selectPageVO(page, dto);\n");
        sb.append("    }\n\n");

        // listVO
        sb.append("    @Override\n");
        sb.append("    public List<").append(voClass).append("> listVO(").append(dtoClass).append(" dto) {\n");
        sb.append("        return baseMapper.selectListVO(dto);\n");
        sb.append("    }\n\n");

        // getVOById
        if (pkInfo.isComposite()) {
            sb.append("    @Override\n");
            sb.append("    public ").append(voClass).append(" getVOById(Map<String, Object> pkMap) {\n");
            sb.append("        QueryWrapper<").append(entityClass).append("> wrapper = new QueryWrapper<>();\n");
            for (ColumnInfo col : pkInfo.getColumns()) {
                sb.append("        wrapper.eq(\"").append(col.getColumnName()).append("\", pkMap.get(\"").append(col.getJavaFieldName()).append("\"));\n");
            }
            sb.append("        ").append(entityClass).append(" entity = getOne(wrapper);\n");
            sb.append("        if (entity == null) {\n");
            sb.append("            return null;\n");
            sb.append("        }\n");
            sb.append("        return BeanUtil.copyProperties(entity, ").append(voClass).append(".class);\n");
            sb.append("    }\n\n");
        } else {
            sb.append("    @Override\n");
            sb.append("    public ").append(voClass).append(" getVOById(").append(pkType).append(" ").append(pkField).append(") {\n");
            sb.append("        ").append(entityClass).append(" entity = getById(").append(pkField).append(");\n");
            sb.append("        if (entity == null) {\n");
            sb.append("            return null;\n");
            sb.append("        }\n");
            sb.append("        return BeanUtil.copyProperties(entity, ").append(voClass).append(".class);\n");
            sb.append("    }\n\n");
        }

        // add
        sb.append("    @Override\n");
        sb.append("    @Transactional(rollbackFor = Exception.class)\n");
        sb.append("    public Boolean add(").append(dtoClass).append(" dto) {\n");
        sb.append("        ").append(entityClass).append(" entity = BeanUtil.copyProperties(dto, ").append(entityClass).append(".class);\n");
        // 修改原因:根据主键类型决定是否自动生成ID,而非仅判断字段名是否包含"id"
        // 修改前:只要字段名包含id就生成
        // 修改后:String类型使用IdWorker.getIdStr(),Long/Integer不生成(使用自增),其他类型使用IdWorker.getId()
        if (!pkInfo.isComposite() && !pkInfo.getColumns().isEmpty()) {
            ColumnInfo pkCol = pkInfo.getColumns().get(0);
            String fieldName = pkCol.getJavaFieldName();
            String pkTypeCol = pkCol.getJavaType();
            if ("String".equals(pkTypeCol)) {
                // String类型主键:使用IdWorker生成字符串ID
                sb.append("        entity.set").append(StrUtil.upperFirst(fieldName)).append("(IdWorker.getIdStr());\n");
            } else if ("Long".equals(pkTypeCol) || "Integer".equals(pkTypeCol)) {
                // Long/Integer类型主键:不生成,使用数据库自增
                // 不做任何操作
            } else {
                // 其他类型:使用IdWorker生成数字ID
                sb.append("        entity.set").append(StrUtil.upperFirst(fieldName)).append("(IdWorker.getId());\n");
            }
        }
        if (logicDeleteField != null) {
            sb.append("        entity.set").append(StrUtil.upperFirst(logicDeleteField)).append("(0);\n");
        }
        sb.append("        return save(entity);\n");
        sb.append("    }\n\n");

        // batchAdd
        sb.append("    @Override\n");
        sb.append("    @Transactional(rollbackFor = Exception.class)\n");
        sb.append("    public Boolean batchAdd(List<").append(dtoClass).append("> dtoList) {\n");
        sb.append("        if (dtoList == null || dtoList.isEmpty()) {\n");
        sb.append("            return true;\n");
        sb.append("        }\n");
        sb.append("        List<").append(entityClass).append("> list = dtoList.stream()\n");
        sb.append("                .map(d -> {\n");
        sb.append("                    ").append(entityClass).append(" entity = BeanUtil.copyProperties(d, ").append(entityClass).append(".class);\n");
        // 修改原因:根据主键类型决定是否自动生成ID,与add方法保持一致
        // 修改前:只要字段名包含id就生成
        // 修改后:String类型使用IdWorker.getIdStr(),Long/Integer不生成(使用自增),其他类型使用IdWorker.getId()
        if (!pkInfo.isComposite() && !pkInfo.getColumns().isEmpty()) {
            ColumnInfo pkCol = pkInfo.getColumns().get(0);
            String fieldName = pkCol.getJavaFieldName();
            String pkTypeCol = pkCol.getJavaType();
            if ("String".equals(pkTypeCol)) {
                // String类型主键:使用IdWorker生成字符串ID
                sb.append("                    entity.set").append(StrUtil.upperFirst(fieldName)).append("(IdWorker.getIdStr());\n");
            } else if ("Long".equals(pkTypeCol) || "Integer".equals(pkTypeCol)) {
                // Long/Integer类型主键:不生成,使用数据库自增
                // 不做任何操作
            } else {
                // 其他类型:使用IdWorker生成数字ID
                sb.append("                    entity.set").append(StrUtil.upperFirst(fieldName)).append("(IdWorker.getId());\n");
            }
        }
        if (logicDeleteField != null) {
            sb.append("                    entity.set").append(StrUtil.upperFirst(logicDeleteField)).append("(0);\n");
        }
        sb.append("                    return entity;\n");
        sb.append("                })\n");
        sb.append("                .collect(Collectors.toList());\n");
        sb.append("        return insertBatch(list);\n");
        sb.append("    }\n\n");

        // insertBatch
        sb.append("    /**\n");
        sb.append("     * 批量插入(分片处理)\n");
        sb.append("     *\n");
        sb.append("     * @param list 实体列表\n");
        sb.append("     * @return 是否全部插入成功\n");
        sb.append("     */\n");
        sb.append("    private Boolean insertBatch(List<").append(entityClass).append("> list) {\n");
        sb.append("        int total = list.size();\n");
        sb.append("        int inserted = 0;\n");
        sb.append("        for (int i = 0; i < total; i += BATCH_SIZE) {\n");
        sb.append("            int end = Math.min(i + BATCH_SIZE, total);\n");
        sb.append("            List<").append(entityClass).append("> subList = list.subList(i, end);\n");
        sb.append("            inserted += baseMapper.insertBatch(subList);\n");
        sb.append("        }\n");
        sb.append("        return inserted == total;\n");
        sb.append("    }\n\n");

        // update
        sb.append("    @Override\n");
        sb.append("    @Transactional(rollbackFor = Exception.class)\n");
        sb.append("    public Boolean update(").append(dtoClass).append(" dto) {\n");
        if (pkInfo.isComposite()) {
            for (ColumnInfo col : pkInfo.getColumns()) {
                String fieldName = col.getJavaFieldName();
                sb.append("        if (dto.get").append(StrUtil.upperFirst(fieldName)).append("() == null) {\n");
                sb.append("            throw new IllegalArgumentException(\"更新时主键字段 ").append(fieldName).append(" 不能为空\");\n");
                sb.append("        }\n");
            }
        } else if (!pkInfo.getColumns().isEmpty()) {
            String fieldName = pkInfo.getColumns().get(0).getJavaFieldName();
            sb.append("        if (dto.get").append(StrUtil.upperFirst(fieldName)).append("() == null) {\n");
            sb.append("            throw new IllegalArgumentException(\"更新时主键不能为空\");\n");
            sb.append("        }\n");
        }
        sb.append("        return updateById(BeanUtil.copyProperties(dto, ").append(entityClass).append(".class));\n");
        sb.append("    }\n\n");

        // deleteById
        if (pkInfo.isComposite()) {
            sb.append("    @Override\n");
            sb.append("    @Transactional(rollbackFor = Exception.class)\n");
            sb.append("    public Boolean deleteById(Map<String, Object> pkMap) {\n");
            sb.append("        QueryWrapper<").append(entityClass).append("> wrapper = new QueryWrapper<>();\n");
            for (ColumnInfo col : pkInfo.getColumns()) {
                sb.append("        wrapper.eq(\"").append(col.getColumnName()).append("\", pkMap.get(\"").append(col.getJavaFieldName()).append("\"));\n");
            }
            if (logicDeleteField != null) {
                sb.append("        ").append(entityClass).append(" entity = new ").append(entityClass).append("();\n");
                sb.append("        entity.set").append(StrUtil.upperFirst(logicDeleteField)).append("(1);\n");
                sb.append("        return update(entity, wrapper);\n");
            } else {
                sb.append("        return remove(wrapper);\n");
            }
            sb.append("    }\n\n");
        } else {
            sb.append("    @Override\n");
            sb.append("    @Transactional(rollbackFor = Exception.class)\n");
            sb.append("    public Boolean deleteById(").append(pkType).append(" ").append(pkField).append(") {\n");
            if (logicDeleteField != null) {
                sb.append("        ").append(entityClass).append(" entity = new ").append(entityClass).append("();\n");
                String fieldName = pkInfo.getColumns().isEmpty() ? "id" : pkInfo.getColumns().get(0).getJavaFieldName();
                sb.append("        entity.set").append(StrUtil.upperFirst(fieldName)).append("(").append(pkField).append(");\n");
                sb.append("        entity.set").append(StrUtil.upperFirst(logicDeleteField)).append("(1);\n");
                sb.append("        return updateById(entity);\n");
            } else {
                sb.append("        return removeById(").append(pkField).append(");\n");
            }
            sb.append("    }\n\n");
        }

        sb.append("}\n");
        writeFile(SERVICE_IMPL_PACKAGE, className + ".java", sb.toString());
    }
    // ==================== Controller ====================
    private static void generateController(TableInfo table) {
        String className = table.getClassName() + CONTROLLER_SUFFIX;
        String serviceClass = table.getClassName() + SERVICE_SUFFIX;
        String voClass = table.getClassName() + VO_SUFFIX;
        String dtoClass = table.getClassName() + DTO_SUFFIX;

        PrimaryKeyInfo pkInfo = getPrimaryKeyInfo(table);
        String pkType = getPrimaryKeyType(table);
        String pkField = getPrimaryKeyFieldName(table);
        String path = getControllerPath(getTablePath(table));

        StringBuilder sb = new StringBuilder();
        sb.append("package ").append(getFullPackage(CONTROLLER_PACKAGE)).append(";\n\n");
        sb.append("import io.swagger.annotations.Api;\n");
        sb.append("import io.swagger.annotations.ApiOperation;\n");
        sb.append("import io.swagger.annotations.ApiParam;\n");
        sb.append("import ").append(RESULT_CLASS).append(";\n");
        sb.append("import com.baomidou.mybatisplus.core.metadata.IPage;\n");
        sb.append("import ").append(getFullClassName(VO_PACKAGE, voClass)).append(";\n");
        sb.append("import ").append(getFullClassName(DTO_PACKAGE, dtoClass)).append(";\n");
        sb.append("import ").append(getFullClassName(SERVICE_PACKAGE, serviceClass)).append(";\n");
        sb.append("import lombok.RequiredArgsConstructor;\n");
        sb.append("import lombok.extern.slf4j.Slf4j;\n");
        sb.append("import org.springframework.web.bind.annotation.*;\n\n");
        sb.append("import javax.validation.Valid;\n");
        sb.append("import java.util.List;\n");
        if (pkInfo.isComposite()) {
            sb.append("import java.util.Map;\n");
        }
        sb.append("\n");

        sb.append("/**\n");
        sb.append(" * ").append(getTableComment(table)).append("控制器\n");
        sb.append(" *\n");
        sb.append(" * @author ").append(AUTHOR).append("\n");
        sb.append(" */\n");
        sb.append("@Slf4j\n");
        sb.append("@RestController\n");
        sb.append("@RequiredArgsConstructor\n");
        sb.append("@RequestMapping(\"").append(path).append("\")\n");
        sb.append("@Api(tags = \"").append(getTableComment(table)).append("管理\")\n");
        sb.append("public class ").append(className).append(" {\n\n");
        sb.append("    private final ").append(serviceClass).append(" ").append(table.getVarName()).append("Service;\n\n");

        // page - 修改原因:支持不传参数也能分页查询,使用三元运算符更简洁
        // 修改前:@RequestBody @Valid Dto dto(必须传递参数),使用if判断
        // 修改后:@RequestBody(required = false) @Valid Dto dto(可选参数),使用三元运算符
        sb.append("    @PostMapping(\"/page\")\n");
        sb.append("    @ApiOperation(\"条件分页查询\")\n");
        sb.append("    public Result<IPage<").append(voClass).append(">> page(@RequestBody(required = false) @Valid ").append(dtoClass).append(" dto) {\n");
        sb.append("        dto = dto == null ? new ").append(dtoClass).append("() : dto;\n");
        sb.append("        return Result.success(").append(table.getVarName()).append("Service.pageVO(dto));\n");
        sb.append("    }\n\n");

        // list - 修改原因:支持不传参数也能查询所有数据,使用三元运算符更简洁
        // 修改前:@RequestBody Dto dto(必须传递参数),使用if判断
        // 修改后:@RequestBody(required = false) Dto dto(可选参数),使用三元运算符
        sb.append("    @PostMapping(\"/list\")\n");
        sb.append("    @ApiOperation(\"条件查询所有\")\n");
        sb.append("    public Result<List<").append(voClass).append(">> list(@RequestBody(required = false) ").append(dtoClass).append(" dto) {\n");
        sb.append("        dto = dto == null ? new ").append(dtoClass).append("() : dto;\n");
        sb.append("        return Result.success(").append(table.getVarName()).append("Service.listVO(dto));\n");
        sb.append("    }\n\n");

        // getById
        if (pkInfo.isComposite()) {
            sb.append("    @PostMapping(\"/detail\")\n");
            sb.append("    @ApiOperation(\"根据主键查询(复合主键)\")\n");
            sb.append("    public Result<").append(voClass).append("> getById(@RequestBody Map<String, Object> pkMap) {\n");
            sb.append("        return Result.success(").append(table.getVarName()).append("Service.getVOById(pkMap));\n");
            sb.append("    }\n\n");
        } else {
            sb.append("    @GetMapping(\"/{").append(pkField).append("}\")\n");
            sb.append("    @ApiOperation(\"根据主键查询\")\n");
            sb.append("    public Result<").append(voClass).append("> getById(@ApiParam(\"主键ID\") @PathVariable ").append(pkType).append(" ").append(pkField).append(") {\n");
            sb.append("        return Result.success(").append(table.getVarName()).append("Service.getVOById(").append(pkField).append("));\n");
            sb.append("    }\n\n");
        }

        // add
        sb.append("    @PostMapping\n");
        sb.append("    @ApiOperation(\"新增\")\n");
        sb.append("    public Result<Boolean> add(@RequestBody @Valid ").append(dtoClass).append(" dto) {\n");
        sb.append("        return Result.success(\"新增成功\", ").append(table.getVarName()).append("Service.add(dto));\n");
        sb.append("    }\n\n");

        // batchAdd
        sb.append("    @PostMapping(\"/batch\")\n");
        sb.append("    @ApiOperation(\"批量新增\")\n");
        sb.append("    public Result<Boolean> batchAdd(@RequestBody @Valid List<").append(dtoClass).append("> dtoList) {\n");
        sb.append("        return Result.success(\"批量新增成功\", ").append(table.getVarName()).append("Service.batchAdd(dtoList));\n");
        sb.append("    }\n\n");

        // update - 修改原因:同时支持PUT和POST两种请求方法
        // 修改前:仅 @PutMapping
        // 修改后:@PutMapping 和 @PostMapping("/update") 同时支持
        sb.append("    @PutMapping\n");
        sb.append("    @PostMapping(\"/update\")\n");
        sb.append("    @ApiOperation(\"修改\")\n");
        sb.append("    public Result<Boolean> update(@RequestBody @Valid ").append(dtoClass).append(" dto) {\n");
        sb.append("        return Result.success(\"修改成功\", ").append(table.getVarName()).append("Service.update(dto));\n");
        sb.append("    }\n\n");

        // delete
        if (pkInfo.isComposite()) {
            sb.append("    @DeleteMapping\n");
            sb.append("    @ApiOperation(\"根据主键删除(复合主键)\")\n");
            sb.append("    public Result<Boolean> delete(@RequestBody Map<String, Object> pkMap) {\n");
            sb.append("        return Result.success(\"删除成功\", ").append(table.getVarName()).append("Service.deleteById(pkMap));\n");
            sb.append("    }\n\n");
        } else {
            sb.append("    @DeleteMapping(\"/{").append(pkField).append("}\")\n");
            sb.append("    @ApiOperation(\"根据主键删除\")\n");
            sb.append("    public Result<Boolean> delete(@ApiParam(\"主键ID\") @PathVariable ").append(pkType).append(" ").append(pkField).append(") {\n");
            sb.append("        return Result.success(\"删除成功\", ").append(table.getVarName()).append("Service.deleteById(").append(pkField).append("));\n");
            sb.append("    }\n\n");
        }

        sb.append("}\n");
        writeFile(CONTROLLER_PACKAGE, className + ".java", sb.toString());
    }
    // ==================== 文件写入 ====================
    private static void writeFile(String pkgDir, String fileName, String content) {
        String pkgPath = pkgDir.replace(".", "/");
        String path = String.format("%s/src/main/java/%s/%s/%s",
                OUTPUT_DIR,
                BASE_PACKAGE.replace(".", "/"),
                pkgPath,
                fileName);
        FileUtil.writeUtf8String(content, path);
    }

    private static void writeXmlFile(String xmlDir, String fileName, String content) {
        String path = String.format("%s/src/main/resources/%s/%s", OUTPUT_DIR, xmlDir, fileName);
        FileUtil.writeUtf8String(content, path);
    }

    private static void writeCommonFile(String pkgDir, String fileName, String content) {
        String pkgPath = pkgDir.replace(".", "/");
        String path = String.format("%s/src/main/java/%s/%s/%s",
                OUTPUT_DIR,
                BASE_PACKAGE.replace(".", "/"),
                pkgPath,
                fileName);
        FileUtil.writeUtf8String(content, path);
        System.out.println("  ✔ " + fileName);
    }

    // ==================== 工具方法 ====================
    private static String getControllerPath(String tablePath) {
        StringBuilder sb = new StringBuilder(CONTROLLER_BASE_PATH);
        if (INCLUDE_MODULE_IN_PATH && StrUtil.isNotBlank(MODULE_NAME)) {
            sb.append("/").append(MODULE_NAME);
        }
        if (StrUtil.isNotBlank(tablePath)) {
            sb.append("/").append(tablePath);
        }
        return sb.toString();
    }

    private static String getTablePath(TableInfo table) {
        return USE_TABLE_NAME_AS_PATH ? table.getTableName().toLowerCase() : table.getVarName();
    }

    private static String getFullPackage(String pkg) {
        return BASE_PACKAGE + "." + pkg;
    }

    private static String getFullClassName(String pkg, String className) {
        return getFullPackage(pkg) + "." + className;
    }

    private static String tableNameToClassName(String tableName) {
        String name = tableName.toLowerCase();
        for (String p : TABLE_PREFIX) {
            if (name.startsWith(p)) {
                name = name.substring(p.length());
                break;
            }
        }
        return StrUtil.upperFirst(StrUtil.toCamelCase(name));
    }

    private static String tableNameToVarName(String tableName) {
        String name = tableName.toLowerCase();
        for (String p : TABLE_PREFIX) {
            if (name.startsWith(p)) {
                name = name.substring(p.length());
                break;
            }
        }
        return StrUtil.toCamelCase(name);
    }

    private static String dbTypeToJavaType(String dbType) {
        if (dbType == null) {
            return "String";
        }
        switch (dbType.toLowerCase()) {
            case "int":
            case "integer":
            case "tinyint":
            case "smallint":
            case "mediumint":
                return "Integer";
            case "bigint":
                return "Long";
            case "float":
                return "Float";
            case "double":
                return "Double";
            case "decimal":
            case "numeric":
                return "BigDecimal";
            case "date":
            case "datetime":
            case "timestamp":
                return "LocalDateTime";
            case "bit":
            case "boolean":
                return "Boolean";
            default:
                return "String";
        }
    }

    private static String getExampleValue(ColumnInfo col) {
        String name = col.getJavaFieldName().toLowerCase();
        switch (col.getJavaType()) {
            case "String":
                if (name.contains("mail") || name.contains("email")) {
                    return "example@email.com";
                }
                if (name.contains("phone") || name.contains("mobile")) {
                    return "13800138000";
                }
                return "示例" + col.getJavaFieldName();
            case "Integer":
                return name.contains("age") ? "18" : "1";
            case "Long":
                return "1";
            case "Double":
            case "Float":
                return "1.0";
            case "BigDecimal":
                return "0.00";
            case "Boolean":
                return "true";
            case "LocalDateTime":
                return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            default:
                return null;
        }
    }

    private static boolean isLogicDeleteField(String columnName) {
        for (String f : LOGIC_DELETE_FIELDS) {
            if (f.equalsIgnoreCase(columnName)) {
                return true;
            }
        }
        return false;
    }

    private static boolean isCreateUpdateField(String columnName) {
        for (String f : CREATE_UPDATE_FIELDS) {
            if (f.equalsIgnoreCase(columnName)) {
                return true;
            }
        }
        return false;
    }

    private static String getTableComment(TableInfo table) {
        return StrUtil.isNotBlank(table.getTableComment()) ? table.getTableComment() : table.getClassName();
    }

    // ==================== 主键信息类 ====================
    private static class PrimaryKeyInfo {
        private final List<ColumnInfo> columns = new ArrayList<>();
        private boolean composite;

        public List<ColumnInfo> getColumns() {
            return columns;
        }

        public boolean isComposite() {
            return composite;
        }

        public void setComposite(boolean composite) {
            this.composite = composite;
        }

        public String getPrimaryKeyField() {
            if (columns.isEmpty()) {
                return "id";
            }
            return columns.get(0).getJavaFieldName();
        }

        public String getPrimaryKeyType() {
            if (columns.isEmpty()) {
                return "Long";
            }
            if (composite) {
                return "String";
            }
            return columns.get(0).getJavaType();
        }

        public String getColumnNames() {
            if (columns.isEmpty()) {
                return "id";
            }
            List<String> names = new ArrayList<>();
            for (ColumnInfo col : columns) {
                names.add(col.getColumnName());
            }
            return String.join(", ", names);
        }
    }

    // ==================== 主键辅助方法 ====================
    private static PrimaryKeyInfo getPrimaryKeyInfo(TableInfo table) {
        PrimaryKeyInfo pkInfo = new PrimaryKeyInfo();
        if (table.getColumns() == null) {
            return pkInfo;
        }
        for (ColumnInfo col : table.getColumns()) {
            if (col.isPrimaryKey()) {
                pkInfo.getColumns().add(col);
            }
        }
        pkInfo.setComposite(pkInfo.getColumns().size() > 1);
        return pkInfo;
    }

    private static String getPrimaryKeyType(TableInfo table) {
        PrimaryKeyInfo pkInfo = getPrimaryKeyInfo(table);
        if (pkInfo.getColumns().isEmpty()) {
            return "Long";
        }
        if (pkInfo.isComposite()) {
            return "String";
        }
        return pkInfo.getColumns().get(0).getJavaType();
    }

    private static String getPrimaryKeyFieldName(TableInfo table) {
        PrimaryKeyInfo pkInfo = getPrimaryKeyInfo(table);
        if (pkInfo.getColumns().isEmpty()) {
            return "id";
        }
        if (pkInfo.isComposite()) {
            return "id";
        }
        return pkInfo.getColumns().get(0).getJavaFieldName();
    }

    // ==================== 内部类 ====================
    @Data
    @Accessors(chain = true)
    private static class TableInfo {
        private String tableName;
        private String tableComment;
        private String className;
        private String varName;
        private List<ColumnInfo> columns;
    }

    @Data
    @Accessors(chain = true)
    private static class ColumnInfo {
        private String columnName;
        private String dataType;
        private String columnComment;
        private boolean nullable;
        private String columnDefault;
        private Integer maxLength;
        private boolean primaryKey;
        private String javaType;
        private String javaFieldName;
    }
}
<?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
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version>
        <relativePath/>
    </parent>

    <groupId>org.example</groupId>
    <artifactId>generator</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <hutool.version>5.8.20</hutool.version>
        <mysql.version>8.0.32</mysql.version>
        <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
        <lombok.version>1.18.38</lombok.version>
        <swagger.version>1.5.22</swagger.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-core</artifactId>
            <version>${swagger.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>com.fasterxml.jackson.core</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
</project>
server:
  port: 18090
  servlet:
    context-path: /hr-service
spring:
  application:
    name: hr-service
  datasource:
    # 修改原因:URL中 ? 应该是 /,且需要添加时区等参数
    # 修改前:jdbc:mysql://localhost:3306?hr_center (? 导致数据库名为空)
    # 修改后:jdbc:mysql://localhost:3306/hr_center (正确指定数据库名)
    url: jdbc:mysql://localhost:3306/hr_center?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: root
    # 修改原因:使用新版驱动类
    # 修改前:com.mysql.jdbc.Driver (过时)
    # 修改后:com.mysql.cj.jdbc.Driver (新版)
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: auto
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

 

posted @ 2026-06-13 15:35  chen1777  阅读(11)  评论(0)    收藏  举报