flush()方法_支持URI、方法、RPC的Mock平台

5300d3af58cee0ad8a8c592d0d8df71d.png

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

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

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

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

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

下面是具体代码过程:

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

@Configurationpublic class MockContext implements ApplicationContextAware {    private static final boolean online = !EnvUtils.isOffline();    private static FastThreadLocal 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 PairgetMockConfigDO(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平台中获取对应的配置,有配置就返回,没有的话就继续正常流程。

@Configurationpublic 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注解,生成代理。

@Configurationpublic 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 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 Invokerjoin(Directory directory) throws RpcException {        return new PH3636MockClusterInvoker(directory, this.cluster.join(directory));    }}

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

public class PH3636MockClusterInvoker<T> implements Invoker<T> {    private final Directory directory;    private final Invoker invoker;    public PH3636MockClusterInvoker(Directory directory, Invoker 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 ClassgetInterface() {        return directory.getInterface();    }    @Override    public Result invoke(Invocation invocation) throws RpcException {        if (MockContext.online()) {            return invoker.invoke(invocation);        }        Pair 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()));    }}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值