Skip to content

自定义模板

定制 OpenAPI Generator 的代码生成模板

模板引擎概述

OpenAPI Generator 使用 Mustache 模板引擎,通过模板定制可以:

  • 🎨 自定义代码风格: 符合团队编码规范
  • 🔧 添加特定功能: 日志、监控、缓存等
  • 📦 集成框架特性: 依赖注入、AOP 等
  • 🏢 企业标准化: 版权信息、安全要求

模板结构

模板文件组织

custom-templates/
├── api/                    # API 相关模板
│   ├── api.mustache       # 接口定义
│   ├── apiController.mustache  # 控制器实现
│   └── apiService.mustache     # 服务层
├── model/                  # 数据模型模板
│   ├── model.mustache     # 模型类
│   └── enum.mustache      # 枚举类型
├── supporting/             # 支持文件模板
│   ├── pom.mustache       # Maven 配置
│   ├── README.mustache    # 项目说明
│   └── Dockerfile.mustache # Docker 配置
└── config.json            # 模板配置

模板变量

常用的 Mustache 变量:

mustache
{{package}}              # 包名
{{classname}}            # 类名
{{#operations}}          # 操作列表
  {{classname}}          # API 类名
  {{#operation}}         # 单个操作
    {{operationId}}      # 操作 ID
    {{httpMethod}}       # HTTP 方法
    {{path}}             # 路径
    {{summary}}          # 摘要
    {{notes}}            # 详细说明
    {{#allParams}}       # 所有参数
      {{paramName}}      # 参数名
      {{dataType}}       # 数据类型
      {{required}}       # 是否必需
    {{/allParams}}
  {{/operation}}
{{/operations}}

创建自定义模板

1. Java Spring Boot 模板

自定义控制器模板

mustache
{{! apiController.mustache }}
package {{package}};

{{#imports}}import {{import}};
{{/imports}}
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import io.micrometer.core.annotation.Timed;
import io.micrometer.core.annotation.Counted;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CacheEvict;

/**
 * {{#appName}}{{appName}} - {{/appName}}{{classname}} 控制器
 * {{#appDescription}}{{appDescription}}{{/appDescription}}
 * 
 * @author OpenAPI Generator
 * @version {{appVersion}}
 * @since {{generatedDate}}
 */
@Slf4j
@RestController
@RequestMapping("{{#contextPath}}{{contextPath}}{{/contextPath}}{{basePath}}")
@RequiredArgsConstructor
@Validated
@Tag(name = "{{baseName}}", description = "{{description}}")
public class {{classname}}Controller implements {{classname}} {

    private final {{classname}}Service service;
    private final MetricsService metricsService;
    
    {{#operations}}
    {{#operation}}
    /**
     * {{summary}}
     {{#notes}}
     * {{notes}}
     {{/notes}}
     */
    @Override
    @{{httpMethod}}("{{path}}")
    @Timed(value = "api.{{operationIdCamelCase}}.time", description = "{{summary}} 执行时间")
    @Counted(value = "api.{{operationIdCamelCase}}.count", description = "{{summary}} 调用次数")
    {{#vendorExtensions.x-cached}}
    @Cacheable(value = "{{operationIdCamelCase}}", key = "#{{#allParams}}{{#isPathParam}}{{paramName}}{{/isPathParam}}{{/allParams}}")
    {{/vendorExtensions.x-cached}}
    public ResponseEntity<{{>returnTypes}}> {{operationId}}(
        {{#allParams}}
        {{>queryParams}}{{>pathParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{#hasMore}},{{/hasMore}}
        {{/allParams}}
    ) {
        // 记录请求
        log.info("API 调用: {{operationId}} {{#allParams}}{{#isPathParam}}- {{paramName}}: {}{{/isPathParam}}{{/allParams}}"{{#allParams}}{{#isPathParam}}, {{paramName}}{{/isPathParam}}{{/allParams}});
        
        // 开始计时
        long startTime = System.currentTimeMillis();
        
        try {
            // 参数验证
            {{#allParams}}
            {{#required}}
            Objects.requireNonNull({{paramName}}, "参数 {{paramName}} 不能为空");
            {{/required}}
            {{/allParams}}
            
            // 调用服务层
            {{#returnType}}{{returnType}} result = {{/returnType}}service.{{operationId}}({{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}});
            
            // 记录响应时间
            long duration = System.currentTimeMillis() - startTime;
            log.info("API 响应: {{operationId}} - 耗时: {}ms", duration);
            metricsService.recordApiCall("{{operationId}}", duration, true);
            
            {{#returnType}}
            return ResponseEntity.ok()
                .header("X-Response-Time", String.valueOf(duration))
                .body(result);
            {{/returnType}}
            {{^returnType}}
            return ResponseEntity.ok()
                .header("X-Response-Time", String.valueOf(duration))
                .build();
            {{/returnType}}
            
        } catch (NotFoundException e) {
            log.warn("资源未找到: {}", e.getMessage());
            metricsService.recordApiCall("{{operationId}}", System.currentTimeMillis() - startTime, false);
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage());
        } catch (ValidationException e) {
            log.warn("参数验证失败: {}", e.getMessage());
            metricsService.recordApiCall("{{operationId}}", System.currentTimeMillis() - startTime, false);
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
        } catch (Exception e) {
            log.error("处理请求失败", e);
            metricsService.recordApiCall("{{operationId}}", System.currentTimeMillis() - startTime, false);
            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "内部服务器错误");
        }
    }
    
    {{#vendorExtensions.x-cached}}
    @CacheEvict(value = "{{operationIdCamelCase}}", allEntries = true)
    public void evict{{operationIdCamelCase}}Cache() {
        log.info("清除缓存: {{operationIdCamelCase}}");
    }
    {{/vendorExtensions.x-cached}}
    
    {{/operation}}
    {{/operations}}
}

自定义模型模板

mustache
{{! model.mustache }}
package {{package}};

{{#imports}}import {{import}};
{{/imports}}
import lombok.*;
import lombok.experimental.SuperBuilder;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.*;
import javax.validation.Valid;
import java.io.Serializable;
import java.time.OffsetDateTime;
import java.util.*;

/**
 * {{#description}}{{description}}{{/description}}{{^description}}{{classname}}{{/description}}
 */
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = {{#parent}}true{{/parent}}{{^parent}}false{{/parent}})
@ToString(callSuper = {{#parent}}true{{/parent}}{{^parent}}false{{/parent}})
@Schema(description = "{{description}}")
{{#parent}}
public class {{classname}} extends {{parent}} implements Serializable {
{{/parent}}
{{^parent}}
public class {{classname}} implements Serializable {
{{/parent}}
    
    private static final long serialVersionUID = 1L;
    
    {{#vars}}
    /**
     * {{description}}
     */
    @JsonProperty("{{baseName}}")
    @Schema(
        description = "{{description}}"{{#example}},
        example = "{{example}}"{{/example}}{{#required}},
        required = true{{/required}}
    )
    {{#required}}
    @NotNull(message = "{{baseName}} 不能为空")
    {{/required}}
    {{#isString}}
    {{#minLength}}
    @Size(min = {{minLength}}{{#maxLength}}, max = {{maxLength}}{{/maxLength}}, message = "{{baseName}} 长度必须在 {{minLength}}{{#maxLength}} 到 {{maxLength}}{{/maxLength}} 之间")
    {{/minLength}}
    {{^minLength}}
    {{#maxLength}}
    @Size(max = {{maxLength}}, message = "{{baseName}} 长度不能超过 {{maxLength}}")
    {{/maxLength}}
    {{/minLength}}
    {{#pattern}}
    @Pattern(regexp = "{{pattern}}", message = "{{baseName}} 格式不正确")
    {{/pattern}}
    {{/isString}}
    {{#isEmail}}
    @Email(message = "{{baseName}} 必须是有效的邮箱地址")
    {{/isEmail}}
    {{#minimum}}
    @Min(value = {{minimum}}, message = "{{baseName}} 不能小于 {{minimum}}")
    {{/minimum}}
    {{#maximum}}
    @Max(value = {{maximum}}, message = "{{baseName}} 不能大于 {{maximum}}")
    {{/maximum}}
    {{#isContainer}}
    @Valid
    {{/isContainer}}
    private {{{dataType}}} {{name}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}};
    
    {{/vars}}
    
    {{#hasEnums}}
    {{#vars}}
    {{#isEnum}}
    /**
     * {{description}}
     */
    @JsonCreator
    public enum {{datatypeWithEnum}} {
        {{#allowableValues}}
        {{#enumVars}}
        @JsonProperty("{{value}}")
        {{name}}("{{value}}"){{^-last}},{{/-last}}{{#-last}};{{/-last}}
        {{/enumVars}}
        {{/allowableValues}}
        
        private final String value;
        
        {{datatypeWithEnum}}(String value) {
            this.value = value;
        }
        
        @JsonValue
        public String getValue() {
            return value;
        }
        
        @Override
        public String toString() {
            return String.valueOf(value);
        }
        
        public static {{datatypeWithEnum}} fromValue(String value) {
            for ({{datatypeWithEnum}} e : {{datatypeWithEnum}}.values()) {
                if (e.value.equals(value)) {
                    return e;
                }
            }
            throw new IllegalArgumentException("Unexpected value '" + value + "'");
        }
    }
    {{/isEnum}}
    {{/vars}}
    {{/hasEnums}}
    
    /**
     * 验证对象
     */
    public boolean isValid() {
        {{#requiredVars}}
        if ({{name}} == null) {
            return false;
        }
        {{/requiredVars}}
        return true;
    }
    
    /**
     * 转换为 Map
     */
    public Map<String, Object> toMap() {
        Map<String, Object> map = new HashMap<>();
        {{#vars}}
        map.put("{{baseName}}", {{name}});
        {{/vars}}
        return map;
    }
}

2. TypeScript 模板

自定义 API 客户端模板

mustache
{{! apiClient.mustache }}
/* tslint:disable */
/* eslint-disable */
/**
 * {{appName}}
 * {{appDescription}}
 *
 * OpenAPI spec version: {{appVersion}}
 * {{#contact}}Contact: {{contactEmail}}{{/contact}}
 *
 * NOTE: This file is auto generated by OpenAPI Generator
 * Do not edit this file manually.
 */

{{#imports}}
import { {{classname}} } from '{{filename}}';
{{/imports}}
import { Configuration } from './configuration';
import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import axios from 'axios';

export interface {{classname}}Interface {
{{#operations}}
{{#operation}}
    /**
     * {{summary}}
     {{#notes}}
     * {{&notes}}
     {{/notes}}
     */
    {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}{{#hasMore}}, {{/hasMore}}{{/allParams}}): Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}>;
{{/operation}}
{{/operations}}
}

export class {{classname}} implements {{classname}}Interface {
    protected axios: AxiosInstance;
    protected basePath: string;
    protected configuration: Configuration;
    
    // 请求拦截器
    private requestInterceptor?: number;
    private responseInterceptor?: number;
    
    // 缓存
    private cache = new Map<string, { data: any; expires: number }>();
    
    constructor(configuration?: Configuration, basePath?: string, axiosInstance?: AxiosInstance) {
        this.configuration = configuration || new Configuration();
        this.basePath = basePath || this.configuration.basePath || '{{basePath}}';
        this.axios = axiosInstance || axios.create();
        
        this.setupInterceptors();
    }
    
    /**
     * 设置拦截器
     */
    private setupInterceptors(): void {
        // 请求拦截器
        this.requestInterceptor = this.axios.interceptors.request.use(
            (config) => {
                // 添加认证信息
                if (this.configuration.accessToken) {
                    config.headers.Authorization = `Bearer ${this.configuration.accessToken}`;
                }
                
                // 添加请求 ID
                config.headers['X-Request-ID'] = this.generateRequestId();
                
                // 添加时间戳
                config.params = {
                    ...config.params,
                    _t: Date.now()
                };
                
                console.log(`[API] ${config.method?.toUpperCase()} ${config.url}`);
                return config;
            },
            (error) => {
                console.error('[API] Request error:', error);
                return Promise.reject(error);
            }
        );
        
        // 响应拦截器
        this.responseInterceptor = this.axios.interceptors.response.use(
            (response) => {
                const duration = Date.now() - (response.config as any).startTime;
                console.log(`[API] Response ${response.status} - ${duration}ms`);
                return response;
            },
            async (error) => {
                if (error.response?.status === 401 && this.configuration.onTokenExpired) {
                    // 尝试刷新令牌
                    const newToken = await this.configuration.onTokenExpired();
                    if (newToken) {
                        this.configuration.accessToken = newToken;
                        error.config.headers.Authorization = `Bearer ${newToken}`;
                        return this.axios.request(error.config);
                    }
                }
                
                console.error('[API] Response error:', error.response?.status, error.message);
                return Promise.reject(this.handleError(error));
            }
        );
    }
    
    /**
     * 生成请求 ID
     */
    private generateRequestId(): string {
        return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
    }
    
    /**
     * 错误处理
     */
    private handleError(error: any): Error {
        if (error.response) {
            const { status, data } = error.response;
            const message = data?.message || error.message;
            const apiError = new Error(message) as any;
            apiError.status = status;
            apiError.code = data?.code;
            apiError.details = data?.details;
            return apiError;
        }
        return error;
    }
    
    /**
     * 缓存管理
     */
    private getCached<T>(key: string): T | null {
        const cached = this.cache.get(key);
        if (cached && cached.expires > Date.now()) {
            console.log(`[API] Cache hit: ${key}`);
            return cached.data;
        }
        this.cache.delete(key);
        return null;
    }
    
    private setCached<T>(key: string, data: T, ttl: number = 60000): void {
        this.cache.set(key, {
            data,
            expires: Date.now() + ttl
        });
        console.log(`[API] Cache set: ${key} (TTL: ${ttl}ms)`);
    }
    
    /**
     * 清理资源
     */
    public dispose(): void {
        if (this.requestInterceptor !== undefined) {
            this.axios.interceptors.request.eject(this.requestInterceptor);
        }
        if (this.responseInterceptor !== undefined) {
            this.axios.interceptors.response.eject(this.responseInterceptor);
        }
        this.cache.clear();
    }

{{#operations}}
{{#operation}}
    /**
     * {{summary}}
     {{#notes}}
     * {{&notes}}
     {{/notes}}
     */
    public async {{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}{{#hasMore}}, {{/hasMore}}{{/allParams}}): Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}> {
        {{#vendorExtensions.x-cached}}
        // 检查缓存
        const cacheKey = `{{nickname}}-${JSON.stringify({{#allParams}}{ {{paramName}} }{{#hasMore}}, {{/hasMore}}{{/allParams}})}`;
        const cached = this.getCached<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}>(cacheKey);
        if (cached !== null) {
            return cached;
        }
        {{/vendorExtensions.x-cached}}
        
        const localVarPath = `{{{path}}}`{{#pathParams}}
            .replace('{' + '{{baseName}}' + '}', encodeURIComponent(String({{paramName}}))){{/pathParams}};
        const localVarUrlObj = new URL(localVarPath, this.basePath);
        const localVarRequestOptions: AxiosRequestConfig = {
            method: '{{httpMethod}}',
            (startTime as any): Date.now()
        };
        const localVarHeaderParameter = {} as any;
        const localVarQueryParameter = {} as any;
        {{#hasFormParams}}
        const localVarFormParams = new FormData();
        {{/hasFormParams}}
        
        {{#queryParams}}
        {{#required}}
        localVarQueryParameter['{{baseName}}'] = {{paramName}};
        {{/required}}
        {{^required}}
        if ({{paramName}} !== undefined) {
            localVarQueryParameter['{{baseName}}'] = {{paramName}};
        }
        {{/required}}
        {{/queryParams}}
        
        {{#headerParams}}
        {{#required}}
        localVarHeaderParameter['{{baseName}}'] = String({{paramName}});
        {{/required}}
        {{^required}}
        if ({{paramName}} !== undefined && {{paramName}} !== null) {
            localVarHeaderParameter['{{baseName}}'] = String({{paramName}});
        }
        {{/required}}
        {{/headerParams}}
        
        {{#hasBodyParam}}
        {{#bodyParam}}
        localVarHeaderParameter['Content-Type'] = 'application/json';
        {{/bodyParam}}
        {{/hasBodyParam}}
        
        localVarUrlObj.search = new URLSearchParams(localVarQueryParameter).toString();
        localVarRequestOptions.headers = { ...localVarHeaderParameter };
        {{#hasBodyParam}}
        {{#bodyParam}}
        localVarRequestOptions.data = {{paramName}};
        {{/bodyParam}}
        {{/hasBodyParam}}
        
        try {
            const response = await this.axios.request<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}>(localVarRequestOptions);
            
            {{#vendorExtensions.x-cached}}
            // 缓存结果
            this.setCached(cacheKey, response.data, {{vendorExtensions.x-cache-ttl}}{{^vendorExtensions.x-cache-ttl}}60000{{/vendorExtensions.x-cache-ttl}});
            {{/vendorExtensions.x-cached}}
            
            return response.data;
        } catch (error) {
            throw this.handleError(error);
        }
    }
{{/operation}}
{{/operations}}
}

3. Python 模板

自定义服务层模板

mustache
{{! service.mustache }}
# -*- coding: utf-8 -*-
"""
{{appName}} - {{classname}} Service
{{appDescription}}

Generated by OpenAPI Generator
"""

{{#imports}}
{{import}}
{{/imports}}
import logging
from typing import List, Optional, Dict, Any
from datetime import datetime
import asyncio
from functools import lru_cache, wraps
import time
from prometheus_client import Counter, Histogram, Gauge

logger = logging.getLogger(__name__)

# Prometheus 指标
api_requests_total = Counter('api_requests_total', 'Total API requests', ['method', 'endpoint', 'status'])
api_request_duration = Histogram('api_request_duration_seconds', 'API request duration', ['method', 'endpoint'])
api_active_requests = Gauge('api_active_requests', 'Active API requests', ['method', 'endpoint'])

def monitor_performance(method: str, endpoint: str):
    """性能监控装饰器"""
    def decorator(func):
        @wraps(func)
        async def async_wrapper(*args, **kwargs):
            start_time = time.time()
            api_active_requests.labels(method=method, endpoint=endpoint).inc()
            
            try:
                result = await func(*args, **kwargs)
                api_requests_total.labels(method=method, endpoint=endpoint, status='success').inc()
                return result
            except Exception as e:
                api_requests_total.labels(method=method, endpoint=endpoint, status='error').inc()
                logger.error(f"Error in {method} {endpoint}: {str(e)}", exc_info=True)
                raise
            finally:
                duration = time.time() - start_time
                api_request_duration.labels(method=method, endpoint=endpoint).observe(duration)
                api_active_requests.labels(method=method, endpoint=endpoint).dec()
                logger.info(f"{method} {endpoint} completed in {duration:.3f}s")
        
        @wraps(func)
        def sync_wrapper(*args, **kwargs):
            start_time = time.time()
            api_active_requests.labels(method=method, endpoint=endpoint).inc()
            
            try:
                result = func(*args, **kwargs)
                api_requests_total.labels(method=method, endpoint=endpoint, status='success').inc()
                return result
            except Exception as e:
                api_requests_total.labels(method=method, endpoint=endpoint, status='error').inc()
                logger.error(f"Error in {method} {endpoint}: {str(e)}", exc_info=True)
                raise
            finally:
                duration = time.time() - start_time
                api_request_duration.labels(method=method, endpoint=endpoint).observe(duration)
                api_active_requests.labels(method=method, endpoint=endpoint).dec()
                logger.info(f"{method} {endpoint} completed in {duration:.3f}s")
        
        return async_wrapper if asyncio.iscoroutinefunction(func) else sync_wrapper
    return decorator

class {{classname}}Service:
    """{{description}}"""
    
    def __init__(self, repository=None, cache_client=None, config=None):
        self.repository = repository
        self.cache_client = cache_client
        self.config = config or {}
        self._initialize()
    
    def _initialize(self):
        """初始化服务"""
        logger.info(f"Initializing {{classname}}Service")
        # 可以在这里初始化连接池、缓存等
    
    {{#operations}}
    {{#operation}}
    @monitor_performance("{{httpMethod}}", "{{path}}")
    {{#vendorExtensions.x-async}}async {{/vendorExtensions.x-async}}def {{operationIdSnakeCase}}(
        self,
        {{#allParams}}
        {{paramName}}: {{^required}}Optional[{{/required}}{{dataType}}{{^required}}] = None{{/required}},
        {{/allParams}}
    ) -> {{#returnType}}{{returnType}}{{/returnType}}{{^returnType}}None{{/returnType}}:
        """
        {{summary}}
        {{#notes}}
        
        {{notes}}
        {{/notes}}
        
        Args:
            {{#allParams}}
            {{paramName}}: {{description}}
            {{/allParams}}
        
        Returns:
            {{#returnType}}{{returnType}}: {{/returnType}}{{returnTypeDescription}}
        
        Raises:
            ValidationError: 参数验证失败
            NotFoundError: 资源不存在
            ApiError: API 调用失败
        """
        logger.debug(f"{{operationIdSnakeCase}} called with: {{#allParams}}{{paramName}}={{{{paramName}}}, {{/allParams}}")
        
        # 参数验证
        {{#allParams}}
        {{#required}}
        if {{paramName}} is None:
            raise ValidationError("参数 {{paramName}} 不能为空")
        {{/required}}
        {{#hasValidation}}
        self._validate_{{paramName}}({{paramName}})
        {{/hasValidation}}
        {{/allParams}}
        
        {{#vendorExtensions.x-cached}}
        # 检查缓存
        cache_key = f"{{operationIdSnakeCase}}:{{#allParams}}{{#isPathParam}}{{{{paramName}}}{{/isPathParam}}{{/allParams}}"
        cached_result = {{#vendorExtensions.x-async}}await {{/vendorExtensions.x-async}}self._get_from_cache(cache_key)
        if cached_result is not None:
            logger.debug(f"Cache hit for {cache_key}")
            return cached_result
        {{/vendorExtensions.x-cached}}
        
        try:
            # 业务逻辑实现
            {{#vendorExtensions.x-async}}
            result = await self._{{operationIdSnakeCase}}_impl({{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}})
            {{/vendorExtensions.x-async}}
            {{^vendorExtensions.x-async}}
            result = self._{{operationIdSnakeCase}}_impl({{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}})
            {{/vendorExtensions.x-async}}
            
            {{#vendorExtensions.x-cached}}
            # 缓存结果
            {{#vendorExtensions.x-async}}await {{/vendorExtensions.x-async}}self._set_cache(cache_key, result, ttl={{vendorExtensions.x-cache-ttl}}{{^vendorExtensions.x-cache-ttl}}3600{{/vendorExtensions.x-cache-ttl}})
            {{/vendorExtensions.x-cached}}
            
            return result
            
        except Exception as e:
            logger.error(f"Error in {{operationIdSnakeCase}}: {str(e)}", exc_info=True)
            raise
    
    def _{{operationIdSnakeCase}}_impl(self, {{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}):
        """{{summary}} - 实现"""
        # TODO: 实现业务逻辑
        raise NotImplementedError("{{operationIdSnakeCase}} 尚未实现")
    
    {{#allParams}}
    {{#hasValidation}}
    def _validate_{{paramName}}(self, value: {{dataType}}) -> None:
        """验证 {{paramName}}"""
        {{#minLength}}
        if len(value) < {{minLength}}:
            raise ValidationError("{{paramName}} 长度不能小于 {{minLength}}")
        {{/minLength}}
        {{#maxLength}}
        if len(value) > {{maxLength}}:
            raise ValidationError("{{paramName}} 长度不能大于 {{maxLength}}")
        {{/maxLength}}
        {{#minimum}}
        if value < {{minimum}}:
            raise ValidationError("{{paramName}} 不能小于 {{minimum}}")
        {{/minimum}}
        {{#maximum}}
        if value > {{maximum}}:
            raise ValidationError("{{paramName}} 不能大于 {{maximum}}")
        {{/maximum}}
        {{#pattern}}
        import re
        if not re.match(r"{{pattern}}", value):
            raise ValidationError("{{paramName}} 格式不正确")
        {{/pattern}}
    {{/hasValidation}}
    {{/allParams}}
    
    {{/operation}}
    {{/operations}}
    
    # 缓存辅助方法
    {{#vendorExtensions.x-async}}async {{/vendorExtensions.x-async}}def _get_from_cache(self, key: str) -> Optional[Any]:
        """从缓存获取数据"""
        if not self.cache_client:
            return None
        try:
            return {{#vendorExtensions.x-async}}await {{/vendorExtensions.x-async}}self.cache_client.get(key)
        except Exception as e:
            logger.warning(f"Cache get error: {e}")
            return None
    
    {{#vendorExtensions.x-async}}async {{/vendorExtensions.x-async}}def _set_cache(self, key: str, value: Any, ttl: int = 3600) -> None:
        """设置缓存"""
        if not self.cache_client:
            return
        try:
            {{#vendorExtensions.x-async}}await {{/vendorExtensions.x-async}}self.cache_client.set(key, value, ttl=ttl)
        except Exception as e:
            logger.warning(f"Cache set error: {e}")

使用自定义模板

1. 命令行使用

bash
# 使用自定义模板目录
openapi-generator-cli generate \
  -i api/openapi.yaml \
  -g spring \
  -o generated \
  -t ./custom-templates

# 指定特定模板文件
openapi-generator-cli generate \
  -i api/openapi.yaml \
  -g spring \
  -o generated \
  --template-dir ./custom-templates \
  --import-mappings DateTime=java.time.OffsetDateTime

2. 配置文件使用

yaml
# generator-config.yaml
generatorName: spring
outputDir: ./generated
inputSpec: ./api/openapi.yaml
templateDir: ./custom-templates

# 模板变量
additionalProperties:
  appName: "用户管理系统"
  appDescription: "提供用户管理相关功能"
  appVersion: "1.0.0"
  
  # 自定义扩展属性
  enableMetrics: true
  enableCaching: true
  enableAsync: true

# 自定义文件生成
files:
  # 额外生成的文件
  - templateFile: "README.mustache"
    outputFile: "README.md"
  - templateFile: "Dockerfile.mustache"
    outputFile: "Dockerfile"
  - templateFile: "docker-compose.mustache"
    outputFile: "docker-compose.yml"

3. 编程方式使用

java
// CustomGenerator.java
import org.openapitools.codegen.*;
import org.openapitools.codegen.languages.SpringCodegen;

public class CustomSpringCodegen extends SpringCodegen {
    
    @Override
    public void processOpts() {
        super.processOpts();
        
        // 添加自定义模板
        supportingFiles.add(new SupportingFile(
            "service.mustache",
            sourceFolder + "/" + apiPackage().replace(".", "/"),
            "Service.java"
        ));
        
        // 添加自定义属性
        additionalProperties.put("enableMetrics", true);
        additionalProperties.put("currentYear", Year.now().getValue());
    }
    
    @Override
    public String toApiName(String name) {
        // 自定义 API 命名规则
        return super.toApiName(name) + "Custom";
    }
}

模板开发技巧

1. 调试模板

bash
# 生成调试信息
openapi-generator-cli generate \
  -i api/openapi.yaml \
  -g spring \
  -o generated \
  -t ./custom-templates \
  --global-property debugModels,debugOperations

# 查看模板变量
openapi-generator-cli config-help -g spring

2. 条件渲染

mustache
{{! 条件渲染示例 }}
{{#required}}
// 必填字段
@NotNull(message = "{{paramName}} 不能为空")
{{/required}}

{{^required}}
// 可选字段
@Nullable
{{/required}}

{{#hasValidation}}
// 包含验证规则
{{#minLength}}@Size(min = {{minLength}}){{/minLength}}
{{#pattern}}@Pattern(regexp = "{{pattern}}"){{/pattern}}
{{/hasValidation}}

3. 循环和嵌套

mustache
{{! 循环示例 }}
{{#operations}}
public interface {{classname}}Service {
    {{#operation}}
    {{returnType}} {{operationId}}({{#allParams}}{{dataType}} {{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}});
    {{/operation}}
}
{{/operations}}

{{! 嵌套对象 }}
{{#models}}
{{#model}}
public class {{classname}} {
    {{#vars}}
    private {{dataType}} {{name}};
    {{/vars}}
}
{{/model}}
{{/models}}

4. 自定义辅助函数

mustache
{{! 使用 Lambda 表达式 }}
{{#lambda.uppercase}}{{operationId}}{{/lambda.uppercase}}

{{! 使用自定义标签 }}
{{>customHeader}}

{{! 包含其他模板 }}
{{>model/pojo}}

模板最佳实践

1. 保持模板简洁

mustache
{{! 好的做法:使用部分模板 }}
{{>imports}}
{{>classAnnotations}}
public class {{classname}} {
    {{>fields}}
    {{>methods}}
}

{{! 避免:在一个模板中包含太多逻辑 }}

2. 复用通用部分

mustache
{{! partials/header.mustache }}
/*
 * {{appName}}
 * {{appDescription}}
 * 
 * API Version: {{appVersion}}
 * Generated: {{generatedDate}}
 */

3. 版本控制

custom-templates/
├── v1/           # 版本 1 模板
├── v2/           # 版本 2 模板
└── current/      # 当前使用的模板(符号链接)

总结

自定义模板让我们能够:

  • 🎯 统一代码风格: 符合团队规范
  • 🚀 增强功能: 添加监控、缓存、日志等
  • 📦 集成框架: 无缝集成企业框架
  • 🔧 提高效率: 减少重复代码编写

通过合理使用自定义模板,可以让生成的代码更好地适应项目需求。

SOLO Development Guide