支持URI、方法、RPC的Mock平台

mock平台支持三种方式(URI、方法、RPC)

 

URI:当前端需要根据接口的各种状态来进行不同的展示时,而后端还没有写完完整的代码时,可以在项目开发初期通过该方式进行自定义设置接口返回结果。

 

方法:当后端需要某个方法返回某些特定的值时,而这时真实的情况又很难构造这个值时,就可以采用该方式。

 

RPC:当后端要与别的服务进行合作时,而别的服务方接口提供较慢或有问题时,可以采用该方式进行自测。

 

mock平台的数据存储可以是数据库或者其他平台等等,主要存储对应关系,key可以为URI或方法的全限定名和参数,value为具体的json结构,为了防止影响别人,用用户ID进行区分,如用户ID和key组成唯一键,这些数据是从控制台进行设置。

 

下面是具体代码过程:

模拟上下文配置,包括当前所处环境,spring容器,存储当前用户登陆状态的ThreadLocal,类方法参数拼接key的方法,获取mock平台数据的方法,平台数据存储采用数据库,以及自动生成指定类方法所代表的key的方法,方便使用者在控制台进行key-value添加。

@Configuration
public class MockContext implements ApplicationContextAware {

    private static final boolean online = !EnvUtils.isOffline();
    private static FastThreadLocal<LoginUser> fastThreadLocal = new FastThreadLocal<>();
    private static ApplicationContext instance;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        instance = applicationContext;
    }

    public static ApplicationContext getInstance() {
        return instance;
    }

    /**
     * 是否是线上环境
     */
    public static boolean online() {
        return online;
    }

    public static void set(LoginUser loginUser) {
        fastThreadLocal.set(loginUser);
    }

    public static LoginUser get() {
        return fastThreadLocal.get();
    }

    public static void remove() {
        fastThreadLocal.remove();
    }

    /**
     * 唯一url(类,方法,参数拼接)
     */
    public static String getUrl(String className, String methodName, Class<?>[] parameterTypes) {
        StringBuilder sb = new StringBuilder(className);
        sb.append(".").append(methodName);
        StringJoiner stringJoiner = new StringJoiner(",", "(", ")");
        if (parameterTypes != null) {
            for (Class<?> parameterType : parameterTypes) {
                if (parameterType != null) {
                    stringJoiner.add(parameterType.getName());
                }
            }
        }
        return sb.append(stringJoiner.toString()).toString();
    }

    /**
     * 数据库配置
     * 可以替换为http形式等等
     */
    public static MockDO getMockConfigDO(String url) {
        LoginUser loginUser = MockContext.get();
        if (loginUser == null) {
            return null;
        }
        return MockContext.getInstance().getBean(MockService.class).loadByUserIdUrl(loginUser.getUserId(), url);
    }

    public static Pair<String, MockDO> getMockConfigDO(String className, String methodName, Class<?>[] parameterTypes) {
        String url = getUrl(className, methodName, parameterTypes);
        return Pair.of(url, getMockConfigDO(url));
    }

    public static boolean notContent(MockDO mockDO) {
        return mockDO == null;
    }

    public static boolean notReturnType(Class<?> returnType) {
        return returnType == null || returnType == void.class || returnType == Void.class;
    }

    /**
     * 获取唯一key
     * 第一个参数为类的全限定名(必填),第二个参数为该类的方法名(选填)
     * 控制台方式:用英文逗号分割
     */
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String val;
        do {
            System.out.print("请输入:");
            val = input.next();
            print(val.split(","));
        } while (!"exit".equals(val));
        System.out.println("程序退出!");
        input.close();
    }

    public static void print(String[] args) {
        if (args == null || args.length < 1 || "exit".equals(args[0])) {
            return;
        }
        Class<?> c;
        try {
            c = Class.forName(args[0]);
        } catch (ClassNotFoundException ignore) {
            System.out.println("类不存在!" + ignore.fillInStackTrace());
            return;
        }
        Method[] methods = c.getMethods();
        String m = args.length > 1 ? args[1] : null;
        for (Method method : methods) {
            if (StringUtils.isBlank(m)) {
                System.out.println(getUrl(c.getName(), method.getName(), method.getParameterTypes()));
            } else if (method.getName().equals(m)) {
                System.out.println(getUrl(c.getName(), method.getName(), method.getParameterTypes()));
            }
        }
    }
}

针对URI形式的拦截器,判断环境,设置登陆状态,根据uri从mock平台中获取对应的配置,有配置就返回,没有的话就继续正常流程。


