knife4j (Swagger) 的二次封装, Spring Security 注解展示, Mybatis Plus 排序字段展示, Page对象忽略不必要的字段

6 篇文章 1 订阅
5 篇文章 0 订阅
/*
 * Copyright 20221-2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

knife4j(Swagger) 的二次封装

我是一个不爱说话的宅男, 经常因为接口问题需要被迫和前端交流, 虽然 knife4j(Swagger) 能够生成API文档, 但是经常会出现一些沟通上面的问题如

  • XXX api 接口的权限是什么
  • 这个接口支持那些排序
  • XXX对象的参数太多了,能不能不要全部给我啊, 特别是你的分页对象

“Talk is cheap. Show me the code.” ― Linus Torvalds
特别喜欢这句话, 所以就放在这里了😬. 下方都是代码copy就能用

详细代码详见GitHub https://github.com/galaxy-sea/galaxy-blogs/tree/master/code/swagger-support

Swagger 提供springfox.documentation.spi.service.OperationBuilderPlugin让我们对springfox.documentation.spi.service.contexts.OperationContext进行进行一些自定义对封装.

开发环境配置

  • jdk 17
  • knife4j 3.0.3
  • Spring Boot 2.7.7
    • Spring Security
  • mybatis plus 3.5.2

Spring Security 注解展示

将Spring Security的PostAuthorizePostFilterPreAuthorizePreFilter的注解信息追加到接口描述中.


/**
 * @author changjin wei(魏昌进)
 * @since 2022/12/27
 */
@Order(SwaggerPluginSupport.OAS_PLUGIN_ORDER + 100)
@Component
public class SecurityAnnotationOperationBuilderPlugin implements OperationBuilderPlugin {

    private static final String PACKAGE_PREFIX = "org.springframework.security.access.prepost.";

    @Override
    public void apply(OperationContext context) {
        String notes = context.operationBuilder().build().getNotes();
        StringBuffer notesBuffer = new StringBuffer(notes == null ? "" : notes);
        getClassAnnotationNote(notesBuffer, context);
        getMethodAnnotationNote(notesBuffer, context);
        context.operationBuilder().notes(notesBuffer.toString());
    }


    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }

    private void getClassAnnotationNote(StringBuffer notesBuffer, OperationContext context) {
        StringBuffer securityNotes = new StringBuffer();
        context.findControllerAnnotation(PostAuthorize.class)
               .ifPresent(ann -> append(securityNotes, ann));
        context.findControllerAnnotation(PostFilter.class)
               .ifPresent(ann -> append(securityNotes, ann));
        context.findControllerAnnotation(PreAuthorize.class)
               .ifPresent(ann -> append(securityNotes, ann));
        context.findControllerAnnotation(PreFilter.class)
               .ifPresent(ann -> append(securityNotes, ann));
        if (securityNotes.length() > 0) {
            notesBuffer.append("<p />");
            notesBuffer.append("class: ");
            notesBuffer.append(securityNotes);
        }
    }

    private void getMethodAnnotationNote(StringBuffer notesBuffer, OperationContext context) {
        StringBuffer securityNotes = new StringBuffer();
        context.findAnnotation(PostAuthorize.class)
               .ifPresent(ann -> append(securityNotes, ann));
        context.findAnnotation(PostFilter.class)
               .ifPresent(ann -> append(securityNotes, ann));
        context.findAnnotation(PreAuthorize.class)
               .ifPresent(ann -> append(securityNotes, ann));
        context.findAnnotation(PreFilter.class)
               .ifPresent(ann -> append(securityNotes, ann));
        if (securityNotes.length() > 0) {
            notesBuffer.append("<p />");
            notesBuffer.append("method: ");
            notesBuffer.append(securityNotes);
        }
    }

    private void append(StringBuffer securityNotes, Annotation ann) {
        String txt = ann.toString().replace(PACKAGE_PREFIX, "").replace("\\'", "'");
        securityNotes.append(txt)
                     .append(" ");
    }

}

MyBatis Plus

orderBy 参数展示

展示mybatis plus page对象支持的排序的字段

/**
 * @author changjin wei(魏昌进)
 * @since 2022/8/12
 */
@Component
@Order(SwaggerPluginSupport.OAS_PLUGIN_ORDER + 101)
public class OrderByFieldsOperationBuilderPlugin implements OperationBuilderPlugin {
    @Override
    public void apply(OperationContext context) {
        List<ResolvedMethodParameter> parameters = context.getParameters();

        for (ResolvedMethodParameter resolvedMethodParameter : parameters) {
            Class<?> erasedType = resolvedMethodParameter.getParameterType().getErasedType();
            if (IPage.class.isAssignableFrom(erasedType)) {
                List<String> orderItems = this.toOrderItems(resolvedMethodParameter);
                if (!CollectionUtils.isEmpty(orderItems)) {
                    this.joinNotes(orderItems, context);
                }
                return;
            }
        }
    }

