Jfinal集成Swagger

前一段时间翻阅了大量的前辈写的文章,但是发现要么版本落后一些,要么集成的是自己的Swagger-UI,因此打算利用原生+jfinal写一下Jfinal与Swagger的融合:

首先需要使用的依赖(因为我没有使用maven所以下面只列了一些jar包的名称):

jackson-dataformat-yaml-2.11.4.jar

jackson-annotations-2.11.4.jar

jackson-databind-2.11.4.jar

jackson-core-2.11.4.jar

lombok.jar

jetty-util-9.4.30.v20200611.jar

gson-2.8.5.jar

swagger-models-1.5.22.jar

swagger-core-1.5.22.jar

springfox-swagger-ui-3.0.0.jar

springfox-swagger2-3.0.0.jar

路由注册:

me.add("/swagger", SwaggerAction.class);	//Swagger管理路由

后台Action:

package com.swagger.action;

import com.jfinal.core.Controller;
import com.jfinal.core.NotAction;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Record;
import com.pansoft.constants.Constant;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.models.Contact;
import io.swagger.models.Info;
import io.swagger.models.License;
import io.swagger.models.Operation;
import io.swagger.models.Path;
import io.swagger.models.Response;
import io.swagger.models.Scheme;
import io.swagger.models.Swagger;
import io.swagger.models.Tag;
import io.swagger.models.parameters.Parameter;
import io.swagger.models.parameters.PathParameter;
import io.swagger.util.Json;
import java.io.File;
import java.io.FileFilter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.collections4.map.HashedMap;
import org.eclipse.jetty.util.StringUtil;

public class SwaggerAction extends Controller {
	// 设置需要扫描哪个包里面的类生产api
	public static final String BASE_PACKAGE = "com.api.action";

	public void index() {
		String RequestURI = this.getRequest().getRequestURI();
		String ContextPath = this.getRequest().getContextPath();
		String functionPath = RequestURI.replaceAll(ContextPath, "");
		Record renderRcd = Db.findFirst("SELECT F_PATH FROM SYS_MENU_DCT WHERE F_FUNC_PATH = ?",
				functionPath);
		Enumeration<String> enums = this.getParaNames();
		Map<String, Object> paraMaps = new HashedMap<String, Object>();
		while(enums.hasMoreElements()){
           String key = enums.nextElement();
           paraMaps.put(key, this.getPara(key, ""));
        }
		if(null == renderRcd) {
			this.render(Constant.PAGE404PATH);
		}else {
			this.setAttrs(paraMaps);
			this.render(renderRcd.getStr("F_PATH"));
		}
	}

