Skip to content

高级配置

本章将介绍代码生成器的高级配置功能,包括自定义模板、扩展配置、性能优化、多数据源配置以及与CI/CD的集成等内容。

自定义模板配置

模板引擎架构

代码生成器基于Apache Velocity模板引擎,支持完全自定义的代码模板。

模板目录结构

src/main/resources/vm/
├── java/                    # Java后端模板
│   ├── controller.java.vm   # 控制器模板
│   ├── service.java.vm      # 服务接口模板
│   ├── serviceImpl.java.vm  # 服务实现模板
│   ├── mapper.java.vm       # Mapper接口模板
│   ├── domain.java.vm       # 实体类模板
│   ├── bo.java.vm          # 业务对象模板
│   └── vo.java.vm          # 视图对象模板
├── vue/                     # 前端Vue模板
│   ├── index.vue.vm        # 标准页面模板
│   ├── index-tree.vue.vm   # 树形页面模板
│   └── child.vue.vm        # 子表组件模板
├── ts/                      # TypeScript模板
│   ├── api.ts.vm           # API接口模板
│   └── types.ts.vm         # 类型定义模板
├── xml/                     # MyBatis XML模板
│   └── mapper.xml.vm       # Mapper XML模板
└── sql/                     # SQL脚本模板
    ├── sql.vm              # MySQL脚本
    ├── oracle/sql.vm       # Oracle脚本
    ├── postgres/sql.vm     # PostgreSQL脚本
    └── sqlserver/sql.vm    # SQL Server脚本

创建自定义模板

1. 模板语法基础

## 这是注释
## 变量引用
${packageName}
${className}
${tableName}

## 条件判断
#if($column.required)
    @NotNull(message = "${column.columnComment}不能为空")
#end

## 循环遍历
#foreach($column in $columns)
    private ${column.javaType} ${column.javaField};
#end

## 包含其他模板
#parse("vm/common/header.vm")

## 方法调用
$!{StringUtils.capitalize($column.javaField)}

2. 可用的模板变量

## 表基本信息
${tableName}          ## 数据库表名
${tableComment}       ## 表注释
${className}          ## Java类名(首字母大写)
${classname}          ## Java类名(首字母小写)
${BusinessName}       ## 业务名(首字母大写)
${businessName}       ## 业务名(首字母小写)

## 包和路径信息
${packageName}        ## 包名
${moduleName}         ## 模块名
${frontendPath}       ## 前端路径
${componentPath}      ## 组件路径

## 功能信息
${functionName}       ## 功能名称
${author}            ## 作者
${datetime}          ## 生成时间

## 表结构信息
${pkColumn}          ## 主键列信息
${columns}           ## 所有列信息
${importList}        ## 需要导入的包列表

## 权限信息
${permissionPrefix}  ## 权限前缀

## 字典信息
${dicts}            ## 字典类型列表

## 树形结构(tree模板)
${treeCode}         ## 树编码字段
${treeParentCode}   ## 树父编码字段
${treeName}         ## 树名称字段

## 主子表(sub模板)
${subTable}         ## 子表信息
${subTableName}     ## 子表名
${subTableFkName}   ## 子表外键名

3. 自定义Service模板示例

package ${packageName}.service.impl;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import plus.ruoyi.common.mybatis.core.service.impl.BaseServiceImpl;
import plus.ruoyi.common.mybatis.core.query.PlusLambdaQuery;
import ${packageName}.domain.entity.${ClassName};
import ${packageName}.domain.bo.${ClassName}Bo;
import ${packageName}.domain.vo.${ClassName}Vo;
import ${packageName}.mapper.${ClassName}Mapper;
import ${packageName}.service.I${ClassName}Service;

import java.util.Collection;
import java.util.Map;

