丁云鹏 1 سال پیش
کامیت
70f9f30983
59فایلهای تغییر یافته به همراه8551 افزوده شده و 0 حذف شده
  1. 50 0
      .gitignore
  2. 99 0
      abtest-client/pom.xml
  3. 31 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/ModelClient.java
  4. 30 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/ProtobufUtils.java
  5. 49 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/Common.java
  6. 79 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/Exp.java
  7. 237 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/ExpService.java
  8. 288 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/ExpServiceGrpc.java
  9. 897 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/GetExpRequest.java
  10. 51 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/GetExpRequestOrBuilder.java
  11. 893 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/GetExpResponse.java
  12. 58 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/GetExpResponseOrBuilder.java
  13. 897 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/GetGroupRequest.java
  14. 51 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/GetGroupRequestOrBuilder.java
  15. 745 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/GetGroupResponse.java
  16. 36 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/GetGroupResponseOrBuilder.java
  17. 67 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/Group.java
  18. 237 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/GroupService.java
  19. 288 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/GroupServiceGrpc.java
  20. 237 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/ModelService.java
  21. 288 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/ModelServiceGrpc.java
  22. 637 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/Result.java
  23. 31 0
      abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/ResultOrBuilder.java
  24. 9 0
      abtest-client/src/main/proto/com/tzld/piaoquan/abtest/common.proto
  25. 23 0
      abtest-client/src/main/proto/com/tzld/piaoquan/abtest/exp.proto
  26. 23 0
      abtest-client/src/main/proto/com/tzld/piaoquan/abtest/group.proto
  27. 179 0
      abtest-service/pom.xml
  28. 41 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/Application.java
  29. 40 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/common/CommonThreadPoolExecutor.java
  30. 358 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/common/RecommendLoghubAppender.java
  31. 37 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/common/RecommendLoghubAppenderCallback.java
  32. 28 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/common/ThreadPoolFactory.java
  33. 68 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/common/enums/AppTypeEnum.java
  34. 63 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/config/RedisTemplateConfig.java
  35. 32 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/grpcservice/ExpGrpcService.java
  36. 32 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/grpcservice/GroupGrpcService.java
  37. 25 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/grpcservice/GrpcAspect.java
  38. 42 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/model/ExpConfig.java
  39. 47 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/repository/WxVideoStatus.java
  40. 12 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/repository/WxVideoStatusRepository.java
  41. 73 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/service/ExpService.java
  42. 183 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/service/GroupService.java
  43. 23 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/service/ServiceBeanFactory.java
  44. 25 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/service/WarmUpService.java
  45. 59 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/util/CommonCollectionUtils.java
  46. 49 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/util/JSONUtils.java
  47. 52 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/util/TraceUtils.java
  48. 42 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/web/ControllerAspect.java
  49. 29 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/web/ExpController.java
  50. 24 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/web/GlobalExceptionHandler.java
  51. 29 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/web/GroupController.java
  52. 18 0
      abtest-service/src/main/java/com/tzld/piaoquan/abtest/web/HealthCheckController.java
  53. 55 0
      abtest-service/src/main/resources/application-dev.yml
  54. 51 0
      abtest-service/src/main/resources/application-pre.yml
  55. 50 0
      abtest-service/src/main/resources/application-prod.yml
  56. 51 0
      abtest-service/src/main/resources/application-test.yml
  57. 49 0
      abtest-service/src/main/resources/application.yml
  58. 328 0
      abtest-service/src/main/resources/logback-spring.xml
  59. 26 0
      pom.xml

+ 50 - 0
.gitignore

@@ -0,0 +1,50 @@
+# ---> Java
+*.class
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### log ###
+logs/*

+ 99 - 0
abtest-client/pom.xml

@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>abtest</artifactId>
+        <groupId>com.tzld.piaoquan</groupId>
+        <version>1.0.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>abtest-client</artifactId>
+    <version>1.0.0</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>net.devh</groupId>
+            <artifactId>grpc-client-spring-boot-starter</artifactId>
+            <version>2.9.0.RELEASE</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.protobuf</groupId>
+            <artifactId>protobuf-java</artifactId>
+            <version>3.12.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.16.8</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.protobuf</groupId>
+            <artifactId>protobuf-java-util</artifactId>
+            <version>3.6.0</version>
+            <exclusions>
+                <exclusion>
+                    <artifactId>protobuf-java</artifactId>
+                    <groupId>com.google.protobuf</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
+    <build>
+        <extensions>
+            <extension>
+                <groupId>kr.motd.maven</groupId>
+                <artifactId>os-maven-plugin</artifactId>
+                <version>1.6.0</version>
+            </extension>
+        </extensions>
+        <plugins>
+            <plugin>
+                <groupId>org.xolstice.maven.plugins</groupId>
+                <artifactId>protobuf-maven-plugin</artifactId>
+                <version>0.6.1</version>
+                <configuration>
+                    <protocArtifact>com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier}</protocArtifact>
+                    <pluginId>grpc-java</pluginId>
+                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.34.1:exe:${os.detected.classifier}</pluginArtifact>
+                    <!--设置grpc生成代码到指定路径-->
+                    <outputDirectory>${project.basedir}/src/main/java</outputDirectory>
+                    <!--生成代码前是否清空目录-->
+                    <clearOutputDirectory>false</clearOutputDirectory>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>compile</goal>
+                            <goal>compile-custom</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <!-- 设置多个源文件夹 -->
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>build-helper-maven-plugin</artifactId>
+                <version>3.0.0</version>
+                <executions>
+                    <!-- 添加主源码目录 -->
+                    <execution>
+                        <id>add-source</id>
+                        <phase>generate-sources</phase>
+                        <goals>
+                            <goal>add-source</goal>
+                        </goals>
+                        <configuration>
+                            <sources>
+                                <source>${project.basedir}/src/main/java</source>
+                            </sources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+        </plugins>
+    </build>
+</project>

+ 31 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/ModelClient.java

@@ -0,0 +1,31 @@
+package com.tzld.piaoquan.abtest.client;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * @author dyp
+ */
+@Component
+//@Slf4j
+public class ModelClient {
+//    @GrpcClient("recommend-server")
+//    private ModelServiceGrpc.ModelServiceBlockingStub client;
+//
+//    public Map<String, Double> getScore(long videoId, String mid, String uid, String city, String province) {
+//        ScoreRequest request = ScoreRequest.newBuilder()
+//                .setVideoId(videoId)
+//                .setMid(mid)
+//                .setUid(uid)
+//                .setCity(city)
+//                .setProvince(province)
+//                .build();
+//        ScoreResponse response = client.score(request);
+//        if (response == null || !response.hasResult()) {
+//            return null;
+//        }
+//        if (response.getResult().getCode() != 1) {
+//            return null;
+//        }
+//        return response.getScoreMap();
+//    }
+}

+ 30 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/ProtobufUtils.java

@@ -0,0 +1,30 @@
+package com.tzld.piaoquan.abtest.client;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.Message;
+import com.google.protobuf.MessageOrBuilder;
+import com.google.protobuf.util.JsonFormat;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author dyp
+ */
+@Slf4j
+public class ProtobufUtils {
+    public static String toJson(MessageOrBuilder message) {
+        if (message == null) {
+            return "";
+        }
+        try {
+            return JsonFormat.printer().includingDefaultValueFields().print(message);
+        } catch (InvalidProtocolBufferException e) {
+            log.error("ProtobufUtils toJson error", e);
+        }
+        return "";
+    }
+
+    public static void fromJson(String json, Message.Builder builder) throws InvalidProtocolBufferException {
+        JsonFormat.parser().merge(json, builder);
+    }
+
+}

+ 49 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/Common.java

@@ -0,0 +1,49 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/abtest/common.proto
+
+package com.tzld.piaoquan.abtest.client.model;
+
+public final class Common {
+  private Common() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_Result_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_Result_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n%com/tzld/piaoquan/abtest/common.proto\"" +
+      "\'\n\006Result\022\014\n\004code\030\001 \001(\005\022\017\n\007message\030\002 \001(\t" +
+      "B)\n%com.tzld.piaoquan.abtest.client.mode" +
+      "lP\001b\006proto3"
+    };
+    descriptor = com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+        });
+    internal_static_Result_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_Result_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_Result_descriptor,
+        new java.lang.String[] { "Code", "Message", });
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}

+ 79 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/Exp.java

@@ -0,0 +1,79 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/abtest/exp.proto
+
+package com.tzld.piaoquan.abtest.client.model;
+
+public final class Exp {
+  private Exp() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_GetExpRequest_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_GetExpRequest_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_GetExpResponse_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_GetExpResponse_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_GetExpResponse_ExpEntry_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_GetExpResponse_ExpEntry_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n\"com/tzld/piaoquan/abtest/exp.proto\032%co" +
+      "m/tzld/piaoquan/abtest/common.proto\"J\n\rG" +
+      "etExpRequest\022\013\n\003mid\030\001 \001(\t\022\013\n\003uid\030\002 \001(\t\022\020" +
+      "\n\010app_type\030\003 \001(\005\022\r\n\005group\030\004 \001(\t\"|\n\016GetEx" +
+      "pResponse\022\027\n\006result\030\001 \001(\0132\007.Result\022%\n\003ex" +
+      "p\030\002 \003(\0132\030.GetExpResponse.ExpEntry\032*\n\010Exp" +
+      "Entry\022\013\n\003key\030\001 \001(\t\022\r\n\005value\030\002 \001(\t:\0028\00127\n" +
+      "\nExpService\022)\n\006getExp\022\016.GetExpRequest\032\017." +
+      "GetExpResponseB,\n%com.tzld.piaoquan.abte" +
+      "st.client.modelP\001\210\001\001b\006proto3"
+    };
+    descriptor = com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+          com.tzld.piaoquan.abtest.client.model.Common.getDescriptor(),
+        });
+    internal_static_GetExpRequest_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_GetExpRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_GetExpRequest_descriptor,
+        new java.lang.String[] { "Mid", "Uid", "AppType", "Group", });
+    internal_static_GetExpResponse_descriptor =
+      getDescriptor().getMessageTypes().get(1);
+    internal_static_GetExpResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_GetExpResponse_descriptor,
+        new java.lang.String[] { "Result", "Exp", });
+    internal_static_GetExpResponse_ExpEntry_descriptor =
+      internal_static_GetExpResponse_descriptor.getNestedTypes().get(0);
+    internal_static_GetExpResponse_ExpEntry_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_GetExpResponse_ExpEntry_descriptor,
+        new java.lang.String[] { "Key", "Value", });
+    com.tzld.piaoquan.abtest.client.model.Common.getDescriptor();
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}

+ 237 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/ExpService.java

@@ -0,0 +1,237 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/abtest/exp.proto
+
+package com.tzld.piaoquan.abtest.client.model;
+
+/**
+ * Protobuf service {@code ExpService}
+ */
+public  abstract class ExpService
+    implements com.google.protobuf.Service {
+  protected ExpService() {}
+
+  public interface Interface {
+    /**
+     * <code>rpc getExp(.GetExpRequest) returns (.GetExpResponse);</code>
+     */
+    public abstract void getExp(
+        com.google.protobuf.RpcController controller,
+        com.tzld.piaoquan.abtest.client.model.GetExpRequest request,
+        com.google.protobuf.RpcCallback<com.tzld.piaoquan.abtest.client.model.GetExpResponse> done);
+
+  }
+
+  public static com.google.protobuf.Service newReflectiveService(
+      final Interface impl) {
+    return new ExpService() {
+      @java.lang.Override
+      public  void getExp(
+          com.google.protobuf.RpcController controller,
+          com.tzld.piaoquan.abtest.client.model.GetExpRequest request,
+          com.google.protobuf.RpcCallback<com.tzld.piaoquan.abtest.client.model.GetExpResponse> done) {
+        impl.getExp(controller, request, done);
+      }
+
+    };
+  }
+
+  public static com.google.protobuf.BlockingService
+      newReflectiveBlockingService(final BlockingInterface impl) {
+    return new com.google.protobuf.BlockingService() {
+      public final com.google.protobuf.Descriptors.ServiceDescriptor
+          getDescriptorForType() {
+        return getDescriptor();
+      }
+
+      public final com.google.protobuf.Message callBlockingMethod(
+          com.google.protobuf.Descriptors.MethodDescriptor method,
+          com.google.protobuf.RpcController controller,
+          com.google.protobuf.Message request)
+          throws com.google.protobuf.ServiceException {
+        if (method.getService() != getDescriptor()) {
+          throw new java.lang.IllegalArgumentException(
+            "Service.callBlockingMethod() given method descriptor for " +
+            "wrong service type.");
+        }
+        switch(method.getIndex()) {
+          case 0:
+            return impl.getExp(controller, (com.tzld.piaoquan.abtest.client.model.GetExpRequest)request);
+          default:
+            throw new java.lang.AssertionError("Can't get here.");
+        }
+      }
+
+      public final com.google.protobuf.Message
+          getRequestPrototype(
+          com.google.protobuf.Descriptors.MethodDescriptor method) {
+        if (method.getService() != getDescriptor()) {
+          throw new java.lang.IllegalArgumentException(
+            "Service.getRequestPrototype() given method " +
+            "descriptor for wrong service type.");
+        }
+        switch(method.getIndex()) {
+          case 0:
+            return com.tzld.piaoquan.abtest.client.model.GetExpRequest.getDefaultInstance();
+          default:
+            throw new java.lang.AssertionError("Can't get here.");
+        }
+      }
+
+      public final com.google.protobuf.Message
+          getResponsePrototype(
+          com.google.protobuf.Descriptors.MethodDescriptor method) {
+        if (method.getService() != getDescriptor()) {
+          throw new java.lang.IllegalArgumentException(
+            "Service.getResponsePrototype() given method " +
+            "descriptor for wrong service type.");
+        }
+        switch(method.getIndex()) {
+          case 0:
+            return com.tzld.piaoquan.abtest.client.model.GetExpResponse.getDefaultInstance();
+          default:
+            throw new java.lang.AssertionError("Can't get here.");
+        }
+      }
+
+    };
+  }
+
+  /**
+   * <code>rpc getExp(.GetExpRequest) returns (.GetExpResponse);</code>
+   */
+  public abstract void getExp(
+      com.google.protobuf.RpcController controller,
+      com.tzld.piaoquan.abtest.client.model.GetExpRequest request,
+      com.google.protobuf.RpcCallback<com.tzld.piaoquan.abtest.client.model.GetExpResponse> done);
+
+  public static final
+      com.google.protobuf.Descriptors.ServiceDescriptor
+      getDescriptor() {
+    return com.tzld.piaoquan.abtest.client.model.Exp.getDescriptor().getServices().get(0);
+  }
+  public final com.google.protobuf.Descriptors.ServiceDescriptor
+      getDescriptorForType() {
+    return getDescriptor();
+  }
+
+  public final void callMethod(
+      com.google.protobuf.Descriptors.MethodDescriptor method,
+      com.google.protobuf.RpcController controller,
+      com.google.protobuf.Message request,
+      com.google.protobuf.RpcCallback<
+        com.google.protobuf.Message> done) {
+    if (method.getService() != getDescriptor()) {
+      throw new java.lang.IllegalArgumentException(
+        "Service.callMethod() given method descriptor for wrong " +
+        "service type.");
+    }
+    switch(method.getIndex()) {
+      case 0:
+        this.getExp(controller, (com.tzld.piaoquan.abtest.client.model.GetExpRequest)request,
+          com.google.protobuf.RpcUtil.<com.tzld.piaoquan.abtest.client.model.GetExpResponse>specializeCallback(
+            done));
+        return;
+      default:
+        throw new java.lang.AssertionError("Can't get here.");
+    }
+  }
+
+  public final com.google.protobuf.Message
+      getRequestPrototype(
+      com.google.protobuf.Descriptors.MethodDescriptor method) {
+    if (method.getService() != getDescriptor()) {
+      throw new java.lang.IllegalArgumentException(
+        "Service.getRequestPrototype() given method " +
+        "descriptor for wrong service type.");
+    }
+    switch(method.getIndex()) {
+      case 0:
+        return com.tzld.piaoquan.abtest.client.model.GetExpRequest.getDefaultInstance();
+      default:
+        throw new java.lang.AssertionError("Can't get here.");
+    }
+  }
+
+  public final com.google.protobuf.Message
+      getResponsePrototype(
+      com.google.protobuf.Descriptors.MethodDescriptor method) {
+    if (method.getService() != getDescriptor()) {
+      throw new java.lang.IllegalArgumentException(
+        "Service.getResponsePrototype() given method " +
+        "descriptor for wrong service type.");
+    }
+    switch(method.getIndex()) {
+      case 0:
+        return com.tzld.piaoquan.abtest.client.model.GetExpResponse.getDefaultInstance();
+      default:
+        throw new java.lang.AssertionError("Can't get here.");
+    }
+  }
+
+  public static Stub newStub(
+      com.google.protobuf.RpcChannel channel) {
+    return new Stub(channel);
+  }
+
+  public static final class Stub extends com.tzld.piaoquan.abtest.client.model.ExpService implements Interface {
+    private Stub(com.google.protobuf.RpcChannel channel) {
+      this.channel = channel;
+    }
+
+    private final com.google.protobuf.RpcChannel channel;
+
+    public com.google.protobuf.RpcChannel getChannel() {
+      return channel;
+    }
+
+    public  void getExp(
+        com.google.protobuf.RpcController controller,
+        com.tzld.piaoquan.abtest.client.model.GetExpRequest request,
+        com.google.protobuf.RpcCallback<com.tzld.piaoquan.abtest.client.model.GetExpResponse> done) {
+      channel.callMethod(
+        getDescriptor().getMethods().get(0),
+        controller,
+        request,
+        com.tzld.piaoquan.abtest.client.model.GetExpResponse.getDefaultInstance(),
+        com.google.protobuf.RpcUtil.generalizeCallback(
+          done,
+          com.tzld.piaoquan.abtest.client.model.GetExpResponse.class,
+          com.tzld.piaoquan.abtest.client.model.GetExpResponse.getDefaultInstance()));
+    }
+  }
+
+  public static BlockingInterface newBlockingStub(
+      com.google.protobuf.BlockingRpcChannel channel) {
+    return new BlockingStub(channel);
+  }
+
+  public interface BlockingInterface {
+    public com.tzld.piaoquan.abtest.client.model.GetExpResponse getExp(
+        com.google.protobuf.RpcController controller,
+        com.tzld.piaoquan.abtest.client.model.GetExpRequest request)
+        throws com.google.protobuf.ServiceException;
+  }
+
+  private static final class BlockingStub implements BlockingInterface {
+    private BlockingStub(com.google.protobuf.BlockingRpcChannel channel) {
+      this.channel = channel;
+    }
+
+    private final com.google.protobuf.BlockingRpcChannel channel;
+
+    public com.tzld.piaoquan.abtest.client.model.GetExpResponse getExp(
+        com.google.protobuf.RpcController controller,
+        com.tzld.piaoquan.abtest.client.model.GetExpRequest request)
+        throws com.google.protobuf.ServiceException {
+      return (com.tzld.piaoquan.abtest.client.model.GetExpResponse) channel.callBlockingMethod(
+        getDescriptor().getMethods().get(0),
+        controller,
+        request,
+        com.tzld.piaoquan.abtest.client.model.GetExpResponse.getDefaultInstance());
+    }
+
+  }
+
+  // @@protoc_insertion_point(class_scope:ExpService)
+}
+

+ 288 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/ExpServiceGrpc.java

