zoukankan      html  css  js  c++  java
  • 项目集成swagger,并暴露指定端点给swagger

    项目集成swagger

    一:思考:

    1.swagger解决了我们什么问题?

    传统开发中,我们在开发完成一个接口后,为了测试我们的接口,我们通常会编写单元测试,以测试我们的接口的可用性,或者用postman等第三方接口测试工具进行测试,但是这也有一些弊端,我们需要为接口准备测试数据,但有时,数据量较大时,前期准备工作可能相当耗时

    2.除了用于接口测试,我们还能利用它做些什么有意义的事呢?

    在项目的开发过程中,我们通常会用到大量的定时任务;
    a.分布式环境:quartz
    b.单机环境:有spring提供的schedule 或者用线程(局限性较大)
    因为公司没有引入调度中心,没有一个统一的地方对项目中的定时任务进行统一的管理,所以需要每个项目集成自己的调度框架

    例如:我现在有一个需求,是每天早上7点的时候,对昨日告警发送一个统计日报,最简单的做法是引入@schedule(cron="0 0 7 * * ?")

    我们经过本地的辛苦开发测试,一切正常,可是当我们将项目发布到QA环境,发现自己无法对这个定时任务的代码进行有效测试,如果需要观察我们甚至需要等到明天早上7点

    问题的根源在于 Schedule 方法基于时间触发,如果使其暴露出来,能够手工触发,将大大增加灵活性。

    我通过对其定制,可以暴露使用到了schedule的地方,并提供手动触发,

    二:集成swagger

    1.引入依赖

    <dependency>
           <groupId>io.springfox</groupId>
           <artifactId>springfox-swagger2</artifactId>
           <version>2.7.0</version>
       </dependency>
       <dependency>
           <groupId>io.springfox</groupId>
           <artifactId>springfox-swagger-ui</artifactId>
           <version>2.7.0</version>
       </dependency>

    2.启用swagger

    在配置类上加上注解@EnableSwagger2

    三:为定时调度任务schedule暴露接口

    #### 1.HandlerMapping 扩展

    新建 ScheduleMethodHandlerMapping 继承自 RequestMappingInfoHandlerMapping ,实现 HandlerMapping 接口。


    public class ScheduleMethodHandlerMapping
            extends RequestMappingInfoHandlerMapping
            implements HandlerMapping{
        /**
         * 包含@Scheduled注解方法的bean为handler
         * @param beanType
         * @return
         */
        @Override
        protected boolean isHandler(Class<?> beanType) {
            return Arrays.asList(MethodUtil.getMethods(beanType)).stream()
                        .anyMatch(method -> method.getAnnotation(Scheduled.class) != null);
        }
    ​
        /**
         * 处理@Scheduled标注方法,生成对于的RequestMappingInfo
         * @param method
         * @param handlerType
         * @return
         */
        @Override
        protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
            if (method.getAnnotation(Scheduled.class) == null){
                return null;
            }
            return RequestMappingInfo.paths("/schedule/" + handlerType.getSimpleName() + "/" + method.getName())
                    .methods(RequestMethod.GET)
                    .build();
        }
    ​
        /**
         * 提高该HandlerMapping在HandlerMapping中的顺序,使其能优先处理
         * @return
         */
        @Override
        public int getOrder() {
            return 1;
        }
    }
     

    2.HandlerAdapter 扩展

    andlerAdapter 扩展主要解决 Schedule 方法的执行问题。

    带有 @Scheduled 注解的 Method 方法,需要单独的 HandlerAdapter 进行处理,并将处理结果通过 json 方式返回。

    新建 ScheduleMethodHandlerAdapter

    ScheduleMethodHandlerAdapter 继承自 AbstractHandlerMethodAdapter ,实现 HandlerAdapter 接口。

    public class ScheduleMethodHandlerAdapter
            extends AbstractHandlerMethodAdapter
            implements HandlerAdapter {
    ​
        /**
         * 支持使用@Scheduled标准的handlerMethod对象
         * @param handlerMethod
         * @return
         */
        @Override
        protected boolean supportsInternal(HandlerMethod handlerMethod) {
            return handlerMethod.hasMethodAnnotation(Scheduled.class);
        }
    ​
        /**
         * 调用HandlerMethod方法,并返回json视图
         * @param request
         * @param response
         * @param handlerMethod
         * @return
         * @throws Exception
         */
        @Override
        protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    ​
            Map<String, Object> data = Maps.newHashMap();
            data.put("service", handlerMethod.getBeanType().getSimpleName());
            data.put("method", handlerMethod.getMethod().getName());
            Stopwatch stopwatch = Stopwatch.createStarted();
            try {
                // 调用HandleMethod方法
                handlerMethod.getMethod().invoke(handlerMethod.getBean());
                stopwatch.stop();
                data.put("result", "success");
            }catch (Exception e){
                data.put("result", "error");
                data.put("exception", e.toString());
            }finally {
                data.put("cost", stopwatch.elapsed(TimeUnit.MILLISECONDS) + "ms");
    ​
            }
            // 返回Json视图
            return new ModelAndView(new MappingJackson2JsonView(), data);
        }
    ​
        @Override
        protected long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod) {
            return 0;
        }
    ​
        /**
         * 提高在HandlerAdapter列表中的顺序,以优先处理
         * @return
         */
        @Override
        public int getOrder() {
            return 0;
        }
    }

    3.与 Spring Boot 集成

    ScheduleMethodHandlerMappingScheduleMethodHandlerAdapter 开发完成后,我们需要将其与 Spring Boot 进行集成,并通过参数控制是否启用。

    新建 ScheduleEndpointConfiguration

    新建 ScheduleEndpointConfiguration 配置类,对 ScheduleMethodHandlerMapping 和 ScheduleMethodHandlerAdapter 进行注册。

    /**
     * 根据配置属性控制是否启用该配置。
     * <br />
     * spring.schedule.endpoint.enabled = true 启用配置 <br />
     * spring.schedule.endpoint.enabled = false 禁用配置 <br />
     */
    @ConditionalOnProperty(prefix = "spring.schedule.endpoint", value = "enabled", havingValue = "true", matchIfMissing = false)
    @Configuration
    public class ScheduleEndpointConfiguration {
        /**
         * 注册 ScheduleMethodHandlerAdapter Bean
         * @return
         */
        @Bean
        public ScheduleMethodHandlerAdapter scheduleMethodHandlerAdapter(){
            return new ScheduleMethodHandlerAdapter();
        }
    ​
        /**
         * 注册ScheduleMethodHandlerMapping Bean
         * @return
         */
        @Bean
        public ScheduleMethodHandlerMapping scheduleMethodHandlerMapping(){
            return new ScheduleMethodHandlerMapping();
        }
    }

    在 resources 目录新建 META-INF 目录,并新建 spring.factories 文件,文件内声明 ScheduleEndpointConfiguration 为自动配置项。

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.fudax.sqcs.common.schedule.ScheduleEndpointConfiguration
    
    

     

    四:集成效果

    访问接口:/swagger-ui.html

     

    这谁ChangeController下的所有接口

    查看暴露的定时任务接口

    在类MessageServer我有两个定时任务:pushAll()方法 clear()方法

     

    点击 try it out即可立即触发

     五:安全

    这种端点暴露只能在本地测试,开发环境使用,上到生产应该是严格禁止操作的

    所以,我写环境校验,以保证生产的安全性

    package com.fudax.sqcs.common.schedule.condition;
    
    import com.fudax.sqcs.common.constants.Env;
    import com.google.common.collect.Sets;
    import org.springframework.context.annotation.Condition;
    import org.springframework.context.annotation.ConditionContext;
    import org.springframework.core.type.AnnotatedTypeMetadata;
    
    import java.util.Map;
    
    public class OnEnvironmentCondition implements Condition {
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnEnvironment.class.getName());
            Env[] envs = (Env[]) attributes.get("values");
            Env currentEnv = Env.getCurrentEnv();
            return Sets.newHashSet(envs).contains(currentEnv);
        }
    }
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Documented
    @Conditional(OnEnvironmentCondition.class)
    public @interface ConditionalOnEnvironment {
    
        Env[] values() default {Env.LOCAL};
    }
  • 相关阅读:
    关于MySQL中ALTER TABLE 的命令用法——SQL
    replace函数——SQL
    SQL构造一个触发器
    【视频转换】监控视频DAV转mp4
    【pyqt5+opencv】如何将大量图片合成一张图
    【OpenCV+pyqt5】视频抽帧裁剪与图片转视频
    【Caffe】生成数据之修改label
    【labelme】标注工具Trick
    【OpenCV+pyqt5】视频抽帧相关操作
    【pyqt5】Pyinstaller封装OpenCV异常
  • 原文地址:https://www.cnblogs.com/clovejava/p/11134750.html
Copyright © 2011-2022 走看看