/**
 * ${functionName}服务实现
 * 
 * @author ${author}
 * @date ${datetime}
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class ${ClassName}ServiceImpl extends BaseServiceImpl<${ClassName}Mapper, ${ClassName}, ${ClassName}Bo, ${ClassName}Vo> 
    implements I${ClassName}Service {

    /**
     * 构建查询条件
     */
    @Override
    protected PlusLambdaQuery<${ClassName}> boToQuery(${ClassName}Bo bo) {
        Map<String, Object> params = bo.getParams();
        PlusLambdaQuery<${ClassName}> lqw = of();
        
#foreach($column in $columns)
#if($column.query)
#if($column.queryType == "EQ")
        lqw.eq(${ClassName}::get${column.capJavaField}, bo.get${column.capJavaField}());
#elseif($column.queryType == "LIKE")
        lqw.like(${ClassName}::get${column.capJavaField}, bo.get${column.capJavaField}());
#elseif($column.queryType == "BETWEEN")
        lqw.between(${ClassName}::get${column.capJavaField}, 
            params.get("begin${column.capJavaField}"), 
            params.get("end${column.capJavaField}"));
#end
#end
#end
        
        // 模糊查询
        String searchValue = bo.getSearchValue();
        if (StringUtils.isNotBlank(searchValue)) {
            lqw.and(w -> w
#set($first = true)
#foreach($column in $columns)
#if($column.query && $column.javaType == 'String')
#if(!$first)
                .or()
#end
                .like(${ClassName}::get${column.capJavaField}, searchValue)
#set($first = false)
#end
#end
            );
        }
        
        return lqw;
    }

    /**
     * 保存前的数据校验
     */
    @Override
    protected void beforeSave(${ClassName} entity) {
        log.debug("保存${functionName}前置处理: {}", entity);
        
        // 自定义业务校验逻辑
        validateBusinessRules(entity);
    }

    /**
     * 删除前的数据校验
     */
    @Override
    protected void beforeDelete(Collection<${pkColumn.javaType}> ids) {
        log.debug("删除${functionName}前置处理: {}", ids);
        
        // 检查关联数据
        checkRelatedData(ids);
    }

    /**
     * 业务规则校验
     */
    private void validateBusinessRules(${ClassName} entity) {
        // TODO: 实现具体的业务校验逻辑
    }

    /**
     * 检查关联数据
     */
    private void checkRelatedData(Collection<${pkColumn.javaType}> ids) {
        // TODO: 检查是否存在关联数据
    }

    /**
     * 自定义业务方法示例
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean batchUpdateStatus(Collection<${pkColumn.javaType}> ids, String status) {
        log.info("批量更新${functionName}状态: ids={}, status={}", ids, status);
        
        return lambdaUpdate()
            .in(${ClassName}::get${pkColumn.capJavaField}, ids)
            .set(${ClassName}::getStatus, status)
            .update();
### Docker集成

```dockerfile
# Dockerfile.generator
FROM openjdk:17-jre-slim

# 安装必要的工具
RUN apt-get update && apt-get install -y \
    curl \
    unzip \
    && rm -rf /var/lib/apt/lists/*

# 设置工作目录
WORKDIR /app

# 复制应用程序
COPY target/ruoyi-generator.jar app.jar
COPY templates/ templates/

# 环境变量
ENV JAVA_OPTS="-Xms512m -Xmx1024m"
ENV SPRING_PROFILES_ACTIVE=docker

# 暴露端口
EXPOSE 8080

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s \
    CMD curl -f http://localhost:8080/actuator/health || exit 1

# 启动应用
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
yaml
# docker-compose.yml
version: '3.8'

services:
  ruoyi-generator:
    build:
      context: .
      dockerfile: Dockerfile.generator
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=docker
      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/ruoyi
      - SPRING_DATASOURCE_USERNAME=root
      - SPRING_DATASOURCE_PASSWORD=password
    depends_on:
      - mysql
      - redis
    volumes:
      - ./generated-code:/app/generated-code
      - ./custom-templates:/app/custom-templates
    networks:
      - generator-network

  mysql:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=ruoyi
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - generator-network

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    networks:
      - generator-network

volumes:
  mysql_data:

networks:
  generator-network:
    driver: bridge

安全配置

权限控制配置

java
/**
 * 代码生成器安全配置
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class GeneratorSecurityConfig {
    
    /**
     * 代码生成权限验证器
     */
    @Bean
    public CodeGenerationPermissionEvaluator permissionEvaluator() {
        return new CodeGenerationPermissionEvaluator();
    }
    
    /**
     * 安全拦截器
     */
    @Bean
    public CodeGenerationSecurityInterceptor securityInterceptor() {
        return new CodeGenerationSecurityInterceptor();
    }
}

/**
 * 权限验证器
 */
public class CodeGenerationPermissionEvaluator {
    
    /**
     * 验证用户是否有权限操作指定表
     */
    public boolean hasTablePermission(String userId, String dataSource, String tableName) {
        // 检查用户是否有访问指定数据源的权限
        if (!hasDataSourcePermission(userId, dataSource)) {
            return false;
        }
        
        // 检查表级别权限
        return hasSpecificTablePermission(userId, tableName);
    }
    
    /**
     * 验证代码生成权限
     */
    public boolean hasGeneratePermission(String userId, GenTable genTable) {
        // 检查基础权限
        if (!hasTablePermission(userId, genTable.getDataName(), genTable.getTableName())) {
            return false;
        }
        
        // 检查敏感表保护
        if (isSensitiveTable(genTable.getTableName())) {
            return hasAdminPermission(userId);
        }
        
        return true;
    }
    
    private boolean isSensitiveTable(String tableName) {
        List<String> sensitiveTables = Arrays.asList(
            "sys_user", "sys_role", "sys_permission", 
            "sys_config", "financial_", "salary_"
        );
        
        return sensitiveTables.stream()
            .anyMatch(pattern -> tableName.toLowerCase().startsWith(pattern.toLowerCase()));
    }
}

代码生成审计

java
/**
 * 代码生成审计服务
 */
@Service
public class CodeGenerationAuditService {
    
    /**
     * 记录代码生成操作
     */
    @EventListener
    public void handleCodeGenerationEvent(CodeGenerationEvent event) {
        GenerationAuditLog log = new GenerationAuditLog();
        log.setUserId(event.getUserId());
        log.setUserName(event.getUserName());
        log.setTableName(event.getTableName());
        log.setDataSource(event.getDataSource());
        log.setOperationType(event.getOperationType());
        log.setGenerationConfig(JsonUtils.toJsonString(event.getConfig()));
        log.setGeneratedFiles(event.getGeneratedFiles());
        log.setCreateTime(new Date());
        log.setStatus(event.isSuccess() ? "SUCCESS" : "FAILED");
        log.setErrorMessage(event.getErrorMessage());
        
        // 保存审计日志
        saveAuditLog(log);
        
        // 发送通知(如果配置了)
        if (shouldSendNotification(event)) {
            sendNotification(log);
        }
    }
    
    /**
     * 查询审计日志
     */
    public PageResult<GenerationAuditLog> queryAuditLogs(AuditLogQuery query) {
        return auditLogMapper.selectPage(query);
    }
    
    /**
     * 导出审计报告
     */
    public byte[] exportAuditReport(Date startDate, Date endDate) {
        List<GenerationAuditLog> logs = auditLogMapper.selectByDateRange(startDate, endDate);
        return generateAuditReport(logs);
    }
}

敏感信息保护

java
/**
 * 敏感信息保护配置
 */
@Configuration
public class SensitiveDataProtectionConfig {
    
    /**
     * 敏感字段处理器
     */
    @Bean
    public SensitiveFieldProcessor sensitiveFieldProcessor() {
        return new SensitiveFieldProcessor();
    }
}

/**
 * 敏感字段处理器
 */
public class SensitiveFieldProcessor {
    
    private final List<String> sensitiveFields = Arrays.asList(
        "password", "pwd", "secret", "token", 
        "phone", "mobile", "email", "identity_card",
        "bank_account", "credit_card", "salary"
    );
    
