feat: 增加路由管理功能的api
diff --git a/base/api/src/main/java/com/supwisdom/institute/backend/base/api/v1/admin/AdminRouteController.java b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/v1/admin/AdminRouteController.java
new file mode 100644
index 0000000..b7a57ea
--- /dev/null
+++ b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/v1/admin/AdminRouteController.java
@@ -0,0 +1,165 @@
+package com.supwisdom.institute.backend.base.api.v1.admin;
+
+import java.util.List;
+
+import io.swagger.annotations.Api;
+import lombok.extern.slf4j.Slf4j;
+
+import org.springframework.beans.factory.annotation.Autowired;
+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.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.supwisdom.institute.backend.base.api.vo.request.RouteCreateRequest;
+import com.supwisdom.institute.backend.base.api.vo.request.RouteDeleteBatchRequest;
+import com.supwisdom.institute.backend.base.api.vo.request.RouteQueryRequest;
+import com.supwisdom.institute.backend.base.api.vo.request.RouteUpdateRequest;
+import com.supwisdom.institute.backend.base.api.vo.response.RouteCreateResponseData;
+import com.supwisdom.institute.backend.base.api.vo.response.RouteDeleteBatchResponseData;
+import com.supwisdom.institute.backend.base.api.vo.response.RouteLoadResponseData;
+import com.supwisdom.institute.backend.base.api.vo.response.RouteQueryResponseData;
+import com.supwisdom.institute.backend.base.api.vo.response.RouteRemoveResponseData;
+import com.supwisdom.institute.backend.base.api.vo.response.RouteUpdateResponseData;
+import com.supwisdom.institute.backend.base.domain.entity.Route;
+import com.supwisdom.institute.backend.base.domain.service.RouteService;
+import com.supwisdom.institute.backend.common.framework.entity.EntityUtils;
+import com.supwisdom.institute.backend.common.framework.vo.response.DefaultApiResponse;
+
+@Api(value = "BaseAdminRoute", tags = { "BaseAdminRoute" }, description = "路由信息的操作接口")
+@Slf4j
+@RestController
+@RequestMapping("/v1/admin/routes")
+public class AdminRouteController {
+
+  @Autowired
+  private RouteService routeService;
+  
+
+  @GetMapping(produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
+  @ResponseStatus(value = HttpStatus.OK)
+  @ResponseBody
+  public DefaultApiResponse<RouteQueryResponseData> query(RouteQueryRequest queryRequest) {
+
+    Page<Route> page = routeService.selectPageList(
+        queryRequest.isLoadAll(), 
+        queryRequest.getPageIndex(), 
+        queryRequest.getPageSize(),
+        queryRequest.getMapBean(),
+        queryRequest.getOrderBy());
+
+    RouteQueryResponseData data = RouteQueryResponseData.of(queryRequest).build(page);
+
+    return new DefaultApiResponse<RouteQueryResponseData>(data);
+  }
+  
+  @GetMapping(path = "/{id}", produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
+  @ResponseStatus(value = HttpStatus.OK)
+  @ResponseBody
+  public DefaultApiResponse<RouteLoadResponseData> load(@PathVariable("id") String id) {
+
+    if (id == null || id.length() == 0) {
+      throw new RuntimeException("exception.get.id.must.not.empty"); // FIXME: RestException
+    }
+
+    Route route = routeService.selectById(id);
+
+    if (route == null) {
+      throw new RuntimeException("exception.get.domain.not.exist"); // FIXME: RestException
+    }
+    
+    RouteLoadResponseData data = RouteLoadResponseData.of(route);
+
+    return new DefaultApiResponse<RouteLoadResponseData>(data);
+  }
+
+  @PostMapping(consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
+  @ResponseStatus(value = HttpStatus.OK)
+  @ResponseBody
+  public DefaultApiResponse<RouteCreateResponseData> create(
+      @RequestBody RouteCreateRequest createRequest) {
+    
+    // FIXME: 验证数据有效性
+    
+    Route route = createRequest.getEntity();
+    
+    Route ret = routeService.insert(route);
+    
+    RouteCreateResponseData data = RouteCreateResponseData.build(ret);
+
+    return new DefaultApiResponse<RouteCreateResponseData>(data);
+  }
+  
+  @PutMapping(path = "/{id}", consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
+  @ResponseStatus(value = HttpStatus.OK)
+  @ResponseBody
+  public DefaultApiResponse<RouteUpdateResponseData> update(
+      @PathVariable("id") String id, 
+      @RequestBody RouteUpdateRequest updateRequest) {
+
+    if (id == null || id.length() == 0) {
+      throw new RuntimeException("exception.update.id.must.not.empty");
+    }
+
+    Route tmp = routeService.selectById(id);
+    if (tmp == null) {
+      throw new RuntimeException("exception.update.domain.not.exist");
+    }
+
+    Route route = updateRequest.getEntity();
+    route.setId(id);
+
+    route = EntityUtils.merge(tmp, route);
+
+    Route ret = routeService.update(route);
+
+    RouteUpdateResponseData data = RouteUpdateResponseData.build(ret);
+    
+    return new DefaultApiResponse<RouteUpdateResponseData>(data);
+  }
+
+  @DeleteMapping(path = "/{id}", produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
+  @ResponseStatus(value = HttpStatus.OK)
+  @ResponseBody
+  public DefaultApiResponse<RouteRemoveResponseData> delete(
+      @PathVariable("id") String id) {
+
+    if (id == null || id.length() == 0) {
+      throw new RuntimeException("exception.delete.id.must.not.empty"); // FIXME: RestException
+    }
+
+    Route tmp = routeService.selectById(id);
+    if (tmp == null) {
+      throw new RuntimeException("exception.delete.domain.not.exist"); // FIXME: RestException
+    }
+
+    routeService.deleteById(id);
+
+    RouteRemoveResponseData data = RouteRemoveResponseData.build(tmp);
+    return new DefaultApiResponse<RouteRemoveResponseData>(data);
+  }
+
+  @DeleteMapping(path = "/batch", consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
+  @ResponseStatus(value = HttpStatus.OK)
+  @ResponseBody
+  public DefaultApiResponse<RouteDeleteBatchResponseData> deleteBatch(
+      @RequestBody RouteDeleteBatchRequest deleteBatchRequest) {
+    
+    List<String> ids = deleteBatchRequest.getIds();
+    
+    routeService.deleteBatch(ids);
+    
+    RouteDeleteBatchResponseData data = RouteDeleteBatchResponseData.build(ids);
+    return new DefaultApiResponse<RouteDeleteBatchResponseData>(data);
+  }
+
+}
diff --git a/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/request/RouteCreateRequest.java b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/request/RouteCreateRequest.java
new file mode 100644
index 0000000..b3f8d10
--- /dev/null
+++ b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/request/RouteCreateRequest.java
@@ -0,0 +1,21 @@
+package com.supwisdom.institute.backend.base.api.vo.request;
+
+import com.supwisdom.institute.backend.base.domain.entity.Route;
+import com.supwisdom.institute.backend.common.framework.entity.EntityUtils;
+import com.supwisdom.institute.backend.common.framework.vo.request.IApiCreateRequest;
+
+/**
+ * @author loie
+ */
+public class RouteCreateRequest extends Route implements IApiCreateRequest {
+
+  /**
+   * 
+   */
+  private static final long serialVersionUID = -1610189545107922L;
+
+  public Route getEntity() {
+    return EntityUtils.copy(this, new Route());
+  }
+  
+}
diff --git a/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/request/RouteDeleteBatchRequest.java b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/request/RouteDeleteBatchRequest.java
new file mode 100644
index 0000000..9c523c4
--- /dev/null
+++ b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/request/RouteDeleteBatchRequest.java
@@ -0,0 +1,21 @@
+package com.supwisdom.institute.backend.base.api.vo.request;
+
+import java.util.List;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import com.supwisdom.institute.backend.common.framework.vo.request.IApiRequest;
+
+public class RouteDeleteBatchRequest implements IApiRequest {
+
+  /**
+   * 
+   */
+  private static final long serialVersionUID = -6325676083300813571L;
+
+  @Getter
+  @Setter
+  private List<String> ids;
+
+}
diff --git a/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/request/RouteQueryRequest.java b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/request/RouteQueryRequest.java
new file mode 100644
index 0000000..77dee0a
--- /dev/null
+++ b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/request/RouteQueryRequest.java
@@ -0,0 +1,40 @@
+package com.supwisdom.institute.backend.base.api.vo.request;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Map;
+
+import com.supwisdom.institute.backend.common.framework.vo.request.IApiQueryRequest;
+
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * @author loie
+ */
+public class RouteQueryRequest implements IApiQueryRequest {
+  
+  /**
+   * 
+   */
+  private static final long serialVersionUID = 5501929058767629345L;
+
+  @Getter
+  @Setter
+  private boolean loadAll = false;
+  @Getter
+  @Setter
+  private int pageIndex = 0;
+  @Getter
+  @Setter
+  private int pageSize = 20;
+  @Getter
+  @Setter
+  @ApiModelProperty(hidden = true)
+  private Map<String, Object> mapBean;
+  @Getter
+  @Setter
+  @ApiModelProperty(hidden = true)
+  private Map<String, String> orderBy;
+
+}
diff --git a/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/request/RouteUpdateRequest.java b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/request/RouteUpdateRequest.java
new file mode 100644
index 0000000..991e7c4
--- /dev/null
+++ b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/request/RouteUpdateRequest.java
@@ -0,0 +1,28 @@
+package com.supwisdom.institute.backend.base.api.vo.request;
+
+import com.supwisdom.institute.backend.base.domain.entity.Route;
+import com.supwisdom.institute.backend.common.framework.entity.EntityUtils;
+import com.supwisdom.institute.backend.common.framework.vo.request.IApiUpdateRequest;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author loie
+ */
+public class RouteUpdateRequest extends Route implements IApiUpdateRequest {
+
+  /**
+   * 
+   */
+  private static final long serialVersionUID = -1999295284394866961L;
+
+  @Getter
+  @Setter
+  private String id;
+
+  public Route getEntity() {
+    return EntityUtils.copy(this, new Route());
+  }
+
+}
diff --git a/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/response/RouteCreateResponseData.java b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/response/RouteCreateResponseData.java
new file mode 100644
index 0000000..bc4a8fa
--- /dev/null
+++ b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/response/RouteCreateResponseData.java
@@ -0,0 +1,33 @@
+package com.supwisdom.institute.backend.base.api.vo.response;
+
+import com.supwisdom.institute.backend.base.domain.entity.Route;
+import com.supwisdom.institute.backend.common.framework.entity.EntityUtils;
+import com.supwisdom.institute.backend.common.framework.vo.response.data.IApiCreateResponseData;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author loie
+ */
+public class RouteCreateResponseData extends Route implements IApiCreateResponseData {
+
+  /**
+   * 
+   */
+  private static final long serialVersionUID = -3115757571075013907L;
+
+  @Getter
+  @Setter
+  private String id;
+
+  private RouteCreateResponseData() {
+
+  }
+
+  public static RouteCreateResponseData build(Route entity) {
+    RouteCreateResponseData data = new RouteCreateResponseData();
+
+    return EntityUtils.copy(entity, data);
+  }
+}
diff --git a/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/response/RouteDeleteBatchResponseData.java b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/response/RouteDeleteBatchResponseData.java
new file mode 100644
index 0000000..bee5441
--- /dev/null
+++ b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/response/RouteDeleteBatchResponseData.java
@@ -0,0 +1,35 @@
+package com.supwisdom.institute.backend.base.api.vo.response;
+
+import java.util.List;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import com.supwisdom.institute.backend.common.framework.vo.response.data.IApiResponseData;
+
+/**
+ * @author loie
+ */
+public class RouteDeleteBatchResponseData implements IApiResponseData {
+
+  /**
+   * 
+   */
+  private static final long serialVersionUID = -2204591486919573195L;
+
+  @Getter
+  @Setter
+  private List<String> ids;
+
+  private RouteDeleteBatchResponseData() {
+    
+  }
+  
+  public static RouteDeleteBatchResponseData build(List<String> ids) {
+    RouteDeleteBatchResponseData data = new RouteDeleteBatchResponseData();
+    data.setIds(ids);
+    
+    return data;
+  }
+  
+}
diff --git a/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/response/RouteLoadResponseData.java b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/response/RouteLoadResponseData.java
new file mode 100644
index 0000000..f3f8c74
--- /dev/null
+++ b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/response/RouteLoadResponseData.java
@@ -0,0 +1,33 @@
+package com.supwisdom.institute.backend.base.api.vo.response;
+
+import com.supwisdom.institute.backend.base.domain.entity.Route;
+import com.supwisdom.institute.backend.common.framework.entity.EntityUtils;
+import com.supwisdom.institute.backend.common.framework.vo.response.data.IApiLoadResponseData;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author loie
+ */
+public class RouteLoadResponseData extends Route implements IApiLoadResponseData {
+
+  /**
+   * 
+   */
+  private static final long serialVersionUID = 3295217395256011396L;
+
+  @Getter
+  @Setter
+  private String id;
+
+  private RouteLoadResponseData() {
+
+  }
+
+  public static RouteLoadResponseData of(Route entity) {
+    RouteLoadResponseData data = new RouteLoadResponseData();
+    return EntityUtils.copy(entity, data);
+  }
+
+}
diff --git a/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/response/RouteQueryResponseData.java b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/response/RouteQueryResponseData.java
new file mode 100644
index 0000000..932c9c3
--- /dev/null
+++ b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/response/RouteQueryResponseData.java
@@ -0,0 +1,80 @@
+package com.supwisdom.institute.backend.base.api.vo.response;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.data.domain.Page;
+
+import com.supwisdom.institute.backend.base.domain.entity.Route;
+import com.supwisdom.institute.backend.common.framework.vo.request.IApiQueryRequest;
+import com.supwisdom.institute.backend.common.framework.vo.response.data.IApiQueryResponseData;
+
+/**
+ * @author loie
+ */
+public class RouteQueryResponseData implements IApiQueryResponseData<Route> {
+
+  /**
+   * 
+   */
+  private static final long serialVersionUID = -2632433248197514787L;
+
+  private RouteQueryResponseData(boolean loadAll, int pageIndex, int pageSize, Map<String, Object> mapBean, Map<String, String> orderBy) {
+    this.loadAll = loadAll;
+    this.pageIndex = pageIndex;
+    this.pageSize = pageSize;
+    this.mapBean = mapBean;
+    this.orderBy = orderBy;
+  }
+
+  public static RouteQueryResponseData of(IApiQueryRequest queryRequest) {
+    RouteQueryResponseData configQueryResponse = new RouteQueryResponseData(
+        queryRequest.isLoadAll(), 
+        queryRequest.getPageIndex(), 
+        queryRequest.getPageSize(), 
+        queryRequest.getMapBean(), 
+        queryRequest.getOrderBy()
+    );
+    
+    return configQueryResponse;
+  }
+  
+  public RouteQueryResponseData build(Page<Route> page) {
+    this.currentItemCount = page.getNumberOfElements();
+    this.pageCount = page.getTotalPages();
+    this.recordCount = page.getTotalElements();
+    this.items = page.getContent();
+
+    return this;
+  }
+
+  @Getter
+  private boolean loadAll;
+  @Getter
+  private int pageIndex;
+  @Getter
+  private int pageSize;
+  @Getter
+  private Map<String, Object> mapBean;
+  @Getter
+  private Map<String, String> orderBy;
+  
+  @Getter
+  @Setter
+  private int pageCount;
+  @Getter
+  @Setter
+  private long recordCount;
+  
+  @Getter
+  @Setter
+  private int currentItemCount;
+  
+  @Getter
+  @Setter
+  private List<Route> items;
+  
+}
diff --git a/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/response/RouteRemoveResponseData.java b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/response/RouteRemoveResponseData.java
new file mode 100644
index 0000000..ec4497b
--- /dev/null
+++ b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/response/RouteRemoveResponseData.java
@@ -0,0 +1,34 @@
+package com.supwisdom.institute.backend.base.api.vo.response;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import com.supwisdom.institute.backend.base.domain.entity.Route;
+import com.supwisdom.institute.backend.common.framework.entity.EntityUtils;
+import com.supwisdom.institute.backend.common.framework.vo.response.data.IApiRemoveResponseData;
+
+/**
+ * @author loie
+ */
+public class RouteRemoveResponseData implements IApiRemoveResponseData {
+
+  /**
+   * 
+   */
+  private static final long serialVersionUID = -143516413115089421L;
+
+  @Getter
+  @Setter
+  private String id;
+
+  private RouteRemoveResponseData() {
+    
+  }
+  
+  public static RouteRemoveResponseData build(Route entity) {
+    RouteRemoveResponseData data = new RouteRemoveResponseData();
+    
+    return EntityUtils.copy(entity, data);
+  }
+  
+}
diff --git a/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/response/RouteUpdateResponseData.java b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/response/RouteUpdateResponseData.java
new file mode 100644
index 0000000..a101e22
--- /dev/null
+++ b/base/api/src/main/java/com/supwisdom/institute/backend/base/api/vo/response/RouteUpdateResponseData.java
@@ -0,0 +1,34 @@
+package com.supwisdom.institute.backend.base.api.vo.response;
+
+import com.supwisdom.institute.backend.base.domain.entity.Route;
+import com.supwisdom.institute.backend.common.framework.entity.EntityUtils;
+import com.supwisdom.institute.backend.common.framework.vo.response.data.IApiUpdateResponseData;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author loie
+ */
+public class RouteUpdateResponseData extends Route implements IApiUpdateResponseData {
+
+  /**
+   * 
+   */
+  private static final long serialVersionUID = 364945014290674973L;
+
+  @Getter
+  @Setter
+  private String id;
+
+  private RouteUpdateResponseData() {
+
+  }
+
+  public static RouteUpdateResponseData build(Route entity) {
+    RouteUpdateResponseData data = new RouteUpdateResponseData();
+
+    return EntityUtils.copy(entity, data);
+  }
+
+}
diff --git a/base/domain/src/main/java/com/supwisdom/institute/backend/base/domain/entity/Route.java b/base/domain/src/main/java/com/supwisdom/institute/backend/base/domain/entity/Route.java
new file mode 100644
index 0000000..17f91cb
--- /dev/null
+++ b/base/domain/src/main/java/com/supwisdom/institute/backend/base/domain/entity/Route.java
@@ -0,0 +1,69 @@
+package com.supwisdom.institute.backend.base.domain.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import com.supwisdom.institute.backend.common.framework.entity.ABaseEntity;
+
+@Entity
+@Table(name = "TB_BASE_ROUTE")
+public class Route extends ABaseEntity {
+
+  /**
+   * 
+   */
+  private static final long serialVersionUID = -7435404154376360786L;
+
+  /**
+   * 代码
+   */
+  @Column(name = "CODE")
+  @Getter
+  @Setter
+  private String code;
+
+  /**
+   * 名称
+   */
+  @Column(name = "NAME")
+  @Getter
+  @Setter
+  private String name;
+
+  /**
+   * 备注
+   */
+  @Column(name = "MEMO")
+  @Getter
+  @Setter
+  private String memo;
+
+  /**
+   * 状态(1 启用,0 停用)
+   */
+  @Column(name = "STATUS")
+  @Getter
+  @Setter
+  private String status;
+
+  /**
+   * 路由地址
+   */
+  @Column(name = "URI")
+  @Getter
+  @Setter
+  private String uri;
+
+  /**
+   * 路径前缀
+   */
+  @Column(name = "PATH_PREFIX")
+  @Getter
+  @Setter
+  private String pathPrefix;
+
+}
diff --git a/base/domain/src/main/java/com/supwisdom/institute/backend/base/domain/repo/RouteRepository.java b/base/domain/src/main/java/com/supwisdom/institute/backend/base/domain/repo/RouteRepository.java
new file mode 100644
index 0000000..425138b
--- /dev/null
+++ b/base/domain/src/main/java/com/supwisdom/institute/backend/base/domain/repo/RouteRepository.java
@@ -0,0 +1,76 @@
+package com.supwisdom.institute.backend.base.domain.repo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Repository;
+import org.springframework.util.StringUtils;
+
+import com.supwisdom.institute.backend.base.domain.entity.Route;
+import com.supwisdom.institute.backend.common.framework.repo.BaseJpaRepository;
+import com.supwisdom.institute.backend.common.util.MapBeanUtils;
+
+@Repository
+public interface RouteRepository extends BaseJpaRepository<Route> {
+  
+  @Override
+  public default Specification<Route> convertToSpec(Map<String, Object> mapBean) {
+    
+    Specification<Route> spec = new Specification<Route>() {
+
+      /**
+       * 
+       */
+      private static final long serialVersionUID = 9131799274128286026L;
+
+      @Override
+      public Predicate toPredicate(Root<Route> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
+        List<Predicate> predicates = new ArrayList<>();
+        
+        if (mapBean != null) {
+
+          if (MapBeanUtils.getBoolean(mapBean, "deleted") != null) {
+            predicates.add(criteriaBuilder.equal(root.get("deleted"), MapBeanUtils.getBoolean(mapBean, "deleted")));
+          }
+
+          if (!StringUtils.isEmpty(MapBeanUtils.getString(mapBean, "code"))) {
+            predicates.add(criteriaBuilder.like(root.get("code"), "%" + MapBeanUtils.getString(mapBean, "code") + "%"));
+          }
+          if (!StringUtils.isEmpty(MapBeanUtils.getString(mapBean, "name"))) {
+            predicates.add(criteriaBuilder.like(root.get("name"), "%" + MapBeanUtils.getString(mapBean, "name") + "%"));
+          }
+          
+          if (!StringUtils.isEmpty(MapBeanUtils.getString(mapBean, "status"))) {
+            predicates.add(criteriaBuilder.equal(root.get("status"), MapBeanUtils.getString(mapBean, "status")));
+          }
+
+          if (!StringUtils.isEmpty(MapBeanUtils.getString(mapBean, "url"))) {
+            predicates.add(criteriaBuilder.like(root.get("url"), "%" + MapBeanUtils.getString(mapBean, "url") + "%"));
+          }
+          
+          List<Predicate> predicatesKeyword = new ArrayList<>();
+          if (!StringUtils.isEmpty(MapBeanUtils.getString(mapBean, "keyword"))) {
+            predicatesKeyword.add(criteriaBuilder.like(root.get("code"), "%" + MapBeanUtils.getString(mapBean, "keyword") + "%"));
+            predicatesKeyword.add(criteriaBuilder.like(root.get("name"), "%" + MapBeanUtils.getString(mapBean, "keyword") + "%"));
+            predicatesKeyword.add(criteriaBuilder.like(root.get("memo"), "%" + MapBeanUtils.getString(mapBean, "keyword") + "%"));
+            
+            predicates.add(criteriaBuilder.or(predicatesKeyword.toArray(new Predicate[predicatesKeyword.size()])));
+          }
+        }
+        
+        return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
+      }
+      
+    };
+    
+    return spec;
+  }
+
+}
diff --git a/base/domain/src/main/java/com/supwisdom/institute/backend/base/domain/service/RouteService.java b/base/domain/src/main/java/com/supwisdom/institute/backend/base/domain/service/RouteService.java
new file mode 100644
index 0000000..f93595f
--- /dev/null
+++ b/base/domain/src/main/java/com/supwisdom/institute/backend/base/domain/service/RouteService.java
@@ -0,0 +1,32 @@
+package com.supwisdom.institute.backend.base.domain.service;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.supwisdom.institute.backend.base.domain.entity.Route;
+import com.supwisdom.institute.backend.base.domain.repo.RouteRepository;
+import com.supwisdom.institute.backend.common.framework.service.ABaseService;
+
+@Service
+public class RouteService extends ABaseService<Route, RouteRepository> {
+
+  @Override
+  public RouteRepository getRepo() {
+    return routeRepository;
+  }
+  
+  @Autowired
+  private RouteRepository routeRepository;
+
+
+  public void deleteBatch(List<String> ids) {
+    
+    ids.stream().forEach(id -> {
+      this.deleteById(id);
+    });
+  }
+
+  
+}