JFinal与Swagger集成(只需引入前台页面,无需jar包)

public class SwaggerController extends Controller {
    private static Ret swagger = null;

    /**
     * 首页的处理
     */
    public void index() {
        this.redirect("/lib/swagger/index.html");
    }

    /**
     *
     */
    public void json() throws NoSuchFieldException, IllegalAccessException {
        //https://github.com/swagger-api/swagger-ui
        if (!FrameworkConfig.SWAGGER_ENABLE && !FrameworkConfig.DEV_MODE) {
            this.renderJson(Ret.fail("禁止访问"));
            return;
        }

        if (swagger == null) {
            synchronized (this.getClass()) {
                if (swagger == null) {
                    swagger = Ret.ok();
                    initSwagger(swagger);
                }
            }
        }

        this.renderJson(swagger);
    }

    private void initSwagger(Ret swagger) throws NoSuchFieldException, IllegalAccessException {
        Field actionMappingField = JFinal.class.getDeclaredField("actionMapping");
        actionMappingField.setAccessible(true);
        ActionMapping actionMapping = (ActionMapping) actionMappingField.get(JFinal.me());
        Field mappingField = ActionMapping.class.getDeclaredField("mapping");
        mappingField.setAccessible(true);
        Map<String, Action> actions = (Map<String, Action>) mappingField.get(actionMapping);
        Map<? extends Class<? extends Controller>, List<Action>> controllerActionsMap = actions.stream().collect(Collectors.groupingBy(Action::getControllerClass));
        List<Kv> tags = new ArrayList<>();
        Map<String, Kv> paths = new LinkedHashMap<>();

        for (Map.Entry<? extends Class<? extends Controller>, List<Action>> entry : controllerActionsMap.entrySet()) {
            Class<? extends Controller> controller = entry.getKey();


            List<Action> controllerActions = entry.getValue();

            RouteMapping routeMapping = controller.getAnnotation(RouteMapping.class);

            if (routeMapping == null) {
                continue;
            }

            String routePath = routeMapping.value().startsWith("/") ? routeMapping.value() : "/" + routeMapping.value();

            if (controllerActions.stream().noneMatch(e -> e.getMethod().getAnnotation(ApiName.class) != null)) {
                continue;
            }

            tags.add(Kv.by("name", routePath).set("description", routeMapping.name()));

            controllerActions.sort((Action first, Action second) -> {
                String firstMethodName = first.getMethodName();
                String secondMethodName = second.getMethodName();

                return getMethodIndex(firstMethodName) - getMethodIndex(secondMethodName);
            });

            for (Action action : controllerActions) {
                Method method = action.getMethod();
                ApiName apiName = method.getAnnotation(ApiName.class);

                if (apiName == null) {
                    continue;
                }

                Parameter[] paras = method.getParameters();
                Class<?>[] parameterTypes = method.getParameterTypes();
                List<Kv> parameters = new ArrayList<>();

                ApiParams apiParams = method.getAnnotation(ApiParams.class);

                if (apiParams != null) {
                    for (ApiParam apiParam : apiParams.value()) {
                        Class<?> parameterType = apiParam.type();

                        if (parameterType.equals(PageVo.class)) {
                            addPageParameter(parameters);

                            continue;
                        }

                        String typeName = parameterType.getSimpleName().toLowerCase();
                        Kv parameter = Kv.by("name", apiParam.name())
                                .set("in", "formData")
                                .set("description", apiParam.value())
                                .set("type", (typeName.equals(UploadFile.class.getSimpleName().toLowerCase()) || typeName.equals(UploadFileBean.class.getSimpleName().toLowerCase()))
                                        ? "file" : typeName);
                        parameter.set("required", apiParam.required());
                        parameters.add(parameter);
                    }
                } else {
                    for (int i = 0; i < paras.length; i++) {
                        Parameter para = paras[i];
                        Para paraAnno = para.getAnnotation(Para.class);
                        String name = Optional.ofNullable(paraAnno).map(Para::value).filter(e -> !Para.NULL_VALUE.equals(e)).orElse(para.getName());
                        ApiParam apiParam = para.getAnnotation(ApiParam.class);
                        Class<?> parameterType = parameterTypes[i];

                        if (parameterType.equals(PageVo.class)) {
                            addPageParameter(parameters);

                            continue;
                        }

                        String typeName = parameterType.getSimpleName().toLowerCase();
                        Kv parameter = Kv.by("name", name)
                                .set("in", "formData")
                                .set("description", apiParam == null ? name : apiParam.value())
                                .set("type", (typeName.equals(UploadFile.class.getSimpleName().toLowerCase()) || typeName.equals(UploadFileBean.class.getSimpleName().toLowerCase()))
                                        ? "file" : typeName);


                        if (apiParam != null) {
                            parameter.set("required", apiParam.required());
                        }

                        parameters.add(parameter);
                    }
                }


                Kv responses = Kv.by("200", new Kv());
                Kv post = Kv.by("tags", Arrays.asList(routePath))
                        .set("description", apiName.value() + "(" + action.getActionKey() + ")")
                        .set("parameters", parameters)
                        .set("consumes", Arrays.asList("application/x-www-form-urlencoded"))
                        .set("produces", Arrays.asList("application/json"))
                        .set("responses", responses);

                Deprecated annotation = method.getAnnotation(Deprecated.class);

                if (annotation != null) {
                    post.set("deprecated", true);
                }

                paths.put(action.getActionKey(), Kv.by("post", post));
            }
        }

        tags.sort(Comparator.comparing(o -> o.getStr("name")));

        List<String> descriptions = new ArrayList<>();
        descriptions.add("token名称:" + FrameworkConst.TOKEN_NAME);

        swagger.set("schemes", Arrays.asList("HTTP", "HTTPS"))
                .set("swagger", "2.0")
                .set("basePath", "")
                .set("info", Kv.by("version", "3.0").set("title", "至道Api").set("description", StrKit.join(descriptions, ";")))
                .set("tags", tags)
                .set("paths", paths)
        ;
    }

