All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.lastb7.swagger.controller.SwaggerController Maven / Gradle / Ivy

There is a newer version: 3.0.0
Show newest version
package com.lastb7.swagger.controller;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import com.jfinal.core.Action;
import com.jfinal.core.Controller;
import com.jfinal.core.JFinal;
import com.jfinal.kit.Base64Kit;
import com.jfinal.kit.Kv;
import com.jfinal.kit.StrKit;
import com.jfinal.template.Engine;
import com.lastb7.swagger.annotation.ApiRes;
import com.lastb7.swagger.annotation.ApiResProperty;
import com.lastb7.swagger.common.SwaggerConst;
import com.lastb7.swagger.enumeration.ApiEnum;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;

/**
 * Swagger UI Controller
 *
 * @author: lbq
 * 联系方式: [email protected]
 * 创建日期: 2020/9/16
 */
public class SwaggerController extends Controller {

    /**
     * swagger tag
     */
    private List tagsList = new ArrayList<>();

    /**
     * swagger models
     */
    private Map definitionsMap = new LinkedHashMap<>();

    /**
     * swagger path
     */
    private List pathList = new ArrayList();

    /**
     * 通用返回类 名称
     */
    private String commonResName;

    /**
     * 通用返回类 返回值
     */
    private List commonResProperties;

    /**
     * 默认转发
     */
    public void index() {
        this.redirect("swagger-ui/doc.html");
    }

    /**
     * 获取分组信息
     */
    public void resources() {
        Kv kv = new Kv();
        kv.set("swagger_resources", SwaggerConst.CONFIG.get("swagger_resources"));
        kv.set("swaggerVersion", SwaggerConst.CONFIG.get("swaggerVersion"));

        renderJson(Engine.use("swagger").getTemplate("/swagger-resources.jf").renderToString(kv));
    }

    /**
     * 获取api接口解析JSON
     */
    public void api() throws IOException {
        if (!this.basicAuth()) {
            this.response401();

            this.renderNull();
            return;
        }

        // 解析通用返回
        Kv commonResKv = this.parseSwaggerModel(SwaggerConst.COMMON_RES);
        this.commonResName = commonResKv.getStr("name");
        this.commonResProperties = (List) commonResKv.get("properties");

        // 解析JSON
        this.parseGroupPackage(this.getPara("group", ""));

        Kv kv = new Kv();
        kv.set("swagger", SwaggerConst.CONFIG.getProperties());
        kv.set("host", this.getHost());

        kv.set("tags", this.tagsList);
        kv.set("paths", this.pathList);
        kv.set("definitions", this.toDefinitionList(this.definitionsMap));

        renderJson(Engine.use("swagger").getTemplate("/api-docs.jf").renderToString(kv));
    }

    /**
     * 解析分组包
     *
     * @param groupPackage 分组包名
     */
    private void parseGroupPackage(String groupPackage) {
        Map, List> classMap = this.getApiAction(groupPackage);
        classMap.keySet().forEach((Class clazz) -> {
            List actions = classMap.get(clazz);

            // 解析controller
            this.parseController(clazz, actions);

        });
    }

    /**
     * 从JFinal中获取全部Action
     */
    private Map, List> getApiAction(String basePackage) {
        Map, List> apiMap = new HashMap<>(16);

        JFinal.me().getAllActionKeys().forEach(actionKey -> {
            Action action = JFinal.me().getAction(actionKey, new String[1]);
            Class controller = action.getControllerClass();

            if (!controller.getName().startsWith(basePackage)) {
                return;
            }

            if (apiMap.containsKey(controller)) {
                if (action.getMethod().isAnnotationPresent(ApiOperation.class)) {
                    List actions = apiMap.get(controller);
                    if (!actions.contains(action)) {
                        actions.add(action);
                        apiMap.put(controller, actions);
                    }
                }
            } else {
                if (controller.isAnnotationPresent(Api.class)) {
                    if (action.getMethod().isAnnotationPresent(ApiOperation.class)) {
                        List actions = new ArrayList<>();
                        actions.add(action);
                        apiMap.put(controller, actions);
                    }
                }
            }
        });

        List> ctlList = new ArrayList<>(apiMap.keySet());
        ctlList.sort((clazz1, clazz2) -> clazz1.getAnnotation(Api.class).position() - clazz2.getAnnotation(Api.class).position());

        Map, List> result = new LinkedHashMap<>();
        ctlList.forEach(i -> {
            List actions = apiMap.get(i);
            actions.sort((action1, action2) -> action1.getMethod().getAnnotation(ApiOperation.class).position() - action2.getMethod().getAnnotation(ApiOperation.class).position());
            result.put(i, actions);
        });

        return result;
    }