    /**
     * 处理敏感字段
     */
    public void processSensitiveFields(GenTable genTable) {
        genTable.getColumns().forEach(column -> {
            if (isSensitiveField(column)) {
                // 标记为敏感字段
                column.setSensitive(true);
                
                // 设置特殊处理规则
                if (isPasswordField(column)) {
                    column.setHtmlType("password");
                    column.setIsQuery("0"); // 密码字段不参与查询
                }
                
                // 在生成的代码中添加脱敏处理
                addDesensitizationAnnotation(column);
            }
        });
    }
    
    private boolean isSensitiveField(GenTableColumn column) {
        String fieldName = column.getColumnName().toLowerCase();
        String comment = column.getColumnComment().toLowerCase();
        
        return sensitiveFields.stream().anyMatch(sensitive -> 
            fieldName.contains(sensitive) || comment.contains(getChineseMapping(sensitive))
        );
    }
    
    private void addDesensitizationAnnotation(GenTableColumn column) {
        // 在生成代码时添加脱敏注解
        String fieldName = column.getColumnName().toLowerCase();
        
        if (fieldName.contains("phone") || fieldName.contains("mobile")) {
            column.setDesensitizeType("MOBILE_PHONE");
        } else if (fieldName.contains("email")) {
            column.setDesensitizeType("EMAIL");
        } else if (fieldName.contains("identity_card")) {
            column.setDesensitizeType("ID_CARD");
        } else {
            column.setDesensitizeType("DEFAULT");
        }
    }
}

监控和运维配置

性能监控

java
/**
 * 代码生成性能监控配置
 */
@Configuration
public class GeneratorMonitoringConfig {
    
    /**
     * 性能监控切面
     */
    @Bean
    public GeneratorPerformanceAspect generatorPerformanceAspect() {
        return new GeneratorPerformanceAspect();
    }
    
    /**
     * 自定义指标注册
     */
    @Bean
    public MeterRegistry generatorMeterRegistry() {
        return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
    }
}

/**
 * 性能监控切面
 */
@Aspect
@Component
public class GeneratorPerformanceAspect {
    
    private final Counter generationCounter;
    private final Timer generationTimer;
    private final Gauge activeGenerations;
    
    public GeneratorPerformanceAspect(MeterRegistry meterRegistry) {
        this.generationCounter = Counter.builder("generator.code.generation.total")
            .description("Total number of code generations")
            .register(meterRegistry);
            
        this.generationTimer = Timer.builder("generator.code.generation.duration")
            .description("Code generation duration")
            .register(meterRegistry);
            
        this.activeGenerations = Gauge.builder("generator.code.generation.active")
            .description("Active code generations")
            .register(meterRegistry, this, GeneratorPerformanceAspect::getActiveGenerations);
    }
    
    @Around("@annotation(GeneratorMonitored)")
    public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
        Timer.Sample sample = Timer.start();
        
        try {
            Object result = joinPoint.proceed();
            generationCounter.increment("status", "success");
            return result;
        } catch (Exception e) {
            generationCounter.increment("status", "failure");
            throw e;
        } finally {
            sample.stop(generationTimer);
        }
    }
    
    private double getActiveGenerations() {
        // 返回当前活跃的代码生成任务数
        return ThreadPoolTaskExecutor.getActiveCount();
    }
}

健康检查配置

java
/**
 * 代码生成器健康检查
 */
@Component
public class GeneratorHealthIndicator implements HealthIndicator {
    
    private final GenTableService genTableService;
    private final DataSourceManagerService dataSourceManagerService;
    
    @Override
    public Health health() {
        Health.Builder builder = Health.up();
        
        try {
            // 检查数据库连接
            checkDatabaseConnection(builder);
            
            // 检查模板可用性
            checkTemplateAvailability(builder);
            
            // 检查缓存状态
            checkCacheStatus(builder);
            
            // 检查磁盘空间
            checkDiskSpace(builder);
            
        } catch (Exception e) {
            return Health.down()
                .withException(e)
                .build();
        }
        
        return builder.build();
    }
    
    private void checkDatabaseConnection(Health.Builder builder) {
        List<String> dataSourceNames = dataSourceManagerService.getAllDataSourceNames();
        Map<String, String> dbStatus = new HashMap<>();
        
        for (String name : dataSourceNames) {
            try {
                boolean connected = dataSourceManagerService.testConnection(name);
                dbStatus.put(name, connected ? "UP" : "DOWN");
            } catch (Exception e) {
                dbStatus.put(name, "ERROR: " + e.getMessage());
            }
        }
        
        builder.withDetail("databases", dbStatus);
    }
    
    private void checkTemplateAvailability(Health.Builder builder) {
        Map<String, Boolean> templateStatus = new HashMap<>();
        
        List<String> requiredTemplates = Arrays.asList(
            "vm/java/controller.java.vm",
            "vm/java/service.java.vm",
            "vm/vue/index.vue.vm"
        );
        
        for (String template : requiredTemplates) {
            try {
                boolean exists = templateExists(template);
                templateStatus.put(template, exists);
            } catch (Exception e) {
                templateStatus.put(template, false);
            }
        }
        
        builder.withDetail("templates", templateStatus);
    }
    
    private void checkCacheStatus(Health.Builder builder) {
        try {
            // 检查Redis连接
            RedisTemplate<String, Object> redisTemplate = SpringUtils.getBean(RedisTemplate.class);
            String pong = redisTemplate.getConnectionFactory().getConnection().ping();
            builder.withDetail("cache", "UP - " + pong);
        } catch (Exception e) {
            builder.withDetail("cache", "DOWN - " + e.getMessage());
        }
    }
    