@@ -0,0 +1,288 @@
+package com.tzld.piaoquan.abtest.client.model;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler (version 1.34.1)",
+    comments = "Source: com/tzld/piaoquan/abtest/exp.proto")
+public final class ExpServiceGrpc {
+
+  private ExpServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "ExpService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<com.tzld.piaoquan.abtest.client.model.GetExpRequest,
+      com.tzld.piaoquan.abtest.client.model.GetExpResponse> getGetExpMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "getExp",
+      requestType = com.tzld.piaoquan.abtest.client.model.GetExpRequest.class,
+      responseType = com.tzld.piaoquan.abtest.client.model.GetExpResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<com.tzld.piaoquan.abtest.client.model.GetExpRequest,
+      com.tzld.piaoquan.abtest.client.model.GetExpResponse> getGetExpMethod() {
+    io.grpc.MethodDescriptor<com.tzld.piaoquan.abtest.client.model.GetExpRequest, com.tzld.piaoquan.abtest.client.model.GetExpResponse> getGetExpMethod;
+    if ((getGetExpMethod = ExpServiceGrpc.getGetExpMethod) == null) {
+      synchronized (ExpServiceGrpc.class) {
+        if ((getGetExpMethod = ExpServiceGrpc.getGetExpMethod) == null) {
+          ExpServiceGrpc.getGetExpMethod = getGetExpMethod =
+              io.grpc.MethodDescriptor.<com.tzld.piaoquan.abtest.client.model.GetExpRequest, com.tzld.piaoquan.abtest.client.model.GetExpResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(SERVICE_NAME, "getExp"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  com.tzld.piaoquan.abtest.client.model.GetExpRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  com.tzld.piaoquan.abtest.client.model.GetExpResponse.getDefaultInstance()))
+              .setSchemaDescriptor(new ExpServiceMethodDescriptorSupplier("getExp"))
+              .build();
+        }
+      }
+    }
+    return getGetExpMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static ExpServiceStub newStub(io.grpc.Channel channel) {
+    io.grpc.stub.AbstractStub.StubFactory<ExpServiceStub> factory =
+      new io.grpc.stub.AbstractStub.StubFactory<ExpServiceStub>() {
+        @java.lang.Override
+        public ExpServiceStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+          return new ExpServiceStub(channel, callOptions);
+        }
+      };
+    return ExpServiceStub.newStub(factory, channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static ExpServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    io.grpc.stub.AbstractStub.StubFactory<ExpServiceBlockingStub> factory =
+      new io.grpc.stub.AbstractStub.StubFactory<ExpServiceBlockingStub>() {
+        @java.lang.Override
+        public ExpServiceBlockingStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+          return new ExpServiceBlockingStub(channel, callOptions);
+        }
+      };
+    return ExpServiceBlockingStub.newStub(factory, channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static ExpServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    io.grpc.stub.AbstractStub.StubFactory<ExpServiceFutureStub> factory =
+      new io.grpc.stub.AbstractStub.StubFactory<ExpServiceFutureStub>() {
+        @java.lang.Override
+        public ExpServiceFutureStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+          return new ExpServiceFutureStub(channel, callOptions);
+        }
+      };
+    return ExpServiceFutureStub.newStub(factory, channel);
+  }
+
+  /**
+   */
+  public static abstract class ExpServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     */
+    public void getExp(com.tzld.piaoquan.abtest.client.model.GetExpRequest request,
+        io.grpc.stub.StreamObserver<com.tzld.piaoquan.abtest.client.model.GetExpResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getGetExpMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getGetExpMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                com.tzld.piaoquan.abtest.client.model.GetExpRequest,
+                com.tzld.piaoquan.abtest.client.model.GetExpResponse>(
+                  this, METHODID_GET_EXP)))
+          .build();
+    }
+  }
+
+  /**
+   */
+  public static final class ExpServiceStub extends io.grpc.stub.AbstractAsyncStub<ExpServiceStub> {
+    private ExpServiceStub(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ExpServiceStub build(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      return new ExpServiceStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public void getExp(com.tzld.piaoquan.abtest.client.model.GetExpRequest request,
+        io.grpc.stub.StreamObserver<com.tzld.piaoquan.abtest.client.model.GetExpResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getGetExpMethod(), getCallOptions()), request, responseObserver);
+    }
+  }
+
+  /**
+   */
+  public static final class ExpServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub<ExpServiceBlockingStub> {
+    private ExpServiceBlockingStub(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ExpServiceBlockingStub build(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      return new ExpServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public com.tzld.piaoquan.abtest.client.model.GetExpResponse getExp(com.tzld.piaoquan.abtest.client.model.GetExpRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getGetExpMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   */
+  public static final class ExpServiceFutureStub extends io.grpc.stub.AbstractFutureStub<ExpServiceFutureStub> {
+    private ExpServiceFutureStub(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ExpServiceFutureStub build(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      return new ExpServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public com.google.common.util.concurrent.ListenableFuture<com.tzld.piaoquan.abtest.client.model.GetExpResponse> getExp(
+        com.tzld.piaoquan.abtest.client.model.GetExpRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getGetExpMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_GET_EXP = 0;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final ExpServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(ExpServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_GET_EXP:
+          serviceImpl.getExp((com.tzld.piaoquan.abtest.client.model.GetExpRequest) request,
+              (io.grpc.stub.StreamObserver<com.tzld.piaoquan.abtest.client.model.GetExpResponse>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class ExpServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    ExpServiceBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return com.tzld.piaoquan.abtest.client.model.Exp.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("ExpService");
+    }
+  }
+
+  private static final class ExpServiceFileDescriptorSupplier
+      extends ExpServiceBaseDescriptorSupplier {
+    ExpServiceFileDescriptorSupplier() {}
+  }
+
+  private static final class ExpServiceMethodDescriptorSupplier
+      extends ExpServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    ExpServiceMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (ExpServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new ExpServiceFileDescriptorSupplier())
+              .addMethod(getGetExpMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}

+ 897 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/GetExpRequest.java

@@ -0,0 +1,897 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/abtest/exp.proto
+
+package com.tzld.piaoquan.abtest.client.model;
+
+/**
+ * Protobuf type {@code GetExpRequest}
+ */
+public final class GetExpRequest extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:GetExpRequest)
+    GetExpRequestOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use GetExpRequest.newBuilder() to construct.
+  private GetExpRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private GetExpRequest() {
+    mid_ = "";
+    uid_ = "";
+    group_ = "";
+  }
+
+  @java.lang.Override
+  @SuppressWarnings({"unused"})
+  protected java.lang.Object newInstance(
+      UnusedPrivateParameter unused) {
+    return new GetExpRequest();
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private GetExpRequest(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          case 10: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            mid_ = s;
+            break;
+          }
+          case 18: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            uid_ = s;
+            break;
+          }
+          case 24: {
+
+            appType_ = input.readInt32();
+            break;
+          }
+          case 34: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            group_ = s;
+            break;
+          }
+          default: {
+            if (!parseUnknownField(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return com.tzld.piaoquan.abtest.client.model.Exp.internal_static_GetExpRequest_descriptor;
+  }
+
+  @java.lang.Override
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return com.tzld.piaoquan.abtest.client.model.Exp.internal_static_GetExpRequest_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            com.tzld.piaoquan.abtest.client.model.GetExpRequest.class, com.tzld.piaoquan.abtest.client.model.GetExpRequest.Builder.class);
+  }
+
+  public static final int MID_FIELD_NUMBER = 1;
+  private volatile java.lang.Object mid_;
+  /**
+   * <code>string mid = 1;</code>
+   * @return The mid.
+   */
+  @java.lang.Override
+  public java.lang.String getMid() {
+    java.lang.Object ref = mid_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      mid_ = s;
+      return s;
+    }
+  }
+  /**
+   * <code>string mid = 1;</code>
+   * @return The bytes for mid.
+   */
+  @java.lang.Override
+  public com.google.protobuf.ByteString
+      getMidBytes() {
+    java.lang.Object ref = mid_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      mid_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int UID_FIELD_NUMBER = 2;
+  private volatile java.lang.Object uid_;
+  /**
+   * <code>string uid = 2;</code>
+   * @return The uid.
+   */
+  @java.lang.Override
+  public java.lang.String getUid() {
+    java.lang.Object ref = uid_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      uid_ = s;
+      return s;
+    }
+  }
+  /**
+   * <code>string uid = 2;</code>
+   * @return The bytes for uid.
+   */
+  @java.lang.Override
+  public com.google.protobuf.ByteString
+      getUidBytes() {
+    java.lang.Object ref = uid_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      uid_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int APP_TYPE_FIELD_NUMBER = 3;
+  private int appType_;
+  /**
+   * <code>int32 app_type = 3;</code>
+   * @return The appType.
+   */
+  @java.lang.Override
+  public int getAppType() {
+    return appType_;
+  }
+
+  public static final int GROUP_FIELD_NUMBER = 4;
+  private volatile java.lang.Object group_;
+  /**
+   * <code>string group = 4;</code>
+   * @return The group.
+   */
+  @java.lang.Override
+  public java.lang.String getGroup() {
+    java.lang.Object ref = group_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      group_ = s;
+      return s;
+    }
+  }
+  /**
+   * <code>string group = 4;</code>
+   * @return The bytes for group.
+   */
+  @java.lang.Override
+  public com.google.protobuf.ByteString
+      getGroupBytes() {
+    java.lang.Object ref = group_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      group_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  @java.lang.Override
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  @java.lang.Override
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (!getMidBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, mid_);
+    }
+    if (!getUidBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 2, uid_);
+    }
+    if (appType_ != 0) {
+      output.writeInt32(3, appType_);
+    }
+    if (!getGroupBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 4, group_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  @java.lang.Override
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (!getMidBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, mid_);
+    }
+    if (!getUidBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, uid_);
+    }
+    if (appType_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt32Size(3, appType_);
+    }
+    if (!getGroupBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(4, group_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof com.tzld.piaoquan.abtest.client.model.GetExpRequest)) {
+      return super.equals(obj);
+    }
+    com.tzld.piaoquan.abtest.client.model.GetExpRequest other = (com.tzld.piaoquan.abtest.client.model.GetExpRequest) obj;
+
+    if (!getMid()
+        .equals(other.getMid())) return false;
+    if (!getUid()
+        .equals(other.getUid())) return false;
+    if (getAppType()
+        != other.getAppType()) return false;
+    if (!getGroup()
+        .equals(other.getGroup())) return false;
+    if (!unknownFields.equals(other.unknownFields)) return false;
+    return true;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + MID_FIELD_NUMBER;
+    hash = (53 * hash) + getMid().hashCode();
+    hash = (37 * hash) + UID_FIELD_NUMBER;
+    hash = (53 * hash) + getUid().hashCode();
+    hash = (37 * hash) + APP_TYPE_FIELD_NUMBER;
+    hash = (53 * hash) + getAppType();
+    hash = (37 * hash) + GROUP_FIELD_NUMBER;
+    hash = (53 * hash) + getGroup().hashCode();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static com.tzld.piaoquan.abtest.client.model.GetExpRequest parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpRequest parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpRequest parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpRequest parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpRequest parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpRequest parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpRequest parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpRequest parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpRequest parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpRequest parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpRequest parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpRequest parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  @java.lang.Override
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(com.tzld.piaoquan.abtest.client.model.GetExpRequest prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  @java.lang.Override
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code GetExpRequest}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:GetExpRequest)
+      com.tzld.piaoquan.abtest.client.model.GetExpRequestOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return com.tzld.piaoquan.abtest.client.model.Exp.internal_static_GetExpRequest_descriptor;
+    }
+
+    @java.lang.Override
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return com.tzld.piaoquan.abtest.client.model.Exp.internal_static_GetExpRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              com.tzld.piaoquan.abtest.client.model.GetExpRequest.class, com.tzld.piaoquan.abtest.client.model.GetExpRequest.Builder.class);
+    }
+
+    // Construct using com.tzld.piaoquan.abtest.client.model.GetExpRequest.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    @java.lang.Override
+    public Builder clear() {
+      super.clear();
+      mid_ = "";
+
+      uid_ = "";
+
+      appType_ = 0;
+
+      group_ = "";
+
+      return this;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return com.tzld.piaoquan.abtest.client.model.Exp.internal_static_GetExpRequest_descriptor;
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.abtest.client.model.GetExpRequest getDefaultInstanceForType() {
+      return com.tzld.piaoquan.abtest.client.model.GetExpRequest.getDefaultInstance();
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.abtest.client.model.GetExpRequest build() {
+      com.tzld.piaoquan.abtest.client.model.GetExpRequest result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.abtest.client.model.GetExpRequest buildPartial() {
+      com.tzld.piaoquan.abtest.client.model.GetExpRequest result = new com.tzld.piaoquan.abtest.client.model.GetExpRequest(this);
+      result.mid_ = mid_;
+      result.uid_ = uid_;
+      result.appType_ = appType_;
+      result.group_ = group_;
+      onBuilt();
+      return result;
+    }
+
+    @java.lang.Override
+    public Builder clone() {
+      return super.clone();
+    }
+    @java.lang.Override
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return super.setField(field, value);
+    }
+    @java.lang.Override
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return super.clearField(field);
+    }
+    @java.lang.Override
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return super.clearOneof(oneof);
+    }
+    @java.lang.Override
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return super.setRepeatedField(field, index, value);
+    }
+    @java.lang.Override
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return super.addRepeatedField(field, value);
+    }
+    @java.lang.Override
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof com.tzld.piaoquan.abtest.client.model.GetExpRequest) {
+        return mergeFrom((com.tzld.piaoquan.abtest.client.model.GetExpRequest)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(com.tzld.piaoquan.abtest.client.model.GetExpRequest other) {
+      if (other == com.tzld.piaoquan.abtest.client.model.GetExpRequest.getDefaultInstance()) return this;
+      if (!other.getMid().isEmpty()) {
+        mid_ = other.mid_;
+        onChanged();
+      }
+      if (!other.getUid().isEmpty()) {
+        uid_ = other.uid_;
+        onChanged();
+      }
+      if (other.getAppType() != 0) {
+        setAppType(other.getAppType());
+      }
+      if (!other.getGroup().isEmpty()) {
+        group_ = other.group_;
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    @java.lang.Override
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    @java.lang.Override
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      com.tzld.piaoquan.abtest.client.model.GetExpRequest parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (com.tzld.piaoquan.abtest.client.model.GetExpRequest) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private java.lang.Object mid_ = "";
+    /**
+     * <code>string mid = 1;</code>
+     * @return The mid.
+     */
+    public java.lang.String getMid() {
+      java.lang.Object ref = mid_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        mid_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>string mid = 1;</code>
+     * @return The bytes for mid.
+     */
+    public com.google.protobuf.ByteString
+        getMidBytes() {
+      java.lang.Object ref = mid_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        mid_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>string mid = 1;</code>
+     * @param value The mid to set.
+     * @return This builder for chaining.
+     */
+    public Builder setMid(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      mid_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string mid = 1;</code>
+     * @return This builder for chaining.
+     */
+    public Builder clearMid() {
+      
+      mid_ = getDefaultInstance().getMid();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string mid = 1;</code>
+     * @param value The bytes for mid to set.
+     * @return This builder for chaining.
+     */
+    public Builder setMidBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      mid_ = value;
+      onChanged();
+      return this;
+    }
+
+    private java.lang.Object uid_ = "";
+    /**
+     * <code>string uid = 2;</code>
+     * @return The uid.
+     */
+    public java.lang.String getUid() {
+      java.lang.Object ref = uid_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        uid_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>string uid = 2;</code>
+     * @return The bytes for uid.
+     */
+    public com.google.protobuf.ByteString
+        getUidBytes() {
+      java.lang.Object ref = uid_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        uid_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>string uid = 2;</code>
+     * @param value The uid to set.
+     * @return This builder for chaining.
+     */
+    public Builder setUid(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      uid_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string uid = 2;</code>
+     * @return This builder for chaining.
+     */
+    public Builder clearUid() {
+      
+      uid_ = getDefaultInstance().getUid();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string uid = 2;</code>
+     * @param value The bytes for uid to set.
+     * @return This builder for chaining.
+     */
+    public Builder setUidBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      uid_ = value;
+      onChanged();
+      return this;
+    }
+
+    private int appType_ ;
+    /**
+     * <code>int32 app_type = 3;</code>
+     * @return The appType.
+     */
+    @java.lang.Override
+    public int getAppType() {
+      return appType_;
+    }
+    /**
+     * <code>int32 app_type = 3;</code>
+     * @param value The appType to set.
+     * @return This builder for chaining.
+     */
+    public Builder setAppType(int value) {
+      
+      appType_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>int32 app_type = 3;</code>
+     * @return This builder for chaining.
+     */
+    public Builder clearAppType() {
+      
+      appType_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private java.lang.Object group_ = "";
+    /**
+     * <code>string group = 4;</code>
+     * @return The group.
+     */
+    public java.lang.String getGroup() {
+      java.lang.Object ref = group_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        group_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>string group = 4;</code>
+     * @return The bytes for group.
+     */
+    public com.google.protobuf.ByteString
+        getGroupBytes() {
+      java.lang.Object ref = group_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        group_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>string group = 4;</code>
+     * @param value The group to set.
+     * @return This builder for chaining.
+     */
+    public Builder setGroup(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      group_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string group = 4;</code>
+     * @return This builder for chaining.
+     */
+    public Builder clearGroup() {
+      
+      group_ = getDefaultInstance().getGroup();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string group = 4;</code>
+     * @param value The bytes for group to set.
+     * @return This builder for chaining.
+     */
+    public Builder setGroupBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      group_ = value;
+      onChanged();
+      return this;
+    }
+    @java.lang.Override
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFields(unknownFields);
+    }
+
+    @java.lang.Override
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:GetExpRequest)
+  }
+
+  // @@protoc_insertion_point(class_scope:GetExpRequest)
+  private static final com.tzld.piaoquan.abtest.client.model.GetExpRequest DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new com.tzld.piaoquan.abtest.client.model.GetExpRequest();
+  }
+
+  public static com.tzld.piaoquan.abtest.client.model.GetExpRequest getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<GetExpRequest>
+      PARSER = new com.google.protobuf.AbstractParser<GetExpRequest>() {
+    @java.lang.Override
+    public GetExpRequest parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new GetExpRequest(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<GetExpRequest> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<GetExpRequest> getParserForType() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.tzld.piaoquan.abtest.client.model.GetExpRequest getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+

+ 51 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/GetExpRequestOrBuilder.java

@@ -0,0 +1,51 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/abtest/exp.proto
+
+package com.tzld.piaoquan.abtest.client.model;
+
+public interface GetExpRequestOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:GetExpRequest)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>string mid = 1;</code>
+   * @return The mid.
+   */
+  java.lang.String getMid();
+  /**
+   * <code>string mid = 1;</code>
+   * @return The bytes for mid.
+   */
+  com.google.protobuf.ByteString
+      getMidBytes();
+
+  /**
+   * <code>string uid = 2;</code>
+   * @return The uid.
+   */
+  java.lang.String getUid();
+  /**
+   * <code>string uid = 2;</code>
+   * @return The bytes for uid.
+   */
+  com.google.protobuf.ByteString
+      getUidBytes();
+
+  /**
+   * <code>int32 app_type = 3;</code>
+   * @return The appType.
+   */
+  int getAppType();
+
+  /**
+   * <code>string group = 4;</code>
+   * @return The group.
+   */
+  java.lang.String getGroup();
+  /**
+   * <code>string group = 4;</code>
+   * @return The bytes for group.
+   */
+  com.google.protobuf.ByteString
+      getGroupBytes();
+}

+ 893 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/GetExpResponse.java

@@ -0,0 +1,893 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/abtest/exp.proto
+
+package com.tzld.piaoquan.abtest.client.model;
+
+/**
+ * Protobuf type {@code GetExpResponse}
+ */
+public final class GetExpResponse extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:GetExpResponse)
+    GetExpResponseOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use GetExpResponse.newBuilder() to construct.
+  private GetExpResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private GetExpResponse() {
+  }
+
+  @java.lang.Override
+  @SuppressWarnings({"unused"})
+  protected java.lang.Object newInstance(
+      UnusedPrivateParameter unused) {
+    return new GetExpResponse();
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private GetExpResponse(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          case 10: {
+            com.tzld.piaoquan.abtest.client.model.Result.Builder subBuilder = null;
+            if (result_ != null) {
+              subBuilder = result_.toBuilder();
+            }
+            result_ = input.readMessage(com.tzld.piaoquan.abtest.client.model.Result.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(result_);
+              result_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 18: {
+            if (!((mutable_bitField0_ & 0x00000001) != 0)) {
+              exp_ = com.google.protobuf.MapField.newMapField(
+                  ExpDefaultEntryHolder.defaultEntry);
+              mutable_bitField0_ |= 0x00000001;
+            }
+            com.google.protobuf.MapEntry<java.lang.String, java.lang.String>
+            exp__ = input.readMessage(
+                ExpDefaultEntryHolder.defaultEntry.getParserForType(), extensionRegistry);
+            exp_.getMutableMap().put(
+                exp__.getKey(), exp__.getValue());
+            break;
+          }
+          default: {
+            if (!parseUnknownField(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return com.tzld.piaoquan.abtest.client.model.Exp.internal_static_GetExpResponse_descriptor;
+  }
+
+  @SuppressWarnings({"rawtypes"})
+  @java.lang.Override
+  protected com.google.protobuf.MapField internalGetMapField(
+      int number) {
+    switch (number) {
+      case 2:
+        return internalGetExp();
+      default:
+        throw new RuntimeException(
+            "Invalid map field number: " + number);
+    }
+  }
+  @java.lang.Override
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return com.tzld.piaoquan.abtest.client.model.Exp.internal_static_GetExpResponse_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            com.tzld.piaoquan.abtest.client.model.GetExpResponse.class, com.tzld.piaoquan.abtest.client.model.GetExpResponse.Builder.class);
+  }
+
+  public static final int RESULT_FIELD_NUMBER = 1;
+  private com.tzld.piaoquan.abtest.client.model.Result result_;
+  /**
+   * <code>.Result result = 1;</code>
+   * @return Whether the result field is set.
+   */
+  @java.lang.Override
+  public boolean hasResult() {
+    return result_ != null;
+  }
+  /**
+   * <code>.Result result = 1;</code>
+   * @return The result.
+   */
+  @java.lang.Override
+  public com.tzld.piaoquan.abtest.client.model.Result getResult() {
+    return result_ == null ? com.tzld.piaoquan.abtest.client.model.Result.getDefaultInstance() : result_;
+  }
+  /**
+   * <code>.Result result = 1;</code>
+   */
+  @java.lang.Override
+  public com.tzld.piaoquan.abtest.client.model.ResultOrBuilder getResultOrBuilder() {
+    return getResult();
+  }
+
+  public static final int EXP_FIELD_NUMBER = 2;
+  private static final class ExpDefaultEntryHolder {
+    static final com.google.protobuf.MapEntry<
+        java.lang.String, java.lang.String> defaultEntry =
+            com.google.protobuf.MapEntry
+            .<java.lang.String, java.lang.String>newDefaultInstance(
+                com.tzld.piaoquan.abtest.client.model.Exp.internal_static_GetExpResponse_ExpEntry_descriptor, 
+                com.google.protobuf.WireFormat.FieldType.STRING,
+                "",
+                com.google.protobuf.WireFormat.FieldType.STRING,
+                "");
+  }
+  private com.google.protobuf.MapField<
+      java.lang.String, java.lang.String> exp_;
+  private com.google.protobuf.MapField<java.lang.String, java.lang.String>
+  internalGetExp() {
+    if (exp_ == null) {
+      return com.google.protobuf.MapField.emptyMapField(
+          ExpDefaultEntryHolder.defaultEntry);
+    }
+    return exp_;
+  }
+
+  public int getExpCount() {
+    return internalGetExp().getMap().size();
+  }
+  /**
+   * <code>map&lt;string, string&gt; exp = 2;</code>
+   */
+
+  @java.lang.Override
+  public boolean containsExp(
+      java.lang.String key) {
+    if (key == null) { throw new java.lang.NullPointerException(); }
+    return internalGetExp().getMap().containsKey(key);
+  }
+  /**
+   * Use {@link #getExpMap()} instead.
+   */
+  @java.lang.Override
+  @java.lang.Deprecated
+  public java.util.Map<java.lang.String, java.lang.String> getExp() {
+    return getExpMap();
+  }
+  /**
+   * <code>map&lt;string, string&gt; exp = 2;</code>
+   */
+  @java.lang.Override
+
+  public java.util.Map<java.lang.String, java.lang.String> getExpMap() {
+    return internalGetExp().getMap();
+  }
+  /**
+   * <code>map&lt;string, string&gt; exp = 2;</code>
+   */
+  @java.lang.Override
+
+  public java.lang.String getExpOrDefault(
+      java.lang.String key,
+      java.lang.String defaultValue) {
+    if (key == null) { throw new java.lang.NullPointerException(); }
+    java.util.Map<java.lang.String, java.lang.String> map =
+        internalGetExp().getMap();
+    return map.containsKey(key) ? map.get(key) : defaultValue;
+  }
+  /**
+   * <code>map&lt;string, string&gt; exp = 2;</code>
+   */
+  @java.lang.Override
+
+  public java.lang.String getExpOrThrow(
+      java.lang.String key) {
+    if (key == null) { throw new java.lang.NullPointerException(); }
+    java.util.Map<java.lang.String, java.lang.String> map =
+        internalGetExp().getMap();
+    if (!map.containsKey(key)) {
+      throw new java.lang.IllegalArgumentException();
+    }
+    return map.get(key);
+  }
+
+  private byte memoizedIsInitialized = -1;
+  @java.lang.Override
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  @java.lang.Override
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (result_ != null) {
+      output.writeMessage(1, getResult());
+    }
+    com.google.protobuf.GeneratedMessageV3
+      .serializeStringMapTo(
+        output,
+        internalGetExp(),
+        ExpDefaultEntryHolder.defaultEntry,
+        2);
+    unknownFields.writeTo(output);
+  }
+
+  @java.lang.Override
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (result_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, getResult());
+    }
+    for (java.util.Map.Entry<java.lang.String, java.lang.String> entry
+         : internalGetExp().getMap().entrySet()) {
+      com.google.protobuf.MapEntry<java.lang.String, java.lang.String>
+      exp__ = ExpDefaultEntryHolder.defaultEntry.newBuilderForType()
+          .setKey(entry.getKey())
+          .setValue(entry.getValue())
+          .build();
+      size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(2, exp__);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof com.tzld.piaoquan.abtest.client.model.GetExpResponse)) {
+      return super.equals(obj);
+    }
+    com.tzld.piaoquan.abtest.client.model.GetExpResponse other = (com.tzld.piaoquan.abtest.client.model.GetExpResponse) obj;
+
+    if (hasResult() != other.hasResult()) return false;
+    if (hasResult()) {
+      if (!getResult()
+          .equals(other.getResult())) return false;
+    }
+    if (!internalGetExp().equals(
+        other.internalGetExp())) return false;
+    if (!unknownFields.equals(other.unknownFields)) return false;
+    return true;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasResult()) {
+      hash = (37 * hash) + RESULT_FIELD_NUMBER;
+      hash = (53 * hash) + getResult().hashCode();
+    }
+    if (!internalGetExp().getMap().isEmpty()) {
+      hash = (37 * hash) + EXP_FIELD_NUMBER;
+      hash = (53 * hash) + internalGetExp().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static com.tzld.piaoquan.abtest.client.model.GetExpResponse parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpResponse parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpResponse parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpResponse parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpResponse parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpResponse parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpResponse parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpResponse parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpResponse parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpResponse parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpResponse parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetExpResponse parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  @java.lang.Override
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(com.tzld.piaoquan.abtest.client.model.GetExpResponse prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  @java.lang.Override
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code GetExpResponse}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:GetExpResponse)
+      com.tzld.piaoquan.abtest.client.model.GetExpResponseOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return com.tzld.piaoquan.abtest.client.model.Exp.internal_static_GetExpResponse_descriptor;
+    }
+
+    @SuppressWarnings({"rawtypes"})
+    protected com.google.protobuf.MapField internalGetMapField(
+        int number) {
+      switch (number) {
+        case 2:
+          return internalGetExp();
+        default:
+          throw new RuntimeException(
+              "Invalid map field number: " + number);
+      }
+    }
+    @SuppressWarnings({"rawtypes"})
+    protected com.google.protobuf.MapField internalGetMutableMapField(
+        int number) {
+      switch (number) {
+        case 2:
+          return internalGetMutableExp();
+        default:
+          throw new RuntimeException(
+              "Invalid map field number: " + number);
+      }
+    }
+    @java.lang.Override
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return com.tzld.piaoquan.abtest.client.model.Exp.internal_static_GetExpResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              com.tzld.piaoquan.abtest.client.model.GetExpResponse.class, com.tzld.piaoquan.abtest.client.model.GetExpResponse.Builder.class);
+    }
+
+    // Construct using com.tzld.piaoquan.abtest.client.model.GetExpResponse.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    @java.lang.Override
+    public Builder clear() {
+      super.clear();
+      if (resultBuilder_ == null) {
+        result_ = null;
+      } else {
+        result_ = null;
+        resultBuilder_ = null;
+      }
+      internalGetMutableExp().clear();
+      return this;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return com.tzld.piaoquan.abtest.client.model.Exp.internal_static_GetExpResponse_descriptor;
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.abtest.client.model.GetExpResponse getDefaultInstanceForType() {
+      return com.tzld.piaoquan.abtest.client.model.GetExpResponse.getDefaultInstance();
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.abtest.client.model.GetExpResponse build() {
+      com.tzld.piaoquan.abtest.client.model.GetExpResponse result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.abtest.client.model.GetExpResponse buildPartial() {
+      com.tzld.piaoquan.abtest.client.model.GetExpResponse result = new com.tzld.piaoquan.abtest.client.model.GetExpResponse(this);
+      int from_bitField0_ = bitField0_;
+      if (resultBuilder_ == null) {
+        result.result_ = result_;
+      } else {
+        result.result_ = resultBuilder_.build();
+      }
+      result.exp_ = internalGetExp();
+      result.exp_.makeImmutable();
+      onBuilt();
+      return result;
+    }
+
+    @java.lang.Override
+    public Builder clone() {
+      return super.clone();
+    }
+    @java.lang.Override
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return super.setField(field, value);
+    }
+    @java.lang.Override
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return super.clearField(field);
+    }
+    @java.lang.Override
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return super.clearOneof(oneof);
+    }
+    @java.lang.Override
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return super.setRepeatedField(field, index, value);
+    }
+    @java.lang.Override
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return super.addRepeatedField(field, value);
+    }
+    @java.lang.Override
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof com.tzld.piaoquan.abtest.client.model.GetExpResponse) {
+        return mergeFrom((com.tzld.piaoquan.abtest.client.model.GetExpResponse)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(com.tzld.piaoquan.abtest.client.model.GetExpResponse other) {
+      if (other == com.tzld.piaoquan.abtest.client.model.GetExpResponse.getDefaultInstance()) return this;
+      if (other.hasResult()) {
+        mergeResult(other.getResult());
+      }
+      internalGetMutableExp().mergeFrom(
+          other.internalGetExp());
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    @java.lang.Override
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    @java.lang.Override
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      com.tzld.piaoquan.abtest.client.model.GetExpResponse parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (com.tzld.piaoquan.abtest.client.model.GetExpResponse) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private com.tzld.piaoquan.abtest.client.model.Result result_;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.tzld.piaoquan.abtest.client.model.Result, com.tzld.piaoquan.abtest.client.model.Result.Builder, com.tzld.piaoquan.abtest.client.model.ResultOrBuilder> resultBuilder_;
+    /**
+     * <code>.Result result = 1;</code>
+     * @return Whether the result field is set.
+     */
+    public boolean hasResult() {
+      return resultBuilder_ != null || result_ != null;
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     * @return The result.
+     */
+    public com.tzld.piaoquan.abtest.client.model.Result getResult() {
+      if (resultBuilder_ == null) {
+        return result_ == null ? com.tzld.piaoquan.abtest.client.model.Result.getDefaultInstance() : result_;
+      } else {
+        return resultBuilder_.getMessage();
+      }
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    public Builder setResult(com.tzld.piaoquan.abtest.client.model.Result value) {
+      if (resultBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        result_ = value;
+        onChanged();
+      } else {
+        resultBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    public Builder setResult(
+        com.tzld.piaoquan.abtest.client.model.Result.Builder builderForValue) {
+      if (resultBuilder_ == null) {
+        result_ = builderForValue.build();
+        onChanged();
+      } else {
+        resultBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    public Builder mergeResult(com.tzld.piaoquan.abtest.client.model.Result value) {
+      if (resultBuilder_ == null) {
+        if (result_ != null) {
+          result_ =
+            com.tzld.piaoquan.abtest.client.model.Result.newBuilder(result_).mergeFrom(value).buildPartial();
+        } else {
+          result_ = value;
+        }
+        onChanged();
+      } else {
+        resultBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    public Builder clearResult() {
+      if (resultBuilder_ == null) {
+        result_ = null;
+        onChanged();
+      } else {
+        result_ = null;
+        resultBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    public com.tzld.piaoquan.abtest.client.model.Result.Builder getResultBuilder() {
+      
+      onChanged();
+      return getResultFieldBuilder().getBuilder();
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    public com.tzld.piaoquan.abtest.client.model.ResultOrBuilder getResultOrBuilder() {
+      if (resultBuilder_ != null) {
+        return resultBuilder_.getMessageOrBuilder();
+      } else {
+        return result_ == null ?
+            com.tzld.piaoquan.abtest.client.model.Result.getDefaultInstance() : result_;
+      }
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.tzld.piaoquan.abtest.client.model.Result, com.tzld.piaoquan.abtest.client.model.Result.Builder, com.tzld.piaoquan.abtest.client.model.ResultOrBuilder> 
+        getResultFieldBuilder() {
+      if (resultBuilder_ == null) {
+        resultBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            com.tzld.piaoquan.abtest.client.model.Result, com.tzld.piaoquan.abtest.client.model.Result.Builder, com.tzld.piaoquan.abtest.client.model.ResultOrBuilder>(
+                getResult(),
+                getParentForChildren(),
+                isClean());
+        result_ = null;
+      }
+      return resultBuilder_;
+    }
+
+    private com.google.protobuf.MapField<
+        java.lang.String, java.lang.String> exp_;
+    private com.google.protobuf.MapField<java.lang.String, java.lang.String>
+    internalGetExp() {
+      if (exp_ == null) {
+        return com.google.protobuf.MapField.emptyMapField(
+            ExpDefaultEntryHolder.defaultEntry);
+      }
+      return exp_;
+    }
+    private com.google.protobuf.MapField<java.lang.String, java.lang.String>
+    internalGetMutableExp() {
+      onChanged();;
+      if (exp_ == null) {
+        exp_ = com.google.protobuf.MapField.newMapField(
+            ExpDefaultEntryHolder.defaultEntry);
+      }
+      if (!exp_.isMutable()) {
+        exp_ = exp_.copy();
+      }
+      return exp_;
+    }
+
+    public int getExpCount() {
+      return internalGetExp().getMap().size();
+    }
+    /**
+     * <code>map&lt;string, string&gt; exp = 2;</code>
+     */
+
+    @java.lang.Override
+    public boolean containsExp(
+        java.lang.String key) {
+      if (key == null) { throw new java.lang.NullPointerException(); }
+      return internalGetExp().getMap().containsKey(key);
+    }
+    /**
+     * Use {@link #getExpMap()} instead.
+     */
+    @java.lang.Override
+    @java.lang.Deprecated
+    public java.util.Map<java.lang.String, java.lang.String> getExp() {
+      return getExpMap();
+    }
+    /**
+     * <code>map&lt;string, string&gt; exp = 2;</code>
+     */
+    @java.lang.Override
+
+    public java.util.Map<java.lang.String, java.lang.String> getExpMap() {
+      return internalGetExp().getMap();
+    }
+    /**
+     * <code>map&lt;string, string&gt; exp = 2;</code>
+     */
+    @java.lang.Override
+
+    public java.lang.String getExpOrDefault(
+        java.lang.String key,
+        java.lang.String defaultValue) {
+      if (key == null) { throw new java.lang.NullPointerException(); }
+      java.util.Map<java.lang.String, java.lang.String> map =
+          internalGetExp().getMap();
+      return map.containsKey(key) ? map.get(key) : defaultValue;
+    }
+    /**
+     * <code>map&lt;string, string&gt; exp = 2;</code>
+     */
+    @java.lang.Override
+
+    public java.lang.String getExpOrThrow(
+        java.lang.String key) {
+      if (key == null) { throw new java.lang.NullPointerException(); }
+      java.util.Map<java.lang.String, java.lang.String> map =
+          internalGetExp().getMap();
+      if (!map.containsKey(key)) {
+        throw new java.lang.IllegalArgumentException();
+      }
+      return map.get(key);
+    }
+
+    public Builder clearExp() {
+      internalGetMutableExp().getMutableMap()
+          .clear();
+      return this;
+    }
+    /**
+     * <code>map&lt;string, string&gt; exp = 2;</code>
+     */
+
+    public Builder removeExp(
+        java.lang.String key) {
+      if (key == null) { throw new java.lang.NullPointerException(); }
+      internalGetMutableExp().getMutableMap()
+          .remove(key);
+      return this;
+    }
+    /**
+     * Use alternate mutation accessors instead.
+     */
+    @java.lang.Deprecated
+    public java.util.Map<java.lang.String, java.lang.String>
+    getMutableExp() {
+      return internalGetMutableExp().getMutableMap();
+    }
+    /**
+     * <code>map&lt;string, string&gt; exp = 2;</code>
+     */
+    public Builder putExp(
+        java.lang.String key,
+        java.lang.String value) {
+      if (key == null) { throw new java.lang.NullPointerException(); }
+      if (value == null) { throw new java.lang.NullPointerException(); }
+      internalGetMutableExp().getMutableMap()
+          .put(key, value);
+      return this;
+    }
+    /**
+     * <code>map&lt;string, string&gt; exp = 2;</code>
+     */
+
+    public Builder putAllExp(
+        java.util.Map<java.lang.String, java.lang.String> values) {
+      internalGetMutableExp().getMutableMap()
+          .putAll(values);
+      return this;
+    }
+    @java.lang.Override
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFields(unknownFields);
+    }
+
+    @java.lang.Override
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:GetExpResponse)
+  }
+
+  // @@protoc_insertion_point(class_scope:GetExpResponse)
+  private static final com.tzld.piaoquan.abtest.client.model.GetExpResponse DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new com.tzld.piaoquan.abtest.client.model.GetExpResponse();
+  }
+
+  public static com.tzld.piaoquan.abtest.client.model.GetExpResponse getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<GetExpResponse>
+      PARSER = new com.google.protobuf.AbstractParser<GetExpResponse>() {
+    @java.lang.Override
+    public GetExpResponse parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new GetExpResponse(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<GetExpResponse> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<GetExpResponse> getParserForType() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.tzld.piaoquan.abtest.client.model.GetExpResponse getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+

+ 58 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/GetExpResponseOrBuilder.java

@@ -0,0 +1,58 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/abtest/exp.proto
+
+package com.tzld.piaoquan.abtest.client.model;
+
+public interface GetExpResponseOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:GetExpResponse)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>.Result result = 1;</code>
+   * @return Whether the result field is set.
+   */
+  boolean hasResult();
+  /**
+   * <code>.Result result = 1;</code>
+   * @return The result.
+   */
+  com.tzld.piaoquan.abtest.client.model.Result getResult();
+  /**
+   * <code>.Result result = 1;</code>
+   */
+  com.tzld.piaoquan.abtest.client.model.ResultOrBuilder getResultOrBuilder();
+
+  /**
+   * <code>map&lt;string, string&gt; exp = 2;</code>
+   */
+  int getExpCount();
+  /**
+   * <code>map&lt;string, string&gt; exp = 2;</code>
+   */
+  boolean containsExp(
+      java.lang.String key);
+  /**
+   * Use {@link #getExpMap()} instead.
+   */
+  @java.lang.Deprecated
+  java.util.Map<java.lang.String, java.lang.String>
+  getExp();
+  /**
+   * <code>map&lt;string, string&gt; exp = 2;</code>
+   */
+  java.util.Map<java.lang.String, java.lang.String>
+  getExpMap();
+  /**
+   * <code>map&lt;string, string&gt; exp = 2;</code>
+   */
+
+  java.lang.String getExpOrDefault(
+      java.lang.String key,
+      java.lang.String defaultValue);
+  /**
+   * <code>map&lt;string, string&gt; exp = 2;</code>
+   */
+
+  java.lang.String getExpOrThrow(
+      java.lang.String key);
+}

+ 897 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/GetGroupRequest.java

@@ -0,0 +1,897 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/abtest/group.proto
+
+package com.tzld.piaoquan.abtest.client.model;
+
+/**
+ * Protobuf type {@code GetGroupRequest}
+ */
+public final class GetGroupRequest extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:GetGroupRequest)
+    GetGroupRequestOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use GetGroupRequest.newBuilder() to construct.
+  private GetGroupRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private GetGroupRequest() {
+    mid_ = "";
+    uid_ = "";
+    group_ = "";
+  }
+
+  @java.lang.Override
+  @SuppressWarnings({"unused"})
+  protected java.lang.Object newInstance(
+      UnusedPrivateParameter unused) {
+    return new GetGroupRequest();
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private GetGroupRequest(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          case 10: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            mid_ = s;
+            break;
+          }
+          case 18: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            uid_ = s;
+            break;
+          }
+          case 24: {
+
+            appType_ = input.readInt32();
+            break;
+          }
+          case 34: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            group_ = s;
+            break;
+          }
+          default: {
+            if (!parseUnknownField(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return com.tzld.piaoquan.abtest.client.model.Group.internal_static_GetGroupRequest_descriptor;
+  }
+
+  @java.lang.Override
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return com.tzld.piaoquan.abtest.client.model.Group.internal_static_GetGroupRequest_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            com.tzld.piaoquan.abtest.client.model.GetGroupRequest.class, com.tzld.piaoquan.abtest.client.model.GetGroupRequest.Builder.class);
+  }
+
+  public static final int MID_FIELD_NUMBER = 1;
+  private volatile java.lang.Object mid_;
+  /**
+   * <code>string mid = 1;</code>
+   * @return The mid.
+   */
+  @java.lang.Override
+  public java.lang.String getMid() {
+    java.lang.Object ref = mid_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      mid_ = s;
+      return s;
+    }
+  }
+  /**
+   * <code>string mid = 1;</code>
+   * @return The bytes for mid.
+   */
+  @java.lang.Override
+  public com.google.protobuf.ByteString
+      getMidBytes() {
+    java.lang.Object ref = mid_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      mid_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int UID_FIELD_NUMBER = 2;
+  private volatile java.lang.Object uid_;
+  /**
+   * <code>string uid = 2;</code>
+   * @return The uid.
+   */
+  @java.lang.Override
+  public java.lang.String getUid() {
+    java.lang.Object ref = uid_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      uid_ = s;
+      return s;
+    }
+  }
+  /**
+   * <code>string uid = 2;</code>
+   * @return The bytes for uid.
+   */
+  @java.lang.Override
+  public com.google.protobuf.ByteString
+      getUidBytes() {
+    java.lang.Object ref = uid_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      uid_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int APP_TYPE_FIELD_NUMBER = 3;
+  private int appType_;
+  /**
+   * <code>int32 app_type = 3;</code>
+   * @return The appType.
+   */
+  @java.lang.Override
+  public int getAppType() {
+    return appType_;
+  }
+
+  public static final int GROUP_FIELD_NUMBER = 4;
+  private volatile java.lang.Object group_;
+  /**
+   * <code>string group = 4;</code>
+   * @return The group.
+   */
+  @java.lang.Override
+  public java.lang.String getGroup() {
+    java.lang.Object ref = group_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      group_ = s;
+      return s;
+    }
+  }
+  /**
+   * <code>string group = 4;</code>
+   * @return The bytes for group.
+   */
+  @java.lang.Override
+  public com.google.protobuf.ByteString
+      getGroupBytes() {
+    java.lang.Object ref = group_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      group_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  @java.lang.Override
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  @java.lang.Override
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (!getMidBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, mid_);
+    }
+    if (!getUidBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 2, uid_);
+    }
+    if (appType_ != 0) {
+      output.writeInt32(3, appType_);
+    }
+    if (!getGroupBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 4, group_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  @java.lang.Override
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (!getMidBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, mid_);
+    }
+    if (!getUidBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, uid_);
+    }
+    if (appType_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt32Size(3, appType_);
+    }
+    if (!getGroupBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(4, group_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof com.tzld.piaoquan.abtest.client.model.GetGroupRequest)) {
+      return super.equals(obj);
+    }
+    com.tzld.piaoquan.abtest.client.model.GetGroupRequest other = (com.tzld.piaoquan.abtest.client.model.GetGroupRequest) obj;
+
+    if (!getMid()
+        .equals(other.getMid())) return false;
+    if (!getUid()
+        .equals(other.getUid())) return false;
+    if (getAppType()
+        != other.getAppType()) return false;
+    if (!getGroup()
+        .equals(other.getGroup())) return false;
+    if (!unknownFields.equals(other.unknownFields)) return false;
+    return true;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + MID_FIELD_NUMBER;
+    hash = (53 * hash) + getMid().hashCode();
+    hash = (37 * hash) + UID_FIELD_NUMBER;
+    hash = (53 * hash) + getUid().hashCode();
+    hash = (37 * hash) + APP_TYPE_FIELD_NUMBER;
+    hash = (53 * hash) + getAppType();
+    hash = (37 * hash) + GROUP_FIELD_NUMBER;
+    hash = (53 * hash) + getGroup().hashCode();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupRequest parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupRequest parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupRequest parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupRequest parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupRequest parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupRequest parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupRequest parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupRequest parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupRequest parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupRequest parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupRequest parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupRequest parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  @java.lang.Override
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(com.tzld.piaoquan.abtest.client.model.GetGroupRequest prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  @java.lang.Override
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code GetGroupRequest}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:GetGroupRequest)
+      com.tzld.piaoquan.abtest.client.model.GetGroupRequestOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return com.tzld.piaoquan.abtest.client.model.Group.internal_static_GetGroupRequest_descriptor;
+    }
+
+    @java.lang.Override
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return com.tzld.piaoquan.abtest.client.model.Group.internal_static_GetGroupRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              com.tzld.piaoquan.abtest.client.model.GetGroupRequest.class, com.tzld.piaoquan.abtest.client.model.GetGroupRequest.Builder.class);
+    }
+
+    // Construct using com.tzld.piaoquan.abtest.client.model.GetGroupRequest.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    @java.lang.Override
+    public Builder clear() {
+      super.clear();
+      mid_ = "";
+
+      uid_ = "";
+
+      appType_ = 0;
+
+      group_ = "";
+
+      return this;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return com.tzld.piaoquan.abtest.client.model.Group.internal_static_GetGroupRequest_descriptor;
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.abtest.client.model.GetGroupRequest getDefaultInstanceForType() {
+      return com.tzld.piaoquan.abtest.client.model.GetGroupRequest.getDefaultInstance();
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.abtest.client.model.GetGroupRequest build() {
+      com.tzld.piaoquan.abtest.client.model.GetGroupRequest result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.abtest.client.model.GetGroupRequest buildPartial() {
+      com.tzld.piaoquan.abtest.client.model.GetGroupRequest result = new com.tzld.piaoquan.abtest.client.model.GetGroupRequest(this);
+      result.mid_ = mid_;
+      result.uid_ = uid_;
+      result.appType_ = appType_;
+      result.group_ = group_;
+      onBuilt();
+      return result;
+    }
+
+    @java.lang.Override
+    public Builder clone() {
+      return super.clone();
+    }
+    @java.lang.Override
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return super.setField(field, value);
+    }
+    @java.lang.Override
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return super.clearField(field);
+    }
+    @java.lang.Override
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return super.clearOneof(oneof);
+    }
+    @java.lang.Override
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return super.setRepeatedField(field, index, value);
+    }
+    @java.lang.Override
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return super.addRepeatedField(field, value);
+    }
+    @java.lang.Override
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof com.tzld.piaoquan.abtest.client.model.GetGroupRequest) {
+        return mergeFrom((com.tzld.piaoquan.abtest.client.model.GetGroupRequest)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(com.tzld.piaoquan.abtest.client.model.GetGroupRequest other) {
+      if (other == com.tzld.piaoquan.abtest.client.model.GetGroupRequest.getDefaultInstance()) return this;
+      if (!other.getMid().isEmpty()) {
+        mid_ = other.mid_;
+        onChanged();
+      }
+      if (!other.getUid().isEmpty()) {
+        uid_ = other.uid_;
+        onChanged();
+      }
+      if (other.getAppType() != 0) {
+        setAppType(other.getAppType());
+      }
+      if (!other.getGroup().isEmpty()) {
+        group_ = other.group_;
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    @java.lang.Override
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    @java.lang.Override
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      com.tzld.piaoquan.abtest.client.model.GetGroupRequest parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (com.tzld.piaoquan.abtest.client.model.GetGroupRequest) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private java.lang.Object mid_ = "";
+    /**
+     * <code>string mid = 1;</code>
+     * @return The mid.
+     */
+    public java.lang.String getMid() {
+      java.lang.Object ref = mid_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        mid_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>string mid = 1;</code>
+     * @return The bytes for mid.
+     */
+    public com.google.protobuf.ByteString
+        getMidBytes() {
+      java.lang.Object ref = mid_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        mid_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>string mid = 1;</code>
+     * @param value The mid to set.
+     * @return This builder for chaining.
+     */
+    public Builder setMid(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      mid_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string mid = 1;</code>
+     * @return This builder for chaining.
+     */
+    public Builder clearMid() {
+      
+      mid_ = getDefaultInstance().getMid();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string mid = 1;</code>
+     * @param value The bytes for mid to set.
+     * @return This builder for chaining.
+     */
+    public Builder setMidBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      mid_ = value;
+      onChanged();
+      return this;
+    }
+
+    private java.lang.Object uid_ = "";
+    /**
+     * <code>string uid = 2;</code>
+     * @return The uid.
+     */
+    public java.lang.String getUid() {
+      java.lang.Object ref = uid_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        uid_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>string uid = 2;</code>
+     * @return The bytes for uid.
+     */
+    public com.google.protobuf.ByteString
+        getUidBytes() {
+      java.lang.Object ref = uid_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        uid_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>string uid = 2;</code>
+     * @param value The uid to set.
+     * @return This builder for chaining.
+     */
+    public Builder setUid(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      uid_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string uid = 2;</code>
+     * @return This builder for chaining.
+     */
+    public Builder clearUid() {
+      
+      uid_ = getDefaultInstance().getUid();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string uid = 2;</code>
+     * @param value The bytes for uid to set.
+     * @return This builder for chaining.
+     */
+    public Builder setUidBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      uid_ = value;
+      onChanged();
+      return this;
+    }
+
+    private int appType_ ;
+    /**
+     * <code>int32 app_type = 3;</code>
+     * @return The appType.
+     */
+    @java.lang.Override
+    public int getAppType() {
+      return appType_;
+    }
+    /**
+     * <code>int32 app_type = 3;</code>
+     * @param value The appType to set.
+     * @return This builder for chaining.
+     */
+    public Builder setAppType(int value) {
+      
+      appType_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>int32 app_type = 3;</code>
+     * @return This builder for chaining.
+     */
+    public Builder clearAppType() {
+      
+      appType_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private java.lang.Object group_ = "";
+    /**
+     * <code>string group = 4;</code>
+     * @return The group.
+     */
+    public java.lang.String getGroup() {
+      java.lang.Object ref = group_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        group_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>string group = 4;</code>
+     * @return The bytes for group.
+     */
+    public com.google.protobuf.ByteString
+        getGroupBytes() {
+      java.lang.Object ref = group_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        group_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>string group = 4;</code>
+     * @param value The group to set.
+     * @return This builder for chaining.
+     */
+    public Builder setGroup(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      group_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string group = 4;</code>
+     * @return This builder for chaining.
+     */
+    public Builder clearGroup() {
+      
+      group_ = getDefaultInstance().getGroup();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string group = 4;</code>
+     * @param value The bytes for group to set.
+     * @return This builder for chaining.
+     */
+    public Builder setGroupBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      group_ = value;
+      onChanged();
+      return this;
+    }
+    @java.lang.Override
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFields(unknownFields);
+    }
+
+    @java.lang.Override
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:GetGroupRequest)
+  }
+
+  // @@protoc_insertion_point(class_scope:GetGroupRequest)
+  private static final com.tzld.piaoquan.abtest.client.model.GetGroupRequest DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new com.tzld.piaoquan.abtest.client.model.GetGroupRequest();
+  }
+
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupRequest getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<GetGroupRequest>
+      PARSER = new com.google.protobuf.AbstractParser<GetGroupRequest>() {
+    @java.lang.Override
+    public GetGroupRequest parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new GetGroupRequest(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<GetGroupRequest> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<GetGroupRequest> getParserForType() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.tzld.piaoquan.abtest.client.model.GetGroupRequest getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+

+ 51 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/GetGroupRequestOrBuilder.java

@@ -0,0 +1,51 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/abtest/group.proto
+
+package com.tzld.piaoquan.abtest.client.model;
+
+public interface GetGroupRequestOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:GetGroupRequest)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>string mid = 1;</code>
+   * @return The mid.
+   */
+  java.lang.String getMid();
+  /**
+   * <code>string mid = 1;</code>
+   * @return The bytes for mid.
+   */
+  com.google.protobuf.ByteString
+      getMidBytes();
+
+  /**
+   * <code>string uid = 2;</code>
+   * @return The uid.
+   */
+  java.lang.String getUid();
+  /**
+   * <code>string uid = 2;</code>
+   * @return The bytes for uid.
+   */
+  com.google.protobuf.ByteString
+      getUidBytes();
+
+  /**
+   * <code>int32 app_type = 3;</code>
+   * @return The appType.
+   */
+  int getAppType();
+
+  /**
+   * <code>string group = 4;</code>
+   * @return The group.
+   */
+  java.lang.String getGroup();
+  /**
+   * <code>string group = 4;</code>
+   * @return The bytes for group.
+   */
+  com.google.protobuf.ByteString
+      getGroupBytes();
+}

+ 745 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/GetGroupResponse.java

@@ -0,0 +1,745 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/abtest/group.proto
+
+package com.tzld.piaoquan.abtest.client.model;
+
+/**
+ * Protobuf type {@code GetGroupResponse}
+ */
+public final class GetGroupResponse extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:GetGroupResponse)
+    GetGroupResponseOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use GetGroupResponse.newBuilder() to construct.
+  private GetGroupResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private GetGroupResponse() {
+    group_ = "";
+  }
+
+  @java.lang.Override
+  @SuppressWarnings({"unused"})
+  protected java.lang.Object newInstance(
+      UnusedPrivateParameter unused) {
+    return new GetGroupResponse();
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private GetGroupResponse(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          case 10: {
+            com.tzld.piaoquan.abtest.client.model.Result.Builder subBuilder = null;
+            if (result_ != null) {
+              subBuilder = result_.toBuilder();
+            }
+            result_ = input.readMessage(com.tzld.piaoquan.abtest.client.model.Result.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(result_);
+              result_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 18: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            group_ = s;
+            break;
+          }
+          default: {
+            if (!parseUnknownField(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return com.tzld.piaoquan.abtest.client.model.Group.internal_static_GetGroupResponse_descriptor;
+  }
+
+  @java.lang.Override
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return com.tzld.piaoquan.abtest.client.model.Group.internal_static_GetGroupResponse_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            com.tzld.piaoquan.abtest.client.model.GetGroupResponse.class, com.tzld.piaoquan.abtest.client.model.GetGroupResponse.Builder.class);
+  }
+
+  public static final int RESULT_FIELD_NUMBER = 1;
+  private com.tzld.piaoquan.abtest.client.model.Result result_;
+  /**
+   * <code>.Result result = 1;</code>
+   * @return Whether the result field is set.
+   */
+  @java.lang.Override
+  public boolean hasResult() {
+    return result_ != null;
+  }
+  /**
+   * <code>.Result result = 1;</code>
+   * @return The result.
+   */
+  @java.lang.Override
+  public com.tzld.piaoquan.abtest.client.model.Result getResult() {
+    return result_ == null ? com.tzld.piaoquan.abtest.client.model.Result.getDefaultInstance() : result_;
+  }
+  /**
+   * <code>.Result result = 1;</code>
+   */
+  @java.lang.Override
+  public com.tzld.piaoquan.abtest.client.model.ResultOrBuilder getResultOrBuilder() {
+    return getResult();
+  }
+
+  public static final int GROUP_FIELD_NUMBER = 2;
+  private volatile java.lang.Object group_;
+  /**
+   * <code>string group = 2;</code>
+   * @return The group.
+   */
+  @java.lang.Override
+  public java.lang.String getGroup() {
+    java.lang.Object ref = group_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      group_ = s;
+      return s;
+    }
+  }
+  /**
+   * <code>string group = 2;</code>
+   * @return The bytes for group.
+   */
+  @java.lang.Override
+  public com.google.protobuf.ByteString
+      getGroupBytes() {
+    java.lang.Object ref = group_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      group_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  @java.lang.Override
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  @java.lang.Override
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (result_ != null) {
+      output.writeMessage(1, getResult());
+    }
+    if (!getGroupBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 2, group_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  @java.lang.Override
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (result_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, getResult());
+    }
+    if (!getGroupBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, group_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof com.tzld.piaoquan.abtest.client.model.GetGroupResponse)) {
+      return super.equals(obj);
+    }
+    com.tzld.piaoquan.abtest.client.model.GetGroupResponse other = (com.tzld.piaoquan.abtest.client.model.GetGroupResponse) obj;
+
+    if (hasResult() != other.hasResult()) return false;
+    if (hasResult()) {
+      if (!getResult()
+          .equals(other.getResult())) return false;
+    }
+    if (!getGroup()
+        .equals(other.getGroup())) return false;
+    if (!unknownFields.equals(other.unknownFields)) return false;
+    return true;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasResult()) {
+      hash = (37 * hash) + RESULT_FIELD_NUMBER;
+      hash = (53 * hash) + getResult().hashCode();
+    }
+    hash = (37 * hash) + GROUP_FIELD_NUMBER;
+    hash = (53 * hash) + getGroup().hashCode();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupResponse parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupResponse parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupResponse parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupResponse parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupResponse parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupResponse parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupResponse parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupResponse parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupResponse parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupResponse parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupResponse parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupResponse parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  @java.lang.Override
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(com.tzld.piaoquan.abtest.client.model.GetGroupResponse prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  @java.lang.Override
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code GetGroupResponse}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:GetGroupResponse)
+      com.tzld.piaoquan.abtest.client.model.GetGroupResponseOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return com.tzld.piaoquan.abtest.client.model.Group.internal_static_GetGroupResponse_descriptor;
+    }
+
+    @java.lang.Override
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return com.tzld.piaoquan.abtest.client.model.Group.internal_static_GetGroupResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              com.tzld.piaoquan.abtest.client.model.GetGroupResponse.class, com.tzld.piaoquan.abtest.client.model.GetGroupResponse.Builder.class);
+    }
+
+    // Construct using com.tzld.piaoquan.abtest.client.model.GetGroupResponse.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    @java.lang.Override
+    public Builder clear() {
+      super.clear();
+      if (resultBuilder_ == null) {
+        result_ = null;
+      } else {
+        result_ = null;
+        resultBuilder_ = null;
+      }
+      group_ = "";
+
+      return this;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return com.tzld.piaoquan.abtest.client.model.Group.internal_static_GetGroupResponse_descriptor;
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.abtest.client.model.GetGroupResponse getDefaultInstanceForType() {
+      return com.tzld.piaoquan.abtest.client.model.GetGroupResponse.getDefaultInstance();
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.abtest.client.model.GetGroupResponse build() {
+      com.tzld.piaoquan.abtest.client.model.GetGroupResponse result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.abtest.client.model.GetGroupResponse buildPartial() {
+      com.tzld.piaoquan.abtest.client.model.GetGroupResponse result = new com.tzld.piaoquan.abtest.client.model.GetGroupResponse(this);
+      if (resultBuilder_ == null) {
+        result.result_ = result_;
+      } else {
+        result.result_ = resultBuilder_.build();
+      }
+      result.group_ = group_;
+      onBuilt();
+      return result;
+    }
+
+    @java.lang.Override
+    public Builder clone() {
+      return super.clone();
+    }
+    @java.lang.Override
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return super.setField(field, value);
+    }
+    @java.lang.Override
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return super.clearField(field);
+    }
+    @java.lang.Override
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return super.clearOneof(oneof);
+    }
+    @java.lang.Override
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return super.setRepeatedField(field, index, value);
+    }
+    @java.lang.Override
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return super.addRepeatedField(field, value);
+    }
+    @java.lang.Override
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof com.tzld.piaoquan.abtest.client.model.GetGroupResponse) {
+        return mergeFrom((com.tzld.piaoquan.abtest.client.model.GetGroupResponse)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(com.tzld.piaoquan.abtest.client.model.GetGroupResponse other) {
+      if (other == com.tzld.piaoquan.abtest.client.model.GetGroupResponse.getDefaultInstance()) return this;
+      if (other.hasResult()) {
+        mergeResult(other.getResult());
+      }
+      if (!other.getGroup().isEmpty()) {
+        group_ = other.group_;
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    @java.lang.Override
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    @java.lang.Override
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      com.tzld.piaoquan.abtest.client.model.GetGroupResponse parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (com.tzld.piaoquan.abtest.client.model.GetGroupResponse) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private com.tzld.piaoquan.abtest.client.model.Result result_;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.tzld.piaoquan.abtest.client.model.Result, com.tzld.piaoquan.abtest.client.model.Result.Builder, com.tzld.piaoquan.abtest.client.model.ResultOrBuilder> resultBuilder_;
+    /**
+     * <code>.Result result = 1;</code>
+     * @return Whether the result field is set.
+     */
+    public boolean hasResult() {
+      return resultBuilder_ != null || result_ != null;
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     * @return The result.
+     */
+    public com.tzld.piaoquan.abtest.client.model.Result getResult() {
+      if (resultBuilder_ == null) {
+        return result_ == null ? com.tzld.piaoquan.abtest.client.model.Result.getDefaultInstance() : result_;
+      } else {
+        return resultBuilder_.getMessage();
+      }
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    public Builder setResult(com.tzld.piaoquan.abtest.client.model.Result value) {
+      if (resultBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        result_ = value;
+        onChanged();
+      } else {
+        resultBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    public Builder setResult(
+        com.tzld.piaoquan.abtest.client.model.Result.Builder builderForValue) {
+      if (resultBuilder_ == null) {
+        result_ = builderForValue.build();
+        onChanged();
+      } else {
+        resultBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    public Builder mergeResult(com.tzld.piaoquan.abtest.client.model.Result value) {
+      if (resultBuilder_ == null) {
+        if (result_ != null) {
+          result_ =
+            com.tzld.piaoquan.abtest.client.model.Result.newBuilder(result_).mergeFrom(value).buildPartial();
+        } else {
+          result_ = value;
+        }
+        onChanged();
+      } else {
+        resultBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    public Builder clearResult() {
+      if (resultBuilder_ == null) {
+        result_ = null;
+        onChanged();
+      } else {
+        result_ = null;
+        resultBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    public com.tzld.piaoquan.abtest.client.model.Result.Builder getResultBuilder() {
+      
+      onChanged();
+      return getResultFieldBuilder().getBuilder();
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    public com.tzld.piaoquan.abtest.client.model.ResultOrBuilder getResultOrBuilder() {
+      if (resultBuilder_ != null) {
+        return resultBuilder_.getMessageOrBuilder();
+      } else {
+        return result_ == null ?
+            com.tzld.piaoquan.abtest.client.model.Result.getDefaultInstance() : result_;
+      }
+    }
+    /**
+     * <code>.Result result = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.tzld.piaoquan.abtest.client.model.Result, com.tzld.piaoquan.abtest.client.model.Result.Builder, com.tzld.piaoquan.abtest.client.model.ResultOrBuilder> 
+        getResultFieldBuilder() {
+      if (resultBuilder_ == null) {
+        resultBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            com.tzld.piaoquan.abtest.client.model.Result, com.tzld.piaoquan.abtest.client.model.Result.Builder, com.tzld.piaoquan.abtest.client.model.ResultOrBuilder>(
+                getResult(),
+                getParentForChildren(),
+                isClean());
+        result_ = null;
+      }
+      return resultBuilder_;
+    }
+
+    private java.lang.Object group_ = "";
+    /**
+     * <code>string group = 2;</code>
+     * @return The group.
+     */
+    public java.lang.String getGroup() {
+      java.lang.Object ref = group_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        group_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>string group = 2;</code>
+     * @return The bytes for group.
+     */
+    public com.google.protobuf.ByteString
+        getGroupBytes() {
+      java.lang.Object ref = group_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        group_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>string group = 2;</code>
+     * @param value The group to set.
+     * @return This builder for chaining.
+     */
+    public Builder setGroup(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      group_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string group = 2;</code>
+     * @return This builder for chaining.
+     */
+    public Builder clearGroup() {
+      
+      group_ = getDefaultInstance().getGroup();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string group = 2;</code>
+     * @param value The bytes for group to set.
+     * @return This builder for chaining.
+     */
+    public Builder setGroupBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      group_ = value;
+      onChanged();
+      return this;
+    }
+    @java.lang.Override
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFields(unknownFields);
+    }
+
+    @java.lang.Override
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:GetGroupResponse)
+  }
+
+  // @@protoc_insertion_point(class_scope:GetGroupResponse)
+  private static final com.tzld.piaoquan.abtest.client.model.GetGroupResponse DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new com.tzld.piaoquan.abtest.client.model.GetGroupResponse();
+  }
+
+  public static com.tzld.piaoquan.abtest.client.model.GetGroupResponse getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<GetGroupResponse>
+      PARSER = new com.google.protobuf.AbstractParser<GetGroupResponse>() {
+    @java.lang.Override
+    public GetGroupResponse parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new GetGroupResponse(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<GetGroupResponse> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<GetGroupResponse> getParserForType() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.tzld.piaoquan.abtest.client.model.GetGroupResponse getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+

+ 36 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/GetGroupResponseOrBuilder.java

@@ -0,0 +1,36 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/abtest/group.proto
+
+package com.tzld.piaoquan.abtest.client.model;
+
+public interface GetGroupResponseOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:GetGroupResponse)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>.Result result = 1;</code>
+   * @return Whether the result field is set.
+   */
+  boolean hasResult();
+  /**
+   * <code>.Result result = 1;</code>
+   * @return The result.
+   */
+  com.tzld.piaoquan.abtest.client.model.Result getResult();
+  /**
+   * <code>.Result result = 1;</code>
+   */
+  com.tzld.piaoquan.abtest.client.model.ResultOrBuilder getResultOrBuilder();
+
+  /**
+   * <code>string group = 2;</code>
+   * @return The group.
+   */
+  java.lang.String getGroup();
+  /**
+   * <code>string group = 2;</code>
+   * @return The bytes for group.
+   */
+  com.google.protobuf.ByteString
+      getGroupBytes();
+}

+ 67 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/Group.java

@@ -0,0 +1,67 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/abtest/group.proto
+
+package com.tzld.piaoquan.abtest.client.model;
+
+public final class Group {
+  private Group() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_GetGroupRequest_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_GetGroupRequest_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_GetGroupResponse_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_GetGroupResponse_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n$com/tzld/piaoquan/abtest/group.proto\032%" +
+      "com/tzld/piaoquan/abtest/common.proto\"L\n" +
+      "\017GetGroupRequest\022\013\n\003mid\030\001 \001(\t\022\013\n\003uid\030\002 \001" +
+      "(\t\022\020\n\010app_type\030\003 \001(\005\022\r\n\005group\030\004 \001(\t\":\n\020G" +
+      "etGroupResponse\022\027\n\006result\030\001 \001(\0132\007.Result" +
+      "\022\r\n\005group\030\002 \001(\t2?\n\014GroupService\022/\n\010getGr" +
+      "oup\022\020.GetGroupRequest\032\021.GetGroupResponse" +
+      "B,\n%com.tzld.piaoquan.abtest.client.mode" +
+      "lP\001\210\001\001b\006proto3"
+    };
+    descriptor = com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+          com.tzld.piaoquan.abtest.client.model.Common.getDescriptor(),
+        });
+    internal_static_GetGroupRequest_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_GetGroupRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_GetGroupRequest_descriptor,
+        new java.lang.String[] { "Mid", "Uid", "AppType", "Group", });
+    internal_static_GetGroupResponse_descriptor =
+      getDescriptor().getMessageTypes().get(1);
+    internal_static_GetGroupResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_GetGroupResponse_descriptor,
+        new java.lang.String[] { "Result", "Group", });
+    com.tzld.piaoquan.abtest.client.model.Common.getDescriptor();
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}

+ 237 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/GroupService.java

@@ -0,0 +1,237 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/abtest/group.proto
+
+package com.tzld.piaoquan.abtest.client.model;
+
+/**
+ * Protobuf service {@code GroupService}
+ */
+public  abstract class GroupService
+    implements com.google.protobuf.Service {
+  protected GroupService() {}
+
+  public interface Interface {
+    /**
+     * <code>rpc getGroup(.GetGroupRequest) returns (.GetGroupResponse);</code>
+     */
+    public abstract void getGroup(
+        com.google.protobuf.RpcController controller,
+        com.tzld.piaoquan.abtest.client.model.GetGroupRequest request,
+        com.google.protobuf.RpcCallback<com.tzld.piaoquan.abtest.client.model.GetGroupResponse> done);
+
+  }
+
+  public static com.google.protobuf.Service newReflectiveService(
+      final Interface impl) {
+    return new GroupService() {
+      @java.lang.Override
+      public  void getGroup(
+          com.google.protobuf.RpcController controller,
+          com.tzld.piaoquan.abtest.client.model.GetGroupRequest request,
+          com.google.protobuf.RpcCallback<com.tzld.piaoquan.abtest.client.model.GetGroupResponse> done) {
+        impl.getGroup(controller, request, done);
+      }
+
+    };
+  }
+
+  public static com.google.protobuf.BlockingService
+      newReflectiveBlockingService(final BlockingInterface impl) {
+    return new com.google.protobuf.BlockingService() {
+      public final com.google.protobuf.Descriptors.ServiceDescriptor
+          getDescriptorForType() {
+        return getDescriptor();
+      }
+
+      public final com.google.protobuf.Message callBlockingMethod(
+          com.google.protobuf.Descriptors.MethodDescriptor method,
+          com.google.protobuf.RpcController controller,
+          com.google.protobuf.Message request)
+          throws com.google.protobuf.ServiceException {
+        if (method.getService() != getDescriptor()) {
+          throw new java.lang.IllegalArgumentException(
+            "Service.callBlockingMethod() given method descriptor for " +
+            "wrong service type.");
+        }
+        switch(method.getIndex()) {
+          case 0:
+            return impl.getGroup(controller, (com.tzld.piaoquan.abtest.client.model.GetGroupRequest)request);
+          default:
+            throw new java.lang.AssertionError("Can't get here.");
+        }
+      }
+
+      public final com.google.protobuf.Message
+          getRequestPrototype(
+          com.google.protobuf.Descriptors.MethodDescriptor method) {
+        if (method.getService() != getDescriptor()) {
+          throw new java.lang.IllegalArgumentException(
+            "Service.getRequestPrototype() given method " +
+            "descriptor for wrong service type.");
+        }
+        switch(method.getIndex()) {
+          case 0:
+            return com.tzld.piaoquan.abtest.client.model.GetGroupRequest.getDefaultInstance();
+          default:
+            throw new java.lang.AssertionError("Can't get here.");
+        }
+      }
+
+      public final com.google.protobuf.Message
+          getResponsePrototype(
+          com.google.protobuf.Descriptors.MethodDescriptor method) {
+        if (method.getService() != getDescriptor()) {
+          throw new java.lang.IllegalArgumentException(
+            "Service.getResponsePrototype() given method " +
+            "descriptor for wrong service type.");
+        }
+        switch(method.getIndex()) {
+          case 0:
+            return com.tzld.piaoquan.abtest.client.model.GetGroupResponse.getDefaultInstance();
+          default:
+            throw new java.lang.AssertionError("Can't get here.");
+        }
+      }
+
+    };
+  }
+
+  /**
+   * <code>rpc getGroup(.GetGroupRequest) returns (.GetGroupResponse);</code>
+   */
+  public abstract void getGroup(
+      com.google.protobuf.RpcController controller,
+      com.tzld.piaoquan.abtest.client.model.GetGroupRequest request,
+      com.google.protobuf.RpcCallback<com.tzld.piaoquan.abtest.client.model.GetGroupResponse> done);
+
+  public static final
+      com.google.protobuf.Descriptors.ServiceDescriptor
+      getDescriptor() {
+    return com.tzld.piaoquan.abtest.client.model.Group.getDescriptor().getServices().get(0);
+  }
+  public final com.google.protobuf.Descriptors.ServiceDescriptor
+      getDescriptorForType() {
+    return getDescriptor();
+  }
+
+  public final void callMethod(
+      com.google.protobuf.Descriptors.MethodDescriptor method,
+      com.google.protobuf.RpcController controller,
+      com.google.protobuf.Message request,
+      com.google.protobuf.RpcCallback<
+        com.google.protobuf.Message> done) {
+    if (method.getService() != getDescriptor()) {
+      throw new java.lang.IllegalArgumentException(
+        "Service.callMethod() given method descriptor for wrong " +
+        "service type.");
+    }
+    switch(method.getIndex()) {
+      case 0:
+        this.getGroup(controller, (com.tzld.piaoquan.abtest.client.model.GetGroupRequest)request,
+          com.google.protobuf.RpcUtil.<com.tzld.piaoquan.abtest.client.model.GetGroupResponse>specializeCallback(
+            done));
+        return;
+      default:
+        throw new java.lang.AssertionError("Can't get here.");
+    }
+  }
+
+  public final com.google.protobuf.Message
+      getRequestPrototype(
+      com.google.protobuf.Descriptors.MethodDescriptor method) {
+    if (method.getService() != getDescriptor()) {
+      throw new java.lang.IllegalArgumentException(
+        "Service.getRequestPrototype() given method " +
+        "descriptor for wrong service type.");
+    }
+    switch(method.getIndex()) {
+      case 0:
+        return com.tzld.piaoquan.abtest.client.model.GetGroupRequest.getDefaultInstance();
+      default:
+        throw new java.lang.AssertionError("Can't get here.");
+    }
+  }
+
+  public final com.google.protobuf.Message
+      getResponsePrototype(
+      com.google.protobuf.Descriptors.MethodDescriptor method) {
+    if (method.getService() != getDescriptor()) {
+      throw new java.lang.IllegalArgumentException(
+        "Service.getResponsePrototype() given method " +
+        "descriptor for wrong service type.");
+    }
+    switch(method.getIndex()) {
+      case 0:
+        return com.tzld.piaoquan.abtest.client.model.GetGroupResponse.getDefaultInstance();
+      default:
+        throw new java.lang.AssertionError("Can't get here.");
+    }
+  }
+
+  public static Stub newStub(
+      com.google.protobuf.RpcChannel channel) {
+    return new Stub(channel);
+  }
+
+  public static final class Stub extends com.tzld.piaoquan.abtest.client.model.GroupService implements Interface {
+    private Stub(com.google.protobuf.RpcChannel channel) {
+      this.channel = channel;
+    }
+
+    private final com.google.protobuf.RpcChannel channel;
+
+    public com.google.protobuf.RpcChannel getChannel() {
+      return channel;
+    }
+
+    public  void getGroup(
+        com.google.protobuf.RpcController controller,
+        com.tzld.piaoquan.abtest.client.model.GetGroupRequest request,
+        com.google.protobuf.RpcCallback<com.tzld.piaoquan.abtest.client.model.GetGroupResponse> done) {
+      channel.callMethod(
+        getDescriptor().getMethods().get(0),
+        controller,
+        request,
+        com.tzld.piaoquan.abtest.client.model.GetGroupResponse.getDefaultInstance(),
+        com.google.protobuf.RpcUtil.generalizeCallback(
+          done,
+          com.tzld.piaoquan.abtest.client.model.GetGroupResponse.class,
+          com.tzld.piaoquan.abtest.client.model.GetGroupResponse.getDefaultInstance()));
+    }
+  }
+
+  public static BlockingInterface newBlockingStub(
+      com.google.protobuf.BlockingRpcChannel channel) {
+    return new BlockingStub(channel);
+  }
+
+  public interface BlockingInterface {
+    public com.tzld.piaoquan.abtest.client.model.GetGroupResponse getGroup(
+        com.google.protobuf.RpcController controller,
+        com.tzld.piaoquan.abtest.client.model.GetGroupRequest request)
+        throws com.google.protobuf.ServiceException;
+  }
+
+  private static final class BlockingStub implements BlockingInterface {
+    private BlockingStub(com.google.protobuf.BlockingRpcChannel channel) {
+      this.channel = channel;
+    }
+
+    private final com.google.protobuf.BlockingRpcChannel channel;
+
+    public com.tzld.piaoquan.abtest.client.model.GetGroupResponse getGroup(
+        com.google.protobuf.RpcController controller,
+        com.tzld.piaoquan.abtest.client.model.GetGroupRequest request)
+        throws com.google.protobuf.ServiceException {
+      return (com.tzld.piaoquan.abtest.client.model.GetGroupResponse) channel.callBlockingMethod(
+        getDescriptor().getMethods().get(0),
+        controller,
+        request,
+        com.tzld.piaoquan.abtest.client.model.GetGroupResponse.getDefaultInstance());
+    }
+
+  }
+
+  // @@protoc_insertion_point(class_scope:GroupService)
+}
+

+ 288 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/GroupServiceGrpc.java

@@ -0,0 +1,288 @@
+package com.tzld.piaoquan.abtest.client.model;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler (version 1.34.1)",
+    comments = "Source: com/tzld/piaoquan/abtest/group.proto")
+public final class GroupServiceGrpc {
+
+  private GroupServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "GroupService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<com.tzld.piaoquan.abtest.client.model.GetGroupRequest,
+      com.tzld.piaoquan.abtest.client.model.GetGroupResponse> getGetGroupMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "getGroup",
+      requestType = com.tzld.piaoquan.abtest.client.model.GetGroupRequest.class,
+      responseType = com.tzld.piaoquan.abtest.client.model.GetGroupResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<com.tzld.piaoquan.abtest.client.model.GetGroupRequest,
+      com.tzld.piaoquan.abtest.client.model.GetGroupResponse> getGetGroupMethod() {
+    io.grpc.MethodDescriptor<com.tzld.piaoquan.abtest.client.model.GetGroupRequest, com.tzld.piaoquan.abtest.client.model.GetGroupResponse> getGetGroupMethod;
+    if ((getGetGroupMethod = GroupServiceGrpc.getGetGroupMethod) == null) {
+      synchronized (GroupServiceGrpc.class) {
+        if ((getGetGroupMethod = GroupServiceGrpc.getGetGroupMethod) == null) {
+          GroupServiceGrpc.getGetGroupMethod = getGetGroupMethod =
+              io.grpc.MethodDescriptor.<com.tzld.piaoquan.abtest.client.model.GetGroupRequest, com.tzld.piaoquan.abtest.client.model.GetGroupResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(SERVICE_NAME, "getGroup"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  com.tzld.piaoquan.abtest.client.model.GetGroupRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  com.tzld.piaoquan.abtest.client.model.GetGroupResponse.getDefaultInstance()))
+              .setSchemaDescriptor(new GroupServiceMethodDescriptorSupplier("getGroup"))
+              .build();
+        }
+      }
+    }
+    return getGetGroupMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static GroupServiceStub newStub(io.grpc.Channel channel) {
+    io.grpc.stub.AbstractStub.StubFactory<GroupServiceStub> factory =
+      new io.grpc.stub.AbstractStub.StubFactory<GroupServiceStub>() {
+        @java.lang.Override
+        public GroupServiceStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+          return new GroupServiceStub(channel, callOptions);
+        }
+      };
+    return GroupServiceStub.newStub(factory, channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static GroupServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    io.grpc.stub.AbstractStub.StubFactory<GroupServiceBlockingStub> factory =
+      new io.grpc.stub.AbstractStub.StubFactory<GroupServiceBlockingStub>() {
+        @java.lang.Override
+        public GroupServiceBlockingStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+          return new GroupServiceBlockingStub(channel, callOptions);
+        }
+      };
+    return GroupServiceBlockingStub.newStub(factory, channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static GroupServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    io.grpc.stub.AbstractStub.StubFactory<GroupServiceFutureStub> factory =
+      new io.grpc.stub.AbstractStub.StubFactory<GroupServiceFutureStub>() {
+        @java.lang.Override
+        public GroupServiceFutureStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+          return new GroupServiceFutureStub(channel, callOptions);
+        }
+      };
+    return GroupServiceFutureStub.newStub(factory, channel);
+  }
+
+  /**
+   */
+  public static abstract class GroupServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     */
+    public void getGroup(com.tzld.piaoquan.abtest.client.model.GetGroupRequest request,
+        io.grpc.stub.StreamObserver<com.tzld.piaoquan.abtest.client.model.GetGroupResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getGetGroupMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getGetGroupMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                com.tzld.piaoquan.abtest.client.model.GetGroupRequest,
+                com.tzld.piaoquan.abtest.client.model.GetGroupResponse>(
+                  this, METHODID_GET_GROUP)))
+          .build();
+    }
+  }
+
+  /**
+   */
+  public static final class GroupServiceStub extends io.grpc.stub.AbstractAsyncStub<GroupServiceStub> {
+    private GroupServiceStub(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected GroupServiceStub build(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      return new GroupServiceStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public void getGroup(com.tzld.piaoquan.abtest.client.model.GetGroupRequest request,
+        io.grpc.stub.StreamObserver<com.tzld.piaoquan.abtest.client.model.GetGroupResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getGetGroupMethod(), getCallOptions()), request, responseObserver);
+    }
+  }
+
+  /**
+   */
+  public static final class GroupServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub<GroupServiceBlockingStub> {
+    private GroupServiceBlockingStub(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected GroupServiceBlockingStub build(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      return new GroupServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public com.tzld.piaoquan.abtest.client.model.GetGroupResponse getGroup(com.tzld.piaoquan.abtest.client.model.GetGroupRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getGetGroupMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   */
+  public static final class GroupServiceFutureStub extends io.grpc.stub.AbstractFutureStub<GroupServiceFutureStub> {
+    private GroupServiceFutureStub(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected GroupServiceFutureStub build(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      return new GroupServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public com.google.common.util.concurrent.ListenableFuture<com.tzld.piaoquan.abtest.client.model.GetGroupResponse> getGroup(
+        com.tzld.piaoquan.abtest.client.model.GetGroupRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getGetGroupMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_GET_GROUP = 0;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final GroupServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(GroupServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_GET_GROUP:
+          serviceImpl.getGroup((com.tzld.piaoquan.abtest.client.model.GetGroupRequest) request,
+              (io.grpc.stub.StreamObserver<com.tzld.piaoquan.abtest.client.model.GetGroupResponse>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class GroupServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    GroupServiceBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return com.tzld.piaoquan.abtest.client.model.Group.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("GroupService");
+    }
+  }
+
+  private static final class GroupServiceFileDescriptorSupplier
+      extends GroupServiceBaseDescriptorSupplier {
+    GroupServiceFileDescriptorSupplier() {}
+  }
+
+  private static final class GroupServiceMethodDescriptorSupplier
+      extends GroupServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    GroupServiceMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (GroupServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new GroupServiceFileDescriptorSupplier())
+              .addMethod(getGetGroupMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}

+ 237 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/ModelService.java

@@ -0,0 +1,237 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/abtest/group.proto
+
+package com.tzld.piaoquan.abtest.client.model;
+
+/**
+ * Protobuf service {@code ModelService}
+ */
+public  abstract class ModelService
+    implements com.google.protobuf.Service {
+  protected ModelService() {}
+
+  public interface Interface {
+    /**
+     * <code>rpc getGroup(.GetGroupRequest) returns (.GetGroupResponse);</code>
+     */
+    public abstract void getGroup(
+        com.google.protobuf.RpcController controller,
+        com.tzld.piaoquan.abtest.client.model.GetGroupRequest request,
+        com.google.protobuf.RpcCallback<com.tzld.piaoquan.abtest.client.model.GetGroupResponse> done);
+
+  }
+
+  public static com.google.protobuf.Service newReflectiveService(
+      final Interface impl) {
+    return new ModelService() {
+      @java.lang.Override
+      public  void getGroup(
+          com.google.protobuf.RpcController controller,
+          com.tzld.piaoquan.abtest.client.model.GetGroupRequest request,
+          com.google.protobuf.RpcCallback<com.tzld.piaoquan.abtest.client.model.GetGroupResponse> done) {
+        impl.getGroup(controller, request, done);
+      }
+
+    };
+  }
+
+  public static com.google.protobuf.BlockingService
+      newReflectiveBlockingService(final BlockingInterface impl) {
+    return new com.google.protobuf.BlockingService() {
+      public final com.google.protobuf.Descriptors.ServiceDescriptor
+          getDescriptorForType() {
+        return getDescriptor();
+      }
+
+      public final com.google.protobuf.Message callBlockingMethod(
+          com.google.protobuf.Descriptors.MethodDescriptor method,
+          com.google.protobuf.RpcController controller,
+          com.google.protobuf.Message request)
+          throws com.google.protobuf.ServiceException {
+        if (method.getService() != getDescriptor()) {
+          throw new java.lang.IllegalArgumentException(
+            "Service.callBlockingMethod() given method descriptor for " +
+            "wrong service type.");
+        }
+        switch(method.getIndex()) {
+          case 0:
+            return impl.getGroup(controller, (com.tzld.piaoquan.abtest.client.model.GetGroupRequest)request);
+          default:
+            throw new java.lang.AssertionError("Can't get here.");
+        }
+      }
+
+      public final com.google.protobuf.Message
+          getRequestPrototype(
+          com.google.protobuf.Descriptors.MethodDescriptor method) {
+        if (method.getService() != getDescriptor()) {
+          throw new java.lang.IllegalArgumentException(
+            "Service.getRequestPrototype() given method " +
+            "descriptor for wrong service type.");
+        }
+        switch(method.getIndex()) {
+          case 0:
+            return com.tzld.piaoquan.abtest.client.model.GetGroupRequest.getDefaultInstance();
+          default:
+            throw new java.lang.AssertionError("Can't get here.");
+        }
+      }
+
+      public final com.google.protobuf.Message
+          getResponsePrototype(
+          com.google.protobuf.Descriptors.MethodDescriptor method) {
+        if (method.getService() != getDescriptor()) {
+          throw new java.lang.IllegalArgumentException(
+            "Service.getResponsePrototype() given method " +
+            "descriptor for wrong service type.");
+        }
+        switch(method.getIndex()) {
+          case 0:
+            return com.tzld.piaoquan.abtest.client.model.GetGroupResponse.getDefaultInstance();
+          default:
+            throw new java.lang.AssertionError("Can't get here.");
+        }
+      }
+
+    };
+  }
+
+  /**
+   * <code>rpc getGroup(.GetGroupRequest) returns (.GetGroupResponse);</code>
+   */
+  public abstract void getGroup(
+      com.google.protobuf.RpcController controller,
+      com.tzld.piaoquan.abtest.client.model.GetGroupRequest request,
+      com.google.protobuf.RpcCallback<com.tzld.piaoquan.abtest.client.model.GetGroupResponse> done);
+
+  public static final
+      com.google.protobuf.Descriptors.ServiceDescriptor
+      getDescriptor() {
+    return com.tzld.piaoquan.abtest.client.model.Group.getDescriptor().getServices().get(0);
+  }
+  public final com.google.protobuf.Descriptors.ServiceDescriptor
+      getDescriptorForType() {
+    return getDescriptor();
+  }
+
+  public final void callMethod(
+      com.google.protobuf.Descriptors.MethodDescriptor method,
+      com.google.protobuf.RpcController controller,
+      com.google.protobuf.Message request,
+      com.google.protobuf.RpcCallback<
+        com.google.protobuf.Message> done) {
+    if (method.getService() != getDescriptor()) {
+      throw new java.lang.IllegalArgumentException(
+        "Service.callMethod() given method descriptor for wrong " +
+        "service type.");
+    }
+    switch(method.getIndex()) {
+      case 0:
+        this.getGroup(controller, (com.tzld.piaoquan.abtest.client.model.GetGroupRequest)request,
+          com.google.protobuf.RpcUtil.<com.tzld.piaoquan.abtest.client.model.GetGroupResponse>specializeCallback(
+            done));
+        return;
+      default:
+        throw new java.lang.AssertionError("Can't get here.");
+    }
+  }
+
+  public final com.google.protobuf.Message
+      getRequestPrototype(
+      com.google.protobuf.Descriptors.MethodDescriptor method) {
+    if (method.getService() != getDescriptor()) {
+      throw new java.lang.IllegalArgumentException(
+        "Service.getRequestPrototype() given method " +
+        "descriptor for wrong service type.");
+    }
+    switch(method.getIndex()) {
+      case 0:
+        return com.tzld.piaoquan.abtest.client.model.GetGroupRequest.getDefaultInstance();
+      default:
+        throw new java.lang.AssertionError("Can't get here.");
+    }
+  }
+
+  public final com.google.protobuf.Message
+      getResponsePrototype(
+      com.google.protobuf.Descriptors.MethodDescriptor method) {
+    if (method.getService() != getDescriptor()) {
+      throw new java.lang.IllegalArgumentException(
+        "Service.getResponsePrototype() given method " +
+        "descriptor for wrong service type.");
+    }
+    switch(method.getIndex()) {
+      case 0:
+        return com.tzld.piaoquan.abtest.client.model.GetGroupResponse.getDefaultInstance();
+      default:
+        throw new java.lang.AssertionError("Can't get here.");
+    }
+  }
+
+  public static Stub newStub(
+      com.google.protobuf.RpcChannel channel) {
+    return new Stub(channel);
+  }
+
+  public static final class Stub extends com.tzld.piaoquan.abtest.client.model.ModelService implements Interface {
+    private Stub(com.google.protobuf.RpcChannel channel) {
+      this.channel = channel;
+    }
+
+    private final com.google.protobuf.RpcChannel channel;
+
+    public com.google.protobuf.RpcChannel getChannel() {
+      return channel;
+    }
+
+    public  void getGroup(
+        com.google.protobuf.RpcController controller,
+        com.tzld.piaoquan.abtest.client.model.GetGroupRequest request,
+        com.google.protobuf.RpcCallback<com.tzld.piaoquan.abtest.client.model.GetGroupResponse> done) {
+      channel.callMethod(
+        getDescriptor().getMethods().get(0),
+        controller,
+        request,
+        com.tzld.piaoquan.abtest.client.model.GetGroupResponse.getDefaultInstance(),
+        com.google.protobuf.RpcUtil.generalizeCallback(
+          done,
+          com.tzld.piaoquan.abtest.client.model.GetGroupResponse.class,
+          com.tzld.piaoquan.abtest.client.model.GetGroupResponse.getDefaultInstance()));
+    }
+  }
+
+  public static BlockingInterface newBlockingStub(
+      com.google.protobuf.BlockingRpcChannel channel) {
+    return new BlockingStub(channel);
+  }
+
+  public interface BlockingInterface {
+    public com.tzld.piaoquan.abtest.client.model.GetGroupResponse getGroup(
+        com.google.protobuf.RpcController controller,
+        com.tzld.piaoquan.abtest.client.model.GetGroupRequest request)
+        throws com.google.protobuf.ServiceException;
+  }
+
+  private static final class BlockingStub implements BlockingInterface {
+    private BlockingStub(com.google.protobuf.BlockingRpcChannel channel) {
+      this.channel = channel;
+    }
+
+    private final com.google.protobuf.BlockingRpcChannel channel;
+
+    public com.tzld.piaoquan.abtest.client.model.GetGroupResponse getGroup(
+        com.google.protobuf.RpcController controller,
+        com.tzld.piaoquan.abtest.client.model.GetGroupRequest request)
+        throws com.google.protobuf.ServiceException {
+      return (com.tzld.piaoquan.abtest.client.model.GetGroupResponse) channel.callBlockingMethod(
+        getDescriptor().getMethods().get(0),
+        controller,
+        request,
+        com.tzld.piaoquan.abtest.client.model.GetGroupResponse.getDefaultInstance());
+    }
+
+  }
+
+  // @@protoc_insertion_point(class_scope:ModelService)
+}
+

+ 288 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/ModelServiceGrpc.java

@@ -0,0 +1,288 @@
+package com.tzld.piaoquan.abtest.client.model;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler (version 1.34.1)",
+    comments = "Source: com/tzld/piaoquan/abtest/group.proto")
+public final class ModelServiceGrpc {
+
+  private ModelServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "ModelService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<com.tzld.piaoquan.abtest.client.model.GetGroupRequest,
+      com.tzld.piaoquan.abtest.client.model.GetGroupResponse> getGetGroupMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "getGroup",
+      requestType = com.tzld.piaoquan.abtest.client.model.GetGroupRequest.class,
+      responseType = com.tzld.piaoquan.abtest.client.model.GetGroupResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<com.tzld.piaoquan.abtest.client.model.GetGroupRequest,
+      com.tzld.piaoquan.abtest.client.model.GetGroupResponse> getGetGroupMethod() {
+    io.grpc.MethodDescriptor<com.tzld.piaoquan.abtest.client.model.GetGroupRequest, com.tzld.piaoquan.abtest.client.model.GetGroupResponse> getGetGroupMethod;
+    if ((getGetGroupMethod = ModelServiceGrpc.getGetGroupMethod) == null) {
+      synchronized (ModelServiceGrpc.class) {
+        if ((getGetGroupMethod = ModelServiceGrpc.getGetGroupMethod) == null) {
+          ModelServiceGrpc.getGetGroupMethod = getGetGroupMethod =
+              io.grpc.MethodDescriptor.<com.tzld.piaoquan.abtest.client.model.GetGroupRequest, com.tzld.piaoquan.abtest.client.model.GetGroupResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(SERVICE_NAME, "getGroup"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  com.tzld.piaoquan.abtest.client.model.GetGroupRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  com.tzld.piaoquan.abtest.client.model.GetGroupResponse.getDefaultInstance()))
+              .setSchemaDescriptor(new ModelServiceMethodDescriptorSupplier("getGroup"))
+              .build();
+        }
+      }
+    }
+    return getGetGroupMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static ModelServiceStub newStub(io.grpc.Channel channel) {
+    io.grpc.stub.AbstractStub.StubFactory<ModelServiceStub> factory =
+      new io.grpc.stub.AbstractStub.StubFactory<ModelServiceStub>() {
+        @java.lang.Override
+        public ModelServiceStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+          return new ModelServiceStub(channel, callOptions);
+        }
+      };
+    return ModelServiceStub.newStub(factory, channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static ModelServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    io.grpc.stub.AbstractStub.StubFactory<ModelServiceBlockingStub> factory =
+      new io.grpc.stub.AbstractStub.StubFactory<ModelServiceBlockingStub>() {
+        @java.lang.Override
+        public ModelServiceBlockingStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+          return new ModelServiceBlockingStub(channel, callOptions);
+        }
+      };
+    return ModelServiceBlockingStub.newStub(factory, channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static ModelServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    io.grpc.stub.AbstractStub.StubFactory<ModelServiceFutureStub> factory =
+      new io.grpc.stub.AbstractStub.StubFactory<ModelServiceFutureStub>() {
+        @java.lang.Override
+        public ModelServiceFutureStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+          return new ModelServiceFutureStub(channel, callOptions);
+        }
+      };
+    return ModelServiceFutureStub.newStub(factory, channel);
+  }
+
+  /**
+   */
+  public static abstract class ModelServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     */
+    public void getGroup(com.tzld.piaoquan.abtest.client.model.GetGroupRequest request,
+        io.grpc.stub.StreamObserver<com.tzld.piaoquan.abtest.client.model.GetGroupResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getGetGroupMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getGetGroupMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                com.tzld.piaoquan.abtest.client.model.GetGroupRequest,
+                com.tzld.piaoquan.abtest.client.model.GetGroupResponse>(
+                  this, METHODID_GET_GROUP)))
+          .build();
+    }
+  }
+
+  /**
+   */
+  public static final class ModelServiceStub extends io.grpc.stub.AbstractAsyncStub<ModelServiceStub> {
+    private ModelServiceStub(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ModelServiceStub build(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      return new ModelServiceStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public void getGroup(com.tzld.piaoquan.abtest.client.model.GetGroupRequest request,
+        io.grpc.stub.StreamObserver<com.tzld.piaoquan.abtest.client.model.GetGroupResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getGetGroupMethod(), getCallOptions()), request, responseObserver);
+    }
+  }
+
+  /**
+   */
+  public static final class ModelServiceBlockingStub extends io.grpc.stub.AbstractBlockingStub<ModelServiceBlockingStub> {
+    private ModelServiceBlockingStub(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ModelServiceBlockingStub build(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      return new ModelServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public com.tzld.piaoquan.abtest.client.model.GetGroupResponse getGroup(com.tzld.piaoquan.abtest.client.model.GetGroupRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getGetGroupMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   */
+  public static final class ModelServiceFutureStub extends io.grpc.stub.AbstractFutureStub<ModelServiceFutureStub> {
+    private ModelServiceFutureStub(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ModelServiceFutureStub build(
+        io.grpc.Channel channel, io.grpc.CallOptions callOptions) {
+      return new ModelServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public com.google.common.util.concurrent.ListenableFuture<com.tzld.piaoquan.abtest.client.model.GetGroupResponse> getGroup(
+        com.tzld.piaoquan.abtest.client.model.GetGroupRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getGetGroupMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_GET_GROUP = 0;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final ModelServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(ModelServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_GET_GROUP:
+          serviceImpl.getGroup((com.tzld.piaoquan.abtest.client.model.GetGroupRequest) request,
+              (io.grpc.stub.StreamObserver<com.tzld.piaoquan.abtest.client.model.GetGroupResponse>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class ModelServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    ModelServiceBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return com.tzld.piaoquan.abtest.client.model.Group.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("ModelService");
+    }
+  }
+
+  private static final class ModelServiceFileDescriptorSupplier
+      extends ModelServiceBaseDescriptorSupplier {
+    ModelServiceFileDescriptorSupplier() {}
+  }
+
+  private static final class ModelServiceMethodDescriptorSupplier
+      extends ModelServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    ModelServiceMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (ModelServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new ModelServiceFileDescriptorSupplier())
+              .addMethod(getGetGroupMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}

+ 637 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/Result.java

@@ -0,0 +1,637 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/abtest/common.proto
+
+package com.tzld.piaoquan.abtest.client.model;
+
+/**
+ * Protobuf type {@code Result}
+ */
+public final class Result extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:Result)
+    ResultOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use Result.newBuilder() to construct.
+  private Result(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private Result() {
+    message_ = "";
+  }
+
+  @java.lang.Override
+  @SuppressWarnings({"unused"})
+  protected java.lang.Object newInstance(
+      UnusedPrivateParameter unused) {
+    return new Result();
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private Result(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          case 8: {
+
+            code_ = input.readInt32();
+            break;
+          }
+          case 18: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            message_ = s;
+            break;
+          }
+          default: {
+            if (!parseUnknownField(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return com.tzld.piaoquan.abtest.client.model.Common.internal_static_Result_descriptor;
+  }
+
+  @java.lang.Override
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return com.tzld.piaoquan.abtest.client.model.Common.internal_static_Result_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            com.tzld.piaoquan.abtest.client.model.Result.class, com.tzld.piaoquan.abtest.client.model.Result.Builder.class);
+  }
+
+  public static final int CODE_FIELD_NUMBER = 1;
+  private int code_;
+  /**
+   * <pre>
+   * 1 成功
+   * </pre>
+   *
+   * <code>int32 code = 1;</code>
+   * @return The code.
+   */
+  @java.lang.Override
+  public int getCode() {
+    return code_;
+  }
+
+  public static final int MESSAGE_FIELD_NUMBER = 2;
+  private volatile java.lang.Object message_;
+  /**
+   * <code>string message = 2;</code>
+   * @return The message.
+   */
+  @java.lang.Override
+  public java.lang.String getMessage() {
+    java.lang.Object ref = message_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      message_ = s;
+      return s;
+    }
+  }
+  /**
+   * <code>string message = 2;</code>
+   * @return The bytes for message.
+   */
+  @java.lang.Override
+  public com.google.protobuf.ByteString
+      getMessageBytes() {
+    java.lang.Object ref = message_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      message_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  @java.lang.Override
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  @java.lang.Override
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (code_ != 0) {
+      output.writeInt32(1, code_);
+    }
+    if (!getMessageBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 2, message_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  @java.lang.Override
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (code_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt32Size(1, code_);
+    }
+    if (!getMessageBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, message_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof com.tzld.piaoquan.abtest.client.model.Result)) {
+      return super.equals(obj);
+    }
+    com.tzld.piaoquan.abtest.client.model.Result other = (com.tzld.piaoquan.abtest.client.model.Result) obj;
+
+    if (getCode()
+        != other.getCode()) return false;
+    if (!getMessage()
+        .equals(other.getMessage())) return false;
+    if (!unknownFields.equals(other.unknownFields)) return false;
+    return true;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + CODE_FIELD_NUMBER;
+    hash = (53 * hash) + getCode();
+    hash = (37 * hash) + MESSAGE_FIELD_NUMBER;
+    hash = (53 * hash) + getMessage().hashCode();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static com.tzld.piaoquan.abtest.client.model.Result parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.Result parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.Result parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.Result parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.Result parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.Result parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.Result parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.Result parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.Result parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.Result parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.Result parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static com.tzld.piaoquan.abtest.client.model.Result parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  @java.lang.Override
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(com.tzld.piaoquan.abtest.client.model.Result prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  @java.lang.Override
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code Result}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:Result)
+      com.tzld.piaoquan.abtest.client.model.ResultOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return com.tzld.piaoquan.abtest.client.model.Common.internal_static_Result_descriptor;
+    }
+
+    @java.lang.Override
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return com.tzld.piaoquan.abtest.client.model.Common.internal_static_Result_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              com.tzld.piaoquan.abtest.client.model.Result.class, com.tzld.piaoquan.abtest.client.model.Result.Builder.class);
+    }
+
+    // Construct using com.tzld.piaoquan.abtest.client.model.Result.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    @java.lang.Override
+    public Builder clear() {
+      super.clear();
+      code_ = 0;
+
+      message_ = "";
+
+      return this;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return com.tzld.piaoquan.abtest.client.model.Common.internal_static_Result_descriptor;
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.abtest.client.model.Result getDefaultInstanceForType() {
+      return com.tzld.piaoquan.abtest.client.model.Result.getDefaultInstance();
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.abtest.client.model.Result build() {
+      com.tzld.piaoquan.abtest.client.model.Result result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    @java.lang.Override
+    public com.tzld.piaoquan.abtest.client.model.Result buildPartial() {
+      com.tzld.piaoquan.abtest.client.model.Result result = new com.tzld.piaoquan.abtest.client.model.Result(this);
+      result.code_ = code_;
+      result.message_ = message_;
+      onBuilt();
+      return result;
+    }
+
+    @java.lang.Override
+    public Builder clone() {
+      return super.clone();
+    }
+    @java.lang.Override
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return super.setField(field, value);
+    }
+    @java.lang.Override
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return super.clearField(field);
+    }
+    @java.lang.Override
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return super.clearOneof(oneof);
+    }
+    @java.lang.Override
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return super.setRepeatedField(field, index, value);
+    }
+    @java.lang.Override
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return super.addRepeatedField(field, value);
+    }
+    @java.lang.Override
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof com.tzld.piaoquan.abtest.client.model.Result) {
+        return mergeFrom((com.tzld.piaoquan.abtest.client.model.Result)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(com.tzld.piaoquan.abtest.client.model.Result other) {
+      if (other == com.tzld.piaoquan.abtest.client.model.Result.getDefaultInstance()) return this;
+      if (other.getCode() != 0) {
+        setCode(other.getCode());
+      }
+      if (!other.getMessage().isEmpty()) {
+        message_ = other.message_;
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    @java.lang.Override
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    @java.lang.Override
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      com.tzld.piaoquan.abtest.client.model.Result parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (com.tzld.piaoquan.abtest.client.model.Result) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private int code_ ;
+    /**
+     * <pre>
+     * 1 成功
+     * </pre>
+     *
+     * <code>int32 code = 1;</code>
+     * @return The code.
+     */
+    @java.lang.Override
+    public int getCode() {
+      return code_;
+    }
+    /**
+     * <pre>
+     * 1 成功
+     * </pre>
+     *
+     * <code>int32 code = 1;</code>
+     * @param value The code to set.
+     * @return This builder for chaining.
+     */
+    public Builder setCode(int value) {
+      
+      code_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * 1 成功
+     * </pre>
+     *
+     * <code>int32 code = 1;</code>
+     * @return This builder for chaining.
+     */
+    public Builder clearCode() {
+      
+      code_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private java.lang.Object message_ = "";
+    /**
+     * <code>string message = 2;</code>
+     * @return The message.
+     */
+    public java.lang.String getMessage() {
+      java.lang.Object ref = message_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        message_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>string message = 2;</code>
+     * @return The bytes for message.
+     */
+    public com.google.protobuf.ByteString
+        getMessageBytes() {
+      java.lang.Object ref = message_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        message_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>string message = 2;</code>
+     * @param value The message to set.
+     * @return This builder for chaining.
+     */
+    public Builder setMessage(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      message_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string message = 2;</code>
+     * @return This builder for chaining.
+     */
+    public Builder clearMessage() {
+      
+      message_ = getDefaultInstance().getMessage();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string message = 2;</code>
+     * @param value The bytes for message to set.
+     * @return This builder for chaining.
+     */
+    public Builder setMessageBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      message_ = value;
+      onChanged();
+      return this;
+    }
+    @java.lang.Override
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFields(unknownFields);
+    }
+
+    @java.lang.Override
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:Result)
+  }
+
+  // @@protoc_insertion_point(class_scope:Result)
+  private static final com.tzld.piaoquan.abtest.client.model.Result DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new com.tzld.piaoquan.abtest.client.model.Result();
+  }
+
+  public static com.tzld.piaoquan.abtest.client.model.Result getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<Result>
+      PARSER = new com.google.protobuf.AbstractParser<Result>() {
+    @java.lang.Override
+    public Result parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new Result(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<Result> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<Result> getParserForType() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.tzld.piaoquan.abtest.client.model.Result getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+

+ 31 - 0
abtest-client/src/main/java/com/tzld/piaoquan/abtest/client/model/ResultOrBuilder.java

@@ -0,0 +1,31 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: com/tzld/piaoquan/abtest/common.proto
+
+package com.tzld.piaoquan.abtest.client.model;
+
+public interface ResultOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:Result)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * 1 成功
+   * </pre>
+   *
+   * <code>int32 code = 1;</code>
+   * @return The code.
+   */
+  int getCode();
+
+  /**
+   * <code>string message = 2;</code>
+   * @return The message.
+   */
+  java.lang.String getMessage();
+  /**
+   * <code>string message = 2;</code>
+   * @return The bytes for message.
+   */
+  com.google.protobuf.ByteString
+      getMessageBytes();
+}

+ 9 - 0
abtest-client/src/main/proto/com/tzld/piaoquan/abtest/common.proto

@@ -0,0 +1,9 @@
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_package = "com.tzld.piaoquan.abtest.client.model";
+
+message Result {
+  int32 code = 1; // 1 成功
+  string message = 2;
+}

+ 23 - 0
abtest-client/src/main/proto/com/tzld/piaoquan/abtest/exp.proto

@@ -0,0 +1,23 @@
+syntax = "proto3";
+
+import "com/tzld/piaoquan/abtest/common.proto";
+
+option java_multiple_files = true;
+option java_package = "com.tzld.piaoquan.abtest.client.model";
+option java_generic_services = true;
+
+message GetExpRequest {
+  string mid = 1;
+  string uid = 2;
+  int32 app_type = 3;
+  string group = 4;
+}
+
+message GetExpResponse {
+  Result result = 1;
+  map<string, string> exp = 2;
+}
+
+service ExpService {
+  rpc getExp (GetExpRequest) returns (GetExpResponse);
+}

+ 23 - 0
abtest-client/src/main/proto/com/tzld/piaoquan/abtest/group.proto

@@ -0,0 +1,23 @@
+syntax = "proto3";
+
+import "com/tzld/piaoquan/abtest/common.proto";
+
+option java_multiple_files = true;
+option java_package = "com.tzld.piaoquan.abtest.client.model";
+option java_generic_services = true;
+
+message GetGroupRequest {
+  string mid = 1;
+  string uid = 2;
+  int32 app_type = 3;
+  string group = 4;
+}
+
+message GetGroupResponse {
+  Result result = 1;
+  string group = 2;
+}
+
+service GroupService {
+  rpc getGroup (GetGroupRequest) returns (GetGroupResponse);
+}

+ 179 - 0
abtest-service/pom.xml

@@ -0,0 +1,179 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.tzld.piaoquan</groupId>
+        <artifactId>abtest</artifactId>
+        <version>1.0.0</version>
+    </parent>
+    <artifactId>abtest-service</artifactId>
+    <name>abtest-service</name>
+    <description>abtest-service</description>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-commons</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-math3</artifactId>
+            <version>3.6.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.4</version>
+        </dependency>
+        <dependency>
+            <groupId>redis.clients</groupId>
+            <artifactId>jedis</artifactId>
+            <version>3.3.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.junit.vintage</groupId>
+                    <artifactId>junit-vintage-engine</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.ctrip.framework.apollo</groupId>
+            <artifactId>apollo-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.csp</groupId>
+            <artifactId>sentinel-datasource-apollo</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun.openservices</groupId>
+            <artifactId>aliyun-log-logback-appender</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun.openservices</groupId>
+            <artifactId>aliyun-log-producer</artifactId>
+            <version>0.3.10</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun.openservices</groupId>
+            <artifactId>aliyun-log</artifactId>
+            <version>0.6.35</version>
+            <classifier>jar-with-dependencies</classifier>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>net.devh</groupId>
+            <artifactId>grpc-server-spring-boot-starter</artifactId>
+            <version>2.9.0.RELEASE</version>
+        </dependency>
+        <dependency>
+            <groupId>com.tzld.piaoquan</groupId>
+            <artifactId>abtest-client</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.protobuf</groupId>
+            <artifactId>protobuf-java</artifactId>
+            <version>3.12.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.24</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.sun.jersey</groupId>
+            <artifactId>jersey-client</artifactId>
+            <version>1.9.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.jersey</groupId>
+            <artifactId>jersey-core</artifactId>
+            <version>1.9.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.jersey.contribs</groupId>
+            <artifactId>jersey-apache-client4</artifactId>
+            <version>1.9.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-collections4</artifactId>
+            <version>4.1</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+            <version>2.4.3</version>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+    </dependencies>
+
+
+    <build>
+        <finalName>abtest-service</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <mainClass>com.tzld.piaoquan.abtest.Application</mainClass>
+                    <layout>ZIP</layout>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 41 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/Application.java

@@ -0,0 +1,41 @@
+package com.tzld.piaoquan.abtest;
+
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration;
+import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter;
+
+
+/**
+ * https://github.com/grpc-swagger/grpc-swagger/blob/master/README_CN.md
+ */
+@SpringBootApplication(exclude = {
+        RedisReactiveAutoConfiguration.class
+})
+@ComponentScan({
+        "com.tzld.piaoquan.abtest.service",
+        "com.tzld.piaoquan.abtest.grpcservice",
+        "com.tzld.piaoquan.abtest.config",
+        "com.tzld.piaoquan.abtest.web",
+})
+@EnableEurekaClient
+@EnableAspectJAutoProxy
+public class Application {
+    public static void main(String[] args) {
+        SpringApplication.run(Application.class, args);
+    }
+
+
+    @Bean
+    public ProtobufHttpMessageConverter protobufHttpMessageConverter() {
+        return new ProtobufHttpMessageConverter();
+    }
+
+
+}
+

+ 40 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/common/CommonThreadPoolExecutor.java

@@ -0,0 +1,40 @@
+package com.tzld.piaoquan.abtest.common;
+
+import org.slf4j.MDC;
+
+import java.util.Map;
+import java.util.concurrent.*;
+
+/**
+ * @author dyp
+ */
+public class CommonThreadPoolExecutor extends ThreadPoolExecutor {
+    public CommonThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
+        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
+    }
+
+    public CommonThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
+        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
+    }
+
+    public CommonThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
+        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
+    }
+
+    public CommonThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
+        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
+    }
+
+    @Override
+    public void execute(Runnable command) {
+        Map<String, String> mdcMap = MDC.getCopyOfContextMap();
+        super.execute(() -> {
+            if (mdcMap != null) {
+                MDC.setContextMap(mdcMap);
+            }
+            command.run();
+            MDC.clear();
+        });
+    }
+
+}

+ 358 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/common/RecommendLoghubAppender.java

@@ -0,0 +1,358 @@
+package com.tzld.piaoquan.abtest.common;
+
+import ch.qos.logback.classic.spi.IThrowableProxy;
+import ch.qos.logback.classic.spi.LoggingEvent;
+import ch.qos.logback.classic.spi.StackTraceElementProxy;
+import ch.qos.logback.classic.spi.ThrowableProxyUtil;
+import ch.qos.logback.core.CoreConstants;
+import ch.qos.logback.core.UnsynchronizedAppenderBase;
+import ch.qos.logback.core.encoder.Encoder;
+import com.aliyun.openservices.aliyun.log.producer.LogProducer;
+import com.aliyun.openservices.aliyun.log.producer.Producer;
+import com.aliyun.openservices.aliyun.log.producer.ProducerConfig;
+import com.aliyun.openservices.aliyun.log.producer.ProjectConfig;
+import com.aliyun.openservices.aliyun.log.producer.errors.ProducerException;
+import com.aliyun.openservices.log.common.LogItem;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+import java.time.Instant;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author dyp
+ */
+public class RecommendLoghubAppender<E> extends UnsynchronizedAppenderBase<E> {
+    private String project;
+    private String endpoint;
+    private String accessKeyId;
+    private String accessKeySecret;
+    private String userAgent = "logback";
+    protected Encoder<E> encoder;
+    protected ProducerConfig producerConfig = new ProducerConfig();
+    protected ProjectConfig projectConfig;
+    protected Producer producer;
+    protected String logStore;
+    protected String topic = "";
+    protected String source = "";
+    protected String timeZone = "UTC";
+    protected String timeFormat = "yyyy-MM-dd'T'HH:mmZ";
+    protected DateTimeFormatter formatter;
+    protected java.time.format.DateTimeFormatter formatter1;
+    private String mdcFields;
+
+    public RecommendLoghubAppender() {
+    }
+
+    public void start() {
+        try {
+            this.doStart();
+        } catch (Exception var2) {
+            this.addError("Failed to start LoghubAppender.", var2);
+        }
+
+    }
+
+    private void doStart() {
+        try {
+            this.formatter = DateTimeFormat.forPattern(this.timeFormat).withZone(DateTimeZone.forID(this.timeZone));
+        } catch (Exception var2) {
+            this.formatter1 = java.time.format.DateTimeFormatter.ofPattern(this.timeFormat).withZone(ZoneId.of(this.timeZone));
+        }
+
+        this.producer = this.createProducer();
+        super.start();
+    }
+
+    public Producer createProducer() {
+        this.projectConfig = this.buildProjectConfig();
+        Producer producer = new LogProducer(this.producerConfig);
+        producer.putProjectConfig(this.projectConfig);
+        return producer;
+    }
+
+    private ProjectConfig buildProjectConfig() {
+        return new ProjectConfig(this.project, this.endpoint, this.accessKeyId, this.accessKeySecret, (String) null, this.userAgent);
+    }
+
+    public void stop() {
+        try {
+            this.doStop();
+        } catch (Exception var2) {
+            this.addError("Failed to stop LoghubAppender.", var2);
+        }
+
+    }
+
+    private void doStop() throws InterruptedException, ProducerException {
+        if (this.isStarted()) {
+            super.stop();
+            this.producer.close();
+        }
+    }
+
+    public void append(E eventObject) {
+        try {
+            this.appendEvent(eventObject);
+        } catch (Exception var3) {
+            this.addError("Failed to append event.", var3);
+        }
+
+    }
+
+    private void appendEvent(E eventObject) {
+        if (eventObject instanceof LoggingEvent) {
+            LoggingEvent event = (LoggingEvent) eventObject;
+            List<LogItem> logItems = new ArrayList();
+            LogItem item = new LogItem();
+            logItems.add(item);
+            item.SetTime((int) (event.getTimeStamp() / 1000L));
+            if (this.formatter != null) {
+                DateTime dateTime = new DateTime(event.getTimeStamp());
+                item.PushBack("time", dateTime.toString(this.formatter));
+            } else {
+                Instant instant = Instant.ofEpochMilli(event.getTimeStamp());
+                item.PushBack("time", this.formatter1.format(instant));
+            }
+
+            item.PushBack("level", event.getLevel().toString());
+            item.PushBack("thread", event.getThreadName());
+            StackTraceElement[] caller = event.getCallerData();
+            if (caller != null && caller.length > 0) {
+                item.PushBack("location", caller[0].toString());
+            }
+
+            String message = event.getFormattedMessage();
+            item.PushBack("message", message);
+            IThrowableProxy iThrowableProxy = event.getThrowableProxy();
+            if (iThrowableProxy != null) {
+                String throwable = this.getExceptionInfo(iThrowableProxy);
+                throwable = throwable + this.fullDump(event.getThrowableProxy().getStackTraceElementProxyArray());
+                item.PushBack("throwable", throwable);
+            }
+
+            if (this.encoder != null) {
+                item.PushBack("log", new String(this.encoder.encode(eventObject)));
+            }
+
+            Optional.ofNullable(this.mdcFields).ifPresent((f) -> {
+                event.getMDCPropertyMap().entrySet().stream().filter((v) -> {
+                    return Arrays.stream(f.split(",")).anyMatch((i) -> {
+                        return i.equals(v.getKey());
+                    });
+                }).forEach((map) -> {
+                    item.PushBack((String) map.getKey(), (String) map.getValue());
+                });
+            });
+
+            try {
+                this.producer.send(this.projectConfig.getProject(), this.logStore, this.topic, this.source, logItems,
+                        new RecommendLoghubAppenderCallback(this, this.projectConfig.getProject(), this.logStore,
+                                this.topic, this.source, logItems));
+            } catch (Exception var9) {
+                this.addError("Failed to send log, project=" + this.project + ", logStore=" + this.logStore + ", topic=" + this.topic + ", source=" + this.source + ", logItem=" + logItems, var9);
+            }
+
+        }
+    }
+
+    public String getTimeFormat() {
+        return this.timeFormat;
+    }
+
+    public void setTimeFormat(String timeFormat) {
+        this.timeFormat = timeFormat;
+    }
+
+    private String getExceptionInfo(IThrowableProxy iThrowableProxy) {
+        String s = iThrowableProxy.getClassName();
+        String message = iThrowableProxy.getMessage();
+        return message != null ? s + ": " + message : s;
+    }
+
+    private String fullDump(StackTraceElementProxy[] stackTraceElementProxyArray) {
+        StringBuilder builder = new StringBuilder();
+        StackTraceElementProxy[] var3 = stackTraceElementProxyArray;
+        int var4 = stackTraceElementProxyArray.length;
+
+        for (int var5 = 0; var5 < var4; ++var5) {
+            StackTraceElementProxy step = var3[var5];
+            builder.append(CoreConstants.LINE_SEPARATOR);
+            String string = step.toString();
+            builder.append('\t').append(string);
+            ThrowableProxyUtil.subjoinPackagingData(builder, step);
+        }
+
+        return builder.toString();
+    }
+
+    public String getLogStore() {
+        return this.logStore;
+    }
+
+    public void setLogStore(String logStore) {
+        this.logStore = logStore;
+    }
+
+    public String getTopic() {
+        return this.topic;
+    }
+
+    public void setTopic(String topic) {
+        this.topic = topic;
+    }
+
+    public String getSource() {
+        return this.source;
+    }
+
+    public void setSource(String source) {
+        this.source = source;
+    }
+
+    public String getTimeZone() {
+        return this.timeZone;
+    }
+
+    public void setTimeZone(String timeZone) {
+        this.timeZone = timeZone;
+    }
+
+    public String getProject() {
+        return this.project;
+    }
+
+    public void setProject(String project) {
+        this.project = project;
+    }
+
+    public String getEndpoint() {
+        return this.endpoint;
+    }
+
+    public void setEndpoint(String endpoint) {
+        this.endpoint = endpoint;
+    }
+
+    public String getAccessKeyId() {
+        return this.accessKeyId;
+    }
+
+    public void setAccessKeyId(String accessKeyId) {
+        this.accessKeyId = accessKeyId;
+    }
+
+    public String getAccessKeySecret() {
+        return this.accessKeySecret;
+    }
+
+    public void setAccessKeySecret(String accessKeySecret) {
+        this.accessKeySecret = accessKeySecret;
+    }
+
+    public String getUserAgent() {
+        return this.userAgent;
+    }
+
+    public void setUserAgent(String userAgent) {
+        this.userAgent = userAgent;
+    }
+
+    public int getTotalSizeInBytes() {
+        return this.producerConfig.getTotalSizeInBytes();
+    }
+
+    public void setTotalSizeInBytes(int totalSizeInBytes) {
+        this.producerConfig.setTotalSizeInBytes(totalSizeInBytes);
+    }
+
+    public long getMaxBlockMs() {
+        return this.producerConfig.getMaxBlockMs();
+    }
+
+    public void setMaxBlockMs(long maxBlockMs) {
+        this.producerConfig.setMaxBlockMs(maxBlockMs);
+    }
+
+    public int getIoThreadCount() {
+        return this.producerConfig.getIoThreadCount();
+    }
+
+    public void setIoThreadCount(int ioThreadCount) {
+        this.producerConfig.setIoThreadCount(ioThreadCount);
+    }
+
+    public int getBatchSizeThresholdInBytes() {
+        return this.producerConfig.getBatchSizeThresholdInBytes();
+    }
+
+    public void setBatchSizeThresholdInBytes(int batchSizeThresholdInBytes) {
+        this.producerConfig.setBatchSizeThresholdInBytes(batchSizeThresholdInBytes);
+    }
+
+    public int getBatchCountThreshold() {
+        return this.producerConfig.getBatchCountThreshold();
+    }
+
+    public void setBatchCountThreshold(int batchCountThreshold) {
+        this.producerConfig.setBatchCountThreshold(batchCountThreshold);
+    }
+
+    public int getLingerMs() {
+        return this.producerConfig.getLingerMs();
+    }
+
+    public void setLingerMs(int lingerMs) {
+        this.producerConfig.setLingerMs(lingerMs);
+    }
+
+    public int getRetries() {
+        return this.producerConfig.getRetries();
+    }
+
+    public void setRetries(int retries) {
+        this.producerConfig.setRetries(retries);
+    }
+
+    public int getMaxReservedAttempts() {
+        return this.producerConfig.getMaxReservedAttempts();
+    }
+
+    public void setMaxReservedAttempts(int maxReservedAttempts) {
+        this.producerConfig.setMaxReservedAttempts(maxReservedAttempts);
+    }
+
+    public long getBaseRetryBackoffMs() {
+        return this.producerConfig.getBaseRetryBackoffMs();
+    }
+
+    public void setBaseRetryBackoffMs(long baseRetryBackoffMs) {
+        this.producerConfig.setBaseRetryBackoffMs(baseRetryBackoffMs);
+    }
+
+    public long getMaxRetryBackoffMs() {
+        return this.producerConfig.getMaxRetryBackoffMs();
+    }
+
+    public void setMaxRetryBackoffMs(long maxRetryBackoffMs) {
+        this.producerConfig.setMaxRetryBackoffMs(maxRetryBackoffMs);
+    }
+
+    public Encoder<E> getEncoder() {
+        return this.encoder;
+    }
+
+    public void setEncoder(Encoder<E> encoder) {
+        this.encoder = encoder;
+    }
+
+    public void setMdcFields(String mdcFields) {
+        this.mdcFields = mdcFields;
+    }
+}

+ 37 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/common/RecommendLoghubAppenderCallback.java

@@ -0,0 +1,37 @@
+package com.tzld.piaoquan.abtest.common;
+
+
+import com.aliyun.openservices.aliyun.log.producer.Callback;
+import com.aliyun.openservices.aliyun.log.producer.Result;
+import com.aliyun.openservices.log.common.LogItem;
+
+import java.util.List;
+
+/**
+ * @author dyp
+ */
+public class RecommendLoghubAppenderCallback<E> implements Callback {
+    protected RecommendLoghubAppender<E> loghubAppender;
+    protected String project;
+    protected String logstore;
+    protected String topic;
+    protected String source;
+    protected List<LogItem> logItems;
+
+    public RecommendLoghubAppenderCallback(RecommendLoghubAppender<E> loghubAppender, String project, String logstore,
+                                           String topic, String source, List<LogItem> logItems) {
+        this.loghubAppender = loghubAppender;
+        this.project = project;
+        this.logstore = logstore;
+        this.topic = topic;
+        this.source = source;
+        this.logItems = logItems;
+    }
+
+    public void onCompletion(Result result) {
+        if (!result.isSuccessful()) {
+            this.loghubAppender.addError("Failed to send log, project=" + this.project + ", logStore=" + this.logstore + ", topic=" + this.topic + ", source=" + this.source + ", logItem=" + this.logItems + ", errorCode=" + result.getErrorCode() + ", errorMessage=" + result.getErrorMessage());
+        }
+
+    }
+}

+ 28 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/common/ThreadPoolFactory.java

@@ -0,0 +1,28 @@
+package com.tzld.piaoquan.abtest.common;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author dyp
+ */
+public final class ThreadPoolFactory {
+    private final static ExecutorService DEFAULT = new CommonThreadPoolExecutor(
+            128,
+            128,
+            0L, TimeUnit.SECONDS,
+            new LinkedBlockingQueue<>(1000),
+            new ThreadFactoryBuilder().setNameFormat("DEFAULT-%d").build(),
+            new ThreadPoolExecutor.AbortPolicy());
+
+
+    public static ExecutorService defaultPool() {
+        return DEFAULT;
+    }
+
+
+}

+ 68 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/common/enums/AppTypeEnum.java

@@ -0,0 +1,68 @@
+package com.tzld.piaoquan.abtest.common.enums;
+
+/**
+ * AppType
+ * 目前只维护 0 1 4 13 4个应用
+ *
+ * @author supeng
+ * @date 2020/08/19
+ */
+public enum AppTypeEnum {
+    /**
+     * vlog小程序
+     */
+    VLOG(0, "VLOG"),
+    /**
+     * 票圈视频APP
+     */
+    QING_QU_APP(1, "轻趣视频APP"),
+    SMILE(2, "搞笑视频"),
+    LOVE_MOVIE(3, "爱电影"),
+    /**
+     * 票圈视频小程序
+     */
+    LOVE_LIVE(4, "爱生活"),
+    /**
+     * 长视频小程序
+     */
+    LONG_VIDEO(5, "长视频"),
+    SHORT_VIDEO(6, "短视频"),
+    SURPRISE(7, "惊奇视频"),
+    PC(8, "PC端"),
+    CIRCLE_APP(9, "票圈长视频APP"),
+    LONG_VIDEO_LITE(10, "票圈长视频lite"),
+    /**
+     * 票圈相册小程序
+     */
+    ALBUM(11, "票圈相册"),
+    H5(12, "H5端"),
+    /**
+     * 极速版APP
+     */
+    APP_SPEED(13, "APP极速"),
+    SMALL_SPEED(14, "小程序极速"),
+    WAN_NENG_VIDEO(17, "万能影视屋(票圈|信仰之路)"),
+    LAO_HAO_KAN_VIDEO(18, "老好看视频"),
+    ZUI_JING_QI(19, "票圈最惊奇"),
+    PIAO_QUAN_VIDEO_PLUS(21, "票圈视频+"),
+    JOURNEY(22, "票圈足迹"),
+    PIAO_QUAN_MEIHAO_ZHUFU(25, "票圈美好祝福"),
+    ADMIN_CRAWLER(888888, "后台-爬虫"),
+    OTHERS(999999, "其他的调用方,比如后台管理系统");
+
+    private int code;
+    private String desc;
+
+    public int getCode() {
+        return code;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    private AppTypeEnum(Integer code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+}

+ 63 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/config/RedisTemplateConfig.java

@@ -0,0 +1,63 @@
+package com.tzld.piaoquan.abtest.config;
+
+import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
+import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
+import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
+import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+@Configuration
+public class RedisTemplateConfig {
+
+    @Bean("redisPool")
+    @ConfigurationProperties(prefix = "spring.redis.lettuce.pool")
+    public GenericObjectPoolConfig<LettucePoolingClientConfiguration> redisPool() {
+        return new GenericObjectPoolConfig<>();
+    }
+
+    @Bean("redisConfig")
+    @ConfigurationProperties(prefix = "spring.redis")
+    public RedisStandaloneConfiguration tairConfig() {
+        return new RedisStandaloneConfiguration();
+    }
+
+    @Bean("redisFactory")
+    @Primary
+    public LettuceConnectionFactory factory(@Qualifier("redisPool") GenericObjectPoolConfig<LettucePoolingClientConfiguration> redisPool,
+                                            @Qualifier("redisConfig") RedisStandaloneConfiguration redisConfig) {
+        LettuceClientConfiguration lettuceClientConfiguration =
+                LettucePoolingClientConfiguration.builder().poolConfig(redisPool).build();
+        return new LettuceConnectionFactory(redisConfig, lettuceClientConfiguration);
+    }
+
+    @Bean(name = "redisTemplate")
+    public RedisTemplate<String, String> getRedisTemplate(@Qualifier("redisFactory") RedisConnectionFactory factory) {
+        return buildRedisTemplateByString(factory);
+    }
+
+    /**
+     * 构建redisTemplate 使用string序列化
+     *
+     * @param factory
+     * @return
+     */
+    public RedisTemplate<String, String> buildRedisTemplateByString(RedisConnectionFactory factory) {
+        RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
+        redisTemplate.setConnectionFactory(factory);
+        // key的序列化类型 保证可读性
+        redisTemplate.setKeySerializer(new StringRedisSerializer());
+        redisTemplate.setValueSerializer(new StringRedisSerializer());
+        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+        redisTemplate.setHashValueSerializer(new StringRedisSerializer());
+        return redisTemplate;
+    }
+
+}

+ 32 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/grpcservice/ExpGrpcService.java

@@ -0,0 +1,32 @@
+package com.tzld.piaoquan.recommend.server.grpcservice;
+
+import com.tzld.piaoquan.abtest.client.ProtobufUtils;
+import com.tzld.piaoquan.abtest.client.model.ExpServiceGrpc;
+import com.tzld.piaoquan.abtest.client.model.GetExpRequest;
+import com.tzld.piaoquan.abtest.client.model.GetExpResponse;
+import com.tzld.piaoquan.abtest.service.ExpService;
+import io.grpc.stub.StreamObserver;
+import lombok.extern.slf4j.Slf4j;
+import net.devh.boot.grpc.server.service.GrpcService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * @author dyp
+ */
+@GrpcService
+@Slf4j
+public class ExpGrpcService extends ExpServiceGrpc.ExpServiceImplBase {
+    @Autowired
+    private ExpService expService;
+
+    @Override
+    public void getExp(GetExpRequest request, StreamObserver<GetExpResponse> responseObserver) {
+        log.info("ExpGrpcService getExp request={}", ProtobufUtils.toJson(request));
+        GetExpResponse response = expService.getExp(request);
+        log.info("ExpGrpcService getExp response={}", ProtobufUtils.toJson(response));
+
+        responseObserver.onNext(response);
+        responseObserver.onCompleted();
+    }
+
+}

+ 32 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/grpcservice/GroupGrpcService.java

@@ -0,0 +1,32 @@
+package com.tzld.piaoquan.abtest.grpcservice;
+
+import com.tzld.piaoquan.abtest.client.ProtobufUtils;
+import com.tzld.piaoquan.abtest.client.model.GetGroupRequest;
+import com.tzld.piaoquan.abtest.client.model.GetGroupResponse;
+import com.tzld.piaoquan.abtest.client.model.GroupServiceGrpc;
+import com.tzld.piaoquan.abtest.service.GroupService;
+import io.grpc.stub.StreamObserver;
+import lombok.extern.slf4j.Slf4j;
+import net.devh.boot.grpc.server.service.GrpcService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * @author dyp
+ */
+@GrpcService
+@Slf4j
+public class GroupGrpcService extends GroupServiceGrpc.GroupServiceImplBase {
+    @Autowired
+    private GroupService groupService;
+
+    @Override
+    public void getGroup(GetGroupRequest request, StreamObserver<GetGroupResponse> responseObserver) {
+        log.info("GroupGrpcService getGroup request={}", ProtobufUtils.toJson(request));
+        GetGroupResponse response = groupService.getGroup(request);
+        log.info("GroupGrpcService getGroup response={}", ProtobufUtils.toJson(response));
+
+        responseObserver.onNext(response);
+        responseObserver.onCompleted();
+    }
+
+}

+ 25 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/grpcservice/GrpcAspect.java

@@ -0,0 +1,25 @@
+package com.tzld.piaoquan.abtest.grpcservice;
+
+import com.tzld.piaoquan.abtest.util.TraceUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author dyp
+ */
+@Aspect
+@Component
+@Slf4j
+public class GrpcAspect {
+
+    @Around("execution(* com.tzld.piaoquan.abtest.grpcservice.*GrpcService.*(..))")
+    public Object around(ProceedingJoinPoint pjp) throws Throwable {
+        TraceUtils.setMDC();
+        Object result = pjp.proceed();
+        TraceUtils.removeMDC();
+        return result;
+    }
+}

+ 42 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/model/ExpConfig.java

@@ -0,0 +1,42 @@
+package com.tzld.piaoquan.abtest.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author dyp
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class ExpConfig {
+    private String appType;
+    private List<Layer> layers;
+
+
+    @Getter
+    @Setter
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class Layer {
+        private String layerId;
+        private int bucketNum;
+        private List<Exp> exps;
+        private Map<String, String> groupRule;
+    }
+
+    @Getter
+    @Setter
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class Exp {
+        private String expId;
+        private int[] range;
+    }
+}

+ 47 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/repository/WxVideoStatus.java

@@ -0,0 +1,47 @@
+package com.tzld.piaoquan.abtest.repository;
+
+import lombok.Data;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@Entity
+@Table(name = "wx_video_status")
+public class WxVideoStatus implements Serializable {
+    @Id
+    private Long videoId;
+
+    private Long videoUid;
+
+    private Integer auditStatus;
+
+    private Integer appAuditStatus;
+
+    private String auditTranscationId;
+
+    private Integer chargeExamineStatus;
+
+    private Integer videoStatus;
+
+    private Integer examineStatus;
+
+    private Integer recommendStatus;
+
+    private Integer appRecommendStatus;
+
+    private Integer sensitiveStatus;
+
+    private Integer stageRecommendExamineStatus;
+
+    private Date videoGmtCreate;
+
+    private Long videoGmtCreateTimestamp;
+
+    private Integer originalStatus;
+
+    private Integer isReupload;
+}

+ 12 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/repository/WxVideoStatusRepository.java

@@ -0,0 +1,12 @@
+package com.tzld.piaoquan.abtest.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Collection;
+import java.util.List;
+
+@Repository
+public interface WxVideoStatusRepository extends JpaRepository<WxVideoStatus, Long> {
+    List<WxVideoStatus> findAllByVideoIdIn(Collection<Long> videoIds);
+}

+ 73 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/service/ExpService.java

@@ -0,0 +1,73 @@
+package com.tzld.piaoquan.abtest.service;
+
+import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
+import com.google.common.reflect.TypeToken;
+import com.tzld.piaoquan.abtest.client.model.GetExpRequest;
+import com.tzld.piaoquan.abtest.client.model.GetExpResponse;
+import com.tzld.piaoquan.abtest.client.model.Result;
+import com.tzld.piaoquan.abtest.model.ExpConfig;
+import com.tzld.piaoquan.abtest.util.CommonCollectionUtils;
+import com.tzld.piaoquan.abtest.util.JSONUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.MapUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author dyp
+ */
+@Service
+@Slf4j
+public class ExpService {
+
+    @Autowired
+    @Qualifier("redisTemplate")
+    private RedisTemplate<String, String> redisTemplate;
+
+    @ApolloJsonValue("${new.exp,config.v2:[]}")
+    private List<ExpConfig> expConfigs;
+
+    public GetExpResponse getExp(GetExpRequest request) {
+        // TODO:hard code 为了快速做AB验证,应该由AB系统支持
+        Map<String, List<ExpConfig.Layer>> layerMap = CommonCollectionUtils.toMap(expConfigs, c -> c.getAppType(),
+                c -> c.getLayers());
+        if (MapUtils.isNotEmpty(layerMap)) {
+            Map<String, Integer> bucketMap = JSONUtils.fromJson(request.getGroup(),
+                    new TypeToken<Map<String, Integer>>() {
+                    }, Collections.emptyMap());
+            if (MapUtils.isNotEmpty(bucketMap)) {
+                List<ExpConfig.Layer> layers = layerMap.get(request.getAppType() + "");
+
+                if (CollectionUtils.isNotEmpty(layers)) {
+                    Map<String, String> expIdMap = new HashMap<>();
+                    for (ExpConfig.Layer layer : layers) {
+                        for (ExpConfig.Exp exp : layer.getExps()) {
+                            if (bucketMap.containsKey(layer.getLayerId())
+                                    && exp.getRange().length == 2
+                                    && exp.getRange()[0] <= bucketMap.get(layer.getLayerId())
+                                    && exp.getRange()[1] >= bucketMap.get(layer.getLayerId())) {
+                                expIdMap.put(layer.getLayerId(), exp.getExpId());
+                            }
+                        }
+                    }
+                    return GetExpResponse.newBuilder()
+                            .setResult(Result.newBuilder().setCode(1))
+                            .putAllExp(expIdMap)
+                            .build();
+                }
+            }
+        }
+        return GetExpResponse.newBuilder()
+                .setResult(Result.newBuilder().setCode(1))
+                .build();
+    }
+
+}

+ 183 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/service/GroupService.java

@@ -0,0 +1,183 @@
+package com.tzld.piaoquan.abtest.service;
+
+import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
+import com.google.common.base.Strings;
+import com.google.common.reflect.TypeToken;
+import com.tzld.piaoquan.abtest.client.model.GetGroupRequest;
+import com.tzld.piaoquan.abtest.client.model.GetGroupResponse;
+import com.tzld.piaoquan.abtest.client.model.Result;
+import com.tzld.piaoquan.abtest.model.ExpConfig;
+import com.tzld.piaoquan.abtest.util.CommonCollectionUtils;
+import com.tzld.piaoquan.abtest.util.JSONUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.MapUtils;
+import org.apache.commons.lang.RandomStringUtils;
+import org.apache.commons.lang.math.NumberUtils;
+import org.apache.commons.lang3.RandomUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import java.net.InetAddress;
+import java.util.*;
+
+/**
+ * @author dyp
+ */
+@Service
+@Slf4j
+public class GroupService {
+
+    @Autowired
+    @Qualifier("redisTemplate")
+    private RedisTemplate<String, String> redisTemplate;
+
+    @ApolloJsonValue("${new.exp,config.v2:[]}")
+    private List<ExpConfig> expConfigs;
+
+    private final String exp_param_date = "date";
+    private final String exp_param_group = "group";
+    private final String exp_param_isFromCard = "isFromCard";
+    private final String exp_param_chain = "chain";
+
+    private final String scene_open = "open";
+    private final String scene_daily_share_return = "daily_share_return";
+    private final String scene_not_daily_share_return = "not_daily_share_return";
+
+
+    public GetGroupResponse getGroup(GetGroupRequest request) {
+        try {
+
+
+            Map<String, List<ExpConfig.Layer>> layerMap = CommonCollectionUtils.toMap(expConfigs, c -> c.getAppType(),
+                    c -> c.getLayers());
+            List<ExpConfig.Layer> layers = layerMap.get(String.valueOf(request.getAppType()));
+
+            if (CollectionUtils.isEmpty(layers)) {
+                return GetGroupResponse.newBuilder()
+                        .setResult(Result.newBuilder().setCode(1))
+                        .setGroup(Strings.nullToEmpty(request.getGroup()))
+                        .build();
+            }
+
+            //TODO 1. 确定场景 主动打开、当日分享、历史分享
+            String scene = "";
+            Map<String, String> expMap = JSONUtils.fromJson(request.getGroup(), new TypeToken<Map<String, String>>() {
+            }, Collections.emptyMap());
+            long time = NumberUtils.toLong(expMap.get(exp_param_date), 0);
+            String isFromCard = expMap.getOrDefault(exp_param_isFromCard, "0");
+            if (org.apache.commons.lang.StringUtils.equals(isFromCard, "1")) {
+                if (isToday(time)) {
+                    // 当日分享
+                    scene = scene_daily_share_return;
+                } else {
+                    // 历史分享
+                    scene = scene_not_daily_share_return;
+                }
+            } else {
+                // 主动打开
+                scene = scene_open;
+            }
+
+            log.info("ExperimentInterceptor scene {}", scene);
+
+            //TODO 2. 根据场景 确定策略并分组
+            Map<String, Object> groupMap = new HashMap<>();
+            Map<String, String> oldGroupMap = org.apache.commons.lang.StringUtils.equals(expMap.get(exp_param_group), "ab100")
+                    ? Collections.emptyMap()
+                    : JSONUtils.fromJson(expMap.get(exp_param_group), new TypeToken<Map<String, String>>() {
+            }, Collections.emptyMap());
+            for (ExpConfig.Layer layer : layers) {
+                int bucketNum = layer.getBucketNum() <= 0 ? 100 : layer.getBucketNum();
+                Map<String, String> groupRule = layer.getGroupRule();
+                if (MapUtils.isEmpty(groupRule)) {
+                    // random
+                    groupMap.put(layer.getLayerId(), RandomUtils.nextInt(0, bucketNum));
+                } else {
+                    String policy = groupRule.get(scene);
+                    if (org.apache.commons.lang.StringUtils.isBlank(policy)) {
+                        // random
+                        groupMap.put(layer.getLayerId(), RandomUtils.nextInt(0, bucketNum));
+                    }
+                    switch (policy) {
+
+                        case "inherit":
+                            groupMap.put(layer.getLayerId(), oldGroupMap.getOrDefault(layer.getLayerId(),
+                                    RandomUtils.nextInt(0, bucketNum) + ""));
+                            break;
+                        case "random_once":
+                            if (!isToday(time)) {
+                                groupMap.put(layer.getLayerId(), RandomUtils.nextInt(0, bucketNum));
+                            } else {
+                                groupMap.put(layer.getLayerId(), oldGroupMap.getOrDefault(layer.getLayerId(),
+                                        RandomUtils.nextInt(0, bucketNum) + ""));
+                            }
+                            break;
+                        case "random_every":
+                        default:
+                            groupMap.put(layer.getLayerId(), RandomUtils.nextInt(0, bucketNum) + "");
+                            break;
+                    }
+                }
+            }
+
+            // 3. 写入header
+            Map<String, Object> map = new HashMap<>();
+            map.put(exp_param_date, System.currentTimeMillis());
+            map.put(exp_param_group, JSONUtils.toJson(groupMap));
+            map.put(exp_param_isFromCard, isFromCard);
+            map.put(exp_param_chain, org.apache.commons.lang.StringUtils.equals(isFromCard, "1")
+                    ? expMap.get(exp_param_chain)
+                    : genChainId());
+
+            return GetGroupResponse.newBuilder()
+                    .setResult(Result.newBuilder().setCode(1))
+                    .setGroup(JSONUtils.toJson(map))
+                    .build();
+
+        } catch (Exception e) {
+            log.error("get group error", e);
+            return GetGroupResponse.newBuilder()
+                    .setResult(Result.newBuilder().setCode(0).setMessage("get group error"))
+                    .build();
+        }
+    }
+
+
+    public static String genChainId() {
+        StringBuilder traceIdBuilder = new StringBuilder();
+        InetAddress address = InetAddress.getLoopbackAddress();
+        if (address == null) {
+            return "default";
+        }
+        String addressStr = address.getHostAddress();
+        if (org.apache.commons.lang3.StringUtils.isBlank(addressStr)) {
+            return "default";
+        }
+        String[] addressArr = addressStr.split("\\.");
+        if (addressArr.length != 4) {
+            return "default";
+        }
+
+        traceIdBuilder.append(org.apache.commons.lang3.StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addressArr[0])), 2, '0'))
+                .append(org.apache.commons.lang3.StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addressArr[1])), 2, '0'))
+                .append(org.apache.commons.lang3.StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addressArr[2])), 2, '0'))
+                .append(org.apache.commons.lang3.StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addressArr[3])), 2, '0'))
+                .append(System.currentTimeMillis())
+                .append(RandomStringUtils.randomAlphabetic(4))
+                .append(Thread.currentThread().getId());
+        return traceIdBuilder.toString();
+    }
+
+    private boolean isToday(long time) {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeInMillis(time);
+        Calendar current = Calendar.getInstance();
+        current.setTime(new Date());
+        return calendar.get(Calendar.YEAR) == current.get(Calendar.YEAR)
+                && calendar.get(Calendar.MONTH) == current.get(Calendar.MONTH)
+                && calendar.get(Calendar.DAY_OF_MONTH) == current.get(Calendar.DAY_OF_MONTH);
+    }
+}

+ 23 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/service/ServiceBeanFactory.java

@@ -0,0 +1,23 @@
+package com.tzld.piaoquan.abtest.service;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author dyp
+ */
+@Component
+public class ServiceBeanFactory implements ApplicationContextAware {
+    private static ApplicationContext applicationContext;
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        this.applicationContext = applicationContext;
+    }
+
+    public static <T> T getBean(Class<T> clazz) {
+        return applicationContext.getBean(clazz);
+    }
+}

+ 25 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/service/WarmUpService.java

@@ -0,0 +1,25 @@
+package com.tzld.piaoquan.abtest.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.annotation.Order;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * @author dyp
+ */
+@Component
+@Order(1)
+public class WarmUpService {
+    @Autowired
+    @Qualifier("redisTemplate")
+    private RedisTemplate<String, String> redisTemplate;
+
+    @PostConstruct
+    public void warmup() {
+        redisTemplate.opsForValue().get("");
+    }
+}

+ 59 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/util/CommonCollectionUtils.java

@@ -0,0 +1,59 @@
+package com.tzld.piaoquan.abtest.util;
+
+import org.apache.commons.collections4.CollectionUtils;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * @author dyp
+ */
+public class CommonCollectionUtils {
+
+    public static <T> List<T> iterableToList(Iterable<T> iterable) {
+        List<T> list = new ArrayList<>();
+        for (T element : iterable) {
+            list.add(element);
+        }
+        return list;
+    }
+
+    public static <T, R> List<R> toList(List<T> list, Function<T, R> map) {
+        if (CollectionUtils.isEmpty(list)) {
+            return Collections.emptyList();
+        }
+        return list.stream().map(map).collect(Collectors.toList());
+    }
+
+    public static <T, R> List<R> toListDistinct(List<T> list, Function<T, R> map) {
+        if (CollectionUtils.isEmpty(list)) {
+            return Collections.emptyList();
+        }
+        return list.stream().map(map).distinct().collect(Collectors.toList());
+    }
+
+
+    public static <T, K, V> Map<K, V> toMap(Collection<T> col, Function<T, K> keyFunc, Function<T, V> valueFunc) {
+        if (CollectionUtils.isEmpty(col)) {
+            return Collections.emptyMap();
+        }
+        return col.stream().collect(Collectors.toMap(keyFunc::apply, valueFunc::apply, (u, v) -> v, HashMap::new));
+    }
+
+    public static <T, R> Set<R> toSet(Collection<T> col, Function<T, R> func) {
+        if (CollectionUtils.isEmpty(col)) {
+            return Collections.emptySet();
+        }
+        return col.stream()
+                .map(c -> func.apply(c))
+                .collect(Collectors.toSet());
+    }
+
+    public static <T> boolean contains(Collection<T> col, T ele) {
+        if (CollectionUtils.isEmpty(col)) {
+            return false;
+        }
+        return col.contains(ele);
+    }
+}

+ 49 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/util/JSONUtils.java

@@ -0,0 +1,49 @@
+package com.tzld.piaoquan.abtest.util;
+
+import com.alibaba.fastjson.JSONObject;
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+@Slf4j
+public class JSONUtils {
+
+
+    public static String toJsonNew(Object obj) {
+        if (obj == null) {
+            return "";
+        }
+        try {
+            return JSONObject.toJSONString(obj);
+        } catch (Exception e) {
+            log.error("toJson exception", e);
+            return "";
+        }
+    }
+    public static String toJson(Object obj) {
+        if (obj == null) {
+            return "";
+        }
+        try {
+            return new Gson().toJson(obj);
+        } catch (Exception e) {
+            log.error("toJson exception", e);
+            return "";
+        }
+    }
+
+    public static <T> T fromJson(String value, TypeToken<T> typeToken, T defaultValue) {
+
+        if (StringUtils.isBlank(value)) {
+            return defaultValue;
+        }
+        try {
+            return JSONObject.parseObject(value, typeToken.getType());
+        } catch (Exception e) {
+            log.error("parseObject error! value=[{}]", value, e);
+        }
+        return defaultValue;
+    }
+
+}

+ 52 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/util/TraceUtils.java

@@ -0,0 +1,52 @@
+package com.tzld.piaoquan.abtest.util;
+
+import org.apache.commons.lang.RandomStringUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.MDC;
+
+import java.net.InetAddress;
+
+/**
+ * @author dyp
+ */
+public class TraceUtils {
+    public static final String TRACE_ID_KEY = "traceId";
+    public static final String DEFAULT_TRACE_ID = "default_trace_id";
+
+    public static void setMDC() {
+        MDC.put(TraceUtils.TRACE_ID_KEY, genTraceId());
+    }
+
+    public static String currentTraceId() {
+        return MDC.get(TraceUtils.TRACE_ID_KEY);
+    }
+
+    public static void removeMDC() {
+        MDC.remove(TraceUtils.TRACE_ID_KEY);
+    }
+
+    public static String genTraceId() {
+        StringBuilder traceIdBuilder = new StringBuilder();
+        InetAddress address = InetAddress.getLoopbackAddress();
+        if (address == null) {
+            return DEFAULT_TRACE_ID;
+        }
+        String addressStr = address.getHostAddress();
+        if (StringUtils.isBlank(addressStr)) {
+            return DEFAULT_TRACE_ID;
+        }
+        String[] addressArr = addressStr.split("\\.");
+        if (addressArr.length != 4) {
+            return DEFAULT_TRACE_ID;
+        }
+
+        traceIdBuilder.append(StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addressArr[0])), 2, '0'))
+                .append(StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addressArr[1])), 2, '0'))
+                .append(StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addressArr[2])), 2, '0'))
+                .append(StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addressArr[3])), 2, '0'))
+                .append(System.currentTimeMillis())
+                .append(RandomStringUtils.randomAlphabetic(4))
+                .append(Thread.currentThread().getId());
+        return traceIdBuilder.toString();
+    }
+}