    private void addPageParameter(List<Kv> parameters) {
        Kv pageNumber = Kv.by("name", "offset")
                .set("in", "formData")
                .set("description", "数据偏移量")
                .set("type", Integer.class.getSimpleName().toLowerCase());

        parameters.add(pageNumber);

        Kv pageSize = Kv.by("name", "limit")
                .set("in", "formData")
                .set("description", "每页显示的条数")
                .set("type", Integer.class.getSimpleName().toLowerCase());

        parameters.add(pageSize);
    }

    private int getMethodIndex(String methodName) {

        if (methodName.startsWith("index")) {
            return 1;
        }

        if (methodName.startsWith("list")) {
            return 2;
        }

        if (methodName.startsWith("page")) {
            return 3;
        }

        if (methodName.startsWith("add")) {
            return 4;
        }

        if (methodName.startsWith("save")) {
            return 5;
        }

        if (methodName.startsWith("edit")) {
            return 6;
        }

        if (methodName.startsWith("update")) {
            return 7;
        }

        if (methodName.startsWith("delete")) {
            return 8;
        }

        return 10;
    }
}
@RouteMapping(value = "/stayApply", name = "留宿申请", viewPath = "/_admin/stayApply")
public class StayApplyController extends AdminBaseController {
    private static StayApplyServiceImpl stayApplyService = Aop.get(StayApplyServiceImpl.class);

    @ApiName("初始化参数")
    public void index() {
        Ret ret = Ret.ok();
        this.renderJson(ret);
    }

    @ApiName("列表(分页)")
    public void list(PageVo pageVo) {
        Page<StayApply> page = stayApplyService.page(this.getUserLogin(), pageVo);
        this.renderJson(Ret.page(page));
    }

    @ApiName("打开新增")
    public void add() {
        this.renderJson(Ret.ok());
    }