    private void checkDiskSpace(Health.Builder builder) {
        File tempDir = new File(System.getProperty("java.io.tmpdir"));
        long freeSpace = tempDir.getFreeSpace();
        long totalSpace = tempDir.getTotalSpace();
        double usagePercentage = (double) (totalSpace - freeSpace) / totalSpace * 100;
        
        builder.withDetail("diskSpace", Map.of(
            "total", FileUtils.byteCountToDisplaySize(totalSpace),
            "free", FileUtils.byteCountToDisplaySize(freeSpace),
            "usage", String.format("%.2f%%", usagePercentage)
        ));
        
        if (usagePercentage > 90) {
            builder.down().withDetail("diskSpaceWarning", "Disk usage is above 90%");
        }
    }
}

日志配置

yaml
# logback-spring.xml
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    
    <!-- 代码生成器专用日志配置 -->
    <appender name="GENERATOR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/generator.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/generator.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>3GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level [%logger{36}] - %msg%n</pattern>
        </encoder>
    </appender>
    
    <!-- 审计日志 -->
    <appender name="AUDIT" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/generator-audit.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/generator-audit.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>365</maxHistory>
        </rollingPolicy>
        <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
            <providers>
                <timestamp/>
                <logLevel/>
                <loggerName/>
                <message/>
                <mdc/>
                <arguments/>
            </providers>
        </encoder>
    </appender>
    
    <!-- 性能日志 -->
    <appender name="PERFORMANCE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/generator-performance.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/generator-performance.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level - %msg%n</pattern>
        </encoder>
    </appender>
    
    <!-- 日志记录器配置 -->
    <logger name="plus.ruoyi.generator" level="INFO" additivity="false">
        <appender-ref ref="GENERATOR"/>
        <appender-ref ref="CONSOLE"/>
    </logger>
    
    <logger name="plus.ruoyi.generator.audit" level="INFO" additivity="false">
        <appender-ref ref="AUDIT"/>
    </logger>
    
    <logger name="plus.ruoyi.generator.performance" level="INFO" additivity="false">
        <appender-ref ref="PERFORMANCE"/>
    </logger>
    
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>

配置最佳实践

环境隔离配置

yaml
# application-dev.yml (开发环境)
spring:
  datasource:
    dynamic:
      datasource:
        master:
          url: jdbc:mysql://dev-mysql:3306/ruoyi_dev
          username: dev_user
          password: dev_password

generator:
  config:
    author: "Dev Team"
    outputPath: "/tmp/generated-code"
    enableCache: false
    enableAudit: false
  security:
    enablePermissionCheck: false
    sensitiveDataProtection: false

---
# application-test.yml (测试环境)  
spring:
  datasource:
    dynamic:
      datasource:
        master:
          url: jdbc:mysql://test-mysql:3306/ruoyi_test
          username: test_user
          password: test_password

generator:
  config:
    author: "Test Team"
    outputPath: "/app/generated-code"
    enableCache: true
    enableAudit: true
  security:
    enablePermissionCheck: true
    sensitiveDataProtection: true

---
# application-prod.yml (生产环境)
spring:
  datasource:
    dynamic:
      datasource:
        master:
          url: jdbc:mysql://prod-mysql:3306/ruoyi_prod
          username: ${DB_USERNAME}
          password: ${DB_PASSWORD}

generator:
  config:
    author: "Production Team" 
    outputPath: "/data/generated-code"
    enableCache: true
    enableAudit: true
    auditRetentionDays: 365
  security:
    enablePermissionCheck: true
    sensitiveDataProtection: true
    requireApproval: true
  performance:
    maxConcurrentGenerations: 5
    timeoutSeconds: 300

配置验证

java
/**
 * 配置验证器
 */
@Component
@ConfigurationProperties(prefix = "generator")
@Validated
public class GeneratorProperties {
    
    @Valid
    private ConfigProperties config = new ConfigProperties();
    
    @Valid 
    private SecurityProperties security = new SecurityProperties();
    
    @Valid
    private PerformanceProperties performance = new PerformanceProperties();
    
    @Data
    @Validated
    public static class ConfigProperties {
        
        @NotBlank(message = "作者信息不能为空")
        private String author;
        
        @NotBlank(message = "输出路径不能为空")
        private String outputPath;
        
        private boolean enableCache = true;
        private boolean enableAudit = true;
        
        @Min(value = 1, message = "审计日志保留天数不能小于1")
        @Max(value = 3650, message = "审计日志保留天数不能大于3650")
        private int auditRetentionDays = 90;
    }
    
    @Data
    @Validated
    public static class SecurityProperties {
        private boolean enablePermissionCheck = true;
        private boolean sensitiveDataProtection = true;
        private boolean requireApproval = false;
        
        @Size(min = 1, message = "至少需要配置一个管理员")
        private List<String> administrators = new ArrayList<>();
    }
    
    @Data
    @Validated
    public static class PerformanceProperties {
        
        @Min(value = 1, message = "最大并发生成数不能小于1")
        @Max(value = 50, message = "最大并发生成数不能大于50")
        private int maxConcurrentGenerations = 10;
        
        @Min(value = 30, message = "超时时间不能小于30秒")
        @Max(value = 3600, message = "超时时间不能大于1小时")
        private int timeoutSeconds = 300;
        
        @Min(value = 100, message = "缓存大小不能小于100")
        private int cacheSize = 1000;
    }
    
    /**
     * 配置后置处理
     */
    @PostConstruct
    public void validateConfiguration() {
        // 验证输出路径是否可写
        File outputDir = new File(config.getOutputPath());
        if (!outputDir.exists() && !outputDir.mkdirs()) {
            throw new IllegalStateException("无法创建输出目录: " + config.getOutputPath());
        }
        
        if (!outputDir.canWrite()) {
            throw new IllegalStateException("输出目录不可写: " + config.getOutputPath());
        }
        
        // 验证管理员配置
        if (security.isRequireApproval() && security.getAdministrators().isEmpty()) {
            throw new IllegalStateException("启用审批模式时必须配置管理员");
        }
    }
}

