Java SpringBoot Swagger

Swagger Client: Swagger document and Generate proxy(Spring boot Feign Client)

葉宗原 2020/06/30 11:00:00
2053

Introduction

We know that swagger api make it convenient and easy to connect systems. There's a easy read UI page, also we can try it at once. Not only that, there's another page machine can read. That's read the page even if we are not machine.

Situation

In this section, we will NOT show you how to create a Swagger project. On the contrary, we just want to CALL swagger API.

It's a small swagger sample.

In spring boot project, we can write a Feign Client to call swagger/RESTful API.

package tpi.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.alibaba.fastjson.JSONObject;

@FeignClient(name = "DemoFeignClient", url = "http://localhost:7901")
public interface DemoFeignClient {
	@RequestMapping(value = "/books", method = RequestMethod.POST, consumes = "application/json")
	JSONObject getBooks(JSONObject banksRq);

	@RequestMapping(value = "/books/{bookId}", method = RequestMethod.GET, consumes = "application/json")
	JSONObject getBook(@PathVariable("bookId") String bookId);

	@RequestMapping(value = "/locations", method = RequestMethod.POST, consumes = "application/json")
	JSONObject getLocations(JSONObject locationsRq);

	@RequestMapping(value = "/locations", method = RequestMethod.GET, consumes = "application/json")
	JSONObject getLocations(@RequestParam("locationId") String locationId);
}

Done.

 

But, it's not good enough!

Model

We found that com.alibaba.fastjson.JSONObject(or a Map) is usable, but not easy to use because:

  1. We don't know that what param we can set into the REQUEST; and what value we can get from RESPONSE.
  2. The value type is not Clearly.

 

We can use JavaBean to Avoid making mistakes.

Let's see swagger document. The "definitions" Part show what we have to prepare. We can find the document link at top of welcome page.(${host}/v2/api-docs)

{
    "swagger": "2.0",
    "info": {
        "version": "1.0",
        "title": "demo"
    },
    "host": "localhost:7901",
    "basePath": "/",
    "tags": [{
        "name": "books",
        "description": "Books"
    }, {
        "name": "locations",
        "description": "Locations"
    }],
    "paths": {
    //...
    },
    "definitions": {
        "BooksRq": {
            "type": "object",
            "properties": {
                "kind": {
                    "type": "string",
                    "description": "分類"
                },
                "localeStr": {
                    "type": "string",
                    "description": "語系"
                }
            },
            "title": "BooksRq"
        },
        "BooksRs": {
            "type": "object",
            "properties": {
                "bookMasters": {
                    "type": "array",
                    "description": "書名清單",
                    "items": {
                        "$ref": "#/definitions/BzBookBO"
                    }
                },
                "errorParam": {
                    "type": "array",
                    "description": "異常參數",
                    "items": {
                        "type": "string"
                    }
                },
                "errorStatus": {
                    "description": "異常物件",
                    "$ref": "#/definitions/ErrorStatus"
                }
            },
            "title": "BooksRs"
        },
        "BzBookBO": {
            "type": "object",
            "properties": {
                "bookName": {
                    "type": "string",
                    "description": "書名"
                },
                "bookNo": {
                    "type": "string",
                    "description": "書本編號"
                },
                "locationId": {
                    "type": "integer",
                    "format": "int32",
                    "description": "分館別"
                }
            },
            "title": "BzBookBO"
        },
        "ErrorStatus": {
            "type": "object",
            "required": ["errorCode", "severity", "systemId"],
            "properties": {
                "errorCode": {
                    "type": "string",
                    "description": "錯誤代碼"
                },
                "errorDesc": {
                    "type": "string",
                    "description": "狀態描述"
                },
                "severity": {
                    "type": "string",
                    "description": "狀態等級",
                    "enum": ["INFO", "WARN", "ERROR", "TIMEOUT", "FATAL", "UNKNOWN"]
                },
                "systemId": {
                    "type": "string",
                    "description": "系統代碼"
                }
            },
            "title": "ErrorStatus"
        },
        //...
    }
}

Maybe you'll consider that WHY NOT just check the Models Part of the UI page? Today our sample is so small and just 7 Objects; in the future, maybe we have to handle 700+ Objects. And now, I'll show that how to use loop program to generate JavaBean.

 

        "BooksRq": {
            "type": "object",
            "properties": {
                "kind": {
                    "type": "string",
                    "description": "分類"
                },
                "localeStr": {
                    "type": "string",
                    "description": "語系"
                }
            },
            "title": "BooksRq"
        }

Generally, "properties" part show all fields of the Model. "type" is the class of the field.

        "BooksRs": {
            "type": "object",
            "properties": {
                "bookMasters": {
                    "type": "array",
                    "description": "銀行清單",
                    "items": {
                        "$ref": "#/definitions/BzBookBO"
                    }
                },
                "errorParam": {
                    "type": "array",
                    "description": "異常參數",
                    "items": {
                        "type": "string"
                    }
                },
                "errorStatus": {
                    "description": "異常物件",
                    "$ref": "#/definitions/ErrorStatus"
                }
            },
            "title": "BooksRs"
        }

Sometimes, it's complexity.

For example, in "errorStatus", there's no "type" but "$ref" part. "$ref" part means other Model in this document. Here it's "ErrorStatus".

Another example, if "type" is "array", you should know type of array elements.

Let's see "item" part.

In "errorParam", it Clearly stated the type.

In "bookMasters", there's not "type" but "$ref" part. Also means other Model in this document. Here it's "BzBookBO".

        "ErrorStatus": {
            "type": "object",
            "required": ["errorCode", "severity", "systemId"],
            "properties": {
                "errorCode": {
                    "type": "string",
                    "description": "錯誤代碼"
                },
                "errorDesc": {
                    "type": "string",
                    "description": "狀態描述"
                },
                "severity": {
                    "type": "string",
                    "description": "狀態等級",
                    "enum": ["INFO", "WARN", "ERROR", "TIMEOUT", "FATAL", "UNKNOWN"]
                },
                "systemId": {
                    "type": "string",
                    "description": "系統代碼"
                }
            },
            "title": "ErrorStatus"
        },

If "type" is "string" and also has "enum" part (like "severity"), we can design as a Enum. "enum" is value of the Enum field.

 

Based on these rule to implement, the following is the program to generate JavaBean.

public class RobotEasy {

	protected Logger log = LoggerFactory.getLogger(this.getClass());
	String modelPackage = "tpi.model";

	protected String srcDir;

	public RobotEasy() {
		this(System.getProperty("user.dir"));
		System.out.println("當前工作目錄:" + System.getProperty("user.dir"));
	}

	public RobotEasy(String userDir) {
		srcDir = userDir + File.separator + "src" + File.separator + "main" + File.separator + "java" + File.separator;
	}

	public static void main(String[] args) throws Exception {
		Robot robot = new Robot();
		robot.setModelPackage("tpi.model");

		String swaggerDocUrl = "http://localhost:7901/v2/api-docs";
		// step 1. get doc
		String swaggerJsonDocStr = robot.getSwaggerJsonDoc(swaggerDocUrl);
		// escape doller sign error
		JSONObject swaggerJsonDoc = JSON.parseObject(swaggerJsonDocStr.replace("$ref", "_ref"));

		// step 2.
		robot.fnModel(swaggerJsonDoc);
	}