    /**
     * 解析controller
     */
    private void parseController(Class clazz, List actions) {
        // controller 信息
        Api api = clazz.getAnnotation(Api.class);
        boolean hidden = api.hidden();
        if (hidden) {
            return;
        }

        for (String tags : api.tags()) {
            Kv tag = new Kv();
            tag.set("name", tags);
            tag.set("controllerKey", actions.get(0).getControllerKey());
            tag.set("controllerName", clazz.getSimpleName());

            this.tagsList.add(tag);
        }

        // 解析action
        this.parseAction(actions);
    }

    /**
     * 解析action
     */
    private void parseAction(List actions) {
        actions.forEach((Action action) -> {
            Method method = action.getMethod();

            ApiOperation apiAction = method.getAnnotation(ApiOperation.class);

            if (apiAction.hidden()) {
                return;
            }

            String controllerKey = this.getControllerKey(action.getControllerKey());
            String actionName = action.getMethodName();

            Kv actionKv = new Kv();
            actionKv.set("tags", StrKit.notBlank(apiAction.tags()) ? apiAction.tags() : actions.get(0).getControllerClass().getAnnotation(Api.class).tags())
                    .set("summary", apiAction.value())
                    .set("description", apiAction.notes())
                    .set("deprecated", method.isAnnotationPresent(Deprecated.class))
                    .set("parameters", this.parseActionParameters(method))
                    .set("responses", this.parseActionResponse(controllerKey, actionName, method))
                    .set("methods", StrKit.isBlank(apiAction.httpMethod()) ? ApiEnum.METHOD_GET : apiAction.httpMethod())
                    .set("consumes", StrKit.isBlank(apiAction.consumes()) ? ApiEnum.CONSUMES_URLENCODED : apiAction.consumes())
                    .set("produces", StrKit.isBlank(apiAction.produces()) ? ApiEnum.PRODUCES_DEFAULT : apiAction.produces())
                    .set("controllerKey", controllerKey)
                    .set("actionName", actionName);

            pathList.add(actionKv);
        });

    }

    /**
     * 解析action 参数文档
     */
    private List parseActionParameters(Method method) {
        // 获取参数注解信息
        List params = new ArrayList<>();
        if (method.isAnnotationPresent(ApiImplicitParams.class)) {
            params.addAll(Arrays.asList(method.getAnnotation(ApiImplicitParams.class).value()));
        }
        if (method.isAnnotationPresent(ApiImplicitParams.class)) {
            ApiImplicitParam[] paramArray = method.getAnnotationsByType(ApiImplicitParam.class);
            params.addAll(Arrays.asList(paramArray));
        }

        // 构建参数列表(包含全局参数)
        List paramList = new ArrayList<>();

        params.forEach(param -> {
            Kv kv = Kv.by("name", param.name())
                    .set("description", param.value())
                    .set("required", param.required())
                    .set("format", param.format())
                    .set("defaultValue", param.defaultValue())
                    .set("allowMultiple", param.allowMultiple())
                    .set("schema", this.toParameterSchema(param))
                    .set("dataType", StrKit.isBlank(param.dataType()) ? ApiEnum.STRING : param.dataType())
                    .set("paramType", StrKit.isBlank(param.paramType()) ? ApiEnum.PARAM_TYPE_QUERY : param.paramType());

            paramList.add(kv);
        });
        return paramList;
    }

    /**
     * 解析action 返回文档
     */
    private List parseActionResponse(String controllerKey, String actionName, Method method) {
        List responseList = new ArrayList();

        SwaggerConst.HTTP_CODE.forEach((key, value) -> {
            if (key == 200) {
                responseList.add(Kv.by("name", key).set("description", value)
                        .set("schema", this.parseResponse(controllerKey, actionName, method)));
            } else {
                responseList.add(Kv.by("name", key).set("description", value));
            }

        });
        return responseList;
    }