+ 42 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/web/ControllerAspect.java

@@ -0,0 +1,42 @@
+package com.tzld.piaoquan.abtest.web;
+
+import com.google.common.base.Stopwatch;
+import com.tzld.piaoquan.recommend.server.util.JSONUtils;
+import com.tzld.piaoquan.recommend.server.util.TraceUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author dyp
+ */
+@Aspect
+@Component
+@Slf4j
+public class ControllerAspect {
+
+    @Around("execution(* com.tzld.piaoquan.abtest.web.*Controller.*(..))")
+    public Object around(ProceedingJoinPoint pjp) throws Throwable {
+        TraceUtils.setMDC();
+
+        String className = pjp.getTarget().getClass().getSimpleName();
+        MethodSignature signature = (MethodSignature) pjp.getSignature();
+        Stopwatch stopwatch = Stopwatch.createStarted();
+        log.info("request className=[{}], method=[{}], param=[{}]", className, signature.getName(),
+                JSONUtils.toJson(pjp.getArgs()));
+        Object result = pjp.proceed();
+        if (result != null && result instanceof String) {
+            log.info("response result=[{}] cost=[{}]", result, stopwatch.stop().elapsed(TimeUnit.MILLISECONDS));
+        } else {
+            log.info("response result=[{}] cost=[{}]", JSONUtils.toJson(result), stopwatch.stop().elapsed(TimeUnit.MILLISECONDS));
+        }
+
+        TraceUtils.removeMDC();
+        return result;
+    }
+}

+ 29 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/web/ExpController.java

@@ -0,0 +1,29 @@
+package com.tzld.piaoquan.abtest.web;
+
+import com.tzld.piaoquan.abtest.client.ProtobufUtils;
+import com.tzld.piaoquan.abtest.client.model.GetExpRequest;
+import com.tzld.piaoquan.abtest.client.model.GetExpResponse;
+import com.tzld.piaoquan.abtest.service.ExpService;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.MDC;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author dyp
+ */
+@RestController
+@Slf4j
+public class ExpController {
+    @Autowired
+    private ExpService expService;
+
+    @RequestMapping("/exp")
+    public String getExp(@RequestBody GetExpRequest httpRequest) {
+        MDC.put("appType", String.valueOf(httpRequest.getAppType()));
+        GetExpResponse response = expService.getExp(httpRequest);
+        return ProtobufUtils.toJson(response);
+    }
+}

+ 24 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/web/GlobalExceptionHandler.java

@@ -0,0 +1,24 @@
+package com.tzld.piaoquan.abtest.web;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.exception.ExceptionUtils;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.servlet.http.HttpServletRequest;
+
+@ControllerAdvice
+@Slf4j
+public class GlobalExceptionHandler {
+
+    @ExceptionHandler
+    @ResponseBody
+    public String handleException(HttpServletRequest hsr, Exception e) {
+        if (e instanceof RuntimeException) {
+            log.error("request uri {} error:{}", hsr.getRequestURI(), ExceptionUtils.getFullStackTrace(e));
+        }
+        return "";
+    }
+
+}

+ 29 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/web/GroupController.java

@@ -0,0 +1,29 @@
+package com.tzld.piaoquan.abtest.web;
+
+import com.tzld.piaoquan.abtest.client.ProtobufUtils;
+import com.tzld.piaoquan.abtest.client.model.GetGroupRequest;
+import com.tzld.piaoquan.abtest.client.model.GetGroupResponse;
+import com.tzld.piaoquan.abtest.service.GroupService;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.MDC;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author dyp
+ */
+@RestController
+@Slf4j
+public class GroupController {
+    @Autowired
+    private GroupService groupService;
+
+    @RequestMapping("/group")
+    public String getGroup(@RequestBody GetGroupRequest httpRequest) {
+        MDC.put("appType", String.valueOf(httpRequest.getAppType()));
+        GetGroupResponse response = groupService.getGroup(httpRequest);
+        return ProtobufUtils.toJson(response);
+    }
+}

+ 18 - 0
abtest-service/src/main/java/com/tzld/piaoquan/abtest/web/HealthCheckController.java

@@ -0,0 +1,18 @@
+package com.tzld.piaoquan.abtest.web;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author dyp
+ */
+@RestController
+@Slf4j
+public class HealthCheckController {
+
+    @GetMapping("/ok")
+    public String ok() {
+        return "ok";
+    }
+}

