准备
从 Github 获取源码:
| git clone https://github.com/swagger-api/swagger-codegen.git |
项目结构:
- 模板位置:
modules/swagger-codegen/src/main/resources
- 程序入口:
modules/swagger-codegen-cli/src/main/java/io/swagger/codegen/SwaggerCodegen.java
运行程序:
| generate -l java -i "E:\code\swagger-codegen\api.yaml" -o "E:\temp\out" -c "E:\code\swagger-codegen\config.json" |
源码分析
程序入口
使用 Airline 解析命令行参数。
当我们传递 generate -l java -i api.yaml -o "E:\temp\out"
时,parse()
会通过反射创建 io.swagger.codegen.cmd.Generate
类实例。
| |
| public class SwaggerCodegen { |
| public static void main(String[] args) { |
| String version = Version.readVersionFromResources(); |
| |
| |
| @SuppressWarnings("unchecked") |
| Cli.CliBuilder<Runnable> builder = |
| Cli.<Runnable>builder("swagger-codegen-cli") |
| .withDescription( |
| String.format( |
| "Swagger code generator CLI (version %s). More info on swagger.io", |
| version)) |
| .withDefaultCommand(Langs.class) |
| .withCommands(Generate.class, Meta.class, Langs.class, Help.class, |
| ConfigHelp.class, Validate.class, Version.class); |
| |
| builder.build().parse(args).run(); |
| } |
| } |
| |
| public class DefaultCommandFactory<T> implements CommandFactory<T> { |
| @SuppressWarnings("unchecked") |
| @Override |
| public T createInstance(Class<?> type) { |
| return (T) ParserUtil.createInstance(type); |
| } |
| } |
命令类
Generate 源码分析
详见 Generate.java:
| @Command(name = "generate", description = "Generate code with chosen lang") |
| public class Generate implements Runnable { |
| |
| @Option(name = {"-v", "--verbose"}) |
| private Boolean verbose; |
| |
| @Option(name = {"-l", "--lang"}, title = "language", required = true) |
| private String lang; |
| |
| |
| |
| @Override |
| public void run() { |
| |
| |
| CodegenConfigurator configurator = CodegenConfigurator.fromFile(configFile); |
| |
| |
| if (configurator == null) { |
| |
| configurator = new CodegenConfigurator(); |
| } |
| |
| |
| |
| if (verbose != null) { |
| configurator.setVerbose(verbose); |
| } |
| |
| if (skipOverwrite != null) { |
| configurator.setSkipOverwrite(skipOverwrite); |
| } |
| |
| |
| |
| if (ignoreImportMappings != null) { |
| additionalProperties.add(String.format("%s=%s", CodegenConstants.IGNORE_IMPORT_MAPPING_OPTION, Boolean.parseBoolean(ignoreImportMappings))); |
| } |
| |
| |
| applySystemPropertiesKvpList(systemProperties, configurator); |
| applyInstantiationTypesKvpList(instantiationTypes, configurator); |
| applyImportMappingsKvpList(importMappings, configurator); |
| applyTypeMappingsKvpList(typeMappings, configurator); |
| applyAdditionalPropertiesKvpList(additionalProperties, configurator); |
| applyLanguageSpecificPrimitivesCsvList(languageSpecificPrimitives, configurator); |
| applyReservedWordsMappingsKvpList(reservedWordsMappings, configurator); |
| final ClientOptInput clientOptInput = configurator.toClientOptInput(); |
| |
| new DefaultGenerator().opts(clientOptInput).generate(); |
| } |
| } |
使用已有的数据模型而不自动生成该模型
这篇文章 OpenAPI Generator - target external models 有解释 --language-specific-primitives
的作用,但 Swagger CodeGen 的实现似乎没有使用到这个参数来跳过模型生成。该参数的意思是将一些类型加入原始类型列表,这样生成器就不会为其生成数据模型代码。
如果我们想使用一些已有的数据模型而且不想 Swagger CodeGen 为我们生成,可以这样配置:
| --ignore-import-mapping=false --import-mappings=Pet=com.xxx.model.Pet |
也可以通过设置这个扩展字段跳过模型生成:
| |
| protected Map<String, Object> processModels(CodegenConfig config, Map<String, Model> definitions, Map<String, Model> allDefinitions) { |
| for (String key : definitions.keySet()) { |
| Model mm = definitions.get(key); |
| if(mm.getVendorExtensions() != null && mm.getVendorExtensions().containsKey("x-codegen-ignore")) { |
| |
| LOGGER.debug("skipping model " + key); |
| return null; |
| } |
| } |
| } |
相关源码:
| |
| @Override |
| public void run() { |
| |
| |
| CodegenConfigurator configurator = CodegenConfigurator.fromFile(configFile); |
| |
| |
| if (configurator == null) { |
| |
| configurator = new CodegenConfigurator(); |
| } |
| |
| |
| |
| |
| if (ignoreImportMappings != null) { |
| additionalProperties.add(String.format("%s=%s", CodegenConstants.IGNORE_IMPORT_MAPPING_OPTION, Boolean.parseBoolean(ignoreImportMappings))); |
| } |
| |
| |
| final ClientOptInput clientOptInput = configurator.toClientOptInput(); |
| new DefaultGenerator().opts(clientOptInput).generate(); |
| } |
| |
| protected void generateModels(List<File> files, List<Object> allModels) { |
| for (String name : modelKeys) { |
| |
| |
| if (!config.getIgnoreImportMapping() && config.importMapping().containsKey(name)) { |
| LOGGER.info("Model " + name + " not imported due to import mapping"); |
| continue; |
| } |
| } |
| } |
读取 OpenAPI 文档
读取 OpenAPI 文档时,有个地方值得关注,就是读取方法参数类型信息。
| |
| public ClientOptInput toClientOptInput() { |
| |
| |
| Swagger swagger = new SwaggerParser().read(inputSpec, authorizationValues, parseOptions); |
| |
| input.opts(new ClientOpts()) |
| .swagger(swagger); |
| } |
| |
| public Swagger read(String location, List<AuthorizationValue> auths, ParseOptions options) { |
| |
| Swagger output; |
| output = (new Swagger20Parser()).read(location, auths); |
| } |
| |
| public Swagger read(String location, List<AuthorizationValue> auths) throws IOException { |
| |
| |
| location = location.replaceAll("\\\\", "/"); |
| String data; |
| if (location.toLowerCase().startsWith("http")) { |
| data = RemoteUrl.urlToString(location, auths); |
| } else { |
| Path pathpath = Paths.get(location); |
| } |
| |
| if (Files.exists(path, new LinkOption[0])) { |
| data = FileUtils.readFileToString(path.toFile(), "UTF-8"); |
| } else { |
| data = ClasspathHelper.loadFileFromClasspath(location); |
| } |
| |
| return this.convertToSwagger(data); |
| } |
一路上弯弯绕绕:
将 JSON 或 YAML 文件解析成字符串:
| |
| private Swagger convertToSwagger(String data) throws IOException { |
| |
| JsonNode rootNode; |
| if (data.trim().startsWith("{")) { |
| |
| ObjectMapper mapper = Json.mapper(); |
| rootNode = mapper.readTree(data); |
| } else { |
| |
| rootNode = this.deserializeYaml(data); |
| } |
| |
| JsonNode swaggerNode = rootNode.get("swagger"); |
| SwaggerDeserializationResult result = (new SwaggerDeserializer()).deserialize(rootNode); |
| Swagger convertValue = result.getSwagger(); |
| |
| return convertValue; |
| } |
| |
| public SwaggerDeserializationResult deserialize(JsonNode rootNode) { |
| SwaggerDeserializationResult result = new SwaggerDeserializationResult(); |
| SwaggerDeserializer.ParseResult rootParse = new SwaggerDeserializer.ParseResult(); |
| Swagger swagger = this.parseRoot(rootNode, rootParse); |
| result.setSwagger(swagger); |
| result.setMessages(rootParse.getMessages()); |
| return result; |
| } |
解析 OpenAPI 文档:
| |
| |
| public Swagger parseRoot(JsonNode node, SwaggerDeserializer.ParseResult result) { |
| String location = ""; |
| Swagger swagger = new Swagger(); |
| |
| |
| obj = this.getObject("paths", on, true, location, result); |
| Map<String, Path> paths = this.paths(obj, "paths", result); |
| swagger.paths(paths); |
| |
| obj = this.getObject("definitions", on, false, location, result); |
| Map<String, Model> definitions = this.definitions(obj, "definitions", result); |
| swagger.setDefinitions(definitions); |
| |
| obj = this.getObject("parameters", on, false, location, result); |
| Map<String, Parameter> parameters = new LinkedHashMap(); |
| swagger.setParameters(parameters); |
| } |
解析 paths 定义:
| |
| public Map<String, Path> paths(ObjectNode obj, String location, SwaggerDeserializer.ParseResult result) { |
| Set<String> pathKeys = this.getKeys(obj); |
| Iterator var6 = pathKeys.iterator(); |
| while(true) { |
| while(var6.hasNext()) { |
| String pathName = (String)var6.next(); |
| JsonNode pathValue = obj.get(pathName); |
| ObjectNode path = (ObjectNode)pathValue; |
| Path pathObj = this.path(path, location + ".'" + pathName + "'", result); |
| List<String> eachPart = new ArrayList(); |
| Matcher m = Pattern.compile("\\{(.+?)\\}").matcher(pathName); |
| } |
| return output; |
| } |
| } |
解析 path 定义:
| |
| public Path path(ObjectNode obj, String location, SwaggerDeserializer.ParseResult result) { |
| Path path = new Path(); |
| |
| ArrayNode parameters = this.getArray("parameters", obj, false, location, result); |
| path.setParameters(this.parameters(parameters, location, result)); |
| ObjectNode on = this.getObject("get", obj, false, location, result); |
| Operation op; |
| |
| if (on != null) { |
| op = this.operation(on, location + "(get)", result); |
| if (op != null) { |
| path.setGet(op); |
| } |
| } |
| |
| on = this.getObject("post", obj, false, location, result); |
| if (on != null) { |
| op = this.operation(on, location + "(post)", result); |
| if (op != null) { |
| path.setPost(op); |
| } |
| } |
| |
| |
| return path; |
| } |
解析接口方法:
| |
| |
| public Operation operation(ObjectNode obj, String location, SwaggerDeserializer.ParseResult result) { |
| |
| Operation output = new Operation(); |
| |
| ArrayNode parameters = this.getArray("parameters", obj, false, location, result); |
| output.setParameters(this.parameters(parameters, location, result)); |
| |
| return output; |
| } |
解析接口方法参数:
| |
| public List<Parameter> parameters(ArrayNode obj, String location, SwaggerDeserializer.ParseResult result) { |
| List<Parameter> output = new ArrayList(); |
| Iterator var5 = obj.iterator(); |
| while(var5.hasNext()) { |
| JsonNode item = (JsonNode)var5.next(); |
| if (item.getNodeType().equals(JsonNodeType.OBJECT)) { |
| Parameter param = this.parameter((ObjectNode)item, location, result); |
| if (param != null) { |
| output.add(param); |
| } |
| } |
| } |
| return output; |
| } |
终于来到解析参数类型的地方,请注意注释中的【关键】:
| |
| |
| public Parameter parameter(ObjectNode obj, String location, SwaggerDeserializer.ParseResult result) { |
| String value = this.getString("in", obj, true, location, result); |
| if (value != null) { |
| |
| |
| String type = this.getString("type", obj, false, location, result); |
| String format = this.getString("format", obj, false, location, result); |
| AbstractSerializableParameter<?> sp = null; |
| if ("query".equals(value)) { |
| sp = new QueryParameter(); |
| } else if ("header".equals(value)) { |
| sp = new HeaderParameter(); |
| } else if ("path".equals(value)) { |
| sp = new PathParameter(); |
| } else if ("formData".equals(value)) { |
| sp = new FormParameter(); |
| } |
| |
| if (sp != null) { |
| String paramType = this.getString("type", obj, true, location, result); |
| Map<PropertyId, Object> map = new LinkedHashMap(); |
| map.put(PropertyId.TYPE, type); |
| map.put(PropertyId.FORMAT, format); |
| Property prop = PropertyBuilder.build(type, format, map); |
| if (prop != null) { |
| ((AbstractSerializableParameter)sp).setProperty(prop); |
| ObjectNode items = this.getObject("items", obj, false, location, result); |
| if (items != null) { |
| Property inner = this.schema((Map)null, items, location, result); |
| ((AbstractSerializableParameter)sp).setItems(inner); |
| } |
| } |
| String collectionFormat = this.getString("collectionFormat", obj, false, location, result); |
| ((AbstractSerializableParameter)sp).setCollectionFormat(collectionFormat); |
| output = sp; |
| } else if ("body".equals(value)) { |
| |
| BodyParameter bp = new BodyParameter(); |
| JsonNode node = obj.get("schema"); |
| if (node != null && node instanceof ObjectNode) { |
| bp.setSchema(this.definition((ObjectNode)node, location, result)); |
| } |
| output = bp; |
| } |
| return (Parameter)output; |
| } |
Swagger 数据类型:
type |
format |
Comments |
integer |
int32 |
signed 32 bits |
integer |
int64 |
signed 64 bits (a.k.a long) |
number |
float |
|
number |
double |
|
string |
|
|
string |
byte |
base64 encoded characters |
string |
binary |
any sequence of octets |
boolean |
|
|
string |
date |
As defined by full-date - RFC3339 |
string |
date-time |
As defined by date-time - RFC3339 |
string |
password |
A hint to UIs to obscure input. |
| |
| |
| public static Property build(String type, String format, Map<PropertyId, Object> args) { |
| final Processor processor = Processor.fromType(type, format); |
| return processor.build(fixedArgs); |
| } |
这里列举了一些 Property 的实现类,可以看到其 OpenAPI 规范的对应书写方式,即 type 和 format 的值:
| |
| public class DateProperty extends AbstractProperty implements Property { |
| public DateProperty() { |
| super.type = "string"; |
| super.format = "date"; |
| } |
| } |
| |
| |
| public class DateTimeProperty extends AbstractProperty implements Property { |
| public DateTimeProperty() { |
| super.type = "string"; |
| super.format = "date-time"; |
| } |
| } |
如何生成 LocalDate 和 LocalDateTime
OpenAPI 文档:
| paths: |
| /example: |
| get: |
| operationId: "example" |
| parameters: |
| - in: "query" |
| name: arg0 |
| type: string |
| format: date |
| - in: 'query' |
| name: arg1 |
| type: string |
| format: date-time |
| responses: |
| "200": |
| description: "OK" |
配置:
| --additional-properties="dateLibrary=java8-localdatetime" |
相关源码:
| |
| @Override |
| public List<File> generate() { |
| |
| configureGeneratorProperties(); |
| |
| } |
| |
| protected void configureGeneratorProperties() { |
| |
| config.processOpts(); |
| |
| } |
| |
| @Override |
| public void processOpts() { |
| |
| |
| if (additionalProperties.containsKey(JAVA_8)) { |
| this.setJava8(Boolean.valueOf(additionalProperties.get(JAVA_8).toString())); |
| } |
| if (this.java8) { |
| additionalProperties.put("javaVersion", "1.8"); |
| additionalProperties.put("jdk8", "true"); |
| if (!additionalProperties.containsKey(DATE_LIBRARY)) { |
| setDateLibrary("java8"); |
| } |
| } |
| |
| |
| super.processOpts(); |
| |
| } |
根据参数选择日期类型:
| |
| @Override |
| public void processOpts() { |
| super.processOpts(); |
| |
| if (additionalProperties.containsKey(DATE_LIBRARY)) { |
| setDateLibrary(additionalProperties.get("dateLibrary").toString()); |
| } else if (java8Mode) { |
| setDateLibrary("java8"); |
| } |
| |
| if ("threetenbp".equals(dateLibrary)) { |
| additionalProperties.put("threetenbp", true); |
| additionalProperties.put("jsr310", "true"); |
| typeMapping.put("date", "LocalDate"); |
| typeMapping.put("DateTime", "OffsetDateTime"); |
| importMapping.put("LocalDate", "org.threeten.bp.LocalDate"); |
| importMapping.put("OffsetDateTime", "org.threeten.bp.OffsetDateTime"); |
| } else if ("joda".equals(dateLibrary)) { |
| additionalProperties.put("joda", true); |
| typeMapping.put("date", "LocalDate"); |
| typeMapping.put("DateTime", "DateTime"); |
| importMapping.put("LocalDate", "org.joda.time.LocalDate"); |
| importMapping.put("DateTime", "org.joda.time.DateTime"); |
| } else if (dateLibrary.startsWith("java8")) { |
| additionalProperties.put("java8", true); |
| additionalProperties.put("jsr310", "true"); |
| if ("java8-localdatetime".equals(dateLibrary)) { |
| typeMapping.put("date", "LocalDate"); |
| typeMapping.put("DateTime", "LocalDateTime"); |
| importMapping.put("LocalDate", "java.time.LocalDate"); |
| importMapping.put("LocalDateTime", "java.time.LocalDateTime"); |
| } else if ("java8-instant".equals(dateLibrary)) { |
| typeMapping.put("date", "Instant"); |
| typeMapping.put("DateTime", "Instant"); |
| importMapping.put("Instant", "java.time.Instant"); |
| } else { |
| typeMapping.put("date", "LocalDate"); |
| typeMapping.put("DateTime", "OffsetDateTime"); |
| importMapping.put("LocalDate", "java.time.LocalDate"); |
| importMapping.put("OffsetDateTime", "java.time.OffsetDateTime"); |
| } |
| } else if (dateLibrary.equals("legacy")) { |
| additionalProperties.put("legacyDates", true); |
| } |
| } |
如何生成 Map
生成 Map<String, String>
类型:
| paths: |
| /example: |
| get: |
| operationId: "example" |
| consumes: |
| - multipart/form-data |
| parameters: |
| - in: "formData" |
| name: arg0 |
| type: object |
| items: |
| type: integer |
| format: int64 |
| responses: |
| "200": |
| description: "example" |
生成 Map<String, Pet>
类型,虽然这样写 Swagger 编辑器会报错,但确实只有这样写才会生成想要的代码,原因见下面源码分析:
| paths: |
| /example: |
| get: |
| operationId: "example" |
| consumes: |
| - multipart/form-data |
| parameters: |
| - in: "formData" |
| name: arg0 |
| type: object |
| items: |
| $ref: "#/definitions/Pet" |
| responses: |
| "200": |
| description: "example" |
| definitions: |
| Pet: |
| type: object |
| properties: |
| name: |
| type: string |
相关源码:
解析 OpenAPI 文档:
| |
| public Parameter parameter(ObjectNode obj, String location, SwaggerDeserializer.ParseResult result) { |
| Property prop = PropertyBuilder.build(type, format, map); |
| if (prop != null) { |
| ((AbstractSerializableParameter)sp).setProperty(prop); |
| ObjectNode items = this.getObject("items", obj, false, location, result); |
| if (items != null) { |
| Property inner = this.schema((Map)null, items, location, result); |
| ((AbstractSerializableParameter)sp).setItems(inner); |
| } |
| } |
| } |
将 Swagger 类型转成 Java 类型:
| |
| |
| public CodegenParameter fromParameter(Parameter param, Set<String> imports) { |
| if (param instanceof SerializableParameter) { |
| SerializableParameter qp = (SerializableParameter) param; |
| Property property; |
| String type = qp.getType(); |
| if ("array".equals(type)) { |
| |
| } else if ("object".equals(type)) { |
| Property inner = qp.getItems(); |
| if (inner == null) { |
| inner = new StringProperty().description("//TODO automatically added by swagger-codegen"); |
| } |
| property = new MapProperty(inner); |
| collectionFormat = qp.getCollectionFormat(); |
| CodegenProperty pr = fromProperty("inner", inner); |
| p.items = pr; |
| p.baseType = pr.datatype; |
| p.isContainer = true; |
| p.isMapContainer = true; |
| |
| while (pr != null) { |
| imports.add(pr.baseType); |
| pr = pr.items; |
| } |
| } else { |
| |
| } |
| |
| } |
| } |
生成代码文件
接下来,看 DefaultGenerator.generate()
方法,详见:DefaultGenerator.java
| @Override |
| public List<File> generate() { |
| |
| if (swagger == null || config == null) { |
| throw new RuntimeException("missing swagger input or config!"); |
| } |
| |
| |
| configureGeneratorProperties(); |
| |
| configureSwaggerInfo(); |
| |
| |
| List<File> files = new ArrayList<File>(); |
| |
| List<Object> allModels = new ArrayList<Object>(); |
| generateModels(files, allModels); |
| |
| List<Object> allOperations = new ArrayList<Object>(); |
| generateApis(files, allOperations, allModels); |
| |
| |
| Map<String, Object> bundle = buildSupportFileBundle(allOperations, allModels); |
| generateSupportingFiles(files, bundle); |
| config.processSwagger(swagger); |
| return files; |
| } |
生成数据模型文件
生成数据模型文件:
- 如果系统属性配置了
models
,则只生成这些数据模型
- 对数据模型进行排序
- 如果定义的数据模型是可以导入的类型,则不生成这些数据模型文件
- 对数据模型进行处理
- 对于每个模型,都会遍历模型模板并生成响应的模型代码文件
- 生成测试文件(可选)
- 生成文档文件(可选)
| protected void generateModels(List<File> files, List<Object> allModels) { |
| |
| if (!isGenerateModels) { |
| return; |
| } |
| |
| |
| final Map<String, Model> definitions = swagger.getDefinitions(); |
| if (definitions == null) { |
| return; |
| } |
| |
| |
| String modelNames = System.getProperty("models"); |
| Set<String> modelsToGenerate = null; |
| if (modelNames != null && !modelNames.isEmpty()) { |
| modelsToGenerate = new HashSet<String>(Arrays.asList(modelNames.split(","))); |
| } |
| |
| Set<String> modelKeys = definitions.keySet(); |
| if (modelsToGenerate != null && !modelsToGenerate.isEmpty()) { |
| Set<String> updatedKeys = new HashSet<String>(); |
| for (String m : modelKeys) { |
| if (modelsToGenerate.contains(m)) { |
| updatedKeys.add(m); |
| } |
| } |
| modelKeys = updatedKeys; |
| } |
| |
| |
| |
| Map<String, Object> allProcessedModels = new TreeMap<String, Object>(new Comparator<String>() { |
| @Override |
| public int compare(String o1, String o2) { |
| Model model1 = definitions.get(o1); |
| Model model2 = definitions.get(o2); |
| |
| }); |
| |
| |
| for (String name : modelKeys) { |
| try { |
| |
| |
| if (!config.getIgnoreImportMapping() && config.importMapping().containsKey(name)) { |
| LOGGER.info("Model " + name + " not imported due to import mapping"); |
| continue; |
| } |
| Model model = definitions.get(name); |
| Map<String, Model> modelMap = new HashMap<String, Model>(); |
| modelMap.put(name, model); |
| |
| Map<String, Object> models = processModels(config, modelMap, definitions); |
| if (models != null) { |
| models.put("classname", config.toModelName(name)); |
| models.putAll(config.additionalProperties()); |
| |
| allProcessedModels.put(name, models); |
| } |
| } catch (Exception e) { |
| String message = "Could not process model '" + name + "'" + ". Please make sure that your schema is correct!"; |
| LOGGER.error(message, e); |
| throw new RuntimeException(message, e); |
| } |
| } |
| |
| |
| allProcessedModels = config.postProcessAllModels(allProcessedModels); |
| |
| final boolean skipAlias = config.getSkipAliasGeneration() != null && config.getSkipAliasGeneration(); |
| |
| |
| |
| for (String modelName : allProcessedModels.keySet()) { |
| Map<String, Object> models = (Map<String, Object>) allProcessedModels.get(modelName); |
| models.put("modelPackage", config.modelPackage()); |
| try { |
| |
| if (!config.getIgnoreImportMapping() && config.importMapping().containsKey(modelName)) { |
| continue; |
| } |
| Map<String, Object> modelTemplate = (Map<String, Object>) ((List<Object>) models.get("models")).get(0); |
| if (skipAlias) { |
| |
| if (modelTemplate != null && modelTemplate.containsKey("model")) { |
| CodegenModel m = (CodegenModel) modelTemplate.get("model"); |
| if (m.isAlias) { |
| continue; |
| } |
| } |
| } |
| allModels.add(modelTemplate); |
| |
| for (String templateName : config.modelTemplateFiles().keySet()) { |
| |
| String filename = config.modelFilename(templateName, modelName); |
| if (!config.shouldOverwrite(filename)) { |
| LOGGER.info("Skipped overwriting " + filename); |
| continue; |
| } |
| |
| File written = processTemplateToFile(models, templateName, filename); |
| if (written != null) { |
| files.add(written); |
| } |
| } |
| |
| if(isGenerateModelTests) { |
| generateModelTests(files, models, modelName); |
| } |
| |
| if(isGenerateModelDocumentation) { |
| |
| generateModelDocumentation(files, models, modelName); |
| } |
| } catch (Exception e) { |
| throw new RuntimeException("Could not generate model '" + modelName + "'", e); |
| } |
| } |
| if (System.getProperty("debugModels") != null) { |
| LOGGER.info("############ Model info ############"); |
| Json.prettyPrint(allModels); |
| } |
| |
| } |
生成接口文件
生成接口文件:
- 对接口进行分组,默认是按接口的 tags 属性分组,spring 可以选择按路径的第一段分组
- 如果指定了系统属性 apis,只这生成这些接口
- 遍历每个分组并生成接口文件
| protected void generateApis(List<File> files, List<Object> allOperations, List<Object> allModels) { |
| if (!isGenerateApis) { |
| return; |
| } |
| |
| Map<String, List<CodegenOperation>> paths = processPaths(swagger.getPaths()); |
| |
| |
| Set<String> apisToGenerate = null; |
| String apiNames = System.getProperty("apis"); |
| if (apiNames != null && !apiNames.isEmpty()) { |
| apisToGenerate = new HashSet<String>(Arrays.asList(apiNames.split(","))); |
| } |
| if (apisToGenerate != null && !apisToGenerate.isEmpty()) { |
| Map<String, List<CodegenOperation>> updatedPaths = new TreeMap<String, List<CodegenOperation>>(); |
| for (String m : paths.keySet()) { |
| if (apisToGenerate.contains(m)) { |
| updatedPaths.put(m, paths.get(m)); |
| } |
| } |
| paths = updatedPaths; |
| } |
| |
| for (String tag : paths.keySet()) { |
| try { |
| List<CodegenOperation> ops = paths.get(tag); |
| Collections.sort(ops, new Comparator<CodegenOperation>() { |
| @Override |
| public int compare(CodegenOperation one, CodegenOperation another) { |
| return ObjectUtils.compare(one.operationId, another.operationId); |
| } |
| }); |
| Map<String, Object> operation = processOperations(config, tag, ops, allModels); |
| |
| operation.put("hostWithoutBasePath", getHostWithoutBasePath()); |
| operation.put("basePath", basePath); |
| operation.put("basePathWithoutHost", basePathWithoutHost); |
| operation.put("contextPath", contextPath); |
| operation.put("baseName", tag); |
| operation.put("apiPackage", config.apiPackage()); |
| operation.put("modelPackage", config.modelPackage()); |
| operation.putAll(config.additionalProperties()); |
| operation.put("classname", config.toApiName(tag)); |
| operation.put("classVarName", config.toApiVarName(tag)); |
| operation.put("importPath", config.toApiImport(tag)); |
| operation.put("classFilename", config.toApiFilename(tag)); |
| |
| if (!config.vendorExtensions().isEmpty()) { |
| operation.put("vendorExtensions", config.vendorExtensions()); |
| } |
| |
| |
| boolean sortParamsByRequiredFlag = true; |
| if (this.config.additionalProperties().containsKey(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG)) { |
| sortParamsByRequiredFlag = Boolean.valueOf(this.config.additionalProperties().get(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG).toString()); |
| } |
| operation.put("sortParamsByRequiredFlag", sortParamsByRequiredFlag); |
| |
| processMimeTypes(swagger.getConsumes(), operation, "consumes"); |
| processMimeTypes(swagger.getProduces(), operation, "produces"); |
| |
| allOperations.add(new HashMap<String, Object>(operation)); |
| for (int i = 0; i < allOperations.size(); i++) { |
| Map<String, Object> oo = (Map<String, Object>) allOperations.get(i); |
| if (i < (allOperations.size() - 1)) { |
| oo.put("hasMore", "true"); |
| } |
| } |
| |
| for (String templateName : config.apiTemplateFiles().keySet()) { |
| String filename = config.apiFilename(templateName, tag); |
| if (!config.shouldOverwrite(filename) && new File(filename).exists()) { |
| LOGGER.info("Skipped overwriting " + filename); |
| continue; |
| } |
| |
| File written = processTemplateToFile(operation, templateName, filename); |
| if (written != null) { |
| files.add(written); |
| } |
| } |
| |
| if(isGenerateApiTests) { |
| |
| for (String templateName : config.apiTestTemplateFiles().keySet()) { |
| String filename = config.apiTestFilename(templateName, tag); |
| |
| if (new File(filename).exists()) { |
| LOGGER.info("File exists. Skipped overwriting " + filename); |
| continue; |
| } |
| |
| File written = processTemplateToFile(operation, templateName, filename); |
| if (written != null) { |
| files.add(written); |
| } |
| } |
| } |
| |
| |
| if(isGenerateApiDocumentation) { |
| |
| for (String templateName : config.apiDocTemplateFiles().keySet()) { |
| String filename = config.apiDocFilename(templateName, tag); |
| if (!config.shouldOverwrite(filename) && new File(filename).exists()) { |
| LOGGER.info("Skipped overwriting " + filename); |
| continue; |
| } |
| |
| File written = processTemplateToFile(operation, templateName, filename); |
| if (written != null) { |
| files.add(written); |
| } |
| } |
| } |
| |
| } catch (Exception e) { |
| throw new RuntimeException("Could not generate api file for '" + tag + "'", e); |
| } |
| } |
| if (System.getProperty("debugOperations") != null) { |
| LOGGER.info("############ Operation info ############"); |
| Json.prettyPrint(allOperations); |
| } |
| |
| } |
| |
对接口方法进行分组的代码:
| public Map<String, List<CodegenOperation>> processPaths(Map<String, Path> paths) { |
| Map<String, List<CodegenOperation>> ops = new TreeMap<String, List<CodegenOperation>>(); |
| for (String resourcePath : paths.keySet()) { |
| Path path = paths.get(resourcePath); |
| processOperation(resourcePath, "get", path.getGet(), ops, path); |
| processOperation(resourcePath, "post", path.getPost(), ops, path); |
| |
| } |
| return ops; |
| } |
| |
| protected void processOperation(String resourcePath, String httpMethod, Operation operation, Map<String, List<CodegenOperation>> operations, Path path) { |
| config.addOperationToGroup(config.sanitizeTag(tag.getName()), resourcePath, operation, codegenOperation, operations); |
| } |
| |
| |
| |
| public void addOperationToGroup(String tag, String resourcePath, Operation operation, CodegenOperation co, Map<String, List<CodegenOperation>> operations) { |
| |
| if((library.equals(DEFAULT_LIBRARY) || library.equals(SPRING_MVC_LIBRARY)) && !useTags) { |
| String basePath = resourcePath; |
| if (basePath.startsWith("/")) { |
| basePath = basePath.substring(1); |
| } |
| int pos = basePath.indexOf("/"); |
| if (pos > 0) { |
| basePath = basePath.substring(0, pos); |
| } |
| |
| if (basePath.equals("")) { |
| basePath = "default"; |
| } else { |
| co.subresourceOperation = !co.path.isEmpty(); |
| } |
| List<CodegenOperation> opList = operations.get(basePath); |
| if (opList == null) { |
| opList = new ArrayList<CodegenOperation>(); |
| operations.put(basePath, opList); |
| } |
| opList.add(co); |
| co.baseName = basePath; |
| } else { |
| |
| super.addOperationToGroup(tag, resourcePath, operation, co, operations); |
| } |
| } |
| |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具