    @SuppressWarnings("NullableProblems")
    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }

    private void joinNotes(List<String> orderItems, OperationContext context) {
        StringBuilder order = new StringBuilder();
        order.append("OrderBy: ");
        for (String orderItem : orderItems) {
            order.append(orderItem).append(" ");
        }
        String notes = context.operationBuilder().build().getNotes();

        notes = StringUtils.hasText(notes) ? notes + "<p />" + order : order.toString();
        context.operationBuilder().notes(notes);
    }


    private List<String> toOrderItems(ResolvedMethodParameter resolvedMethodParameter) {
        ResolvedType boundType = resolvedMethodParameter.getParameterType().getTypeBindings().getBoundType(0);
        if (boundType == null) {
            return List.of();
        }
        Class<?> orderClass = boundType.getErasedType();
        return TableInfoHelper.getAllFields(orderClass)
                              .stream()
                              .map(Field::getName)
                              .toList();
    }
}

忽略不必要地请求参数

这个功能是仅支持knife4j不支持Swagger,
经常因为看着page对象一大串不需要的字段而烦恼着, 这里我们就将不需要的字段给忽略掉, 仅保留current, size, orders[].asc, orders[0].column这是个字段.


/**
 * @author changjin wei(魏昌进)
 * @since 2022/8/12
 */
@Order(SwaggerPluginSupport.OAS_PLUGIN_ORDER + 101)
@Component
public class  PageIgnoreOperationBuilderPlugin implements OperationBuilderPlugin {

    public static final String IGNORE_PARAMETER_EXTENSION_NAME = "x-ignoreParameters";

    private static final String[] IGNORE = new String[]{"countId", "maxLimit", "optimizeCountSql", "pages", "records", "searchCount", "total"};


    @Override
    public void apply(OperationContext context) {
        if (this.hasPage(context)) {
            addExtensionParameters(IGNORE, IGNORE_PARAMETER_EXTENSION_NAME, context);
        }
    }


    @SuppressWarnings("NullableProblems")
    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }

    private boolean hasPage(OperationContext context) {
        List<ResolvedMethodParameter> parameters = context.getParameters();
        for (ResolvedMethodParameter resolvedMethodParameter : parameters) {
            Class<?> erasedType = resolvedMethodParameter.getParameterType().getErasedType();
            if (IPage.class.isAssignableFrom(erasedType)) {
                return true;
            }
        }
        return false;
    }

    @SuppressWarnings("SameParameterValue")
    private void addExtensionParameters(String[] params, String extensionName, OperationContext context) {
        if (params != null && params.length > 0) {
            Map<String, Boolean> map = new HashMap<>(8);
            for (String ignore : params) {
                if (ignore != null && !"".equals(ignore) && !"null".equals(ignore)) {
                    map.put(ignore, true);
                }
            }
            if (map.size() > 0) {
                List<Map<String, Boolean>> maps = new ArrayList<>();
                maps.add(map);
                ListVendorExtension<Map<String, Boolean>> listVendorExtension = new ListVendorExtension<>(extensionName, maps);
                //noinspection rawtypes
                List<VendorExtension> vendorExtensions = new ArrayList<>();
                vendorExtensions.add(listVendorExtension);
                context.operationBuilder().extensions(vendorExtensions);
            }
        }
    }


}

展示结果对比

测试代码

/**
 * @author changjin wei(魏昌进)
 * @since 2022/12/27
 */
@RestController
@RequestMapping("/hello")
@PostAuthorize("hasAuthority('class')")
@PostFilter("hasAuthority('class')")
@PreAuthorize("hasAuthority('class')")
@PreFilter("hasAuthority('class')")
public class HelloController {

    @GetMapping("/security")
    @PostAuthorize("hasAuthority('method')")
    @PostFilter("hasAuthority('method')")
    @PreAuthorize("hasAuthority('method')")
    @PreFilter("hasAuthority('method')")
    @ApiOperation(value = "", notes = "Spring Security注解追加到接口描述")
    public String security() {
        return "hello security";
    }
    @SuppressWarnings("unused")
    @GetMapping("/page")
    @ApiOperationSupport(ignoreParameters = {"current",})
    public String page(Page<Object> page) {
        return "hello page";
    }
    @SuppressWarnings("unused")
    @GetMapping("/orderBy")
    public String orderBy(Page<User> page) {
        return "hello orderBy";
    }
}

@TableName
@Data
public class User {
    private String id;
    private String name;
    private String age;
    private String sex;
    private String company;
    private String job;
}

结果对比

  1. Spring Security
    在这里插入图片描述

  2. mybatis plus 排序字段
    在这里插入图片描述

  3. mybatis plus 忽略不必要的字段
    在这里插入图片描述

后记

其实这些功能很久以前就写完了, 一直放在我的heifer中, 但是heifer目前就几家公司在用. 所以渐渐的将heifer框架中的一些功能剥离出来单独发博客.

顺带提一下在我写这篇博客的博客的时候Spring Security的功能已经提交给了 knife4j 并合并进去了, 详见 PR 520

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值