    /**
     * 解析返回值
     */
    private String parseResponse(String controllerKey, String actionName, Method method) {
        // swagger model 引用
        String swaggerModelName;

        List responses = new ArrayList<>();
        if (method.isAnnotationPresent(ApiRes.class)) {
            responses.addAll(Arrays.asList(method.getAnnotation(ApiRes.class).value()));
        }
        if (method.isAnnotationPresent(ApiRes.class)) {
            ApiResProperty[] paramArray = method.getAnnotationsByType(ApiResProperty.class);
            responses.addAll(Arrays.asList(paramArray));
        }

        if (responses.size() == 0) {
            swaggerModelName = this.commonResName;
        } else {
            // 将参数放入commonRes中,作为新的swagger Model引用(knife4j 约定)
            Kv swaggerModelKv = this.parseSwaggerModel(controllerKey, actionName, responses);
            swaggerModelName = swaggerModelKv.getStr("name");

            // 在data中返回参数
            if (SwaggerConst.RESPONSE_IN_DATA) {
                swaggerModelName = this.toResponseInData(swaggerModelName);
            }
        }

        return swaggerModelName;
    }

    /**
     * 在data中返回
     */
    private String toResponseInData(String swaggerModelName) {
        Kv fieldKv = new Kv();
        List propertiesList = new ArrayList();
        propertiesList.addAll(this.commonResProperties);


        fieldKv.set("key", "data");
        fieldKv.set("name", swaggerModelName);
        fieldKv.set("description", "返回值");
        fieldKv.set("type", ApiEnum.RES_OBJECT);

        propertiesList.add(fieldKv);

        swaggerModelName = this.commonResName + "«" + swaggerModelName + "»";

        Kv kv = new Kv();
        kv.set("properties", propertiesList);
        kv.set("name", swaggerModelName);

        this.definitionsMap.put(swaggerModelName, kv);
        return swaggerModelName;
    }


    /**
     * swagger models
     */
    private List toDefinitionList(Map map) {
        List list = new ArrayList();
        map.forEach((key, value) -> list.add(value));
        return list;
    }


    /**
     * 将class解析为swagger model
     */
    private Kv parseSwaggerModel(Class clazz) {
        String modelName = clazz.getSimpleName();

        // 已存在,不重复解析
        Kv modelKv = this.definitionsMap.get(modelName);
        if (null != modelKv) {
            return modelKv;
        }

        ApiModel apiModel = clazz.getAnnotation(ApiModel.class);
        String title = apiModel.description();

        List fieldList = new ArrayList();

        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            ApiModelProperty apiField = field.getAnnotation(ApiModelProperty.class);

            // List 类型
            if (field.getType() == List.class) {
                // 如果是List类型,得到其Generic的类型
                Type genericType = field.getGenericType();
                if (genericType == null) {
                    continue;
                }
                // 如果是泛型参数的类型
                if (genericType instanceof ParameterizedType) {
                    ParameterizedType pt = (ParameterizedType) genericType;
                    //得到泛型里的class类型对象
                    Class genericClazz = (Class) pt.getActualTypeArguments()[0];

                    Kv swaggerModel = this.parseSwaggerModel(genericClazz);

                    Kv fieldKv = new Kv();

                    fieldKv.set("key", field.getName());
                    fieldKv.set("name", swaggerModel.getStr("name"));
                    fieldKv.set("description", apiField.value());
                    fieldKv.set("type", ApiEnum.RES_OBJECT);
                    fieldKv.set("allowMultiple", true);

                    fieldList.add(fieldKv);
                }
                continue;
            }

            Class typeClazz = field.getType();
            if (typeClazz.isAnnotationPresent(ApiModel.class)) {

                Kv swaggerModel = this.parseSwaggerModel(typeClazz);

                Kv fieldKv = new Kv();

                fieldKv.set("key", field.getName());
                fieldKv.set("name", swaggerModel.getStr("name"));
                fieldKv.set("description", apiField.value());
                fieldKv.set("type", ApiEnum.RES_OBJECT);

                fieldList.add(fieldKv);
            } else {
                Kv fieldKv = new Kv();
                fieldKv.set("name", field.getName());
                fieldKv.set("description", apiField.value());
                fieldKv.set("type", StrKit.isBlank(apiField.dataType()) ? field.getType().getSimpleName().toLowerCase() : apiField.dataType());
                fieldKv.set("example", apiField.example());

                fieldList.add(fieldKv);
            }
        }