	public void fnModel(JSONObject swaggerJsonDoc) {
		String fileLocation = srcDir + (modelPackage.replace(".", File.separator)) + File.separator;
		File dstFile = new File(fileLocation);
		if (!dstFile.exists())
			dstFile.mkdirs();

		Map<String, JSONArray> enumMap = new HashMap<>();

		int[] writeCount = { 0, 0 };

		JSONObject definitions = swaggerJsonDoc.getJSONObject("definitions");
		definitions.keySet().stream().forEach(key -> {
			JSONObject classDef = definitions.getJSONObject(key);
			JSONObject properties = classDef.getJSONObject("properties");
			String type = classDef.getString("type");
			log.debug("key:" + key);
			log.debug("classDef:" + classDef);
			if (StringUtils.equals(type, "object")) {
				String className = key;

				if (properties != null) {
					properties.keySet().stream().forEach(fieldName -> {
						JSONObject fieldDef = properties.getJSONObject(fieldName);
						if (fieldDef.containsKey("enum")) {
							// enum instead of string
							String fieldClassName = fieldName.toUpperCase();
							// collect enum
							JSONArray enumList = enumMap.get(fieldClassName);
							if (enumList == null) {
								enumList = new JSONArray();
								enumMap.put(fieldClassName, enumList);
							}
							for (Object e : fieldDef.getJSONArray("enum")) {
								if (!enumList.contains(e))
									enumList.add(e);
							}
						}
					});
				}
				writeCount[0]++;
				File newTxt = new File(fileLocation + className + ".java");
				if (newTxt.exists())
					newTxt.delete();
				try {
					newTxt.createNewFile();

					FileWriter dataFile = new FileWriter(newTxt);
					BufferedWriter input = new BufferedWriter(dataFile);

					input.write("package " + modelPackage + ";");
					input.newLine();
					input.newLine();
					input.write("public class " + className + " {");
					input.newLine();

					if (properties != null)
						properties.keySet().stream().forEach(fieldName -> {
							JSONObject fieldDef = properties.getJSONObject(fieldName);
							try {
								log.debug("fieldName:" + fieldName + " fieldDef:" + fieldDef);
								String fieldClassName = fieldDef2Class(fieldDef);
								if (fieldDef.containsKey("enum")) {
									fieldClassName = fieldName.toUpperCase();
									// enumMap.put(fieldClassName, fieldDef.getJSONArray("enum"));
								}
								input.write(String.format("\t%s %s;", fieldClassName, fieldName));
								input.newLine();

								String methodPart = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);

								String getterName = "get" + methodPart;
								if (StringUtils.equals(fieldClassName, "boolean"))
									getterName = "is" + methodPart;
								input.write(String.format("\tpublic %s %s() {return %s;}", fieldClassName, getterName,
										fieldName));
								input.newLine();

								String setterName = "set" + methodPart;
								input.write(String.format("\tpublic void %s(%s %s) {this.%s = %s;}", setterName,
										fieldClassName, fieldName, fieldName, fieldName));
								input.newLine();
								input.newLine();
							} catch (IOException e) {
							}
						});

					input.write("}");
					input.newLine();

					input.close();
				} catch (IOException e) {
				}
			}
		});

		log.info("Class writeCount:" + writeCount[0]);

		log.debug("enumMap:" + enumMap);

		enumMap.keySet().forEach(enumName -> {
			JSONArray enumArray = enumMap.get(enumName);
			log.debug("enumName:" + enumName);
			log.debug("enumArray:" + enumArray);

			writeCount[1]++;
			File newTxt = new File(fileLocation + enumName + ".java");
			if (newTxt.exists())
				newTxt.delete();
			try {
				newTxt.createNewFile();

				FileWriter dataFile = new FileWriter(newTxt);
				BufferedWriter input = new BufferedWriter(dataFile);

				input.write("package " + modelPackage + ";");
				input.newLine();
				input.newLine();
				input.write("public enum " + enumName + " {");
				input.newLine();
				StringBuilder enumContext = new StringBuilder();
				enumArray.stream().forEach(_v -> enumContext.append("," + _v));
				String enumContextStr = enumContext.toString().substring(1);
				input.write("\t" + enumContextStr);
				input.newLine();
				input.write("}");

				input.close();
			} catch (IOException e) {
//				e.printStackTrace();
			}
		});

		log.info("Enum writeCount:" + writeCount[1]);
	}

	protected String fieldDef2Class(JSONObject fieldDef) {
		String fieldClassName = "Object";
		if (fieldDef.containsKey("type")) {
			String fieldType = fieldDef.getString("type");
			switch (fieldType) {
			case "string":
				fieldClassName = "String";
				if (fieldDef.containsKey("format")) {
					String format = fieldDef.getString("format");
					if (StringUtils.equals(format, "date-time"))
						fieldClassName = "java.util.Date";
				}
				break;
			case "number":
				fieldClassName = "float";
				break;
			case "integer":
				fieldClassName = "int";
				if (fieldDef.containsKey("format")) {
					String format = fieldDef.getString("format");
					if (StringUtils.equals(format, "int64"))
						fieldClassName = "long";
				}
				break;
			case "array":
				fieldClassName = "Object[]";
				JSONObject items = fieldDef.getJSONObject("items");
				if (items.containsKey("type")) {
					String itemType = items.getString("type");
					switch (itemType) {
					case "string":
						fieldClassName = "String[]";
						break;
					}
				} else {
					String _ref = items.getString("_ref");
					fieldClassName = _ref.replace("#/definitions/", "") + "[]";
				}
				break;
			case "object":
				// XXX
				fieldClassName = "java.util.Map";
				break;
			default:
				fieldClassName = fieldType;
			}
		} else if (fieldDef.containsKey("_ref")) {
			// model
			String _ref = fieldDef.getString("_ref");
			fieldClassName = _ref.replace("#/definitions/", "");
		} else if (fieldDef.containsKey("schema")) {
			// parameter
			JSONObject paramSchema = fieldDef.getJSONObject("schema");
			fieldClassName = paramSchema.getString("_ref").replace("#/definitions/", "");
		}
		return fieldClassName;
	}

}

 

The following are results.

package tpi.model;

public class BooksRq {
	String kind;
	public String getKind() {return kind;}
	public void setKind(String kind) {this.kind = kind;}

	String localeStr;
	public String getLocaleStr() {return localeStr;}
	public void setLocaleStr(String localeStr) {this.localeStr = localeStr;}
}
package tpi.model;

public class BooksRs {
	BzBookBO[] bookMasters;
	public BzBookBO[] getBookMasters() {return bookMasters;}
	public void setBookMasters(BzBookBO[] bookMasters) {this.bookMasters = bookMasters;}

	ErrorStatus errorStatus;
	public ErrorStatus getErrorStatus() {return errorStatus;}
	public void setErrorStatus(ErrorStatus errorStatus) {this.errorStatus = errorStatus;}

	String[] errorParam;
	public String[] getErrorParam() {return errorParam;}
	public void setErrorParam(String[] errorParam) {this.errorParam = errorParam;}
}
package tpi.model;

public class BzBookBO {
	String bookNo;
	public String getBookNo() {return bookNo;}
	public void setBookNo(String bookNo) {this.bookNo = bookNo;}

	int locationId;
	public int getLocationId() {return locationId;}
	public void setLocationId(int locationId) {this.locationId = locationId;}

	String bookName;
	public String getBookName() {return bookName;}
	public void setBookName(String bookName) {this.bookName = bookName;}
}
package tpi.model;

public class ErrorStatus {
	SEVERITY severity;
	public SEVERITY getSeverity() {return severity;}
	public void setSeverity(SEVERITY severity) {this.severity = severity;}

	String systemId;
	public String getSystemId() {return systemId;}
	public void setSystemId(String systemId) {this.systemId = systemId;}

	String errorDesc;
	public String getErrorDesc() {return errorDesc;}
	public void setErrorDesc(String errorDesc) {this.errorDesc = errorDesc;}

	String errorCode;
	public String getErrorCode() {return errorCode;}
	public void setErrorCode(String errorCode) {this.errorCode = errorCode;}
}
package tpi.model;

public enum SEVERITY {
	INFO,WARN,ERROR,TIMEOUT,FATAL,UNKNOWN
}

 

Proxy(Feign Client)

Now, our models are ready.And then, we need to rewrite FeignClient. Why not use the same way? Let's make it easy.

Let's see swagger document again.

{
    "swagger": "2.0",
    "info": {
        "version": "1.0",
        "title": "demo"
    },
    "host": "localhost:7901",
    "basePath": "/",
    "tags": [{
        "name": "books",
        "description": "Books"
    }, {
        "name": "locations",
        "description": "Locations"
    }],
    "paths": {
        "/books": {
            "post": {
                "tags": ["books"],
                "summary": "取得書本清單",
                "operationId": "getBanksUsingPOST",
                "consumes": ["application/json"],
                "produces": ["application/json"],
                "parameters": [{
                    "in": "body",
                    "name": "banksRq",
                    "description": "banksRq",
                    "required": true,
                    "schema": {
                        "$ref": "#/definitions/BooksRq"
                    }
                }],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/BooksRs"
                        }
                    },
                    "201": {
                        "description": "Created"
                    },
                    "401": {
                        "description": "Unauthorized"
                    },
                    "403": {
                        "description": "Forbidden"
                    },
                    "404": {
                        "description": "Not Found"
                    }
                },
                "deprecated": false
            }
        },
        "/books/{bankId}": {
            "get": {
                "tags": ["books"],
                "summary": "取得書本資訊",
                "operationId": "getBankInfoUsingGET",
                "consumes": ["*/*"],
                "produces": ["application/json"],
                "parameters": [{
                    "name": "bankId",
                    "in": "path",
                    "description": "bankId",
                    "required": true,
                    "type": "string"
                }],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/BooksRs"
                        }
                    },
                    "401": {
                        "description": "Unauthorized"
                    },
                    "403": {
                        "description": "Forbidden"
                    },
                    "404": {
                        "description": "Not Found"
                    }
                },
                "deprecated": false
            }
        },
        "/locations": {
            "get": {
                "tags": ["locations"],
                "summary": "取得書本資訊",
                "operationId": "getLocationsInfoUsingGET",
                "consumes": ["*/*"],
                "produces": ["application/json"],
                "parameters": [{
                    "name": "locationId",
                    "in": "query",
                    "description": "locationId",
                    "required": false,
                    "type": "integer",
                    "format": "int32"
                }],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/LocationsRs"
                        }
                    },
                    "401": {
                        "description": "Unauthorized"
                    },
                    "403": {
                        "description": "Forbidden"
                    },
                    "404": {
                        "description": "Not Found"
                    }
                },
                "deprecated": false
            },
            "post": {
                "tags": ["locations"],
                "summary": "getLocations",
                "operationId": "getLocationsUsingPOST",
                "consumes": ["application/json"],
                "produces": ["application/json"],
                "parameters": [{
                    "in": "body",
                    "name": "locationsRq",
                    "description": "locationsRq",
                    "required": true,
                    "schema": {
                        "$ref": "#/definitions/LocationsRq"
                    }
                }],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/LocationsRs"
                        }
                    },
                    "201": {
                        "description": "Created"
                    },
                    "401": {
                        "description": "Unauthorized"
                    },
                    "403": {
                        "description": "Forbidden"
                    },
                    "404": {
                        "description": "Not Found"
                    }
                },
                "deprecated": false
            }
        }
    }
}

I recommend using "tags" to classify codes. The swagger api also use "tags".

In the "paths" part, it use the api url as key. Next level, it use RESTful method as key.

Next level, there are 3 kind of "parameters" part:

  • "in":"body"
  • "in":"path"
  • "in":"query"

means 3 way to call the api.

Generally, "type" or "schema" of "parameters" part shows every parameter type you should use. "schema" is the same as "$ref" when we are consider Models.

At the same level, "responses" part shows return type.(Can skip the key NOT EQUALS "200".)

 

Based on these rule to implement, the following is the program to generate FeignClient.

public class RobotEasy {

	protected Logger log = LoggerFactory.getLogger(this.getClass());
	String modelPackage = "tpi.model";
	String feignPackage = "tpi.feign";

	protected String srcDir;

	public RobotEasy() {
		this(System.getProperty("user.dir"));
		System.out.println("當前工作目錄:" + System.getProperty("user.dir"));
	}

	public RobotEasy(String userDir) {
		srcDir = userDir + File.separator + "src" + File.separator + "main" + File.separator + "java" + File.separator;
	}

	public static void main(String[] args) throws Exception {
		Robot robot = new Robot();
		robot.setModelPackage("tpi.model");
		robot.setFeignPackage("tpi.feign");

		String swaggerDocUrl = "http://localhost:7901/v2/api-docs";
		// step 1. get doc
		String swaggerJsonDocStr = robot.getSwaggerJsonDoc(swaggerDocUrl);
		// escape doller sign error
		JSONObject swaggerJsonDoc = JSON.parseObject(swaggerJsonDocStr.replace("$ref", "_ref"));

		// step 2.
		robot.fnModel(swaggerJsonDoc);
		// step 3.
		robot.fnFeign(swaggerJsonDoc);
	}

	public void fnFeign(JSONObject swaggerJsonDoc) {
		String fileLocation = srcDir + (feignPackage.replace(".", File.separator)) + File.separator;
		File dstFile = new File(fileLocation);
		if (!dstFile.exists())
			dstFile.mkdirs();// 建立資料夾

		int[] writeCount = { 0 };

		String host = swaggerJsonDoc.getString("host");
		String basePath = swaggerJsonDoc.getString("basePath");
		JSONArray tags = swaggerJsonDoc.getJSONArray("tags");
		JSONObject paths = swaggerJsonDoc.getJSONObject("paths");
		tags.stream().forEach(obj -> {
			JSONObject tag = (JSONObject) obj;
			String tagName = tag.getString("name");
			String description = tag.getString("description");
			String[] nameArr = tagName.split("-");
			String className = "";
			for (String namePart : nameArr)
				className += namePart.substring(0, 1).toUpperCase() + namePart.substring(1);
			className += "FeignService";
			String url = "http://" + host + basePath;

			writeCount[0]++;
			File newTxt = new File(fileLocation + className + ".java");
			if (newTxt.exists())
				newTxt.delete();
			try {
				newTxt.createNewFile();

				FileWriter dataFile = new FileWriter(newTxt);
				BufferedWriter input = new BufferedWriter(dataFile);

				input.write("package " + feignPackage + ";");
				input.newLine();
				input.newLine();
				input.write("import org.springframework.cloud.openfeign.FeignClient;");
				input.newLine();
				input.write("import org.springframework.web.bind.annotation.*;");
				input.newLine();
				input.write("import " + modelPackage + ".*;");
				input.newLine();
				input.newLine();

				input.write(String.format("@FeignClient(name = \"%s\", url = \"%s\")", className, url));
				input.newLine();

				input.write("public interface " + className + " {");
				input.newLine();

				paths.keySet().stream().forEach(urlValue -> {
					JSONObject restObj = paths.getJSONObject(urlValue);
					restObj.keySet().stream().forEach(restKey -> {
						JSONObject javaMethodDef = restObj.getJSONObject(restKey);
						if (javaMethodDef.getJSONArray("tags").contains(tagName)) {
							log.info("urlValue:" + urlValue);
							JSONArray parameters = javaMethodDef.getJSONArray("parameters");
							JSONObject responses = javaMethodDef.getJSONObject("responses");
							JSONObject responses200 = responses.getJSONObject("200");
							String consumes = javaMethodDef.getJSONArray("consumes").getString(0);
							String produces = javaMethodDef.getJSONArray("produces").getString(0);
							try {
								input.write(String.format(
										"\t@RequestMapping(value = \"%s\", method = RequestMethod.%s, consumes =\"%s\")",
										urlValue, restKey.toUpperCase(), consumes));
								input.newLine();

								String returnClassName = responses200ReturnClassName(responses200);
								String operationId = javaMethodDef.getString("operationId");
								input.write(String.format("\t%s %s(", returnClassName, operationId));
								if (parameters != null) {
									log.info("parameters.size:" + parameters.size());
									log.info("parameters:" + parameters);
									String paramContext = "";
									for (int i = 0; i < parameters.size(); i++) {
										JSONObject parameter = parameters.getJSONObject(i);
										String paramClassName = fieldDef2Class(parameter);
										String paramName = parameter.getString("name");
										paramContext += ",";
										if (parameter.containsKey("in")) {
											String in = parameter.getString("in");
											// if (StringUtils.equals(in, "body"))
											// paramContext += "@RequestBody ";
											if (StringUtils.equals(in, "path"))
												paramContext += String.format("@PathVariable(\"%s\") ", paramName);
											if (StringUtils.equals(in, "query"))
												paramContext += String.format("@RequestParam(\"%s\") ", paramName);
										}
										paramContext += String.format("%s %s", paramClassName, paramName);
									}
									paramContext = paramContext.substring(1);
									input.write(paramContext);
								}
								input.write(");");
								input.newLine();
								input.newLine();

							} catch (IOException e) {
							}
						}
					});
				});

				input.write("}");

				input.close();
			} catch (IOException e) {
			}
		});

		log.info("Feign writeCount:" + writeCount[0]);
	}

	protected String responses200ReturnClassName(JSONObject responses200) {
		String returnClassName = "Object";
		if (responses200.containsKey("schema")) {
			JSONObject schema = responses200.getJSONObject("schema");
//		System.out.println("schema:" + schema);
			if (schema.containsKey("type")) {
				String schemaType = schema.getString("type");
				switch (schemaType) {
				case "string":
					returnClassName = "String";
//					if (schema.containsKey("enum")) {
//						returnClassName = fieldName.toUpperCase();
//					}
					break;
				case "number":
					returnClassName = "float";
					break;
				case "integer":
					returnClassName = "int";
					String format = schema.getString("format");
					if (StringUtils.equals(format, "int64"))
						returnClassName = "long";
					break;
				case "array":
					returnClassName = "Object[]";
					JSONObject items = schema.getJSONObject("items");
					if (items.containsKey("type")) {
						String itemType = items.getString("type");
						switch (itemType) {
						case "string":
							returnClassName = "String[]";
							break;
						}
					} else {
						String _ref = items.getString("_ref");
						returnClassName = _ref.replace("#/definitions/", "") + "[]";
					}
					break;
				case "object":
					// XXX
					returnClassName = "java.util.Map";
					break;
				default:
					returnClassName = schemaType;
				}
			} else {
				returnClassName = schema.getString("_ref").replace("#/definitions/", "");
			}
		} else {
			returnClassName = "void";
		}
		return returnClassName;
	}
}

 

The following are results.

package tpi.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import tpi.model.*;

@FeignClient(name = "BooksFeignService", url = "http://localhost:7901/")
public interface BooksFeignService {
	@RequestMapping(value = "/books/{bookId}", method = RequestMethod.GET, consumes ="*/*")
	BooksRs getBankInfoUsingGET(@PathVariable("bookId") String bookId);

	@RequestMapping(value = "/books", method = RequestMethod.POST, consumes ="application/json")
	BooksRs getBanksUsingPOST(BooksRq banksRq);

}
package tpi.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import tpi.model.*;

@FeignClient(name = "LocationsFeignService", url = "http://localhost:7901/")
public interface LocationsFeignService {
	@RequestMapping(value = "/locations", method = RequestMethod.POST, consumes ="application/json")
	LocationsRs getLocationsUsingPOST(LocationsRq locationsRq);

	@RequestMapping(value = "/locations", method = RequestMethod.GET, consumes ="*/*")
	LocationsRs getLocationsInfoUsingGET(@RequestParam("locationId") int locationId);

}

 

Test

Finally, let's make a test.

package tpi.swagger.robot;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import tpi.feign.BooksFeignService;
import tpi.feign.LocationsFeignService;
import tpi.model.BooksRq;
import tpi.model.BooksRs;
import tpi.model.BzBookBO;
import tpi.model.LocationBo;
import tpi.model.LocationsRq;
import tpi.model.LocationsRs;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
@WebAppConfiguration
public class FeignTest {
	@Autowired
	BooksFeignService booksFeignService;
	@Autowired
	LocationsFeignService locationsFeignService;

	@Autowired
	private WebApplicationContext webApplicationContext;
	MockMvc mvc;

	@Before
	public void setup() {
		mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
	}

	@Test
	public void getBanksUsingPOST() {
		BooksRq banksRq = new BooksRq();
		banksRq.setKind("chinese");
		BooksRs resp = booksFeignService.getBooksUsingPOST(banksRq);
		for (BzBookBO book : resp.getBookMasters()) {
			System.out.println("I have a book:" + book.getBookName());
		}
	}

	@Test
	public void getBankInfoUsingGET() {
		BooksRs resp = booksFeignService.getBookInfoUsingGET("1");
		for (BzBookBO book : resp.getBookMasters()) {
			System.out.println("I choose a book:" + book.getBookName());
		}
	}

	@Test
	public void getLocationsUsingPOST() {
		LocationsRq locationsRq = new LocationsRq();
		locationsRq.setSearchKindCodes(new String[] { "0038" });
		LocationsRs resp = locationsFeignService.getLocationsUsingPOST(locationsRq);
		for (LocationBo locationBo : resp.getLocationList()) {
			System.out.println("Nearby library:" + locationBo.getLocationName() + " tel:" + locationBo.getTel());
		}
	}

	@Test
	public void getLocationsInfoUsingGET() {
		LocationsRs resp = locationsFeignService.getLocationsInfoUsingGET(3);
		for (LocationBo locationBo : resp.getLocationList()) {
			System.out.println("I want to go to:" + locationBo.getLocationName() + ", at " + locationBo.getAddress());
		}
	}
}

Output:

I choose a book:Italian folk

I want to go to:New York Public Library, at 228 E 23rd St, New York, NY 10010 America

Nearby library:New York Public Library tel:02-23451234
Nearby library:The Bodleian Libraries tel:02-23455678

I have a book:Italian folk
I have a book:Chocolate Recipe

It's easy to use and won't make mistakes.

 

Conclusion

By using Swagger document we can connect systems faster.

It enables us to focus more on the core of our application.

In the future, if your boss ask you to call swagger API, you can say 'just a minute' , and you can do that easily.

葉宗原