配置热更新

java
/**
 * 配置热更新支持
 */
@Component
public class GeneratorConfigurationRefresher {
    
    private final GeneratorProperties properties;
    private final ApplicationEventPublisher eventPublisher;
    
    /**
     * 监听配置变更事件
     */
    @EventListener
    public void handleConfigurationRefresh(RefreshScopeRefreshedEvent event) {
        log.info("检测到配置刷新事件,重新加载代码生成器配置");
        
        // 清除缓存
        clearCaches();
        
        // 重新验证配置
        validateConfiguration();
        
        // 发布配置更新事件
        eventPublisher.publishEvent(new GeneratorConfigurationUpdatedEvent(properties));
    }
    
    /**
     * 手动刷新配置
     */
    public void refreshConfiguration() {
        RefreshScope refreshScope = SpringUtils.getBean(RefreshScope.class);
        refreshScope.refreshAll();
    }
    
    private void clearCaches() {
        CacheManager cacheManager = SpringUtils.getBean("generatorCacheManager", CacheManager.class);
        cacheManager.getCacheNames().forEach(cacheName -> {
            Cache cache = cacheManager.getCache(cacheName);
            if (cache != null) {
                cache.clear();
            }
        });
    }
}

故障排除

常见问题诊断

java
/**
 * 代码生成器诊断工具
 */
@RestController
@RequestMapping("/tool/gen/diagnostic")
public class GeneratorDiagnosticController {
    
    /**
     * 系统诊断
     */
    @GetMapping("/system")
    public R<Map<String, Object>> systemDiagnostic() {
        Map<String, Object> diagnostic = new HashMap<>();
        
        // JVM信息
        diagnostic.put("jvm", getJvmInfo());
        
        // 数据源状态
        diagnostic.put("datasources", getDatasourceStatus());
        
        // 模板状态
        diagnostic.put("templates", getTemplateStatus());
        
        // 缓存状态
        diagnostic.put("cache", getCacheStatus());
        
        // 磁盘空间
        diagnostic.put("disk", getDiskSpaceInfo());
        
        return R.ok(diagnostic);
    }
    
    /**
     * 连接测试
     */
    @PostMapping("/connection/test")
    public R<String> testConnection(@RequestBody DataSourceConfig config) {
        try {
            boolean connected = testDatabaseConnection(config);
            return connected ? R.ok("连接成功") : R.fail("连接失败");
        } catch (Exception e) {
            return R.fail("连接异常: " + e.getMessage());
        }
    }
    
    /**
     * 模板验证
     */
    @PostMapping("/template/validate")
    public R<Map<String, Object>> validateTemplate(@RequestParam String templatePath) {
        Map<String, Object> result = new HashMap<>();
        
        try {
            // 检查模板文件是否存在
            boolean exists = templateExists(templatePath);
            result.put("exists", exists);
            
            if (exists) {
                // 语法验证
                boolean syntaxValid = validateTemplateSyntax(templatePath);
                result.put("syntaxValid", syntaxValid);
                
                // 获取模板变量
                List<String> variables = extractTemplateVariables(templatePath);
                result.put("variables", variables);
            }
            
            return R.ok(result);
        } catch (Exception e) {
            result.put("error", e.getMessage());
            return R.fail(result);
        }
    }
    
    /**
     * 性能报告
     */
    @GetMapping("/performance")
    public R<Map<String, Object>> getPerformanceReport() {
        Map<String, Object> report = new HashMap<>();
        
        // 生成统计
        report.put("generationStats", getGenerationStatistics());
        
        // 缓存统计
        report.put("cacheStats", getCacheStatistics());
        
        // 线程池状态
        report.put("threadPool", getThreadPoolStatus());
        
        // 内存使用
        report.put("memory", getMemoryUsage());
        
        return R.ok(report);
    }
}

问题解决指南

问题类型症状可能原因解决方案
连接失败无法获取数据库元数据数据库配置错误、网络问题检查数据源配置、测试网络连接
模板错误代码生成失败,模板异常模板语法错误、变量未定义验证模板语法、检查变量引用
内存不足OutOfMemoryError大量数据同时处理增加堆内存、优化查询逻辑
生成超时代码生成长时间无响应数据量过大、SQL执行慢增加超时配置、优化数据库查询
权限拒绝无法访问指定表用户权限不足检查用户权限配置
缓存异常Redis连接失败Redis服务不可用检查Redis连接配置

通过本章介绍的高级配置功能,你可以根据项目需求对代码生成器进行深度定制,提升生成效率,确保代码质量,并满足企业级应用的安全和运维要求。


### 模板配置管理

#### 1. 模板配置文件