	public void api() {
		Swagger doc = new Swagger();
		Info info = new Info();
		Contact contact = new Contact();
		contact.setEmail("liuzhongqi@pansoft.com");
		contact.setName("Sycho");
		contact.setUrl("http://www.pansoft.com");

		info.setDescription("普联软件股份有限公司接口平台");
		License license = new License();
		license.setName("Pansoft");
		license.setUrl("http://www.pansoft.com");
		info.setLicense(license);
		info.setTitle("运营平台接口");
		info.setTermsOfService("http://www.pansoft.com");
		info.setVersion("2.0");
		info.setContact(contact);

		List<Scheme> schemes = new ArrayList<>();
		schemes.add(Scheme.HTTP);
		schemes.add(Scheme.HTTPS);

		Map<String, Path> paths = new HashMap<>();

		Set<Class<?>> classSet = getClassSet(BASE_PACKAGE);
		List<Tag> tags = new ArrayList<>();
		for (Class<?> cls : classSet) {
			if (cls.isAnnotationPresent(Api.class)) {
				Api api = cls.getAnnotation(Api.class);
				String[] apitags = api.tags();
				String apiValue = api.value();

				if (apitags.length > 0) {
					for (String tag : apitags) {
						Tag tagobj = new Tag();
						tagobj.setName(tag);
						tags.add(tagobj);
					}
				} else {
					Tag tagobj = new Tag();
					tagobj.setName(apiValue);
					tags.add(tagobj);
				}

				Method[] methods = cls.getMethods();

				for (Method method : methods) {
					Annotation[] annotations = method.getAnnotations();
					Path path = new Path();

					for (@SuppressWarnings("unused")
					Annotation annotation : annotations) {
						// Class<?> aclass = annotation.annotationType();
						Operation option = new Operation();
						String opvalue = "";
						if (method.isAnnotationPresent(ApiOperation.class)) {
							ApiOperation opertion = method.getAnnotation(ApiOperation.class);
							ArrayList<String> produces = new ArrayList<>();
							String producesStr = opertion.produces();
							produces.add(producesStr);

							opvalue = opertion.value();
							String notes = opertion.notes();
							/*
							 * String consumesStr = opertion.consumes(); String[] ptagsarray =
							 * opertion.tags();
							 */
							List<String> ptags = new ArrayList<>();
							ptags.add(apitags[0]);
							/*
							 * if(ptagsarray.length>0){ for(String tag:ptagsarray){ ptags.add(tag); } }
							 */

							option.setConsumes(new ArrayList<String>());
							option.setDescription(notes);
							option.setSummary(notes);
							option.setTags(ptags);

						}
						List<Parameter> parameters = new ArrayList<>();
						if (method.isAnnotationPresent(ApiImplicitParams.class)) {
							ApiImplicitParams apiImplicitParams = method.getAnnotation(ApiImplicitParams.class);
							ApiImplicitParam[] apiImplicitParamArray = apiImplicitParams.value();

							for (ApiImplicitParam param : apiImplicitParamArray) {
								PathParameter parameter = new PathParameter();
								String in = param.paramType();
								String name = param.name();
								String value = param.value();
								boolean required = param.required();
								String dataType = param.dataType();

								parameter.setType(dataType);
								parameter.setDefaultValue("");
								parameter.setDescription(value);
								parameter.setRequired(required);
								parameter.setIn(in);
								parameter.setName(name);
								parameters.add(parameter);
							}
						}
						option.setParameters(parameters);

						Map<String, Response> responseMap = new HashMap<>();
						if (method.isAnnotationPresent(ApiResponses.class)) {
							ApiResponses responses = method.getAnnotation(ApiResponses.class);
							ApiResponse[] responseArray = responses.value();
							for (ApiResponse response : responseArray) {
								String code = response.code() + "";
								String msg = response.message();
								Response res = new Response();
								res.setDescription(msg);
								responseMap.put(code, res);
							}
						}
						option.setResponses(responseMap);
						String httpMethod = method.getAnnotation(ApiOperation.class).httpMethod().toLowerCase();//小写
						path.set(httpMethod, option);
						paths.put(opvalue, path);
					}

				}
				doc.setSchemes(schemes);
				doc.setSwagger("2.0");
//				doc.setBasePath("/user");
				doc.setInfo(info);
				String serverName = this.getRequest().getServerName();
				int port = this.getRequest().getServerPort();
				String contextPath = this.getRequest().getContextPath();
				String rootPath = "";
				if(port == 80 || port == 443){
					rootPath = serverName+contextPath;
				}else{
					rootPath = serverName+":"+port+contextPath;
				}
				doc.setHost(rootPath);
				doc.setTags(tags);
				doc.setPaths(paths);
			}
		}
		String jsonOutput = Json.pretty(doc);
		renderText(jsonOutput);
	}

