Просмотр исходного кода

Add user service and user validation when assigning roles

Jason Song 8 лет назад
Родитель
Сommit
566b6fb9c6

+ 1 - 1
apollo-adminservice/pom.xml

@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>com.ctrip.framework.apollo</groupId>
 		<artifactId>apollo</artifactId>
-		<version>0.0.2</version>
+		<version>0.0.3-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>

+ 1 - 1
apollo-assembly/pom.xml

@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>com.ctrip.framework.apollo</groupId>
 		<artifactId>apollo</artifactId>
-		<version>0.0.2</version>
+		<version>0.0.3-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>

+ 1 - 1
apollo-biz/pom.xml

@@ -4,7 +4,7 @@
 	<parent>
 		<artifactId>apollo</artifactId>
 		<groupId>com.ctrip.framework.apollo</groupId>
-		<version>0.0.2</version>
+		<version>0.0.3-SNAPSHOT</version>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
 	<artifactId>apollo-biz</artifactId>

+ 1 - 1
apollo-buildtools/pom.xml

@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>com.ctrip.framework.apollo</groupId>
 		<artifactId>apollo</artifactId>
-		<version>0.0.2</version>
+		<version>0.0.3-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>

+ 1 - 1
apollo-client/pom.xml

@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>com.ctrip.framework.apollo</groupId>
 		<artifactId>apollo</artifactId>
-		<version>0.0.2</version>
+		<version>0.0.3-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>

+ 14 - 2
apollo-client/src/main/java/com/ctrip/framework/apollo/internals/RemoteConfigRepository.java