```yaml
# application-generator.yml
generator:
  template:
    # 模板根目录
    baseDir: "vm"
    # 自定义模板目录
    customDir: "custom-vm" 
    # 模板缓存配置
    cache:
      enabled: true
      size: 100
      ttl: 3600
  # 代码生成配置
  code:
    # 默认作者
    defaultAuthor: "Code Generator"
    # 版权信息
    copyright: "Copyright (c) 2024"
    # 代码格式化
    format:
      enabled: true
      indent: 4

2. 模板加载器配置

java
@Configuration
public class TemplateConfig {
    
    /**
     * 自定义模板加载器
     */
    @Bean
    public VelocityEngine velocityEngine() {
        Properties props = new Properties();
        // 设置模板路径
        props.setProperty("resource.loader.file.class", 
            "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        // 设置编码
        props.setProperty(Velocity.INPUT_ENCODING, "UTF-8");
        props.setProperty(Velocity.OUTPUT_ENCODING, "UTF-8");
        // 设置缓存
        props.setProperty("resource.loader.file.cache", "true");
        props.setProperty("resource.loader.file.modification_check_interval", "3600");
        
        VelocityEngine engine = new VelocityEngine(props);
        engine.init();
        return engine;
    }
    
    /**
     * 自定义模板工具
     */
    @Bean
    public Map<String, Object> templateTools() {
        Map<String, Object> tools = new HashMap<>();
        tools.put("StringUtils", StringUtils.class);
        tools.put("DateUtils", DateUtils.class);
        tools.put("NumberUtils", NumberUtils.class);
        return tools;
    }
}

字段类型扩展

自定义字段类型处理器

java
/**
 * 自定义字段类型处理器
 */
@Component
public class CustomFieldTypeHandler implements FieldTypeHandler {
    
    @Override
    public boolean supports(String dbType) {
        return Arrays.asList("json", "jsonb", "geometry").contains(dbType.toLowerCase());
    }
    
    @Override
    public FieldTypeInfo handle(GenTableColumn column) {
        String dbType = column.getColumnType().toLowerCase();
        FieldTypeInfo typeInfo = new FieldTypeInfo();
        
        switch (dbType) {
            case "json":
            case "jsonb":
                typeInfo.setJavaType("String");
                typeInfo.setHtmlType("textarea");
                typeInfo.setImports(Arrays.asList("com.fasterxml.jackson.annotation.JsonRawValue"));
                break;
            case "geometry":
                typeInfo.setJavaType("String");
                typeInfo.setHtmlType("input");
                break;
        }
        
        return typeInfo;
    }
}

自定义HTML控件类型

java
/**
 * 扩展HTML控件类型
 */
public class ExtendedHtmlTypes {
    
    public static final String HTML_COLOR_PICKER = "colorPicker";
    public static final String HTML_SLIDER = "slider";
    public static final String HTML_RATE = "rate";
    public static final String HTML_SWITCH = "switch";
    
    /**
     * 根据字段特征推断HTML类型
     */
    public static String inferHtmlType(GenTableColumn column) {
        String columnName = column.getColumnName().toLowerCase();
        String comment = column.getColumnComment().toLowerCase();
        
        // 颜色字段
        if (columnName.contains("color") || comment.contains("颜色")) {
            return HTML_COLOR_PICKER;
        }
        
        // 评分字段  
        if (columnName.contains("rate") || columnName.contains("score") || 
            comment.contains("评分") || comment.contains("得分")) {
            return HTML_RATE;
        }
        
        // 开关字段
        if (columnName.startsWith("is_") || comment.contains("是否")) {
            return HTML_SWITCH;
        }
        
        return GenConstants.HTML_INPUT;
    }
}

数据源高级配置

多数据源配置

yaml
spring:
  datasource:
    dynamic:
      # 主数据源
      primary: master
      # 严格模式
      strict: false
      datasource:
        # 主库
        master:
          url: jdbc:mysql://localhost:3306/ruoyi_main
          username: root
          password: password
          driver-class-name: com.mysql.cj.jdbc.Driver
        # 业务库1
        business1:
          url: jdbc:mysql://localhost:3306/business1
          username: root
          password: password
          driver-class-name: com.mysql.cj.jdbc.Driver
        # 业务库2
        business2:
          url: jdbc:postgresql://localhost:5432/business2
          username: postgres
          password: password
          driver-class-name: org.postgresql.Driver
        # Oracle库
        oracle:
          url: jdbc:oracle:thin:@localhost:1521:XE
          username: system
          password: oracle
          driver-class-name: oracle.jdbc.driver.OracleDriver

数据源动态管理

java
/**
 * 数据源动态管理服务
 */
@Service
public class DataSourceManagerService {
    
    private final DynamicRoutingDataSource dynamicDataSource;
    
    /**
     * 添加数据源
     */
    public void addDataSource(String name, DataSourceProperty property) {
        DataSource dataSource = createDataSource(property);
        dynamicDataSource.addDataSource(name, dataSource);
    }
    
    /**
     * 移除数据源
     */
    public void removeDataSource(String name) {
        dynamicDataSource.removeDataSource(name);
    }
    
    /**
     * 测试数据源连接
     */
    public boolean testConnection(String dataSourceName) {
        try {
            DataSource dataSource = dynamicDataSource.getDataSource(dataSourceName);
            try (Connection connection = dataSource.getConnection()) {
                return connection.isValid(5000);
            }
        } catch (Exception e) {
            log.error("测试数据源连接失败: {}", dataSourceName, e);
            return false;
        }
    }
    
    /**
     * 获取数据源元数据
     */
    @DS("#dataSourceName")
    public DatabaseMetaData getDatabaseMetadata(String dataSourceName) throws SQLException {
        return DataSourceUtils.getConnection(
            dynamicDataSource.getDataSource(dataSourceName)
        ).getMetaData();
    }
}

数据库兼容性处理

java
/**
 * 数据库兼容性处理器
 */
@Component
public class DatabaseCompatibilityHandler {
    
    /**
     * 获取数据库特定的SQL方言
     */
    public String getDialect(String dataSourceName) {
        try {
            DatabaseMetaData metaData = getDatabaseMetadata(dataSourceName);
            String productName = metaData.getDatabaseProductName().toLowerCase();
            
            if (productName.contains("mysql")) {
                return "mysql";
            } else if (productName.contains("postgresql")) {
                return "postgresql";
            } else if (productName.contains("oracle")) {
                return "oracle";
            } else if (productName.contains("sql server")) {
                return "sqlserver";
            }
            
            return "mysql"; // 默认
        } catch (SQLException e) {
            log.warn("无法获取数据库类型,使用默认MySQL方言", e);
            return "mysql";
        }
    }
    
    /**
     * 处理数据库特定的字段类型
     */
    public String mapColumnType(String originalType, String dialect) {
        Map<String, String> typeMapping = getTypeMapping(dialect);
        return typeMapping.getOrDefault(originalType.toLowerCase(), originalType);
    }
    
    private Map<String, String> getTypeMapping(String dialect) {
        switch (dialect) {
            case "postgresql":
                return Map.of(
                    "int8", "bigint",
                    "int4", "int",
                    "int2", "smallint",
                    "text", "longtext",
                    "timestamptz", "datetime"
                );
            case "oracle":
                return Map.of(
                    "number", "decimal",
                    "varchar2", "varchar",
                    "nvarchar2", "nvarchar",
                    "clob", "longtext"
                );
            case "sqlserver":
                return Map.of(
                    "ntext", "longtext",
                    "image", "longblob",
                    "datetime2", "datetime"
                );
            default:
                return Collections.emptyMap();
        }
    }
}

代码生成扩展

自定义代码生成器

java
/**
 * 自定义代码生成器
 */
@Component
public class CustomCodeGenerator implements CodeGenerator {
    
    private final VelocityEngine velocityEngine;
    private final List<CodeGeneratorPlugin> plugins;
    
    @Override
    public Map<String, String> generate(GenTable genTable) {
        Map<String, String> codeMap = new HashMap<>();
        
        // 前置插件处理
        plugins.forEach(plugin -> plugin.beforeGenerate(genTable));
        
        try {
            // 准备模板上下文
            VelocityContext context = prepareContext(genTable);
            
            // 生成各类代码
            codeMap.putAll(generateBackendCode(genTable, context));
            codeMap.putAll(generateFrontendCode(genTable, context));
            codeMap.putAll(generateSqlScript(genTable, context));
            
            // 后置插件处理
            plugins.forEach(plugin -> plugin.afterGenerate(genTable, codeMap));
            
        } catch (Exception e) {
            throw new ServiceException("代码生成失败", e);
        }
        
        return codeMap;
    }
    
    /**
     * 生成后端代码
     */
    private Map<String, String> generateBackendCode(GenTable genTable, VelocityContext context) {
        Map<String, String> codeMap = new HashMap<>();
        
        List<String> templates = Arrays.asList(
            "vm/java/domain.java.vm",
            "vm/java/bo.java.vm", 
            "vm/java/vo.java.vm",
            "vm/java/mapper.java.vm",
            "vm/java/service.java.vm",
            "vm/java/serviceImpl.java.vm",
            "vm/java/controller.java.vm"
        );
        
        templates.forEach(template -> {
            String code = renderTemplate(template, context);
            codeMap.put(template, code);
        });
        
        return codeMap;
    }
    
    /**
     * 渲染模板
     */
    private String renderTemplate(String templatePath, VelocityContext context) {
        try (StringWriter writer = new StringWriter()) {
            Template template = velocityEngine.getTemplate(templatePath);
            template.merge(context, writer);
            return writer.toString();
        } catch (Exception e) {
            throw new ServiceException("模板渲染失败: " + templatePath, e);
        }
    }
}

代码生成插件系统

java
/**
 * 代码生成插件接口
 */
public interface CodeGeneratorPlugin {
    
    /**
     * 插件名称
     */
    String getName();
    
    /**
     * 插件优先级
     */
    default int getOrder() {
        return 0;
    }
    
    /**
     * 生成前处理
     */
    void beforeGenerate(GenTable genTable);
    
    /**
     * 生成后处理
     */
    void afterGenerate(GenTable genTable, Map<String, String> codeMap);
}

/**
 * 代码格式化插件
 */
@Component
public class CodeFormatterPlugin implements CodeGeneratorPlugin {
    
    @Override
    public String getName() {
        return "CodeFormatter";
    }
    
    @Override
    public void afterGenerate(GenTable genTable, Map<String, String> codeMap) {
        codeMap.replaceAll((path, code) -> {
            if (path.endsWith(".java.vm")) {
                return formatJavaCode(code);
            } else if (path.endsWith(".vue.vm")) {
                return formatVueCode(code);
            }
            return code;
        });
    }
    
    private String formatJavaCode(String code) {
        // 使用 google-java-format 或其他格式化工具
        return code;
    }
    
    private String formatVueCode(String code) {
        // 使用 prettier 或其他格式化工具
        return code;
    }
}

/**
 * 版权信息插件
 */
@Component
public class CopyrightPlugin implements CodeGeneratorPlugin {
    
    @Override
    public String getName() {
        return "Copyright";
    }
    
    @Override
    public void afterGenerate(GenTable genTable, Map<String, String> codeMap) {
        String copyright = getCopyrightHeader();
        
        codeMap.replaceAll((path, code) -> {
            if (needsCopyright(path)) {
                return copyright + "\n" + code;
            }
            return code;
        });
    }
    
    private String getCopyrightHeader() {
        return "/*\n" +
               " * Copyright (c) " + Year.now().getValue() + " Your Company\n" +
               " * All rights reserved.\n" +
               " */";
    }
    
    private boolean needsCopyright(String path) {
        return path.endsWith(".java.vm") || path.endsWith(".ts.vm");
    }
}

性能优化配置

缓存配置

java
/**
 * 代码生成缓存配置
 */
@Configuration
@EnableCaching
public class GeneratorCacheConfig {
    
    @Bean("generatorCacheManager")
    public CacheManager generatorCacheManager() {
        RedisCacheManager.Builder builder = RedisCacheManager.builder(redisConnectionFactory())
            .cacheDefaults(cacheConfiguration(Duration.ofHours(1)));
        
        // 不同类型数据的缓存配置
        Map<String, RedisCacheConfiguration> configs = new HashMap<>();
        configs.put("tableMetadata", cacheConfiguration(Duration.ofMinutes(30)));
        configs.put("columnMetadata", cacheConfiguration(Duration.ofMinutes(30)));
        configs.put("templates", cacheConfiguration(Duration.ofHours(2)));
        
        return builder.withInitialCacheConfigurations(configs).build();
    }
    
    private RedisCacheConfiguration cacheConfiguration(Duration ttl) {
        return RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(ttl)
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
    }
}

/**
 * 缓存服务
 */
@Service
public class GeneratorCacheService {
    
    /**
     * 缓存表元数据
     */
    @Cacheable(value = "tableMetadata", key = "#dataSource + ':' + #tableName")
    public TableMetadata getTableMetadata(String dataSource, String tableName) {
        return loadTableMetadata(dataSource, tableName);
    }
    
    /**
     * 缓存模板内容
     */
    @Cacheable(value = "templates", key = "#templatePath")
    public String getTemplate(String templatePath) {
        return loadTemplate(templatePath);
    }
    
    /**
     * 清除缓存
     */
    @CacheEvict(value = {"tableMetadata", "columnMetadata", "templates"}, allEntries = true)
    public void clearCache() {
        log.info("清除代码生成器缓存");
    }
}

异步生成配置

java
/**
 * 异步任务配置
 */
@Configuration
@EnableAsync
public class AsyncConfig {
    
    @Bean("generatorTaskExecutor")
    public TaskExecutor generatorTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(4);
        executor.setMaxPoolSize(8);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("generator-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

/**
 * 异步代码生成服务
 */
@Service
public class AsyncCodeGeneratorService {
    
    /**
     * 异步生成代码
     */
    @Async("generatorTaskExecutor")
    public CompletableFuture<String> generateCodeAsync(Long tableId) {
        log.info("开始异步生成代码: tableId={}", tableId);
        
        try {
            // 生成代码逻辑
            String result = doGenerateCode(tableId);
            
            log.info("异步代码生成完成: tableId={}", tableId);
            return CompletableFuture.completedFuture(result);
            
        } catch (Exception e) {
            log.error("异步代码生成失败: tableId={}", tableId, e);
            return CompletableFuture.failedFuture(e);
        }
    }
    
    /**
     * 批量异步生成
     */
    @Async("generatorTaskExecutor")
    public CompletableFuture<Map<Long, String>> batchGenerateAsync(List<Long> tableIds) {
        Map<Long, String> results = new ConcurrentHashMap<>();
        
        List<CompletableFuture<Void>> futures = tableIds.stream()
            .map(tableId -> generateCodeAsync(tableId)
                .thenAccept(result -> results.put(tableId, result)))
            .collect(Collectors.toList());
        
        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
            .thenApply(v -> results);
    }
}

CI/CD 集成

Maven插件集成

xml
<!-- pom.xml -->
<plugin>
    <groupId>plus.ruoyi</groupId>
    <artifactId>ruoyi-generator-maven-plugin</artifactId>
    <version>1.0.0</version>
    <configuration>
        <!-- 数据源配置 -->
        <dataSource>
            <url>jdbc:mysql://localhost:3306/ruoyi</url>
            <username>root</username>
            <password>password</password>
            <driver>com.mysql.cj.jdbc.Driver</driver>
        </dataSource>
        <!-- 生成配置 -->
        <generator>
            <author>Generator Plugin</author>
            <packageName>plus.ruoyi.business</packageName>
            <outputDir>${project.basedir}/src/main/java</outputDir>
            <tables>
                <table>
                    <name>sys_user</name>
                    <template>crud</template>
                </table>
            </tables>
        </generator>
    </configuration>
    <executions>
        <execution>
            <phase>generate-sources</phase>
            <goals>
                <goal>generate</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Gradle插件集成

// build.gradle
plugins {
    id 'plus.ruoyi.generator' version '1.0.0'
}

generator {
    dataSource {
        url = 'jdbc:mysql://localhost:3306/ruoyi'
        username = 'root'
        password = 'password'
        driver = 'com.mysql.cj.jdbc.Driver'
    }
    
    config {
        author = 'Generator Plugin'
        packageName = 'plus.ruoyi.business'
        outputDir = file('src/main/java')
    }
    
    tables {
        table {
            name = 'sys_user'
            template = 'crud'
        }
    }
}

// 在构建时自动生成代码
compileJava.dependsOn generateCode

Jenkins集成

groovy
pipeline {
    agent any
    
    environment {
        DB_URL = credentials('database-url')
        DB_USER = credentials('database-user')
        DB_PASS = credentials('database-password')
    }
    
    stages {
        stage('Checkout') {
            steps {
                git branch: 'main', url: 'https://github.com/your-repo/project.git'
            }
        }
        
        stage('Generate Code') {
            steps {
                script {
                    // 调用代码生成API
                    def response = httpRequest(
                        httpMode: 'POST',
                        url: "${GENERATOR_API_URL}/tool/gen/generateCodesBatch",
                        contentType: 'APPLICATION_JSON',
                        requestBody: '''
                        {
                            "tableIds": ["1", "2", "3"],
                            "outputPath": "./generated-code"
                        }
                        '''
                    )
                    
                    if (response.status != 200) {
                        error("代码生成失败: ${response.content}")
                    }
                }
            }
        }
        
        stage('Code Quality Check') {
            steps {
                // 对生成的代码进行质量检查
                sh 'mvn sonar:sonar -Dsonar.projectKey=generated-code'
            }
        }
        
        stage('Build') {
            steps {
                sh 'mvn clean compile'
            }
        }
        
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        
        stage('Deploy') {
            when {
                branch 'main'
            }
            steps {
                sh 'mvn deploy'
            }
        }
    }
    
    post {
        always {
            // 清理生成的临时文件
            cleanWs()
        }
        success {
            // 发送成功通知
            emailext(
                subject: "代码生成和构建成功 - ${env.JOB_NAME} #${env.BUILD_NUMBER}",
                body: "代码生成和构建已成功完成。",
                to: "${env.CHANGE_AUTHOR_EMAIL}"
            )
        }
        failure {
            // 发送失败通知
            emailext(
                subject: "代码生成或构建失败 - ${env.JOB_NAME} #${env.BUILD_NUMBER}",
                body: "代码生成或构建过程中出现错误,请检查日志。",
                to: "${env.CHANGE_AUTHOR_EMAIL}"
            )
        }
    }
}