diff --git a/repeater-plugin-api/src/main/java/com/alibaba/jvm/sandbox/repeater/plugin/domain/InvokeType.java b/repeater-plugin-api/src/main/java/com/alibaba/jvm/sandbox/repeater/plugin/domain/InvokeType.java index 8e021ce6..7696e5ca 100644 --- a/repeater-plugin-api/src/main/java/com/alibaba/jvm/sandbox/repeater/plugin/domain/InvokeType.java +++ b/repeater-plugin-api/src/main/java/com/alibaba/jvm/sandbox/repeater/plugin/domain/InvokeType.java @@ -37,6 +37,10 @@ public class InvokeType implements java.io.Serializable { public static InvokeType CAFFEINE_CACHE = new InvokeType("caffeine-cache"); + public static InvokeType MYBATISPLUS = new InvokeType("mybatis-plus"); + + public static InvokeType OPENFEIGN = new InvokeType("openfeign"); + private String name; public InvokeType(String name) { diff --git a/repeater-plugins/mybatis-plus-plugin/pom.xml b/repeater-plugins/mybatis-plus-plugin/pom.xml new file mode 100644 index 00000000..8c7e8e82 --- /dev/null +++ b/repeater-plugins/mybatis-plus-plugin/pom.xml @@ -0,0 +1,37 @@ + + + + repeater-plugins + com.alibaba.jvm.sandbox + 1.0.0-SNAPSHOT + + 4.0.0 + + mybatis-plus-plugin + + + ${project.name}-${project.version} + + + org.apache.maven.plugins + maven-assembly-plugin + + + + attached + + package + + + jar-with-dependencies + + + + + + + + + \ No newline at end of file diff --git a/repeater-plugins/mybatis-plus-plugin/src/main/java/com/alibaba/jvm/sandbox/repeater/plugin/mybatisplus/MybatisPlusPlugin.java b/repeater-plugins/mybatis-plus-plugin/src/main/java/com/alibaba/jvm/sandbox/repeater/plugin/mybatisplus/MybatisPlusPlugin.java new file mode 100644 index 00000000..7ef6878e --- /dev/null +++ b/repeater-plugins/mybatis-plus-plugin/src/main/java/com/alibaba/jvm/sandbox/repeater/plugin/mybatisplus/MybatisPlusPlugin.java @@ -0,0 +1,53 @@ +package com.alibaba.jvm.sandbox.repeater.plugin.mybatisplus; + +import com.alibaba.jvm.sandbox.api.event.Event.Type; +import com.alibaba.jvm.sandbox.repeater.plugin.api.InvocationProcessor; +import com.alibaba.jvm.sandbox.repeater.plugin.core.impl.AbstractInvokePluginAdapter; +import com.alibaba.jvm.sandbox.repeater.plugin.core.model.EnhanceModel; +import com.alibaba.jvm.sandbox.repeater.plugin.domain.InvokeType; +import com.alibaba.jvm.sandbox.repeater.plugin.spi.InvokePlugin; +import com.google.common.collect.Lists; +import org.kohsuke.MetaInfServices; + +import java.util.List; + +/** + * @Author: luwenrong + * @Title: mybatisplus 插件 + * @Description: {@code com.baomidou.mybatisplus.core.override}包下面的MybatisMapperMethod实现类 + * @Date: 2021/10/26 + */ +@MetaInfServices(InvokePlugin.class) +public class MybatisPlusPlugin extends AbstractInvokePluginAdapter { + + @Override + protected List getEnhanceModels() { + EnhanceModel em = EnhanceModel.builder() + .classPattern("com.baomidou.mybatisplus.core.override.MybatisMapperMethod") + .methodPatterns(EnhanceModel.MethodPattern.transform("execute")) + .watchTypes(Type.BEFORE, Type.RETURN, Type.THROWS) + .build(); + return Lists.newArrayList(em); + } + + @Override + protected InvocationProcessor getInvocationProcessor() { + return new MybatisPlusProcessor(getType()); + } + + @Override + public InvokeType getType() { + return InvokeType.MYBATISPLUS; + } + + @Override + public String identity() { + return "mybatis-plus"; + } + + @Override + public boolean isEntrance() { + return false; + } + +} diff --git a/repeater-plugins/mybatis-plus-plugin/src/main/java/com/alibaba/jvm/sandbox/repeater/plugin/mybatisplus/MybatisPlusProcessor.java b/repeater-plugins/mybatis-plus-plugin/src/main/java/com/alibaba/jvm/sandbox/repeater/plugin/mybatisplus/MybatisPlusProcessor.java new file mode 100644 index 00000000..ac68fc69 --- /dev/null +++ b/repeater-plugins/mybatis-plus-plugin/src/main/java/com/alibaba/jvm/sandbox/repeater/plugin/mybatisplus/MybatisPlusProcessor.java @@ -0,0 +1,47 @@ +package com.alibaba.jvm.sandbox.repeater.plugin.mybatisplus; + +import com.alibaba.jvm.sandbox.api.event.BeforeEvent; +import com.alibaba.jvm.sandbox.repeater.plugin.core.impl.api.DefaultInvocationProcessor; +import com.alibaba.jvm.sandbox.repeater.plugin.domain.Identity; +import com.alibaba.jvm.sandbox.repeater.plugin.domain.InvokeType; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.commons.lang3.reflect.MethodUtils; + +import java.lang.reflect.Field; +import java.util.HashMap; + +/** + * @Author: luwenrong + * @Title: MybatisPlusProcessor + * @Date: 2021/10/26 + */ +class MybatisPlusProcessor extends DefaultInvocationProcessor { + + MybatisPlusProcessor(InvokeType type) { + super(type); + } + + @Override + public Identity assembleIdentity(BeforeEvent event) { + Object mapperMethod = event.target; + Field field = FieldUtils.getDeclaredField(mapperMethod.getClass(), "command", true); + if (field == null) { + return new Identity(InvokeType.MYBATISPLUS.name(), "Unknown", "Unknown", new HashMap(1)); + } + try { + Object command = field.get(mapperMethod); + Object name = MethodUtils.invokeMethod(command, "getName"); + Object type = MethodUtils.invokeMethod(command, "getType"); + return new Identity(InvokeType.MYBATISPLUS.name(), type.toString(), name.toString(), new HashMap(1)); + } catch (Exception e) { + return new Identity(InvokeType.MYBATISPLUS.name(), "Unknown", "Unknown", new HashMap(1)); + } + } + + @Override + public Object[] assembleRequest(BeforeEvent event) { + // MybatisMapperMethod#execute(SqlSession sqlSession, Object[] args) + return new Object[]{event.argumentArray[1]}; + } + +} diff --git a/repeater-plugins/openfeign-plugin/pom.xml b/repeater-plugins/openfeign-plugin/pom.xml new file mode 100644 index 00000000..9a05445f --- /dev/null +++ b/repeater-plugins/openfeign-plugin/pom.xml @@ -0,0 +1,37 @@ + + + + repeater-plugins + com.alibaba.jvm.sandbox + 1.0.0-SNAPSHOT + + 4.0.0 + + openfeign-plugin + + + ${project.name}-${project.version} + + + org.apache.maven.plugins + maven-assembly-plugin + + + + attached + + package + + + jar-with-dependencies + + + + + + + + + \ No newline at end of file diff --git a/repeater-plugins/openfeign-plugin/src/main/java/com/alibaba/jvm/sandbox/repeater/plugin/openfeign/OpenFeignPlugin.java b/repeater-plugins/openfeign-plugin/src/main/java/com/alibaba/jvm/sandbox/repeater/plugin/openfeign/OpenFeignPlugin.java new file mode 100644 index 00000000..c292d185 --- /dev/null +++ b/repeater-plugins/openfeign-plugin/src/main/java/com/alibaba/jvm/sandbox/repeater/plugin/openfeign/OpenFeignPlugin.java @@ -0,0 +1,51 @@ +package com.alibaba.jvm.sandbox.repeater.plugin.openfeign; + +import com.alibaba.jvm.sandbox.api.event.Event; +import com.alibaba.jvm.sandbox.repeater.plugin.api.InvocationProcessor; +import com.alibaba.jvm.sandbox.repeater.plugin.core.impl.AbstractInvokePluginAdapter; +import com.alibaba.jvm.sandbox.repeater.plugin.core.model.EnhanceModel; +import com.alibaba.jvm.sandbox.repeater.plugin.domain.InvokeType; +import com.alibaba.jvm.sandbox.repeater.plugin.spi.InvokePlugin; +import com.google.common.collect.Lists; +import org.kohsuke.MetaInfServices; + +import java.util.List; + +/** + * @Author: luwenrong + * @Title: openfeign 插件 + * @Description: + * @Date: 2021/11/30 + */ +@MetaInfServices(InvokePlugin.class) +public class OpenFeignPlugin extends AbstractInvokePluginAdapter { + + @Override + protected List getEnhanceModels() { + EnhanceModel enhanceModel = EnhanceModel.builder().classPattern("feign.Client$Default") + .methodPatterns(EnhanceModel.MethodPattern.transform("execute")) + .watchTypes(Event.Type.BEFORE, Event.Type.RETURN, Event.Type.THROWS) + .build(); + return Lists.newArrayList(enhanceModel); + } + + @Override + protected InvocationProcessor getInvocationProcessor() { + return new OpenFeignProcessor(getType()); + } + + @Override + public InvokeType getType() { + return InvokeType.OPENFEIGN; + } + + @Override + public String identity() { + return "openfeign"; + } + + @Override + public boolean isEntrance() { + return false; + } +} diff --git a/repeater-plugins/openfeign-plugin/src/main/java/com/alibaba/jvm/sandbox/repeater/plugin/openfeign/OpenFeignProcessor.java b/repeater-plugins/openfeign-plugin/src/main/java/com/alibaba/jvm/sandbox/repeater/plugin/openfeign/OpenFeignProcessor.java new file mode 100644 index 00000000..ca5d6605 --- /dev/null +++ b/repeater-plugins/openfeign-plugin/src/main/java/com/alibaba/jvm/sandbox/repeater/plugin/openfeign/OpenFeignProcessor.java @@ -0,0 +1,234 @@ +package com.alibaba.jvm.sandbox.repeater.plugin.openfeign; + +import com.alibaba.jvm.sandbox.api.event.BeforeEvent; +import com.alibaba.jvm.sandbox.api.event.Event; +import com.alibaba.jvm.sandbox.api.event.ReturnEvent; +import com.alibaba.jvm.sandbox.repeater.plugin.core.impl.api.DefaultInvocationProcessor; +import com.alibaba.jvm.sandbox.repeater.plugin.core.util.LogUtil; +import com.alibaba.jvm.sandbox.repeater.plugin.domain.Identity; +import com.alibaba.jvm.sandbox.repeater.plugin.domain.Invocation; +import com.alibaba.jvm.sandbox.repeater.plugin.domain.InvokeType; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.commons.lang3.reflect.MethodUtils; + +import java.io.*; +import java.lang.reflect.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.*; + +/** + * @Author: luwenrong + * @Title: OpenFeignProcessor + * @Description: + * @Date: 2021/11/30 + */ +public class OpenFeignProcessor extends DefaultInvocationProcessor { + + OpenFeignProcessor(InvokeType type) { + super(type); + } + + @Override + public Identity assembleIdentity(BeforeEvent event) { + //feign.Request + Object request = event.argumentArray[0]; + String urlStr = "get url failded"; + String methodName = "get method failded"; + try { + /** + * 兼容老版本没有 requestTemplate + */ + if (hasField(request.getClass(), "requestTemplate")) { + Object requestTemplateObject = MethodUtils.invokeMethod(request, "requestTemplate"); + urlStr = (String)MethodUtils.invokeMethod(requestTemplateObject, "url"); + } else { + String requestUrlStr = (String)MethodUtils.invokeMethod(request, "url"); + urlStr = this.getUrl(requestUrlStr); + } + methodName = (String)MethodUtils.invokeMethod(request, "method"); + } catch (Exception e) { + LogUtil.error("openfeign plugin assembleIdentity failed", e); + } + return new Identity(InvokeType.OPENFEIGN.name(), methodName, urlStr, null); + } + + @Override + public Object[] assembleRequest(BeforeEvent event) { + //execute(Request request, Options options) + Object request = event.argumentArray[0]; + try { + String method = (String)MethodUtils.invokeMethod(request, "method"); + Object header = MethodUtils.invokeMethod(request, "headers"); + Object body = MethodUtils.invokeMethod(request, "body"); + + String urlStr = ""; + if (hasField(request.getClass(), "requestTemplate")) { + Object requestTemplateObject = MethodUtils.invokeMethod(request, "requestTemplate"); + urlStr = (String)MethodUtils.invokeMethod(requestTemplateObject, "url"); + } else { + String requestUrlStr = (String)MethodUtils.invokeMethod(request, "url"); + urlStr = this.getUrl(requestUrlStr); + } + + Map params = new HashMap(); + params.put("requestUrl", urlStr); + params.put("requestMethod", method); + params.put("requestHeaders", header); + params.put("requestBody", String.valueOf(body)); + + return new Object[]{params}; + } catch (Exception e) { + e.printStackTrace(); + } + + return super.assembleRequest(event); + } + + @Override + public Object assembleResponse(Event event) { + if (event.type == Event.Type.RETURN){ + ReturnEvent returnEvent = (ReturnEvent) event; + // feign.Response + Object response = returnEvent.object; + try { + int status = Integer.parseInt(String.valueOf(MethodUtils.invokeMethod(response, "status"))); + String reason = (String)MethodUtils.invokeMethod(response, "reason"); + Map> headers = (Map>)MethodUtils.invokeMethod(response, "headers"); + Map responseBody = this.getResponseBody(response); + + Object body = MethodUtils.invokeMethod(response, true, "body"); + Field inputStreamField = FieldUtils.getDeclaredField(body.getClass(), "inputStream", true); +// inputStreamField.setAccessible(true); + inputStreamField.set(body, new ByteArrayInputStream((byte[]) responseBody.get("content"))); + + Map responseMap = new HashMap(); + responseMap.put("responseStatus", status); + responseMap.put("responseReason", reason); + responseMap.put("responseHeaders", headers); + if (MapUtils.isNotEmpty(responseBody)) { + responseMap.put("responseBodyLength", responseBody.get("length")); + responseMap.put("responseBodyContent", responseBody.get("content")); + } + return responseMap; + }catch (Exception e){ + LogUtil.error("feign plugin save response failed", e); + } + } + return null; + } + + @Override + public Object assembleMockResponse(BeforeEvent event, Invocation invocation) { + try { + Object request = event.argumentArray[0]; + Map responseMap = (Map) invocation.getResponse(); + if (MapUtils.isEmpty(responseMap)){ + new Object(); + } + + int status = Integer.parseInt(String.valueOf(responseMap.get("responseStatus"))); + String reason = (String) responseMap.get("responseReason"); + Map> headers = (Map>)responseMap.get("responseHeaders"); + Object length = responseMap.get("responseBodyLength"); + byte[] content = (byte[])responseMap.get("responseBodyContent"); + + Class responseBuilderClass = event.javaClassLoader.loadClass("feign.Response$Builder"); + Constructor constructor = responseBuilderClass.getDeclaredConstructor(); + constructor.setAccessible(true); + Object responseBuilder = constructor.newInstance(); + + MethodUtils.invokeMethod(responseBuilder, true, "status", status); + MethodUtils.invokeMethod(responseBuilder, true, "reason", reason); + MethodUtils.invokeMethod(responseBuilder, true, "headers", headers); + MethodUtils.invokeMethod(responseBuilder, true, "request", request); + MethodUtils.invokeMethod(responseBuilder, true, "body", new ByteArrayInputStream(content), length); + + return MethodUtils.invokeMethod(responseBuilder, true, "build"); + }catch (Exception e){ + LogUtil.error("feign plugin assembleMockResponse failed, event={}", event.javaClassName + "|" + event.javaMethodName, e); + } + return null; + } + + private Map getResponseBody(Object response) throws Exception{ + Map responseBodyMap = new HashMap(); + + Object body = MethodUtils.invokeMethod(response, true, "body"); + InputStream content = (InputStream)MethodUtils.invokeMethod(body, true, "asInputStream"); + Object lengthObject = MethodUtils.invokeMethod(body, true, "length"); + + byte[] contentBuf = new byte[]{}; + if(content != null) { + contentBuf = this.toByteArray(content); + } + + responseBodyMap.put("content", contentBuf); + responseBodyMap.put("length", lengthObject); + return responseBodyMap; + } + + public byte[] toByteArray(InputStream in) throws IOException { + byte[] bytes; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + copyStream(in, out); + bytes = out.toByteArray(); + } finally { + try { + out.close(); + }catch (Exception e) { + + } + } + return bytes; + } + + private long copyStream(InputStream from, OutputStream to) throws IOException { + byte[] buf = new byte[2048]; + long total = 0L; + while(true) { + int r = from.read(buf); + if (r == -1) { + return total; + } + to.write(buf, 0, r); + total += (long)r; + } + } + + private boolean hasField(Class c, String fieldName){ + Field[] fields = c.getDeclaredFields(); + for (Field f : fields) { + if (fieldName.equals(f.getName())) { + return true; + } + } + return false; + } + + /** + * 去除url中的ip端口信息 负载均衡中可能不一样 + * @param url + * @return + */ + private String getUrl(String url) { + try { + URL urlInfo = new URL(url); + StringBuilder urlSB = new StringBuilder(); + urlSB.append(urlInfo.getProtocol()).append("://"); + if (StringUtils.isNotEmpty(urlInfo.getPath())) { + urlSB.append(urlInfo.getPath()); + } + if (StringUtils.isNotEmpty(urlInfo.getQuery())) { + urlSB.append("?").append(urlInfo.getQuery()); + } + return urlSB.toString(); + + } catch (MalformedURLException e) { + return ""; + } + } +} diff --git a/repeater-plugins/pom.xml b/repeater-plugins/pom.xml index d87c2bdb..45b534f8 100644 --- a/repeater-plugins/pom.xml +++ b/repeater-plugins/pom.xml @@ -25,6 +25,8 @@ eh-cache-plugin guava-cache-plugin okhttp-plugin + mybatis-plus-plugin + openfeign-plugin