抽象出CrudApiController
diff --git a/samples/user/src/main/java/com/supwisdom/leaveschool/user/controller/api/CrudApiController.java b/samples/user/src/main/java/com/supwisdom/leaveschool/user/controller/api/CrudApiController.java
new file mode 100644
index 0000000..392ac76
--- /dev/null
+++ b/samples/user/src/main/java/com/supwisdom/leaveschool/user/controller/api/CrudApiController.java
@@ -0,0 +1,337 @@
+package com.supwisdom.leaveschool.user.controller.api;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.collections.IteratorUtils;
+import org.springframework.data.domain.Page;
+import org.springframework.http.HttpStatus;
+import org.springframework.util.MimeTypeUtils;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+import com.supwisdom.leaveschool.common.domain.ABaseDomain;
+import com.supwisdom.leaveschool.common.model.PagerRequestModel;
+import com.supwisdom.leaveschool.common.model.PagerResponseModel;
+import com.supwisdom.leaveschool.common.repository.BaseJpaRepository;
+import com.supwisdom.leaveschool.common.util.DomainUtils;
+
+public abstract class CrudApiController<D extends ABaseDomain, R extends BaseJpaRepository<D>> {
+  
+  protected abstract R getRepository();
+  
+  /**
+   * 
+   * curl -i -s -X GET -H 'Accept:application/json' 'http://localhost:10010/${API_PATH_PREFIX}/domains' 
+   * curl -i -s -X GET -H 'Accept:application/json' 'http://localhost:10010/${API_PATH_PREFIX}/domains?pageIndex=2&pageSize=50' 
+   * curl -i -s -X GET -H 'Accept:application/json' 'http://localhost:10010/${API_PATH_PREFIX}/domains?pageIndex=0&pageSize=20&mapBean[code]=code&mapBean[name]=name&mapBean[status]=1' 
+   * curl -i -s -X GET -H 'Accept:application/json' 'http://localhost:10010/${API_PATH_PREFIX}/domains?pageIndex=0&pageSize=20&mapBean[code]=code&mapBean[name]=name&mapBean[status]=0' 
+   * 
+   * response success: 
+   * 
+   * {
+   *   "pageIndex":0,
+   *   "pageSize":20,
+   *   "mapBean":null,
+   *   "pageCount":1,
+   *   "recordCount":1,
+   *   "items":[
+   *     {
+   *       "id":"ff80808164feb8990164feba0de50000",
+   *       "companyId":"1",
+   *       "deleted":false,
+   *       "addAccount":"group","addTime":"2018-08-03T07:39:23.000+0000",
+   *       "editAccount":null,"editTime":null,
+   *       "deleteAccount":null,"deleteTime":null,
+   *       "code":"test001",
+   *       "name":"测试001",
+   *       "memo":"测试001备注",
+   *       "status":"1"
+   *     }
+   *   ]
+   * }
+   * 
+   * response error 401:
+   * 
+   * {
+   *   "timestamp":"2018-08-03T08:48:25.777+0000",
+   *   "status":401,
+   *   "error":"Http Status 401",
+   *   "message":"Unauthorized",
+   *   "path":"/${API_PATH_PREFIX}/domains"
+   * }
+   * 
+   * @param pagerRequestModel
+   * @return
+   */
+  @GetMapping(produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
+  @ResponseStatus(value = HttpStatus.OK)
+  @ResponseBody
+  public PagerResponseModel<D> list(PagerRequestModel pagerRequestModel) {
+    
+    Page<D> page = getRepository().selectPageList(pagerRequestModel.getPageIndex(), pagerRequestModel.getPageSize(), pagerRequestModel.getMapBean());
+    
+    @SuppressWarnings("unchecked")
+    List<D> list = IteratorUtils.toList(page.iterator());
+    
+    PagerResponseModel<D> pagerResponseModel = PagerResponseModel.of(pagerRequestModel);
+    pagerResponseModel.setPageCount(page.getTotalPages());
+    pagerResponseModel.setRecordCount(page.getTotalElements());
+    pagerResponseModel.setItems(list);
+    
+    return pagerResponseModel;
+  }
+  
+  /**
+   * 
+   * curl -i -s -X GET -H 'Accept:application/json' 'http://localhost:10010/${API_PATH_PREFIX}/domains/1' 
+   * 
+   * response success: 
+   * 
+   * {
+   *   "id":"ff80808164feb8990164feba0de50000",
+   *   "companyId":"1",
+   *   "deleted":false,
+   *   "addAccount":"group","addTime":"2018-08-03T07:39:23.000+0000",
+   *   "editAccount":null,"editTime":null,
+   *   "deleteAccount":null,"deleteTime":null,
+   *   "code":"test001",
+   *   "name":"测试001",
+   *   "memo":"测试001备注",
+   *   "status":"1"
+   * }
+   * 
+   * response error 401:
+   * 
+   * {
+   *   "timestamp":"2018-08-03T08:43:26.080+0000",
+   *   "status":401,
+   *   "error":"Http Status 401",
+   *   "message":"Unauthorized",
+   *   "path":"/${API_PATH_PREFIX}/domains/ff80808164fecf640164fed269480000"
+   * }
+   * 
+   * response error 500:
+   * 
+   * {
+   *   "timestamp":"2018-08-03T07:44:07.963+0000",
+   *   "status":500,
+   *   "error":"Internal Server Error",
+   *   "exception":"java.lang.RuntimeException",
+   *   "message":"exception.get.domain.not.exist",
+   *   "path":"/${API_PATH_PREFIX}/domains/1"
+   * }
+   * 
+   * @param id
+   * @return
+   */
+  @GetMapping(path = "/{id}", produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
+  @ResponseStatus(value = HttpStatus.OK)
+  @ResponseBody
+  public D get(@PathVariable("id") String id) {
+    
+    if (id == null || id.length() == 0) {
+      throw new RuntimeException("exception.get.id.must.not.empty");  // FIXME: RestException
+    }
+    
+    D d = getRepository().selectById(id);
+    
+    if (d == null) {
+      throw new RuntimeException("exception.get.domain.not.exist");  // FIXME: RestException
+    }
+    
+    return d;
+  }
+  
+  /**
+   * 
+   * curl -i -s -X POST -H 'Content-Type:application/json' -H 'Accept:application/json' 'http://localhost:10010/${API_PATH_PREFIX}/domains' \
+   * -d '{"code":"test001","name":"测试001","memo":"测试001备注","status":"1","addAccount":"admin"}'
+   * 
+   * response success: 
+   * 
+   * {
+   *   "success":"info.save.success"
+   * }
+   * 
+   * response error 401:
+   * 
+   * {
+   *   "timestamp":"2018-08-03T08:48:25.777+0000",
+   *   "status":401,
+   *   "error":"Http Status 401",
+   *   "message":"Unauthorized",
+   *   "path":"/${API_PATH_PREFIX}/domains"
+   * }
+   * 
+   * response error: // FIXME: save error
+   * 
+   * {
+   *   "timestamp":"2018-08-03T07:45:43.436+0000",
+   *   "status":500,
+   *   "error":"Internal Server Error",
+   *   "exception":"org.springframework.dao.DataIntegrityViolationException",
+   *   "message":"could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement",
+   *   "path":"/${API_PATH_PREFIX}/domains"
+   * }
+   * 
+   * @param group
+   * @return
+   */
+  @PostMapping(consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
+  @ResponseStatus(value = HttpStatus.OK)
+  @ResponseBody
+  public Map<String, Object> save(@RequestBody D d) {
+    
+    @SuppressWarnings("unused")
+    D ret = getRepository().insert(d);
+    
+    Map<String, Object> res = new HashMap<String, Object>();
+    res.put("success", "info.save.success");
+    
+    return res;
+  }
+  
+  /**
+   * 
+   * curl -i -s -X PUT -H 'Content-Type:application/json' -H 'Accept:application/json' 'http://localhost:10010/${API_PATH_PREFIX}/domains' \
+   * -d '{"id":"1","status":"0"}'
+   * 
+   * response success:
+   * 
+   * {
+   *   "success":"info.update.success"
+   * }
+   * 
+   * response error 401:
+   * 
+   * {
+   *   "timestamp":"2018-08-03T08:48:25.777+0000",
+   *   "status":401,
+   *   "error":"Http Status 401",
+   *   "message":"Unauthorized",
+   *   "path":"/${API_PATH_PREFIX}/domains"
+   * }
+   * 
+   * curl -i -s -X PUT -H 'Content-Type:application/json' -H 'Accept:application/json' 'http://localhost:10010/${API_PATH_PREFIX}/domains' \
+   * -d '{"status":"0"}'
+   * 
+   * response error:
+   * 
+   * {
+   *   "timestamp":"2018-08-03T07:50:52.327+0000",
+   *   "status":500,
+   *   "error":"Internal Server Error",
+   *   "exception":"java.lang.RuntimeException",
+   *   "message":"exception.update.id.must.not.empty",
+   *   "path":"/${API_PATH_PREFIX}/domains"
+   * }
+   * 
+   * curl -i -s -X PUT -H 'Content-Type:application/json' -H 'Accept:application/json' 'http://localhost:10010/${API_PATH_PREFIX}/domains' \
+   * -d '{"id":"1","status":"0"}'
+   * 
+   * response error:
+   * 
+   * {
+   *   "timestamp":"2018-08-03T07:48:24.774+0000",
+   *   "status":500,
+   *   "error":"Internal Server Error",
+   *   "exception":"java.lang.RuntimeException",
+   *   "message":"exception.update.domain.not.exist",
+   *   "path":"/${API_PATH_PREFIX}/domains"
+   * }
+   * 
+   * @param group
+   * @return
+   */
+  @PutMapping(consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
+  @ResponseStatus(value = HttpStatus.OK)
+  @ResponseBody
+  public Map<String, Object> update(@RequestBody D d) {
+    
+    if (d.getId() == null || d.getId().length() == 0) {
+      throw new RuntimeException("exception.update.id.must.not.empty");  // FIXME: RestException
+    }
+    
+    D tmp = getRepository().selectById(d.getId());
+    if (tmp == null) {
+      throw new RuntimeException("exception.update.domain.not.exist");  // FIXME: RestException
+    }
+    
+    tmp = DomainUtils.merge(d, tmp);
+    
+    @SuppressWarnings("unused")
+    D ret = getRepository().update(tmp);
+    getRepository().flush();
+    
+    Map<String, Object> res = new HashMap<String, Object>();
+    res.put("success", "info.update.success");
+    
+    return res;
+  }
+  
+  /**
+   * 
+   * curl -i -s -X DELETE -H 'Accept:application/json' 'http://localhost:10010/${API_PATH_PREFIX}/domains/1'
+   * 
+   * response success: 
+   * 
+   * {
+   *   "success":"info.delete.success"
+   * }
+   * 
+   * response error 401:
+   * 
+   * {
+   *   "timestamp":"2018-08-03T08:48:25.777+0000",
+   *   "status":401,
+   *   "error":"Http Status 401",
+   *   "message":"Unauthorized",
+   *   "path":"/${API_PATH_PREFIX}/domains/1"
+   * }
+   * 
+   * response error 500: 
+   * 
+   * {
+   *   "timestamp":"2018-08-03T08:03:16.364+0000",
+   *   "status":500,
+   *   "error":"Internal Server Error",
+   *   "exception":"java.lang.RuntimeException",
+   *   "message":"exception.delete.domain.not.exist",
+   *   "path":"/${API_PATH_PREFIX}/domains/1"
+   * }
+   * 
+   * @param id
+   * @return
+   */
+  @DeleteMapping(path = "/{id}", produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
+  @ResponseStatus(value = HttpStatus.OK)
+  @ResponseBody
+  public Map<String, Object> delete(@PathVariable("id") String id) {
+    
+    if (id == null || id.length() == 0) {
+      throw new RuntimeException("exception.delete.id.must.not.empty");  // FIXME: RestException
+    }
+    
+    D tmp = getRepository().selectById(id);
+    if (tmp == null) {
+      throw new RuntimeException("exception.delete.domain.not.exist");  // FIXME: RestException
+    }
+    
+    getRepository().delete(tmp);
+    
+    Map<String, Object> res = new HashMap<String, Object>();
+    res.put("success", "info.delete.success");
+    
+    return res;
+  }
+  
+
+}
diff --git a/samples/user/src/main/java/com/supwisdom/leaveschool/user/controller/api/admin/Api1AdminUserController.java b/samples/user/src/main/java/com/supwisdom/leaveschool/user/controller/api/admin/Api1AdminUserController.java
index a635ad9..d46ef0b 100644
--- a/samples/user/src/main/java/com/supwisdom/leaveschool/user/controller/api/admin/Api1AdminUserController.java
+++ b/samples/user/src/main/java/com/supwisdom/leaveschool/user/controller/api/admin/Api1AdminUserController.java
@@ -1,6 +1,7 @@
 package com.supwisdom.leaveschool.user.controller.api.admin;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.commons.collections.IteratorUtils;
@@ -22,15 +23,22 @@
 import com.supwisdom.leaveschool.common.model.PagerRequestModel;
 import com.supwisdom.leaveschool.common.model.PagerResponseModel;
 import com.supwisdom.leaveschool.common.util.DomainUtils;
+import com.supwisdom.leaveschool.user.controller.api.CrudApiController;
 import com.supwisdom.leaveschool.user.domain.User;
 import com.supwisdom.leaveschool.user.repository.UserRepository;
 
 @RestController
 @RequestMapping("/api/v1/admin/users")
-public class Api1AdminUserController {
+public class Api1AdminUserController extends CrudApiController<User, UserRepository> {
 
   @Autowired
   private UserRepository userRepository;
+
+  @Override
+  protected UserRepository getRepository() {
+    
+    return userRepository;
+  }
   
   /**
    * 
@@ -82,6 +90,7 @@
    * @param pagerRequestModel
    * @return
    */
+  @Override
   @GetMapping(produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
   @ResponseStatus(value = HttpStatus.OK)
   @ResponseBody
@@ -89,10 +98,13 @@
     
     Page<User> page = userRepository.selectPageList(pagerRequestModel.getPageIndex(), pagerRequestModel.getPageSize(), pagerRequestModel.getMapBean());
     
+    @SuppressWarnings("unchecked")
+    List<User> list = IteratorUtils.toList(page.iterator());
+    
     PagerResponseModel<User> pagerResponseModel = PagerResponseModel.of(pagerRequestModel);
     pagerResponseModel.setPageCount(page.getTotalPages());
     pagerResponseModel.setRecordCount(page.getTotalElements());
-    pagerResponseModel.setItems(IteratorUtils.toList(page.iterator()));
+    pagerResponseModel.setItems(list);
     
     return pagerResponseModel;
   }
@@ -146,6 +158,7 @@
    * @param id
    * @return
    */
+  @Override
   @GetMapping(path = "/{id}", produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
   @ResponseStatus(value = HttpStatus.OK)
   @ResponseBody
@@ -199,12 +212,14 @@
    * @param user
    * @return
    */
+  @Override
   @PostMapping(consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
   @ResponseStatus(value = HttpStatus.OK)
   @ResponseBody
   public Map<String, Object> save(@RequestBody User user) {
     
-    User u = userRepository.insert(user);
+    @SuppressWarnings("unused")
+    User ret = userRepository.insert(user);
     
     Map<String, Object> res = new HashMap<String, Object>();
     res.put("success", "info.save.success");
@@ -264,6 +279,7 @@
    * @param user
    * @return
    */
+  @Override
   @PutMapping(consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
   @ResponseStatus(value = HttpStatus.OK)
   @ResponseBody
@@ -280,7 +296,8 @@
     
     tmp = DomainUtils.merge(user, tmp);
     
-    User u = userRepository.update(tmp);
+    @SuppressWarnings("unused")
+    User ret = userRepository.update(tmp);
     userRepository.flush();
     
     Map<String, Object> res = new HashMap<String, Object>();
@@ -323,6 +340,7 @@
    * @param id
    * @return
    */
+  @Override
   @DeleteMapping(path = "/{id}", produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
   @ResponseStatus(value = HttpStatus.OK)
   @ResponseBody
@@ -344,5 +362,5 @@
     
     return res;
   }
-  
+
 }