        Kv kv = new Kv();
        kv.set("properties", fieldList);
        kv.set("name", modelName);
        kv.set("title", title);

        this.definitionsMap.put(modelName, kv);

        return kv;
    }

    /**
     * 将action response解析为swagger model
     */
    private Kv parseSwaggerModel(String controllerKey, String actionName, List responses) {
        String modelName = controllerKey + "_" + actionName;

        List propertiesList = new ArrayList();

        // 不在Data中返回参数
        if (!SwaggerConst.RESPONSE_IN_DATA) {
            propertiesList.addAll(this.commonResProperties);
        }

        responses.forEach(apiResponse -> {

            if (apiResponse.dataTypeClass() != Void.class) {
                Kv swaggerModel = this.parseSwaggerModel(apiResponse.dataTypeClass());

                Kv fieldKv = new Kv();

                fieldKv.set("key", apiResponse.name());
                fieldKv.set("name", swaggerModel.getStr("name"));
                fieldKv.set("description", apiResponse.value());
                fieldKv.set("type", ApiEnum.RES_OBJECT);
                fieldKv.set("allowMultiple", apiResponse.allowMultiple());

                propertiesList.add(fieldKv);
            } else {
                Kv fieldKv = new Kv();

                fieldKv.set("name", apiResponse.name());
                fieldKv.set("description", apiResponse.value());
                fieldKv.set("type", StrKit.isBlank(apiResponse.dataType()) ? ApiEnum.RES_STRING : apiResponse.dataType());
                fieldKv.set("format", StrKit.isBlank(apiResponse.format()) ? ApiEnum.FORMAT_STRING : apiResponse.format());
                fieldKv.set("example", apiResponse.example());
                fieldKv.set("exampleEnum", apiResponse.exampleEnum());
                fieldKv.set("allowMultiple", apiResponse.allowMultiple());

                propertiesList.add(fieldKv);
            }
        });

        Kv kv = new Kv();
        kv.set("properties", propertiesList);
        kv.set("name", modelName);

        this.definitionsMap.put(modelName, kv);

        return kv;
    }

    /**
     * 解析对象参数
     */
    private String toParameterSchema(ApiImplicitParam apiParam) {
        if (apiParam.dataTypeClass() != Void.class) {
            Kv swaggerModel = this.parseSwaggerModel(apiParam.dataTypeClass());

            return swaggerModel.getStr("name");
        }
        return null;
    }

    /**
     * 获取host配置
     */
    private String getHost() {
        String host = SwaggerConst.CONFIG.get("host");
        if (StrKit.isBlank(host)) {
            host = getRequest().getServerName();
            if (this.getRequest().getServerPort() != 80) {
                host += ":" + getRequest().getServerPort();
            }
        }
        return host;
    }

    /**
     * 避免JFinal ControllerKey 设置前缀后,与swagger basePath 设置导致前端生成2次
     */
    private String getControllerKey(String actionKey) {
        return actionKey.replaceFirst(SwaggerConst.CONFIG.get("basePath"), "")
                .substring(1);
    }

    /**
     * WWW-Authenticate 简单认证
     */
    private boolean basicAuth() throws IOException {
        String basicAuth = SwaggerConst.CONFIG.get("basicAuth");
        if (StrKit.isBlank(basicAuth)) {
            // 未启用简单认证
            return true;
        }

        String authorization = this.getHeader("Authorization");
        if (StrKit.isBlank(authorization)) {
            // 请求头无认证信息
            return false;
        }

        Map baseAuthMap = new HashMap<>(16);

        String[] baseAuthArr = basicAuth.split(",");
        for (String auth : baseAuthArr) {
            baseAuthMap.put(auth.split("#")[0], auth.split("#")[1]);
        }

        String nameAndPwd = Base64Kit.decodeToStr(authorization.substring(6));
        String[] upArr = nameAndPwd.split(":");

        if (upArr.length != 2) {
            return false;
        }
        String iptName = upArr[0];
        String iptPwd = upArr[1];

        return iptPwd.equals(baseAuthMap.get(iptName));
    }

    private void response401() throws IOException {
        this.getResponse().setStatus(401);
        this.getResponse().setHeader("WWW-Authenticate", "Basic realm=\"请输入Swagger文档访问账号密码\"");
        this.getResponse().getWriter().write("无权限访问");
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy