利用SpringBoot项目做一个Mock挡板;基于事件发布动态自定义URL和响应报文

发布时间 2023-06-06 22:05:00作者: 白嫖老郭

导入SpringbootWEb依赖

  <!--web项目驱动-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring-boot-start-version}</version>
        </dependency>

        <!--redis缓存-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

定义一个Controller,指定RESTFUL接口映射

@RestController
@SwaggerScanClass
public class TestController {


    @Autowired
    private RedisTemplate redisTemplate;


    @Value("${mp.event.aeskey:4b57e89bac82a797}")
    private String aesKey;

    private Map<String, Long> mockUrlTimeOut = new ConcurrentHashMap<>();


    /**
     * Description: 使用 Spring Framework 的事件发布机制发布应用程序事件。
     */
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;
	
	
    @PostMapping("/addMockInfo")
    @ApiOperation("添加Mock信息")
    public Resp addMockInfo(@RequestParam("url") String url, @RequestBody Object body) {
        String redisSaveKey = AES.encrypt(url, aesKey);
        Object returnBody = redisTemplate.opsForValue().get(redisSaveKey);
        if (returnBody != null) {
            redisTemplate.opsForValue().setIfAbsent(redisSaveKey, body, 48, TimeUnit.HOURS);
            return Resp.Ok("url 响应报文更新成功" + url);
        }
        applicationEventPublisher.publishEvent(new AddMockInfoEvent(this, Clock.systemDefaultZone(), url, body));
        return Resp.Ok("url添加Mock成功" + url);
    }

    @PostMapping("/addMockInfoTimeOut")
    @ApiOperation("添加Mock信息Url的阻塞时间")
    public Resp addMockInfoTimeOut(@RequestParam("url") String url, @RequestParam("timeOut") Long timeOut) {
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(url, timeOut, 48, TimeUnit.HOURS);
        mockUrlTimeOut.put(url, timeOut);
        return aBoolean ? Resp.Ok("url添加Mock成功" + timeOut) : Resp.error("添加Mock信息Url的阻塞时间失败");
    }


    @ApiOperation("Mock动态回调Request")
    public Resp addMockInfoByRequest() {
        ServletRequestAttributes httpRequest = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = httpRequest.getRequest();
        String requestURI = request.getRequestURI();
        String redisSaveKey = AES.encrypt(requestURI, aesKey);
        Object returnBody = redisTemplate.opsForValue().get(redisSaveKey);
        if (returnBody == null) {
            return Resp.error("Mock动态回调Request 没找到报文");
        }
        Long timeOut = mockUrlTimeOut.get(requestURI);
        if (timeOut == null || timeOut == 0) {
            Object redisSaveTimeOut = redisTemplate.opsForValue().get(requestURI);
            if (redisSaveTimeOut != null) {
                timeOut = (Long) redisSaveTimeOut;
                mockUrlTimeOut.put(requestURI, timeOut);
            }
        }
        try {
            if (timeOut == null || timeOut == 0) {
                return Resp.Ok(returnBody);
            }
            Thread.sleep(timeOut);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return Resp.Ok(returnBody);

    }

}

定义一个事件 AddMockInfoEvent

/**
 * @description: 扩展来创建调用的自定义事件:extends ApplicationEvent
 * @author: GuoTong
 * @createTime: 2023-06-06 20:03
 * @since JDK 1.8 OR 11
 **/
public class AddMockInfoEvent extends ApplicationEvent {

    /**
     * Description: Mock使用的地址
     */
    private String url;

    /**
     * Description: Mock使用的地址的响应报文
     */
    private Object context;


    public AddMockInfoEvent(Object source) {
        super(source);
    }

    public AddMockInfoEvent(Object source, Clock clock) {
        super(source, clock);
    }

    public AddMockInfoEvent(Object source, String url, Object context) {
        super(source);
        this.url = url;
        this.context = context;
    }

    public AddMockInfoEvent(Object source, Clock clock, String url, Object context) {
        super(source, clock);
        this.url = url;
        this.context = context;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public Object getContext() {
        return context;
    }

    public void setContext(Object context) {
        this.context = context;
    }
}

监听事件的发生:调用了添加动态URL的Mock接口时发生

import com.baomidou.mybatisplus.core.toolkit.AES;
import com.gton.controller.TestController;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.EventListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.UrlPathHelper;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @description: 使用@EventListener注解;启用注解驱动的配置时,不需要其他配置。我们的方法可以监听多个事件,
 * @author: GuoTong
 * @createTime: 2023-06-06 20:05
 * @since JDK 1.8 OR 11
 **/
@Component
@SuppressWarnings("unchecked")
public class SystemDIYEventPushListener {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * Description: Controller AA
     */
    @Autowired
    @Lazy
    private TestController testController;

    @Autowired
    private RequestMappingHandlerMapping requestMappingHandlerMapping;


    @Value("${mp.event.aeskey:4b57e89bac82a797}")
    private String aesKey;


    private String mockUrls = "MockUrls";

    /**
     * Description:  启用注解驱动监听事件,AddMockInfoEvent
     *
     * @param event
     * @author: GuoTong
     * @date: 2023-06-06 20:10:52
     * @return:void
     */
    @EventListener
    public void handleUserRemovedEvent(AddMockInfoEvent event) {
        String url = event.getUrl();
        String redisSaveKeyUrl = AES.encrypt(url, aesKey);
        Object context = event.getContext();
        redisTemplate.opsForValue().setIfAbsent(redisSaveKeyUrl, context, 48, TimeUnit.HOURS);
        // 获取所有的Mock的URL
        Set<String> mockUrlsVar = redisTemplate.opsForSet().members(mockUrls);
        if (CollectionUtils.isEmpty(mockUrlsVar)) {
            // 准备好加入动态就接口的URL的容器
            mockUrlsVar = new HashSet<>();
        } else {
            // 动态添加接口调用| 找出注册的,
            RequestMappingInfo build = RequestMappingInfo.paths(mockUrlsVar.stream().toArray(String[]::new)).build();
            // 准备注销原始接口
            requestMappingHandlerMapping.unregisterMapping(build);
        }
        try {
            // 加入新的URL
            mockUrlsVar.add(url);
            // 往redis里面注册URL
            redisTemplate.opsForSet().add(mockUrls, url);
            // 设置过期时间
            redisTemplate.expire(mockUrls, 48, TimeUnit.HOURS);
            // 动态添加接口调用
            RequestMappingInfo requestMappingInfo =
                    RequestMappingInfo.
                            paths(mockUrlsVar.stream().toArray(String[]::new))
                            .build();
            // 指定接口回调的方法
            Method addMockInfoByGetMethod = TestController.class.getDeclaredMethod("addMockInfoByRequest");
            // 注册新的接口
            requestMappingHandlerMapping.
                    registerMapping(requestMappingInfo,
                            testController,
                            addMockInfoByGetMethod);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

    }

}

测试

image

响应

image

试验新的Mock挡板是否动态添加成功

image

添加延时响应

image

查看效果