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