基于JFinal的接口文档生成工具

因项目需要,需要将项目中的接口生成JSON格式的接口文档,试过swagger,改动太大,遂放弃,自己写了一个小工具,能用到的小伙伴可以参考下。好处是没有三方依赖,拿去就可以用

1. 定义方法的注解类

import java.lang.annotation.*;

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Desc {
    public String apiName() default "";
    public String desc() default "";
    public String[] para() default {};
}

2.定义拦截器的注解类

import java.lang.annotation.*;

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface InterceptorDesc {
    public String name() default "";
    public String desc() default "";
}

3.DescAnnotationParse.java 根据类和方法名,查找注解,并返回Map类型的接口说明

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DescAnnotationParse {

    public static Map<String, Object> parse(Class targetClass, String targetMethod) {

        Method[] methods = targetClass.getMethods();

        for (int i = 0; i < methods.length; i++) {

            Method method = methods[i];
            if (method.getName().equals(targetMethod) && method.isAnnotationPresent(Desc.class)) {//存在注解
                Desc desc = method.getAnnotation(Desc.class);
                String descStr = desc.desc();
                String apiName = desc.apiName();
                Map<String, Object> map = new HashMap<String, Object>();
                map.put("apiName", apiName);
                map.put("desc", descStr);
                map.put("para", desc.para());

                List paras = new ArrayList<>();

                Annotation[][] parameterAnnotations = method.getParameterAnnotations();
                for (Annotation[] annotationps : parameterAnnotations) {
                    for (Annotation annotation : annotationps) {
                        if (annotation instanceof Para) {
                            Map paramap = new HashMap();
                            if (!((Para) annotation).value().equals("-NULL VALUE-"))
                                paramap.put("paraName", ((Para) annotation).value());
                            if (!((Para) annotation).defaultValue().equals("-NULL VALUE-"))
                                paramap.put("defaultValue", ((Para) annotation).defaultValue());
                            //TODO :: 还缺少入参数据的类型
                            paras.add(paramap);

                        }
                    }
                }
                map.put("paras",paras);
                map.put("class",targetClass.getName());
                map.put("method",method.getName());

                List beforeList = new ArrayList();

                // 是否存在拦截器注解
                if(targetClass.isAnnotationPresent(Before.class) && !method.isAnnotationPresent(Clear.class)){
                    Class[] interceptors = ((Before)targetClass.getAnnotation(Before.class)).value();
                    for(Class annoclass : interceptors){
                        if(annoclass.isAnnotationPresent(InterceptorDesc.class)) {
                            Map beforeMap = new HashMap();
                            InterceptorDesc interceptorDesc = (InterceptorDesc) annoclass.getAnnotation(InterceptorDesc.class);
                            beforeMap.put("type",annoclass.getName());
                            beforeMap.put("desc",interceptorDesc.desc());
                            beforeMap.put("name",interceptorDesc.name());

                            beforeList.add(beforeMap);
                        }
                    }
                }

                if(method.isAnnotationPresent(Before.class)){
                    Class[] interceptors = method.getAnnotation(Before.class).value();
                    for(Class annoclass : interceptors){
                        if(annoclass.isAnnotationPresent(InterceptorDesc.class)) {
                            Map beforeMap = new HashMap();
                            InterceptorDesc interceptorDesc = (InterceptorDesc) annoclass.getAnnotation(InterceptorDesc.class);
                            beforeMap.put("type",annoclass.getName());
                            beforeMap.put("desc",interceptorDesc.desc());

                            if (!beforeList.contains(beforeMap))
                                beforeList.add(beforeMap);
                        }
                    }
                }


                map.put("before",beforeList);
                return map;
            }
        }

        return null;

    }
}


4.ApiAnnotationUtils.java , 用于从路由表中查找带有@Desc注解的方法,并使用DescAnnotationParse工具遍历生成接口文档List

import com.jfinal.config.Routes;
import com.jfinal.core.Action;
import com.jfinal.core.JFinal;
import com.jfinal.json.JFinalJson;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ApiAnnotationUtils {

    /**
     * 路由表
     */
    static Map<String, String> mapping = new HashMap<String, String>();

    static Map<String, Map> descmapping = new HashMap<>();

    public static void init() {
        List<String> list = JFinal.me().getAllActionKeys();
        list.stream().forEach(item -> {
            Action action = JFinal.me().getAction(item, null);
            String className = action.getControllerClass().getName();
            String method = action.getMethodName();
            mapping.put(item, className + "." + method);
            try {
                Class newClass = Class.forName(className);
                Map map = DescAnnotationParse.parse(newClass, method);
                if (map != null)
                    descmapping.put(item, map);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        System.out.println("******************************************** Api Desc Start ********************************************\n");
        list.forEach(item -> {
            if (descmapping.containsKey(item)) {
                System.out.println("url : " + item + " \t desc:" + JFinalJson.getJson().toJson(descmapping.get(item)));
            }
        });
        System.out.println("\n********************************************* Api Desc End *********************************************\n");



    }

    public static void addRoute(Routes me,String path){
        me.add(path,RoutesIndexController.class);
    }

}


5.RoutersIndexController.java 用于返回json文档,这个类暂时还没写完,后续增加html展示

import com.jfinal.core.Controller;
import com.jfinal.core.JFinal;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class RoutesIndexController extends Controller {

    public void index(){
        this.renderJson(ApiAnnotationUtils.descmapping.toString());
        List list = JFinal.me().getAllActionKeys();

        List all = new ArrayList();

        list.stream().forEach(item -> {
            if (ApiAnnotationUtils.descmapping.containsKey(item)) {
                Map map = ApiAnnotationUtils.descmapping.get(item);
                map.put("url",item);
                all.add(map);
            }
        });

        renderJson(all);
    }

}


6.使用时,在Config类下添加

public void configRoute(Routes me) {
    
    ...

    ApiAnnotationUtils.addRoute(me,"/api/router");// /api/router 为访问路径

}
public void onStart() {

    ApiAnnotationUtils.init();
}

7.启动项目,console打印

微信图片_20200913104022.png

也可以访问步骤6中的路径,直接拿JSON

8.使用,方法上加注解,例:

微信截图_20200913133618.png


后续有时间会完善带html可视化的版本。

评论区

l745230

2020-09-14 11:47

这,看用法不是还得写注解么. 跟集成swagger 写注解没差吧

A8187

2020-09-14 14:31

@l745230 用法差不多,比较轻量

2020-09-17 09:30

一样是写注解,就算是用swagger,项目一点也不用改动,也是和你这样的写法一样。