美文网首页
Spring Boot 接口扫描

Spring Boot 接口扫描

作者: 霸哥终结者 | 来源:发表于2019-12-06 15:56 被阅读0次

前言

在做RBAC权限控制的时候,通常会在权限表中存有接口的对应信息,手动添加如此多的接口会很麻烦,也很容易出错。有没有办法在Spring项目启动时就可以扫描所有接口并自动加入数据库呢的方法呢?

借助ApplicationListener接口

ApplicationListenerorg.springframework.context包下的接口,它能监听容器中发布的事件。这里不做过多介绍,具体可查看->理解 Spring ApplicationListener

实现ApplicationListener接口

  1. 新建ApiScan实现ApplicationListener<ApplicationReadyEvent>接口,重写void onApplicationEvent(ApplicationReadyEvent event)方法
@Slf4j
@Component // 注入到容器中
@ConditionalOnWebApplication // 判断是否为Web应用,如果不是则不注入
public class ApiScan implements ApplicationListener<ApplicationReadyEvent> {

    private static final AntPathMatcher pathMatch = new AntPathMatcher();

    private ExecutorService executorService = Executors.newFixedThreadPool(2);

    // @Autowired
    // private BaseApiService baseApiService;

    /**
     * 初始化方法
     *
     * @param event
     */
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {

        ConfigurableApplicationContext applicationContext = event.getApplicationContext();
        Environment env = applicationContext.getEnvironment();
        // 服务名称
        String serviceId = env.getProperty("spring.application.name", "application");
        // 所有接口映射
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        // 获取url与类和方法的对应信息
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
        List<RequestMatcher> permitAll = Lists.newArrayList();
        // Spring Security相关信息
        try {
            // 获取所有安全配置适配器
            Map<String, WebSecurityConfigurerAdapter> securityConfigurerAdapterMap = applicationContext.getBeansOfType(WebSecurityConfigurerAdapter.class);
            Iterator<Map.Entry<String, WebSecurityConfigurerAdapter>> iterable = securityConfigurerAdapterMap.entrySet().iterator();
            while (iterable.hasNext()) {
                WebSecurityConfigurerAdapter configurer = iterable.next().getValue();
                HttpSecurity httpSecurity = (HttpSecurity) ReflectionUtils.getFieldValue(configurer, "http");
                FilterSecurityInterceptor filterSecurityInterceptor = httpSecurity.getSharedObject(FilterSecurityInterceptor.class);
                FilterInvocationSecurityMetadataSource metadataSource = filterSecurityInterceptor.getSecurityMetadataSource();
                Map<RequestMatcher, Collection<ConfigAttribute>> requestMap = (Map) ReflectionUtils.getFieldValue(metadataSource, "requestMap");
                Iterator<Map.Entry<RequestMatcher, Collection<ConfigAttribute>>> requestIterable = requestMap.entrySet().iterator();
                while (requestIterable.hasNext()) {
                    Map.Entry<RequestMatcher, Collection<ConfigAttribute>> match = requestIterable.next();
                    if (match.getValue().toString().contains("permitAll")) {
                        permitAll.add(match.getKey());
                    }
                }
            }
        } catch (Exception e) {
            log.error("error:{}", e);
        }
        for (Map.Entry<RequestMappingInfo, HandlerMethod> m : map.entrySet()) {
            RequestMappingInfo info = m.getKey();
            HandlerMethod method = m.getValue();
            /*if (method.getMethod().getDeclaringClass().getAnnotation(RestController.class) == null) {
                // 只扫描RestController
                continue;
            }*/
            if (method.getMethodAnnotation(ApiIgnore.class) != null) {
                // 忽略的接口不扫描
                continue;
            }
            Set<MediaType> mediaTypeSet = info.getProducesCondition().getProducibleMediaTypes();
            for (MethodParameter params : method.getMethodParameters()) {
                if (params.hasParameterAnnotation(RequestBody.class)) {
                    mediaTypeSet.add(MediaType.APPLICATION_JSON_UTF8);
                    break;
                }
            }
            String mediaTypes = getMediaTypes(mediaTypeSet);
            // 请求类型
            RequestMethodsRequestCondition methodsCondition = info.getMethodsCondition();
            String methods = getMethods(methodsCondition.getMethods());
            // 请求路径
            PatternsRequestCondition p = info.getPatternsCondition();
            // 请求路径可能会有多个,用‘,’分开
            String urls = getUrls(p.getPatterns());
            // 类名
            String className = method.getMethod().getDeclaringClass().getName();
            // 方法名
            String methodName = method.getMethod().getName();
            String fullName = className + "." + methodName;
            // md5码,根据服务id,请求路径,方法生成,用于判断唯一性
            String md5 = EncryptUtils.md5Hex(serviceId + urls + methods);
            String name = "";
            String desc = "";
            // 是否需要安全认证 默认:1-是 0-否
            Integer isAuth = 1;
            // 匹配项目中.permitAll()配置
            for (String url : p.getPatterns()) {
                for (RequestMatcher requestMatcher : permitAll) {
                    if (requestMatcher instanceof AntPathRequestMatcher) {
                        AntPathRequestMatcher pathRequestMatcher = (AntPathRequestMatcher) requestMatcher;
                        if (pathMatch.match(pathRequestMatcher.getPattern(), url)) {
                            // 忽略验证
                            isAuth = 0;
                        }
                    }
                }
            }

            ApiOperation apiOperation = method.getMethodAnnotation(ApiOperation.class);
            if (apiOperation != null) {
                name = apiOperation.value();
                desc = apiOperation.notes();
            }
            name = StringUtils.isBlank(name) ? methodName : name;

            BaseApi baseApi = new BaseApi();
            baseApi.setApiCode(md5);
            baseApi.setApiName(name);
            baseApi.setApiDesc(desc);
            baseApi.setRequestMethod(methods);
            baseApi.setContentType(mediaTypes);
            baseApi.setServiceId(serviceId);
            baseApi.setPath(urls);
            baseApi.setIsAuth(isAuth);
            baseApi.setClassName(className);
            baseApi.setMethodName(methodName);
            // 多线程减轻压力
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 根据md5码从数据库查找接口
                        // BaseApi save = baseApiService.getApi(baseApi.getApiCode());
                        // 判断接口数据是否存在
                        // if (save == null) {
                            // 不存在则初始化并添加
                            // baseApi.setIsOpen(0);
                            // baseApi.setIsPersist(1);
                            // baseApiService.addApi(baseApi);
                        // } else {
                            // 存在则更新信息
                            // baseApi.setApiId(save.getApiId());
                            // baseApiService.updateApi(baseApi);
                        // }
                    } catch (Exception e) {
                        log.error("接口存储失败:{}", e);
                    }
                }
            });
        }
        log.info("ApplicationReadyEvent:[{}]", serviceId);

    }

    /**
     * 功能描述: 获取媒体类型,即content-type
     *
     * @param mediaTypes
     * @return java.lang.String
     */
    private String getMediaTypes(Set<MediaType> mediaTypes) {
        StringBuilder sbf = new StringBuilder();
        for (MediaType mediaType : mediaTypes) {
            sbf.append(mediaType.toString()).append(",");
        }
        if (mediaTypes.size() > 0) {
            sbf.deleteCharAt(sbf.length() - 1);
        }
        return sbf.toString();
    }

    /** 
     * 功能描述: 获取请求方法
     *
     * @param requestMethods
     * @return java.lang.String
     */
    private String getMethods(Set<RequestMethod> requestMethods) {
        StringBuilder sbf = new StringBuilder();
        for (RequestMethod requestMethod : requestMethods) {
            sbf.append(requestMethod.toString()).append(",");
        }
        if (requestMethods.size() > 0) {
            sbf.deleteCharAt(sbf.length() - 1);
        }
        return sbf.toString();
    }

    /** 
     * 功能描述: 获取请求路径列表,使用','分割
     *
     * @param urls
     * @return java.lang.String
     */
    private String getUrls(Set<String> urls) {
        StringBuilder sbf = new StringBuilder();
        for (String url : urls) {
            sbf.append(url).append(",");
        }
        if (urls.size() > 0) {
            sbf.deleteCharAt(sbf.length() - 1);
        }
        return sbf.toString();
    }


}

  1. 接口数据库映射