+ 55 - 0
abtest-service/src/main/resources/application-dev.yml

@@ -0,0 +1,55 @@
+server:
+  port: 8001
+
+eureka:
+  instance:
+    prefer-ip-address: true #是否优先使用IP地址作为主机名的标识,默认false
+    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port} #注册到eureka上的唯一实例ID
+    lease-renewal-interval-in-seconds: 10 #表示eureka client发送心跳给server端的频率,默认30
+    lease-expiration-duration-in-seconds: 30 #表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance,默认90
+  client:
+    registry-fetch-interval-seconds: 5  #定时从Eureka Server拉取服务注册信息的间隔时间
+    serviceUrl:
+      defaultZone: http://deveureka-internal.piaoquantv.com/eureka/
+
+spring:
+  redis:
+    hostName: r-bp1ps6my7lzg8rdhwx682.redis.rds.aliyuncs.com
+    port: 6379
+    password: Wqsd@2019
+    timeout: 1000
+    lettuce:
+      pool:
+        max-active: 8
+        max-wait: -1
+        max-idle: 8
+        min-idle: 0
+  datasource:
+    url: jdbc:mysql://rm-bp1k5853td1r25g3n690.mysql.rds.aliyuncs.com:3306/longvideo?useSSL=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
+    username: wx2016_longvideo
+    password: wx2016_longvideoP@assword1234
+    driver-class-name: com.mysql.jdbc.Driver
+    hikari:
+      connection-timeout: 30000
+      minimum-idle: 5
+      maximum-pool-size: 10
+      auto-commit: true
+      idle-timeout: 30000
+  jpa:
+    hibernate:
+      ddl-auto: validate
+    database: mysql
+
+apollo:
+  meta: http://devapolloconfig-internal.piaoquantv.com
+
+aliyun:
+  log:
+    endpoint: cn-hangzhou.log.aliyuncs.com
+    accessKeyId: LTAIP6x1l3DXfSxm
+    accessKeySecret: KbTaM9ars4OX3PMS6Xm7rtxGr1FLon
+    project: abtest-test
+
+logging:
+  file:
+    path: ./${spring.application.name}/logs/