@@ -161,7 +161,13 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
   }
 
   private ApolloConfig loadApolloConfig() {
-    m_loadConfigRateLimiter.tryAcquire(5, TimeUnit.SECONDS);//wait at most 5 seconds
+    if (!m_loadConfigRateLimiter.tryAcquire(5, TimeUnit.SECONDS)) {
+      //wait at most 5 seconds
+      try {
+        TimeUnit.SECONDS.sleep(5);
+      } catch (InterruptedException e) {
+      }
+    }
     String appId = m_configUtil.getAppId();
     String cluster = m_configUtil.getCluster();
     String dataCenter = m_configUtil.getDataCenter();
@@ -285,7 +291,13 @@ public class RemoteConfigRepository extends AbstractConfigRepository {
     final Random random = new Random();
     ServiceDTO lastServiceDto = null;
     while (!m_longPollingStopped.get() && !Thread.currentThread().isInterrupted()) {
-      m_longPollRateLimiter.tryAcquire(5, TimeUnit.SECONDS);//wait at most 5 seconds
+      if (!m_longPollRateLimiter.tryAcquire(5, TimeUnit.SECONDS)) {
+        //wait at most 5 seconds
+        try {
+          TimeUnit.SECONDS.sleep(5);
+        } catch (InterruptedException e) {
+        }
+      }
       Transaction transaction = Cat.newTransaction("Apollo.ConfigService", "pollNotification");
       try {
         if (lastServiceDto == null) {

+ 1 - 1
apollo-common/pom.xml

@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>com.ctrip.framework.apollo</groupId>
 		<artifactId>apollo</artifactId>
-		<version>0.0.2</version>
+		<version>0.0.3-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>

+ 1 - 1
apollo-configservice/pom.xml

@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>com.ctrip.framework.apollo</groupId>
 		<artifactId>apollo</artifactId>
-		<version>0.0.2</version>
+		<version>0.0.3-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>

+ 1 - 1
apollo-core/pom.xml

@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>com.ctrip.framework.apollo</groupId>
 		<artifactId>apollo</artifactId>
-		<version>0.0.2</version>
+		<version>0.0.3-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>

+ 1 - 1
apollo-demo/pom.xml

@@ -4,7 +4,7 @@
 	<parent>
 		<artifactId>apollo</artifactId>
 		<groupId>com.ctrip.framework.apollo</groupId>
-		<version>0.0.2</version>
+		<version>0.0.3-SNAPSHOT</version>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>
 	<artifactId>apollo-demo</artifactId>

+ 1 - 1
apollo-portal/pom.xml

@@ -4,7 +4,7 @@
 	<parent>
 		<groupId>com.ctrip.framework.apollo</groupId>
 		<artifactId>apollo</artifactId>
-		<version>0.0.2</version>
+		<version>0.0.3-SNAPSHOT</version>
 		<relativePath>../pom.xml</relativePath>
 	</parent>
 	<modelVersion>4.0.0</modelVersion>

+ 230 - 0
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/auth/CtripUserService.java

@@ -0,0 +1,230 @@
+package com.ctrip.framework.apollo.portal.auth;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import com.ctrip.framework.apollo.portal.entity.po.UserInfo;
+import com.ctrip.framework.apollo.portal.service.ServerConfigService;
+import com.ctrip.framework.apollo.portal.service.UserService;
+
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @author Jason Song(song_s@ctrip.com)
+ */
+public class CtripUserService implements UserService {
+  private ServerConfigService serverConfigService;
+  private RestTemplate restTemplate;
+  private List<String> searchUserMatchFields;
+  private ParameterizedTypeReference<Map<String, List<UserServiceResponse>>> responseType;
+
+  public CtripUserService(ServerConfigService serverConfigService) {
+    this.serverConfigService = serverConfigService;
+    this.restTemplate = new RestTemplate();
+    this.searchUserMatchFields =
+        Lists.newArrayList("empcode", "empaccount", "displayname", "c_name", "pinyin");
+    this.responseType = new ParameterizedTypeReference<Map<String, List<UserServiceResponse>>>() {
+    };
+  }
+
+  @Override
+  public List<UserInfo> searchUsers(String keyword, int offset, int limit) {
+    UserServiceRequest request = assembleSearchUserRequest(keyword, offset, limit);
+
+    HttpEntity<UserServiceRequest> entity = new HttpEntity<>(request);
+    ResponseEntity<Map<String, List<UserServiceResponse>>> response =
+        restTemplate.exchange(getUserServiceUrl(), HttpMethod.POST, entity, responseType);
+
+    if (!response.getBody().containsKey("result")) {
+      return Collections.emptyList();
+    }
+
+    List<UserInfo> result = Lists.newArrayList();
+    result.addAll(
+        response.getBody().get("result").stream().map(this::transformUserServiceResponseToUserInfo)
+            .collect(Collectors.toList()));
+
+    return result;
+  }
+
+  @Override
+  public UserInfo findByUserId(String userId) {
+    List<UserInfo> userInfoList = this.findByUserIds(Lists.newArrayList(userId));
+    if (CollectionUtils.isEmpty(userInfoList)) {
+      return null;
+    }
+    return userInfoList.get(0);
+  }
+
+  public List<UserInfo> findByUserIds(List<String> userIds) {
+    UserServiceRequest request = assembleFindUserRequest(Lists.newArrayList(userIds));
+
+    HttpEntity<UserServiceRequest> entity = new HttpEntity<>(request);
+    ResponseEntity<Map<String, List<UserServiceResponse>>> response =
+        restTemplate.exchange(getUserServiceUrl(), HttpMethod.POST, entity, responseType);
+
+    if (!response.getBody().containsKey("result")) {
+      return Collections.emptyList();
+    }
+
+    List<UserInfo> result = Lists.newArrayList();
+    result.addAll(
+        response.getBody().get("result").stream().map(this::transformUserServiceResponseToUserInfo)
+            .collect(Collectors.toList()));
+
+    return result;
+  }
+
+  private UserInfo transformUserServiceResponseToUserInfo(UserServiceResponse userServiceResponse) {
+    UserInfo userInfo = new UserInfo();
+    userInfo.setUserId(userServiceResponse.getEmpaccount());
+    userInfo.setName(userServiceResponse.getDisplayname());
+    userInfo.setEmail(userServiceResponse.getEmail());
+    return userInfo;
+  }
+
+  UserServiceRequest assembleSearchUserRequest(String keyword, int offset, int limit) {
+    Map<String, Object> query = Maps.newHashMap();
+    Map<String, Object> multiMatchMap = Maps.newHashMap();
+    multiMatchMap.put("fields", searchUserMatchFields);
+    multiMatchMap.put("operator", "and");
+    multiMatchMap.put("query", keyword);
+    multiMatchMap.put("type", "best_fields");
+    query.put("multi_match", multiMatchMap);
+
+    return assembleUserServiceRequest(query, offset, limit);
+  }
+
+  UserServiceRequest assembleFindUserRequest(List<String> userIds) {
+    Map<String, Object>
+        query =
+        ImmutableMap.of("filtered", ImmutableMap
+            .of("filter", ImmutableMap.of("terms", ImmutableMap.of("empaccount", userIds))));
+
+    return assembleUserServiceRequest(query, 0, userIds.size());
+  }
+
+  private UserServiceRequest assembleUserServiceRequest(Map<String, Object> query, int offset,
+                                                        int limit) {
+    UserServiceRequest request = new UserServiceRequest();
+    request.setAccess_token(getUserServiceAccessToken());
+    request.setType("emloyee");
+
+    UserServiceRequestBody requestBody = new UserServiceRequestBody();
+    requestBody.setIndexAlias("itdb_emloyee");
+    request.setRequest_body(requestBody);
+
+    Map<String, Object> queryJson = Maps.newHashMap();
+    requestBody.setQueryJson(queryJson);
+
+    queryJson.put("query", query);
+
+    queryJson.put("from", offset);
+    queryJson.put("size", limit);
+
+    return request;
+  }
+
+
+  private String getUserServiceUrl() {
+    return serverConfigService.getValue("userService.url");
+  }
+
+  private String getUserServiceAccessToken() {
+    return serverConfigService.getValue("userService.accessToken");
+  }
+
+  static class UserServiceRequest {
+    private String access_token;
+    private String type;
+    private UserServiceRequestBody request_body;
+
+    public String getAccess_token() {
+      return access_token;
+    }
+
+    public void setAccess_token(String access_token) {
+      this.access_token = access_token;
+    }
+
+    public String getType() {
+      return type;
+    }
+
+    public void setType(String type) {
+      this.type = type;
+    }
+
+    public UserServiceRequestBody getRequest_body() {
+      return request_body;
+    }
+
+    public void setRequest_body(
+        UserServiceRequestBody request_body) {
+      this.request_body = request_body;
+    }
+  }
+
+  static class UserServiceRequestBody {
+    private String indexAlias;
+    private Map<String, Object> queryJson;
+
+    public String getIndexAlias() {
+      return indexAlias;
+    }
+
+    public void setIndexAlias(String indexAlias) {
+      this.indexAlias = indexAlias;
+    }
+
+    public Map<String, Object> getQueryJson() {
+      return queryJson;
+    }
+
+    public void setQueryJson(Map<String, Object> queryJson) {
+      this.queryJson = queryJson;
+    }
+  }
+
+  static class UserServiceResponse {
+    private String empaccount;
+    private String displayname;
+    private String email;
+
+    public String getEmpaccount() {
+      return empaccount;
+    }
+
+    public void setEmpaccount(String empaccount) {
+      this.empaccount = empaccount;
+    }
+
+    public String getDisplayname() {
+      return displayname;
+    }
+
+    public void setDisplayname(String displayname) {
+      this.displayname = displayname;
+    }
+
+    public String getEmail() {
+      return email;
+    }
+
+    public void setEmail(String email) {
+      this.email = email;
+    }
+  }
+
+}

+ 45 - 0
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/auth/DefaultUserService.java

@@ -0,0 +1,45 @@
+package com.ctrip.framework.apollo.portal.auth;
+
+import com.google.common.collect.Lists;
+
+import com.ctrip.framework.apollo.portal.entity.po.UserInfo;
+import com.ctrip.framework.apollo.portal.service.UserService;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Jason Song(song_s@ctrip.com)
+ */
+public class DefaultUserService implements UserService {
+
+  @Override
+  public List<UserInfo> searchUsers(String keyword, int offset, int limit) {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public UserInfo findByUserId(String userId) {
+    if (userId.equals("apollo")) {
+      return assembleDefaultUser();
+    }
+    return null;
+  }
+
+  @Override
+  public List<UserInfo> findByUserIds(List<String> userIds) {
+    if (userIds.contains("apollo")) {
+      return Lists.newArrayList(assembleDefaultUser());
+    }
+    return null;
+  }
+
+  private UserInfo assembleDefaultUser() {
+    UserInfo defaultUser = new UserInfo();
+    defaultUser.setUserId("apollo");
+    defaultUser.setName("apollo");
+    defaultUser.setEmail("apollo@acme.com");
+
+    return defaultUser;
+  }
+}

+ 14 - 1
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/configutation/AuthConfiguration.java

@@ -2,11 +2,14 @@ package com.ctrip.framework.apollo.portal.configutation;
 
 import com.ctrip.framework.apollo.portal.auth.CtripLogoutHandler;
 import com.ctrip.framework.apollo.portal.auth.CtripUserInfoHolder;
+import com.ctrip.framework.apollo.portal.auth.CtripUserService;
 import com.ctrip.framework.apollo.portal.auth.DefaultLogoutHandler;
 import com.ctrip.framework.apollo.portal.auth.DefaultUserInfoHolder;
+import com.ctrip.framework.apollo.portal.auth.DefaultUserService;
 import com.ctrip.framework.apollo.portal.auth.LogoutHandler;
 import com.ctrip.framework.apollo.portal.auth.UserInfoHolder;
 import com.ctrip.framework.apollo.portal.service.ServerConfigService;
+import com.ctrip.framework.apollo.portal.service.UserService;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -29,7 +32,6 @@ import javax.servlet.Filter;
 @Configuration
 public class AuthConfiguration {
 
-
   /**
    * 在ctrip内部运行时,会指定 spring.profiles.active = ctrip.
    * ctrip sso是通过cas实现的,所以需要加载相关的filter和listener.
@@ -141,6 +143,11 @@ public class AuthConfiguration {
         throw new RuntimeException("instance listener fail", e);
       }
     }
+
+    @Bean
+    public UserService ctripUserService(ServerConfigService serverConfigService) {
+      return new CtripUserService(serverConfigService);
+    }
   }
 
   /**
@@ -160,6 +167,12 @@ public class AuthConfiguration {
     public DefaultLogoutHandler logoutHandler(){
       return new DefaultLogoutHandler();
     }
+
+    @Bean
+    @ConditionalOnMissingBean(UserService.class)
+    public UserService defaultUserService() {
+      return new DefaultUserService();
+    }
   }
 
 

+ 10 - 2
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/PermissionController.java

@@ -10,6 +10,7 @@ import com.ctrip.framework.apollo.portal.entity.vo.AppRolesAssignedUsers;
 import com.ctrip.framework.apollo.portal.entity.vo.NamespaceRolesAssignedUsers;
 import com.ctrip.framework.apollo.portal.entity.vo.PermissionCondition;
 import com.ctrip.framework.apollo.portal.service.RolePermissionService;
+import com.ctrip.framework.apollo.portal.service.UserService;
 import com.ctrip.framework.apollo.portal.util.RoleUtils;
 
 import org.springframework.beans.factory.annotation.Autowired;
@@ -34,6 +35,8 @@ public class PermissionController {
   private UserInfoHolder userInfoHolder;
   @Autowired
   private RolePermissionService rolePermissionService;
+  @Autowired
+  private UserService userService;
 
   @RequestMapping("/apps/{appId}/permissions/{permissionType}")
   public ResponseEntity<PermissionCondition> hasPermission(@PathVariable String appId, @PathVariable String permissionType) {
@@ -80,7 +83,7 @@ public class PermissionController {
   @RequestMapping(value = "/apps/{appId}/namespaces/{namespaceName}/roles/{roleType}", method = RequestMethod.POST)
   public ResponseEntity<Void> assignNamespaceRoleToUser(@PathVariable String appId, @PathVariable String namespaceName,
                                                         @PathVariable String roleType, @RequestBody String user){
-
+    checkUserExists(user);
     checkArgument(user);
 
     if (!RoleType.isValidRoleType(roleType)){
@@ -121,7 +124,7 @@ public class PermissionController {
   @RequestMapping(value = "/apps/{appId}/roles/{roleType}", method = RequestMethod.POST)
   public ResponseEntity<Void> assignAppRoleToUser(@PathVariable String appId, @PathVariable String roleType,
                                                   @RequestBody String user){
-
+    checkUserExists(user);
     checkArgument(user);
 
     if (!RoleType.isValidRoleType(roleType)){
@@ -147,5 +150,10 @@ public class PermissionController {
     return ResponseEntity.ok().build();
   }
 
+  private void checkUserExists(String userId) {
+    if (userService.findByUserId(userId) == null) {
+      throw new BadRequestException(String.format("User %s does not exist!", userId));
+    }
+  }
 
 }

+ 19 - 0
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/UserInfoController.java

@@ -3,12 +3,16 @@ package com.ctrip.framework.apollo.portal.controller;
 import com.ctrip.framework.apollo.portal.auth.LogoutHandler;
 import com.ctrip.framework.apollo.portal.auth.UserInfoHolder;
 import com.ctrip.framework.apollo.portal.entity.po.UserInfo;
+import com.ctrip.framework.apollo.portal.service.UserService;
 
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.io.IOException;
+import java.util.List;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -21,6 +25,9 @@ public class UserInfoController {
   @Autowired
   private LogoutHandler logoutHandler;
 
+  @Autowired
+  private UserService userService;
+
   @RequestMapping("/user")
   public UserInfo getCurrentUserName() {
       return userInfoHolder.getUser();
@@ -30,4 +37,16 @@ public class UserInfoController {
   public void logout(HttpServletRequest request, HttpServletResponse response) throws IOException {
     logoutHandler.logout(request, response);
   }
+
+  @RequestMapping("/users")
+  public List<UserInfo> searchUsersByKeyword(@RequestParam(value = "keyword") String keyword,
+                                             @RequestParam(value = "offset", defaultValue = "0") int offset,
+                                             @RequestParam(value = "limit", defaultValue = "10") int limit) {
+    return userService.searchUsers(keyword, offset, limit);
+  }
+
+  @RequestMapping("/users/{userId}")
+  public UserInfo getUserByUserId(@PathVariable String userId) {
+    return userService.findByUserId(userId);
+  }
 }

+ 18 - 0
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/po/UserInfo.java

@@ -2,6 +2,8 @@ package com.ctrip.framework.apollo.portal.entity.po;
 
 public class UserInfo {
   private String userId;
+  private String name;
+  private String email;
 
   public String getUserId() {
     return userId;
@@ -10,4 +12,20 @@ public class UserInfo {
   public void setUserId(String userId) {
     this.userId = userId;
   }
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public String getEmail() {
+    return email;
+  }
+
+  public void setEmail(String email) {
+    this.email = email;
+  }
 }

+ 16 - 0
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/UserService.java

@@ -0,0 +1,16 @@
+package com.ctrip.framework.apollo.portal.service;
+
+import com.ctrip.framework.apollo.portal.entity.po.UserInfo;
+
+import java.util.List;
+
+/**
+ * @author Jason Song(song_s@ctrip.com)
+ */
+public interface UserService {
+  List<UserInfo> searchUsers(String keyword, int offset, int limit);
+
+  UserInfo findByUserId(String userId);
+
+  List<UserInfo> findByUserIds(List<String> userIds);
+}

+ 236 - 0
apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/auth/CtripUserServiceTest.java

@@ -0,0 +1,236 @@
+package com.ctrip.framework.apollo.portal.auth;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+
+import com.ctrip.framework.apollo.portal.entity.po.UserInfo;
+import com.ctrip.framework.apollo.portal.service.ServerConfigService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author Jason Song(song_s@ctrip.com)
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class CtripUserServiceTest {
+  private CtripUserService ctripUserService;
+  private String someUserServiceUrl;
+  private String someUserServiceToken;
+  private ParameterizedTypeReference<Map<String, List<CtripUserService.UserServiceResponse>>>
+      someResponseType;
+
+  @Mock
+  private ServerConfigService serverConfigService;
+
+  @Mock
+  private RestTemplate restTemplate;
+
+  @Before
+  public void setUp() throws Exception {
+    ctripUserService = new CtripUserService(serverConfigService);
+    ReflectionTestUtils.setField(ctripUserService, "restTemplate", restTemplate);
+    someResponseType =
+        (ParameterizedTypeReference<Map<String, List<CtripUserService.UserServiceResponse>>>) ReflectionTestUtils
+            .getField(ctripUserService, "responseType");
+
+    someUserServiceUrl = "http://someurl";
+    someUserServiceToken = "someToken";
+    when(serverConfigService.getValue("userService.url")).thenReturn(someUserServiceUrl);
+    when(serverConfigService.getValue("userService.accessToken")).thenReturn(someUserServiceToken);
+  }
+
+  @Test
+  public void testAssembleSearchUserRequest() throws Exception {
+    String someKeyword = "someKeyword";
+    int someOffset = 0;
+    int someLimit = 10;
+
+    CtripUserService.UserServiceRequest request =
+        ctripUserService.assembleSearchUserRequest(someKeyword, someOffset, someLimit);
+
+    assertEquals(someUserServiceToken, request.getAccess_token());
+    assertEquals("emloyee", request.getType());
+
+    CtripUserService.UserServiceRequestBody requestBody = request.getRequest_body();
+
+    assertEquals("itdb_emloyee", requestBody.getIndexAlias());
+
+    Map<String, Object> queryJson = requestBody.getQueryJson();
+    assertEquals(someOffset, queryJson.get("from"));
+    assertEquals(someLimit, queryJson.get("size"));
+
+    Map<String, Object> query = (Map<String, Object>) queryJson.get("query");
+    Map<String, Object> multiMatchMap = (Map<String, Object>) query.get("multi_match");
+    assertEquals(someKeyword, multiMatchMap.get("query"));
+  }
+
+  @Test
+  public void testAssembleFindUserRequest() throws Exception {
+    String someUserId = "someUser";
+    String anotherUserId = "anotherUser";
+    List<String> userIds = Lists.newArrayList(someUserId, anotherUserId);
+
+    CtripUserService.UserServiceRequest request = ctripUserService.assembleFindUserRequest(userIds);
+
+    assertEquals(someUserServiceToken, request.getAccess_token());
+    assertEquals("emloyee", request.getType());
+
+    CtripUserService.UserServiceRequestBody requestBody = request.getRequest_body();
+
+    assertEquals("itdb_emloyee", requestBody.getIndexAlias());
+
+    Map<String, Object> queryJson = requestBody.getQueryJson();
+    assertEquals(0, queryJson.get("from"));
+    assertEquals(2, queryJson.get("size"));
+
+    Map<String, Object> query = (Map<String, Object>) queryJson.get("query");
+    Map<String, Object> terms =
+        getMapFromMap(getMapFromMap(getMapFromMap(query, "filtered"), "filter"), "terms");
+
+    List<String> userIdTerms = (List<String>) terms.get("empaccount");
+
+    assertTrue(userIdTerms.containsAll(userIds));
+
+  }
+
+  private Map<String, Object> getMapFromMap(Map<String, Object> map, String key) {
+    return (Map<String, Object>) map.get(key);
+  }
+
+  @Test
+  public void testSearchUsers() throws Exception {
+    String someUserId = "someUserId";
+    String someName = "someName";
+    String someEmail = "someEmail";
+    String anotherUserId = "anotherUserId";
+    String anotherName = "anotherName";
+    String anotherEmail = "anotherEmail";
+    String someKeyword = "someKeyword";
+    int someOffset = 0;
+    int someLimit = 10;
+
+    CtripUserService.UserServiceResponse someUserResponse =
+        assembleUserServiceResponse(someUserId, someName, someEmail);
+
+    CtripUserService.UserServiceResponse anotherUserResponse =
+        assembleUserServiceResponse(anotherUserId, anotherName, anotherEmail);
+
+    Map<String, List<CtripUserService.UserServiceResponse>> resultMap =
+        ImmutableMap.of("result", Lists.newArrayList(someUserResponse, anotherUserResponse));
+    ResponseEntity<Map<String, List<CtripUserService.UserServiceResponse>>> someResponse
+        = new ResponseEntity<>(resultMap, HttpStatus.OK);
+
+    when(restTemplate.exchange(eq(someUserServiceUrl), eq(HttpMethod.POST), any(HttpEntity.class),
+        eq(someResponseType))).thenReturn(someResponse);
+
+    List<UserInfo> users = ctripUserService.searchUsers(someKeyword, someOffset, someLimit);
+
+    assertEquals(2, users.size());
+    compareUserInfoAndUserServiceResponse(someUserResponse, users.get(0));
+    compareUserInfoAndUserServiceResponse(anotherUserResponse, users.get(1));
+  }
+
+  @Test(expected = HttpClientErrorException.class)
+  public void testSearchUsersWithError() throws Exception {
+    when(restTemplate.exchange(eq(someUserServiceUrl), eq(HttpMethod.POST), any(HttpEntity.class),
+        eq(someResponseType)))
+        .thenThrow(new HttpClientErrorException(HttpStatus.INTERNAL_SERVER_ERROR));
+
+    String someKeyword = "someKeyword";
+    int someOffset = 0;
+    int someLimit = 10;
+
+    ctripUserService.searchUsers(someKeyword, someOffset, someLimit);
+  }
+
+  @Test
+  public void testFindByUserId() throws Exception {
+    String someUserId = "someUserId";
+    String someName = "someName";
+    String someEmail = "someEmail";
+
+    CtripUserService.UserServiceResponse someUserResponse =
+        assembleUserServiceResponse(someUserId, someName, someEmail);
+
+    Map<String, List<CtripUserService.UserServiceResponse>> resultMap =
+        ImmutableMap.of("result", Lists.newArrayList(someUserResponse));
+    ResponseEntity<Map<String, List<CtripUserService.UserServiceResponse>>> someResponse
+        = new ResponseEntity<>(resultMap, HttpStatus.OK);
+
+    when(restTemplate.exchange(eq(someUserServiceUrl), eq(HttpMethod.POST), any(HttpEntity.class),
+        eq(someResponseType))).thenReturn(someResponse);
+
+    UserInfo user = ctripUserService.findByUserId(someUserId);
+    compareUserInfoAndUserServiceResponse(someUserResponse, user);
+  }
+
+  @Test
+  public void testFindByUserIds() throws Exception {
+    String someUserId = "someUserId";
+    String someName = "someName";
+    String someEmail = "someEmail";
+    String anotherUserId = "anotherUserId";
+    String anotherName = "anotherName";
+    String anotherEmail = "anotherEmail";
+    String someKeyword = "someKeyword";
+
+    CtripUserService.UserServiceResponse someUserResponse =
+        assembleUserServiceResponse(someUserId, someName, someEmail);
+
+    CtripUserService.UserServiceResponse anotherUserResponse =
+        assembleUserServiceResponse(anotherUserId, anotherName, anotherEmail);
+
+    Map<String, List<CtripUserService.UserServiceResponse>> resultMap =
+        ImmutableMap.of("result", Lists.newArrayList(someUserResponse, anotherUserResponse));
+    ResponseEntity<Map<String, List<CtripUserService.UserServiceResponse>>> someResponse
+        = new ResponseEntity<>(resultMap, HttpStatus.OK);
+
+    when(restTemplate.exchange(eq(someUserServiceUrl), eq(HttpMethod.POST), any(HttpEntity.class),
+        eq(someResponseType))).thenReturn(someResponse);
+
+    List<UserInfo> users =
+        ctripUserService.findByUserIds(Lists.newArrayList(someUserId, anotherUserId));
+
+    assertEquals(2, users.size());
+    compareUserInfoAndUserServiceResponse(someUserResponse, users.get(0));
+    compareUserInfoAndUserServiceResponse(anotherUserResponse, users.get(1));
+  }
+
+  private void compareUserInfoAndUserServiceResponse(
+      CtripUserService.UserServiceResponse userServiceReponse, UserInfo userInfo) {
+    assertEquals(userServiceReponse.getEmpaccount(), userInfo.getUserId());
+    assertEquals(userServiceReponse.getDisplayname(), userInfo.getName());
+    assertEquals(userServiceReponse.getEmail(), userInfo.getEmail());
+  }
+
+  private CtripUserService.UserServiceResponse assembleUserServiceResponse(String userId,
+                                                                           String name,
+                                                                           String email) {
+    CtripUserService.UserServiceResponse response = new CtripUserService.UserServiceResponse();
+    response.setEmpaccount(userId);
+    response.setDisplayname(name);
+    response.setEmail(email);
+    return response;
+  }
+}

+ 1 - 1
pom.xml

@@ -4,7 +4,7 @@
 	<modelVersion>4.0.0</modelVersion>
 	<groupId>com.ctrip.framework.apollo</groupId>
 	<artifactId>apollo</artifactId>
-	<version>0.0.2</version>
+	<version>0.0.3-SNAPSHOT</version>
 	<name>Apollo</name>
 	<packaging>pom</packaging>
 	<description>Ctrip Configuration Center</description>