@Configuration
public class PH3636MockInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (MockContext.online()) {
            return true;
        }
        if (!(handler instanceof HandlerMethod)) {
            return false;
        }

        LoginUser loginUser = (LoginUser) request.getAttribute(Constants.LOGIN_USER);
        if (loginUser == null) {
            return true;
        }
        MockContext.set(loginUser);
        String uri = request.getRequestURI();
        MockDO mockDO = MockContext.getMockConfigDO(uri);
        if (MockContext.notContent(mockDO)) {
            return true;
        }
        response.setContentType("application/json;charset=UTF-8");
        response.setHeader("Cache-Control", "no-cache");
        PrintWriter pw = response.getWriter();
        try {
            pw.write(mockDO.getContent());
        } finally {
            pw.flush();
            pw.close();
        }
        return false;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                                @Nullable Exception ex) throws Exception {
        if (MockContext.online()) {
            return;
        }
        MockContext.remove();
    }
}

针对方法形式的代理,判断环境,判断当前bean的方法中有没有mock注解,生成代理。

@Configuration
public class PH3636MockBeanPostProcessor implements BeanPostProcessor {

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (MockContext.online()) {
            return bean;
        }
        Mock mock = null;
        for (Method method : bean.getClass().getMethods()) {
            mock = method.getAnnotation(Mock.class);
            if (mock != null) {
                break;
            }
        }
        if (mock == null) {
            return bean;
        }
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setProxyTargetClass(true);
        proxyFactory.setExposeProxy(true);
        proxyFactory.setTarget(bean);
        proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new PH3636MockMethodInterceptor()));
        return proxyFactory.getProxy();
    }
}

判断当前方法是否有mock注解,组装key,获取配置,转化json为对应的对象

public class PH3636MockMethodInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Mock mock = invocation.getMethod().getAnnotation(Mock.class);
        if (mock == null) {
            return invocation.proceed();
        }
        Pair<String, MockDO> pair = MockContext.getMockConfigDO(invocation.getThis().getClass().getName(), invocation.getMethod().getName(), invocation.getMethod().getParameterTypes());
        if (MockContext.notContent(pair.getRight())) {
            return invocation.proceed();
        }
        Class<?> returnType = invocation.getMethod().getReturnType();
        if (MockContext.notReturnType(returnType)) {
            return invocation.proceed();
        }
        return JsonUtil.fromJsonToType(pair.getRight().getContent(), invocation.getMethod().getGenericReturnType());
    }
}

针对RPC(DUBBO)形式的Cluster扩展wrapper形式,在无提供者的情况下正常起作用,不需要额外修改,其他的一些形式都各有弊端,比如filter形式必须要有提供者,ProxyFactory代理需要额外配置,ClusterInterceptor要求版本较高等等。

public class PH3636MockClusterWrapper implements Cluster {

    private Cluster cluster;

    public PH3636MockClusterWrapper(Cluster cluster) {
        this.cluster = cluster;
    }

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new PH3636MockClusterInvoker<T>(directory, this.cluster.join(directory));
    }
}

判断环境,组装key,获取配置,转化json为对应的对象


public class PH3636MockClusterInvoker<T> implements Invoker<T> {

    private final Directory<T> directory;

    private final Invoker<T> invoker;

    public PH3636MockClusterInvoker(Directory<T> directory, Invoker<T> invoker) {
        this.directory = directory;
        this.invoker = invoker;
    }

    @Override
    public URL getUrl() {
        return directory.getUrl();
    }

    @Override
    public boolean isAvailable() {
        return directory.isAvailable();
    }

    @Override
    public void destroy() {
        this.invoker.destroy();
    }

    @Override
    public Class<T> getInterface() {
        return directory.getInterface();
    }

    @Override
    public Result invoke(Invocation invocation) throws RpcException {
        if (MockContext.online()) {
            return invoker.invoke(invocation);
        }
        Pair<String, MockDO> pair = MockContext.getMockConfigDO(invoker.getInterface().getName(), invocation.getMethodName(), invocation.getParameterTypes());
        if (MockContext.notContent(pair.getRight())) {
            return invoker.invoke(invocation);
        }
        Method method;
        try {
            method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());
        } catch (NoSuchMethodException e) {
            return invoker.invoke(invocation);
        }
        if (method == null) {
            return invoker.invoke(invocation);
        }
        Class<?> returnType = method.getReturnType();
        if (MockContext.notReturnType(returnType)) {
            return invoker.invoke(invocation);
        }
        return new RpcResult((Object) JsonUtil.fromJsonToType(pair.getRight().getContent(), method.getGenericReturnType()));
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值