+ 51 - 0
abtest-service/src/main/resources/application-pre.yml

@@ -0,0 +1,51 @@
+server:
+  port: 8080
+
+eureka:
+  instance:
+    prefer-ip-address: true #是否优先使用IP地址作为主机名的标识,默认false
+    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port} #注册到eureka上的唯一实例ID
+    lease-renewal-interval-in-seconds: 10 #表示eureka client发送心跳给server端的频率,默认30
+    lease-expiration-duration-in-seconds: 30 #表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance,默认90
+  client:
+    registry-fetch-interval-seconds: 5  #定时从Eureka Server拉取服务注册信息的间隔时间
+    serviceUrl:
+      defaultZone: http://preeureka-internal.piaoquantv.com/eureka/
+
+spring:
+  redis:
+    hostName: r-bp1fogs2mflr1ybfot.redis.rds.aliyuncs.com
+    port: 6379
+    password: Wqsd@2019
+    timeout: 1000
+    lettuce:
+      pool:
+        max-active: 8
+        max-wait: -1
+        max-idle: 8
+        min-idle: 0
+  datasource:
+    url: jdbc:mysql://rm-bp1jjv3jv98133plv285-vpc-rw.mysql.rds.aliyuncs.com:3306/longvideo?useSSL=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
+    username: wx2016_longvideo
+    password: wx2016_longvideoP@assword1234
+    driver-class-name: com.mysql.jdbc.Driver
+    hikari:
+      connection-timeout: 30000
+      minimum-idle: 5
+      maximum-pool-size: 10
+      auto-commit: true
+      idle-timeout: 30000
+  jpa:
+    hibernate:
+      ddl-auto: validate
+    database: mysql
+
+apollo:
+  meta: http://preapolloconfig-internal.piaoquantv.com
+
+aliyun:
+  log:
+    endpoint: cn-hangzhou-intranet.log.aliyuncs.com
+    accessKeyId: LTAIP6x1l3DXfSxm
+    accessKeySecret: KbTaM9ars4OX3PMS6Xm7rtxGr1FLon
+    project: abtest