    @ApiName("保存")
    //@Before(StayApplyValidator.class)
    public void save(
        @ApiParam(value = "表格id", required = true) Integer tableId,
        @ApiParam(value = "内容", required = true) String content,
        @ApiParam(value = "内容", required = true) Integer contentId,
        @ApiParam(value = "状态", required = true) Integer status,
        @ApiParam(value = "年份", required = false) Integer year,
        @ApiParam(value = "学期", required = false) Integer term,
        @ApiParam(value = "名称", required = true) String name,
        @ApiParam(value = "留宿开始时间", required = true) Date beginTime,
        @ApiParam(value = "留宿结束时间", required = true) Date endTime
    ) {
        StayApply stayApply = new StayApply();
        stayApply.setTableId(tableId);
        stayApply.setContent(content);
        stayApply.setContentId(contentId);
        stayApply.setStatus(status);
        stayApply.setYear(year);
        stayApply.setTerm(term);
        stayApply.setName(name);
        stayApply.setBeginTime(beginTime);
        stayApply.setEndTime(endTime);

        stayApplyService.save(this.getUserLogin(), stayApply);
        this.renderJson(Ret.saveOk());
    }

    @ApiName("打开编辑")
    public void edit(@ApiParam(value = "唯一身份id", required = true) Integer id) {
        StayApply stayApply = stayApplyService.findById(this.getUserLogin(), id);
        this.renderJson(Ret.ok().set("value", stayApply));
    }

    @ApiName("更新")
    //@Before(StayApplyValidator.class)
    public void update(
        @ApiParam(value = "id", required = true) Integer id,
        @ApiParam(value = "表格id", required = true) Integer tableId,
        @ApiParam(value = "内容", required = true) String content,
        @ApiParam(value = "内容", required = true) Integer contentId,
        @ApiParam(value = "状态", required = true) Integer status,
        @ApiParam(value = "年份", required = false) Integer year,
        @ApiParam(value = "学期", required = false) Integer term,
        @ApiParam(value = "名称", required = true) String name,
        @ApiParam(value = "留宿开始时间", required = true) Date beginTime,
        @ApiParam(value = "留宿结束时间", required = true) Date endTime
    ) {
        StayApply stayApply = new StayApply();
        stayApply.setId(id);
        stayApply.setTableId(tableId);
        stayApply.setContent(content);
        stayApply.setContentId(contentId);
        stayApply.setStatus(status);
        stayApply.setYear(year);
        stayApply.setTerm(term);
        stayApply.setName(name);
        stayApply.setBeginTime(beginTime);
        stayApply.setEndTime(endTime);

        stayApplyService.update(this.getUserLogin(),stayApply);
        this.renderJson(Ret.updateOK());
    }

    @ApiName("删除")
    public void delete(@ApiParam(value = "唯一身份id", required = true) Integer id) {
        stayApplyService.delete(this.getUserLogin(), id);
        this.renderJson(Ret.deleteOk());
    }
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiName {
    String value();
}
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiParam {
    String value() default "";

    boolean required() default false;

    String name() default "";

    Class<?> type() default Void.class;
}


如果需要非空判断,需要和JFinal的Interceptor结合,涉及细节较多,就不一一贴代码

评论区

杜福忠

2021-12-17 14:40

看着很赞,反射那块不知道可不可以优化
for(String uri : JFinal.me().getAllActionKeys()){
Action action = JFinal.me().getAction(uri, null);
}

halason

2021-12-17 14:48

@杜福忠 不行的,源码没有对外开放

halason

2021-12-17 14:57

@杜福忠 按你给的代码,理论上应该没问题

山东小木

2021-12-17 22:13

实现效果演示有吗?

zzutligang

2021-12-18 15:02

赞一个,目前我用的是jfinal-swagger-knife4j,总感觉是有点麻烦。

l745230

2021-12-20 10:50

@zzutligang 我也觉得. 经常写着写着就忘记Api是怎么用,然后去翻之前写的代码复制 knife4j本身的文档还不好参考,一大堆Spring的注解..

halason

2021-12-21 11:59

@山东小木 我们线上在用,要使用Interceptor拦截扩展