	/**
	 * 获取指定包名下所有类
	 *
	 * @param packageName
	 * @return
	 */
	@NotAction
	public static Set<Class<?>> getClassSet(String packageName) {
		Set<Class<?>> classSet = new HashSet<Class<?>>();
		try {
			Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".", "/"));
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				if (url != null) {
					String protocol = url.getProtocol();
					if (protocol.equals("file")) {
						String packagePath = url.getPath().replace("%20", " ");
						addClass(classSet, packagePath, packageName);
					} else if (protocol.equals("jar")) {
						JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
						if (jarURLConnection != null) {
							JarFile jarFile = jarURLConnection.getJarFile();
							if (jarFile != null) {
								Enumeration<JarEntry> jarEntries = jarFile.entries();
								while (jarEntries.hasMoreElements()) {
									JarEntry jarEntry = jarEntries.nextElement();
									String jarEntryName = jarEntry.getName();
									if (jarEntryName.endsWith(".class")) {
										String className = jarEntryName.substring(0, jarEntryName.lastIndexOf("."))
												.replaceAll("/", ".");
										doAddClass(classSet, className);
									}
								}
							}
						}
					}
				}
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return classSet;
	}

	/**
	 * 获取类加载器
	 *
	 * @return
	 */
	@NotAction
	public static ClassLoader getClassLoader() {
		return Thread.currentThread().getContextClassLoader();
	}

	@NotAction
	private static void addClass(Set<Class<?>> classSet, String packagePath, String packageName) {
		File[] files = new File(packagePath).listFiles(new FileFilter() {
			public boolean accept(File file) {
				return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
			}
		});
		for (File file : files) {
			String fileName = file.getName();
			if (file.isFile()) {
				String className = fileName.substring(0, fileName.lastIndexOf("."));
				if (StringUtil.isNotBlank(packageName)) {
					className = packageName + "." + className;
				}
				doAddClass(classSet, className);
			} else {
				String subPackagePath = fileName;
				if (StringUtil.isNotBlank(packagePath)) {
					subPackagePath = packagePath + "/" + subPackagePath;
				}
				String subPackageName = fileName;
				if (StringUtil.isNotBlank(packageName)) {
					subPackageName = packageName + "." + subPackageName;
				}
				addClass(classSet, subPackagePath, subPackageName);
			}
		}
	}

	@NotAction
	private static void doAddClass(Set<Class<?>> classSet, String className) {
		Class<?> cls = loadClass(className, false);
		classSet.add(cls);
	}

	/**
	 * 加载类
	 *
	 * @param className
	 * @param isInitialized false 代表装载类的时候 不进行初始化工作[不会执行静态代码块]
	 * @return
	 */
	@NotAction
	public static Class<?> loadClass(String className, boolean isInitialized) {
		Class<?> cls;
		try {
			cls = Class.forName(className, isInitialized, getClassLoader());
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		}
		return cls;
	}
}

前台页面:去网站下载:https://github.com/swagger-api/swagger-ui,dist目录里面是已经打好的前台工程,直接拿出来用,目录如下:

image.png

示例Action类:

package com.api.action;

import java.util.HashMap;
import java.util.Map;
import com.jfinal.core.Controller;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;

@Api(value="MDMController",tags={"获取项目信息tag"})
public class TestController extends Controller {
	
	public void index() {
		this.renderJson();
	}
	
	//测试示例
	@ApiOperation(value = "/api/TestAPI", httpMethod = "POST",  notes = "获取项目信息,返回项目ID和项目名称")
	@ApiImplicitParams({
		@ApiImplicitParam(paramType="query", required = true, name = "id", value = "项目ID",dataType="string"),
		@ApiImplicitParam(paramType="query", required = true, name = "name", value = "项目名称",dataType="string")
	})
	public void TestAPI() {
		String id = this.getPara("id");
		String name = this.getPara("name");
		Map<String, String> returnMap = new HashMap<String, String>();
		returnMap.put("获取到的ID是:", id);
		returnMap.put("获取到的NAME是:", name);
		renderJson(returnMap);
	}
}

启动访问:

image.png

接口测试:

image.png

评论区

山东小木

2021-12-16 16:12

挺好 但是这种写完action还得弄一套注解描述 工作量有点大 如何能给jfinal模式 弄一套不需要这么些注解 自动识别的就行了

zzutligang

2021-12-16 17:42

swagger就是注解对代码侵入太严重,其他的就很好。但无注解无侵入的,目前又没有更好的东西可用。凑合使用吧

小佳

2021-12-17 10:38

不错哟~~ 未来还有优化的空间~~~