+ 50 - 0
abtest-service/src/main/resources/application-prod.yml

@@ -0,0 +1,50 @@
+server:
+  port: 8080
+
+eureka:
+  instance:
+    prefer-ip-address: true #是否优先使用IP地址作为主机名的标识,默认false
+    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port} #注册到eureka上的唯一实例ID
+    lease-renewal-interval-in-seconds: 10 #表示eureka client发送心跳给server端的频率,默认30
+    lease-expiration-duration-in-seconds: 30 #表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance,默认90
+  client:
+    registry-fetch-interval-seconds: 5  #定时从Eureka Server拉取服务注册信息的间隔时间
+    serviceUrl:
+      defaultZone: http://eureka-internal.piaoquantv.com/eureka/
+
+spring:
+  redis:
+    hostName: r-bp1fogs2mflr1ybfot.redis.rds.aliyuncs.com
+    port: 6379
+    password: Wqsd@2019
+    timeout: 1000
+    lettuce:
+      pool:
+        max-active: 8
+        max-wait: -1
+        max-idle: 8
+        min-idle: 0
+  datasource:
+    url: jdbc:mysql://rm-bp1jjv3jv98133plv285-vpc-rw.mysql.rds.aliyuncs.com:3306/longvideo?useSSL=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
+    username: wx2016_longvideo
+    password: wx2016_longvideoP@assword1234
+    driver-class-name: com.mysql.jdbc.Driver
+    hikari:
+      connection-timeout: 30000
+      minimum-idle: 5
+      maximum-pool-size: 100
+      auto-commit: true
+      idle-timeout: 30000
+  jpa:
+    hibernate:
+      ddl-auto: validate
+    database: mysql
+apollo:
+  meta: http://apolloconfig-internal.piaoquantv.com
+
+aliyun:
+  log:
+    endpoint: cn-hangzhou-intranet.log.aliyuncs.com
+    accessKeyId: LTAIP6x1l3DXfSxm
+    accessKeySecret: KbTaM9ars4OX3PMS6Xm7rtxGr1FLon
+    project: abtest

+ 51 - 0
abtest-service/src/main/resources/application-test.yml

@@ -0,0 +1,51 @@
+server:
+  port: 8080
+
+eureka:
+  instance:
+    prefer-ip-address: true #是否优先使用IP地址作为主机名的标识,默认false
+    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port} #注册到eureka上的唯一实例ID
+    lease-renewal-interval-in-seconds: 10 #表示eureka client发送心跳给server端的频率,默认30
+    lease-expiration-duration-in-seconds: 30 #表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance,默认90
+  client:
+    registry-fetch-interval-seconds: 5  #定时从Eureka Server拉取服务注册信息的间隔时间
+    serviceUrl:
+      defaultZone: http://testeureka-internal.piaoquantv.com/eureka/
+
+spring:
+  redis:
+    hostName: r-bp1ps6my7lzg8rdhwx682.redis.rds.aliyuncs.com
+    port: 6379
+    password: Wqsd@2019
+    timeout: 1000
+    lettuce:
+      pool:
+        max-active: 8
+        max-wait: -1
+        max-idle: 8
+        min-idle: 0
+  datasource:
+    url: jdbc:mysql://rm-bp1k5853td1r25g3n690.mysql.rds.aliyuncs.com:3306/longvideo?useSSL=true&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
+    username: wx2016_longvideo
+    password: wx2016_longvideoP@assword1234
+    driver-class-name: com.mysql.jdbc.Driver
+    hikari:
+      connection-timeout: 30000
+      minimum-idle: 5
+      maximum-pool-size: 10
+      auto-commit: true
+      idle-timeout: 30000
+  jpa:
+    hibernate:
+      ddl-auto: validate
+    database: mysql
+
+apollo:
+  meta: http://testapolloconfig-internal.piaoquantv.com
+
+aliyun:
+  log:
+    endpoint: cn-hangzhou-intranet.log.aliyuncs.com
+    accessKeyId: LTAIP6x1l3DXfSxm
+    accessKeySecret: KbTaM9ars4OX3PMS6Xm7rtxGr1FLon
+    project: abtest-test

+ 49 - 0
abtest-service/src/main/resources/application.yml

@@ -0,0 +1,49 @@
+spring:
+  profiles:
+    active: dev
+  application:
+    name: abtest
+#  cloud:
+#    sentinel:
+#      eager: true
+#      transport:
+#        port: 8719
+#      log:
+#        dir: /datalog/sentinel
+#      datasource:
+#        ds1:
+#          apollo:
+#            namespace-name: application
+#            flow-rules-key: sentinel.flowRules
+#            default-flow-rule-value: []
+#            rule-type: flow
+grpc:
+  server:
+    port: 9001
+    reflection-service-enabled: true
+
+server:
+  tomcat:
+    threads:
+      max: 1000
+    uri-encoding: UTF-8
+    accept-count: 1000
+    connection-timeout: 30000
+  servlet:
+    context-path:
+    session:
+      timeout: 60
+pagehelper:
+  helper-dialect: mysql
+
+logging:
+  file:
+    path: /datalog/weblog/${spring.application.name}/
+
+app:
+  id: abtest
+apollo:
+  bootstrap:
+    enabled: true
+    namespaces: application,RD.Experiment
+  cacheDir: /datalog/apollo-cache-dir

+ 328 - 0
abtest-service/src/main/resources/logback-spring.xml

@@ -0,0 +1,328 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
+<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
+<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
+<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
+<configuration scan="true" scanPeriod="10 seconds">
+
+    <!--为了防止进程退出时,内存中的数据丢失,请加上此选项-->
+    <shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
+
+    <!-- 日志上下文名称 -->
+    <contextName>logback</contextName>
+
+    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
+    <!-- 日志输出格式 -->
+    <property name="LOG_PATTERN"
+              value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}\\(%L\\) - %msg%n"/>
+    <property name="mdcFields" value="traceId,appType"></property>
+
+    <!-- spring property使用方式 -->
+    <springProperty name="LOG_PATH" source="logging.file.path"/>
+    <springProperty name="aliyun_log_endpoint" source="aliyun.log.endpoint"/>
+    <springProperty name="aliyun_log_accessKeyId" source="aliyun.log.accessKeyId"/>
+    <springProperty name="aliyun_log_accessKeySecret" source="aliyun.log.accessKeySecret"/>
+    <springProperty name="aliyun_log_project" source="aliyun.log.project"/>
+
+    <!-- 彩色日志 -->
+    <!-- 彩色日志依赖的渲染类 -->
+    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
+    <conversionRule conversionWord="wex"
+                    converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
+    <conversionRule conversionWord="wEx"
+                    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
+    <!-- 彩色日志格式 -->
+    <property name="CONSOLE_LOG_PATTERN"
+              value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}\\(%L\\)){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
+
+    <!--输出到控制台-->
+    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>info</level>
+        </filter>
+        <encoder>
+            <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
+            <!-- 设置字符集 -->
+            <charset>UTF-8</charset>
+        </encoder>
+    </appender>
+
+    <!--输出到文件-->
+    <!-- 时间滚动输出 level为 DEBUG 日志 -->
+    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 正在记录的日志文件的路径及文件名 -->
+        <file>${LOG_PATH}/debug.log</file>
+        <!--日志文件输出格式-->
+        <encoder>
+            <pattern>${LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset> <!-- 设置字符集 -->
+        </encoder>
+        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 日志归档 -->
+            <fileNamePattern>${LOG_PATH}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>100MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+            <!--日志文件保留天数-->
+            <maxHistory>1</maxHistory>
+        </rollingPolicy>
+        <!-- 此日志文件只记录debug级别的 -->
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>debug</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!-- 时间滚动输出 level为 INFO 日志 -->
+    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 正在记录的日志文件的路径及文件名 -->
+        <file>${LOG_PATH}/info.log</file>
+        <!--日志文件输出格式-->
+        <encoder>
+            <pattern>${LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset>
+        </encoder>
+        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 每天日志归档路径以及格式 -->
+            <fileNamePattern>${LOG_PATH}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>100MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+            <!--日志文件保留天数-->
+            <maxHistory>1</maxHistory>
+        </rollingPolicy>
+        <!-- 此日志文件只记录info级别的 -->
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>info</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!-- 时间滚动输出 level为 WARN 日志 -->
+    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 正在记录的日志文件的路径及文件名 -->
+        <file>${LOG_PATH}/warn.log</file>
+        <!--日志文件输出格式-->
+        <encoder>
+            <pattern>${LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
+        </encoder>
+        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_PATH}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>100MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+            <!--日志文件保留天数-->
+            <maxHistory>1</maxHistory>
+        </rollingPolicy>
+        <!-- 此日志文件只记录warn级别的 -->
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>warn</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+
+    <!-- 时间滚动输出 level为 ERROR 日志 -->
+    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 正在记录的日志文件的路径及文件名 -->
+        <file>${LOG_PATH}/error.log</file>
+        <!--日志文件输出格式-->
+        <encoder>
+            <pattern>${LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
+        </encoder>
+        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_PATH}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <maxFileSize>100MB</maxFileSize>
+            </timeBasedFileNamingAndTriggeringPolicy>
+            <!--日志文件保留天数-->
+            <maxHistory>1</maxHistory>
+        </rollingPolicy>
+        <!-- 此日志文件只记录ERROR级别的 -->
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>ERROR</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <appender name="loghubAppenderInfo" class="com.tzld.piaoquan.abtest.common.RecommendLoghubAppender">
+        <!--必选项-->
+        <!-- 账号及网络配置 -->
+        <endpoint>${aliyun_log_endpoint}</endpoint>
+        <accessKeyId>${aliyun_log_accessKeyId}</accessKeyId>
+        <accessKeySecret>${aliyun_log_accessKeySecret}</accessKeySecret>
+        <!-- sls 项目配置 -->
+        <project>${aliyun_log_project}</project>
+        <logStore>info-log</logStore>
+        <!--必选项 (end)-->
+        <!-- 可选项 -->
+        <topic></topic>
+        <source></source>
+        <!-- 可选项 详见 '参数说明'-->
+        <totalSizeInBytes>104857600</totalSizeInBytes>
+        <maxBlockMs>0</maxBlockMs>
+        <ioThreadCount>16</ioThreadCount>
+        <batchSizeThresholdInBytes>524288</batchSizeThresholdInBytes>
+        <batchCountThreshold>4096</batchCountThreshold>
+        <lingerMs>2000</lingerMs>
+        <retries>10</retries>
+        <baseRetryBackoffMs>100</baseRetryBackoffMs>
+        <maxRetryBackoffMs>50000</maxRetryBackoffMs>
+        <!-- 可选项 通过配置 encoder 的 pattern 自定义 log 的格式 -->
+        <!--日志文件输出格式-->
+        <encoder>
+            <pattern>${LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>INFO</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+        <mdcFields>
+            ${mdcFields}
+        </mdcFields>
+    </appender>
+
+    <appender name="loghubAppenderWarn" class="com.tzld.piaoquan.abtest.common.RecommendLoghubAppender">
+        <!--必选项-->
+        <!-- 账号及网络配置 -->
+        <endpoint>${aliyun_log_endpoint}</endpoint>
+        <accessKeyId>${aliyun_log_accessKeyId}</accessKeyId>
+        <accessKeySecret>${aliyun_log_accessKeySecret}</accessKeySecret>
+        <!-- sls 项目配置 -->
+        <project>${aliyun_log_project}</project>
+        <logStore>warn-log</logStore>
+        <!--必选项 (end)-->
+        <!-- 可选项 -->
+        <topic></topic>
+        <source></source>
+        <!-- 可选项 详见 '参数说明'-->
+        <totalSizeInBytes>104857600</totalSizeInBytes>
+        <maxBlockMs>0</maxBlockMs>
+        <ioThreadCount>16</ioThreadCount>
+        <batchSizeThresholdInBytes>524288</batchSizeThresholdInBytes>
+        <batchCountThreshold>4096</batchCountThreshold>
+        <lingerMs>2000</lingerMs>
+        <retries>10</retries>
+        <baseRetryBackoffMs>100</baseRetryBackoffMs>
+        <maxRetryBackoffMs>50000</maxRetryBackoffMs>
+        <!-- 可选项 通过配置 encoder 的 pattern 自定义 log 的格式 -->
+        <!--日志文件输出格式-->
+        <encoder>
+            <pattern>${LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>WARN</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+        <mdcFields>
+            ${mdcFields}
+        </mdcFields>
+    </appender>
+
+    <appender name="loghubAppenderError" class="com.tzld.piaoquan.abtest.common.RecommendLoghubAppender">
+        <!--必选项-->
+        <!-- 账号及网络配置 -->
+        <endpoint>${aliyun_log_endpoint}</endpoint>
+        <accessKeyId>${aliyun_log_accessKeyId}</accessKeyId>
+        <accessKeySecret>${aliyun_log_accessKeySecret}</accessKeySecret>
+        <!-- sls 项目配置 -->
+        <project>${aliyun_log_project}</project>
+        <logStore>error-log</logStore>
+        <!--必选项 (end)-->
+        <!-- 可选项 -->
+        <topic></topic>
+        <source></source>
+        <!-- 可选项 详见 '参数说明'-->
+        <totalSizeInBytes>104857600</totalSizeInBytes>
+        <maxBlockMs>0</maxBlockMs>
+        <ioThreadCount>16</ioThreadCount>
+        <batchSizeThresholdInBytes>524288</batchSizeThresholdInBytes>
+        <batchCountThreshold>4096</batchCountThreshold>
+        <lingerMs>2000</lingerMs>
+        <retries>10</retries>
+        <baseRetryBackoffMs>100</baseRetryBackoffMs>
+        <maxRetryBackoffMs>50000</maxRetryBackoffMs>
+        <!-- 可选项 通过配置 encoder 的 pattern 自定义 log 的格式 -->
+        <!--日志文件输出格式-->
+        <encoder>
+            <pattern>${LOG_PATTERN}</pattern>
+            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>ERROR</level>
+            <onMatch>ACCEPT</onMatch>
+            <onMismatch>DENY</onMismatch>
+        </filter>
+        <mdcFields>
+            ${mdcFields}
+        </mdcFields>
+    </appender>
+
+    <!--
+        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、
+        以及指定<appender>。<logger>仅有一个name属性,
+        一个可选的level和一个可选的addtivity属性。
+        name:用来指定受此logger约束的某一个包或者具体的某一个类。
+        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
+              还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
+              如果未设置此属性,那么当前logger将会继承上级的级别。
+        addtivity:是否向上级logger传递打印信息。默认是true。
+    -->
+    <!--<logger name="org.springframework.web" level="info"/>-->
+    <!--<logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>-->
+    <!--
+        使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
+        第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
+        第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
+     -->
+
+    <!--
+        root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
+        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
+        不能设置为INHERITED或者同义词NULL。默认是DEBUG
+        可以包含零个或多个元素,标识这个appender将会添加到这个logger。
+    -->
+
+    <springProfile name="dev">
+        <logger name="com.tzld.piaoquan.abtest" level="info"/>
+    </springProfile>
+    <springProfile name="test">
+        <logger name="com.tzld.piaoquan.abtest" level="info"/>
+    </springProfile>
+    <springProfile name="pre">
+        <logger name="com.tzld.piaoquan.abtest" level="info"/>
+    </springProfile>
+    <springProfile name="stress">
+        <logger name="com.tzld.piaoquan.abtest" level="info"/>
+    </springProfile>
+    <springProfile name="prod">
+        <logger name="com.tzld.piaoquan.abtest" level="info"/>
+    </springProfile>
+
+    <root level="info">
+        <appender-ref ref="CONSOLE"/>
+        <appender-ref ref="DEBUG_FILE"/>
+        <appender-ref ref="INFO_FILE"/>
+        <appender-ref ref="WARN_FILE"/>
+        <appender-ref ref="ERROR_FILE"/>
+        <appender-ref ref="loghubAppenderInfo"/>
+        <appender-ref ref="loghubAppenderWarn"/>
+        <appender-ref ref="loghubAppenderError"/>
+    </root>
+
+</configuration>

+ 26 - 0
pom.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>pom</packaging>
+    <parent>
+        <groupId>com.tzld.commons</groupId>
+        <artifactId>supom</artifactId>
+        <version>1.0.9</version>
+    </parent>
+    <groupId>com.tzld.piaoquan</groupId>
+    <artifactId>abtest</artifactId>
+    <version>1.0.0</version>
+    <name>abtest</name>
+    <description>abtest</description>
+
+    <modules>
+        <module>abtest-service</module>
+        <module>abtest-client</module>
+    </modules>
+
+    <dependencies>
+    </dependencies>
+
+</project>