public class BaseApi implements Serializable {
    /**
     * 接口ID
     */
    private Long apiId;

    /**
     * 接口编码
     */
    private String apiCode;

    /**
     * 接口名称
     */
    private String apiName;

    /**
     * 接口分类:default-默认分类
     */
    private String apiCategory;

    /**
     * 资源描述
     */
    private String apiDesc;

    /**
     * 请求方式
     */
    private String requestMethod;

    /**
     * 响应类型
     */
    private String contentType;

    /**
     * 服务ID
     */
    private String serviceId;

    /**
     * 请求路径
     */
    private String path;

    /**
     * 优先级
     */
    private Integer priority;

    /**
     * 状态:0-无效 1-有效
     */
    private Integer status;

    /**
     * 保留数据0-否 1-是 不允许删除
     */
    private Integer isPersist;

    /**
     * 是否需要认证: 0-无认证 1-身份认证 默认:1
     */
    private Integer isAuth;

    /**
     * 是否公开: 0-内部的 1-公开的
     */
    private Integer isOpen;

    /**
     * 类名
     */
    private String className;

    /**
     * 方法名
     */
    private String methodName;

    /**
     * 创建时间
     */
    private Date createTime;


    /**
     * 更新时间
     */
    private Date updateTime;

}

别忘了注入,否则不生效

  1. Spring Boot通过@Component注入,即上面所示
  2. Spring Boot通过@Configuration@Bean注入,更加灵活
  3. Spring可通过<bean>标签注入

问题

  1. 上述涵盖了一个接口的所有基本属性,可根据项目情况添加修改或删除
  2. 如没有SwaggerSpring Security也可删除相应内容
  3. 用于判断接口唯一性的md5码根据服务id(及项目名)请求路径列表请求方法列表生成,也可根据实际情况选择生成

相关文章

网友评论

      本文标题:Spring Boot 接口扫描

      本文链接:https://www.haomeiwen.com/subject/jpcegctx.html