This is an automated email from the ASF dual-hosted git repository.

zhangzicheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git


The following commit(s) were added to refs/heads/master by this push:
     new 16a7266fe [ISSUE #4029]  apidoc stage2: api-crud (#4137)
16a7266fe is described below

commit 16a7266fe5cdfca6b1f29b29254d04a807d57457
Author: zhengpeng <[email protected]>
AuthorDate: Fri Nov 4 11:30:26 2022 +0800

    [ISSUE #4029]  apidoc stage2: api-crud (#4137)
    
    * #4029 api crud
    
    * #4029 api crud some unit test
    
    * #4029 api crud and unit test
    
    * #4029 api page query and detail return add tags
    
    * #4029 api add tagId
    
    * #4029 api state byte change to Integer
    
    * #4029 fix api crud
    
    Co-authored-by: dragon-zhang <[email protected]>
---
 .../shenyu/admin/controller/ApiController.java     | 140 ++++
 .../org/apache/shenyu/admin/mapper/ApiMapper.java  |  40 +-
 .../org/apache/shenyu/admin/mapper/TagMapper.java  |  10 +-
 .../shenyu/admin/mapper/TagRelationMapper.java     |  21 +
 .../model/{entity/ApiDO.java => dto/ApiDTO.java}   | 104 ++-
 .../apache/shenyu/admin/model/entity/ApiDO.java    | 295 +++++++-
 .../apache/shenyu/admin/model/query/ApiQuery.java  | 152 ++++
 .../shenyu/admin/model/query/TagRelationQuery.java |  55 ++
 .../org/apache/shenyu/admin/model/vo/ApiVO.java    | 790 +++++++++++++++++++++
 .../apache/shenyu/admin/service/ApiService.java    |  65 ++
 .../shenyu/admin/service/impl/ApiServiceImpl.java  | 179 +++++
 .../src/main/resources/mappers/api-sqlmap.xml      |  61 ++
 .../main/resources/mappers/tag-relation-sqlmap.xml |  31 +
 .../src/main/resources/mappers/tag-sqlmap.xml      |  12 +-
 .../shenyu/admin/controller/ApiControllerTest.java | 196 +++++
 .../apache/shenyu/admin/mapper/ApiMapperTest.java  |   6 +-
 .../shenyu/admin/service/ApiServiceTest.java       | 175 +++++
 .../shenyu/common/constant/AdminConstants.java     |   5 +
 18 files changed, 2321 insertions(+), 16 deletions(-)

diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/ApiController.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/ApiController.java
new file mode 100644
index 000000000..e26f4aa45
--- /dev/null
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/ApiController.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.admin.controller;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shenyu.admin.mapper.ApiMapper;
+import org.apache.shenyu.admin.model.dto.ApiDTO;
+import org.apache.shenyu.admin.model.page.CommonPager;
+import org.apache.shenyu.admin.model.page.PageParameter;
+import org.apache.shenyu.admin.model.query.ApiQuery;
+import org.apache.shenyu.admin.model.result.ShenyuAdminResult;
+import org.apache.shenyu.admin.model.vo.ApiVO;
+import org.apache.shenyu.admin.service.ApiService;
+import org.apache.shenyu.admin.utils.ShenyuResultMessage;
+import org.apache.shenyu.admin.validation.annotation.Existed;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.validation.annotation.Validated;
+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.RestController;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * this is api controller.
+ */
+@Validated
+@RestController
+@RequestMapping("/api")
+public class ApiController {
+
+    private final ApiService apiService;
+
+    public ApiController(final ApiService apiService) {
+        this.apiService = apiService;
+    }
+
+    /**
+     * query apis.
+     *
+     * @param apiPath     api path.
+     * @param state       state.
+     * @param tagId       tagId.
+     * @param currentPage current page.
+     * @param pageSize    page size.
+     * @return {@linkplain ShenyuAdminResult}
+     */
+    @GetMapping("")
+    public ShenyuAdminResult queryApis(final String apiPath, final Integer 
state,
+                                       final String tagId,
+                                       @NotNull final Integer currentPage,
+                                       @NotNull final Integer pageSize) {
+        CommonPager<ApiVO> commonPager = apiService.listByPage(new 
ApiQuery(apiPath, state, tagId, new PageParameter(currentPage, pageSize)));
+        return ShenyuAdminResult.success(ShenyuResultMessage.QUERY_SUCCESS, 
commonPager);
+    }
+
+    /**
+     * detail plugin.
+     *
+     * @param id plugin id.
+     * @return {@linkplain ShenyuAdminResult}
+     */
+    @GetMapping("/{id}")
+    public ShenyuAdminResult detailApi(@PathVariable("id")
+                                       @Existed(message = "api is not existed",
+                                               provider = ApiMapper.class) 
final String id) {
+        ApiVO apiVO = apiService.findById(id);
+        return ShenyuAdminResult.success(ShenyuResultMessage.DETAIL_SUCCESS, 
apiVO);
+    }
+
+    /**
+     * create api.
+     *
+     * @param apiDTO api.
+     * @return {@linkplain ShenyuAdminResult}
+     */
+    @PostMapping("")
+    @RequiresPermissions("system:api:add")
+    public ShenyuAdminResult createApi(@Valid @RequestBody final ApiDTO 
apiDTO) {
+        return ShenyuAdminResult.success(apiService.createOrUpdate(apiDTO));
+    }
+
+    /**
+     * update api.
+     *
+     * @param id     primary key.
+     * @param apiDTO api.
+     * @return {@linkplain ShenyuAdminResult}
+     */
+    @PutMapping("/{id}")
+    @RequiresPermissions("system:api:edit")
+    public ShenyuAdminResult updateApi(@PathVariable("id")
+                                       @Existed(message = "api is not existed",
+                                               provider = ApiMapper.class) 
final String id,
+                                       @Valid @RequestBody final ApiDTO 
apiDTO) {
+        apiDTO.setId(id);
+        return createApi(apiDTO);
+    }
+
+    /**
+     * delete apis.
+     *
+     * @param ids primary key.
+     * @return {@linkplain ShenyuAdminResult}
+     */
+    @DeleteMapping("/batch")
+    @RequiresPermissions("system:api:delete")
+    public ShenyuAdminResult deleteApis(@RequestBody @NotEmpty final 
List<@NotBlank String> ids) {
+        final String result = apiService.delete(ids);
+        if (StringUtils.isNoneBlank(result)) {
+            return ShenyuAdminResult.error(result);
+        }
+        return ShenyuAdminResult.success(ShenyuResultMessage.DELETE_SUCCESS);
+    }
+
+}
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/ApiMapper.java 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/ApiMapper.java
index aa81911e8..ea1b44d71 100644
--- a/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/ApiMapper.java
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/ApiMapper.java
@@ -18,13 +18,19 @@
 package org.apache.shenyu.admin.mapper;
 
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
 import org.apache.shenyu.admin.model.entity.ApiDO;
+import org.apache.shenyu.admin.model.query.ApiQuery;
+import org.apache.shenyu.admin.validation.ExistProvider;
+
+import java.io.Serializable;
+import java.util.List;
 
 /**
  * ApiMapper.
  **/
 @Mapper
-public interface ApiMapper {
+public interface ApiMapper extends ExistProvider {
     /**
      * delete by primary key.
      *
@@ -72,4 +78,36 @@ public interface ApiMapper {
      * @return update count
      */
     int updateByPrimaryKey(ApiDO record);
+
+    /**
+     * existed.
+     *
+     * @param id id
+     * @return existed
+     */
+    @Override
+    Boolean existed(@Param("id") Serializable id);
+
+    /**
+     * select api by query.
+     *
+     * @param query {@linkplain ApiQuery}
+     * @return {@linkplain List}
+     */
+    List<ApiDO> selectByQuery(ApiQuery query);
+
+    /**
+     * select api by ids.
+     * @param ids primary keys.
+     * @return {@linkplain ApiDO}
+     */
+    List<ApiDO> selectByIds(List<String> ids);
+
+    /**
+     * delete api.
+     *
+     * @param ids primary keys.
+     * @return rows int
+     */
+    int deleteByIds(List<String> ids);
 }
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/TagMapper.java 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/TagMapper.java
index 72aec4172..d042666dd 100644
--- a/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/TagMapper.java
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/TagMapper.java
@@ -17,12 +17,13 @@
 
 package org.apache.shenyu.admin.mapper;
 
-import java.util.List;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.shenyu.admin.model.entity.TagDO;
 import org.apache.shenyu.admin.model.query.TagQuery;
 import org.apache.shenyu.admin.validation.ExistProvider;
 
+import java.util.List;
+
 /**
  * this is User Tag Mapper.
  */
@@ -105,4 +106,11 @@ public interface TagMapper extends ExistProvider {
      * @return delete count
      */
     int deleteAllData();
+
+    /**
+     * selectByIds.
+     * @param list ids
+     * @return List
+     */
+    List<TagDO> selectByIds(List<String> list);
 }
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/TagRelationMapper.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/TagRelationMapper.java
index c113f2631..78d1d415f 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/TagRelationMapper.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/mapper/TagRelationMapper.java
@@ -19,6 +19,7 @@ package org.apache.shenyu.admin.mapper;
 
 import java.util.List;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
 import org.apache.shenyu.admin.model.entity.TagRelationDO;
 import org.apache.shenyu.admin.model.query.TagRelationQuery;
 import org.apache.shenyu.admin.validation.ExistProvider;
@@ -93,5 +94,25 @@ public interface TagRelationMapper extends ExistProvider {
      */
     int updateByPrimaryKey(TagRelationDO record);
 
+    /**
+     * batchInsert.
+     * @param list list
+     * @return insert rows
+     */
+    int batchInsert(@Param(value = "list") List<TagRelationDO> list);
+
+    /**
+     * deleteByApiId.
+     * @param apiId apiId
+     * @return delete rows
+     */
+    int deleteByApiId(@Param(value = "apiId") String apiId);
+
+    /**
+     * deleteByApiIds.
+     * @param apiIds apiIds
+     * @return delete rows
+     */
+    int deleteByApiIds(@Param(value = "apiIds") List<String> apiIds);
 }
 
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/entity/ApiDO.java 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/dto/ApiDTO.java
similarity index 73%
copy from 
shenyu-admin/src/main/java/org/apache/shenyu/admin/model/entity/ApiDO.java
copy to shenyu-admin/src/main/java/org/apache/shenyu/admin/model/dto/ApiDTO.java
index b490cd05d..1789cbac2 100644
--- a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/entity/ApiDO.java
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/dto/ApiDTO.java
@@ -15,17 +15,27 @@
  * limitations under the License.
  */
 
-package org.apache.shenyu.admin.model.entity;
+package org.apache.shenyu.admin.model.dto;
 
+import org.apache.shenyu.admin.mapper.ApiMapper;
+import org.apache.shenyu.admin.validation.annotation.Existed;
+
+import java.io.Serializable;
 import java.util.Date;
+import java.util.List;
+import java.util.Objects;
 
 /**
- * api.
+ * this is api from by web front.
  */
-public class ApiDO {
+public class ApiDTO implements Serializable {
+
+    private static final long serialVersionUID = -1859047620316026098L;
+
     /**
      * primary key id.
      */
+    @Existed(provider = ApiMapper.class, nullOfIgnore = true, message = "the 
api is not exited")
     private String id;
 
     /**
@@ -66,7 +76,7 @@ public class ApiDO {
     /**
      * 0-unpublished1-published2-offline.
      */
-    private Byte state;
+    private Integer state;
 
     /**
      * extended fields.
@@ -108,8 +118,14 @@ public class ApiDO {
      */
     private Date dateUpdated;
 
+    /**
+     * tagIds.
+     */
+    private List<String> tagIds;
+
     /**
      * getId.
+     *
      * @return id
      */
     public String getId() {
@@ -118,6 +134,7 @@ public class ApiDO {
 
     /**
      * set id.
+     *
      * @param id id
      */
     public void setId(final String id) {
@@ -126,6 +143,7 @@ public class ApiDO {
 
     /**
      * getContextPath.
+     *
      * @return context path
      */
     public String getContextPath() {
@@ -134,6 +152,7 @@ public class ApiDO {
 
     /**
      * set context path.
+     *
      * @param contextPath context path
      */
     public void setContextPath(final String contextPath) {
@@ -142,6 +161,7 @@ public class ApiDO {
 
     /**
      * getApiPath.
+     *
      * @return apiPath
      */
     public String getApiPath() {
@@ -150,6 +170,7 @@ public class ApiDO {
 
     /**
      * setApiPath.
+     *
      * @param apiPath apiPath
      */
     public void setApiPath(final String apiPath) {
@@ -158,6 +179,7 @@ public class ApiDO {
 
     /**
      * getHttpMethod.
+     *
      * @return http method
      */
     public Integer getHttpMethod() {
@@ -166,6 +188,7 @@ public class ApiDO {
 
     /**
      * setHttpMethod.
+     *
      * @param httpMethod http method
      */
     public void setHttpMethod(final Integer httpMethod) {
@@ -174,6 +197,7 @@ public class ApiDO {
 
     /**
      * getConsume.
+     *
      * @return consume
      */
     public String getConsume() {
@@ -182,6 +206,7 @@ public class ApiDO {
 
     /**
      * setConsume.
+     *
      * @param consume consume
      */
     public void setConsume(final String consume) {
@@ -190,6 +215,7 @@ public class ApiDO {
 
     /**
      * getProduce.
+     *
      * @return produce
      */
     public String getProduce() {
@@ -198,6 +224,7 @@ public class ApiDO {
 
     /**
      * setProduce.
+     *
      * @param produce the produce
      */
     public void setProduce(final String produce) {
@@ -206,6 +233,7 @@ public class ApiDO {
 
     /**
      * getVersion.
+     *
      * @return version
      */
     public String getVersion() {
@@ -214,6 +242,7 @@ public class ApiDO {
 
     /**
      * setVersion.
+     *
      * @param version the version
      */
     public void setVersion(final String version) {
@@ -222,6 +251,7 @@ public class ApiDO {
 
     /**
      * getRpcType.
+     *
      * @return rpc type
      */
     public String getRpcType() {
@@ -230,6 +260,7 @@ public class ApiDO {
 
     /**
      * setRpcType.
+     *
      * @param rpcType the rpc type
      */
     public void setRpcType(final String rpcType) {
@@ -238,23 +269,26 @@ public class ApiDO {
 
     /**
      * getState.
+     *
      * @return state
      */
-    public Byte getState() {
+    public Integer getState() {
         return state;
     }
 
     /**
      * setState.
+     *
      * @param state state
      */
-    public void setState(final Byte state) {
+    public void setState(final Integer state) {
         this.state = state;
     }
 
     /**
      * getExt.
-     * @return  extension.
+     *
+     * @return extension.
      */
     public String getExt() {
         return ext;
@@ -262,6 +296,7 @@ public class ApiDO {
 
     /**
      * setExt.
+     *
      * @param ext extension
      */
     public void setExt(final String ext) {
@@ -270,6 +305,7 @@ public class ApiDO {
 
     /**
      * getApiOwner.
+     *
      * @return apiOwner
      */
     public String getApiOwner() {
@@ -278,6 +314,7 @@ public class ApiDO {
 
     /**
      * setApiOwner.
+     *
      * @param apiOwner apiOwner
      */
     public void setApiOwner(final String apiOwner) {
@@ -286,6 +323,7 @@ public class ApiDO {
 
     /**
      * getApiDesc.
+     *
      * @return apiDesc
      */
     public String getApiDesc() {
@@ -294,6 +332,7 @@ public class ApiDO {
 
     /**
      * setApiDesc.
+     *
      * @param apiDesc apiDesc
      */
     public void setApiDesc(final String apiDesc) {
@@ -302,6 +341,7 @@ public class ApiDO {
 
     /**
      * getApiSource.
+     *
      * @return apiSource
      */
     public Integer getApiSource() {
@@ -310,6 +350,7 @@ public class ApiDO {
 
     /**
      * setSource.
+     *
      * @param apiSource apiSource
      */
     public void setApiSource(final Integer apiSource) {
@@ -318,6 +359,7 @@ public class ApiDO {
 
     /**
      * getDocument.
+     *
      * @return document
      */
     public String getDocument() {
@@ -326,6 +368,7 @@ public class ApiDO {
 
     /**
      * setDocument.
+     *
      * @param document document
      */
     public void setDocument(final String document) {
@@ -334,6 +377,7 @@ public class ApiDO {
 
     /**
      * getDocumentMd5.
+     *
      * @return document md5
      */
     public String getDocumentMd5() {
@@ -342,6 +386,7 @@ public class ApiDO {
 
     /**
      * setDocumentMd5.
+     *
      * @param documentMd5 documentMd5
      */
     public void setDocumentMd5(final String documentMd5) {
@@ -350,6 +395,7 @@ public class ApiDO {
 
     /**
      * getDateCreated.
+     *
      * @return dateCreated
      */
     public Date getDateCreated() {
@@ -358,6 +404,7 @@ public class ApiDO {
 
     /**
      * setDateCreated.
+     *
      * @param dateCreated dateCreated
      */
     public void setDateCreated(final Date dateCreated) {
@@ -366,6 +413,7 @@ public class ApiDO {
 
     /**
      * getDateUpdated.
+     *
      * @return dateUpdated
      */
     public Date getDateUpdated() {
@@ -374,9 +422,51 @@ public class ApiDO {
 
     /**
      * setDateUpdated.
+     *
      * @param dateUpdated dateUpdated
      */
     public void setDateUpdated(final Date dateUpdated) {
         this.dateUpdated = dateUpdated;
     }
+
+    /**
+     * getTagIds.
+     *
+     * @return tagIds
+     */
+    public List<String> getTagIds() {
+        return tagIds;
+    }
+
+    /**
+     * setTagIds.
+     *
+     * @param tagIds tagIds
+     */
+    public void setTagIds(final List<String> tagIds) {
+        this.tagIds = tagIds;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof ApiDTO)) {
+            return false;
+        }
+        ApiDTO apiDTO = (ApiDTO) o;
+        return Objects.equals(id, apiDTO.id) && Objects.equals(contextPath, 
apiDTO.contextPath) && Objects.equals(apiPath, apiDTO.apiPath) && 
Objects.equals(httpMethod, apiDTO.httpMethod)
+                && Objects.equals(consume, apiDTO.consume) && 
Objects.equals(produce, apiDTO.produce) && Objects.equals(version, 
apiDTO.version) && Objects.equals(rpcType, apiDTO.rpcType)
+                && Objects.equals(state, apiDTO.state) && Objects.equals(ext, 
apiDTO.ext) && Objects.equals(apiOwner, apiDTO.apiOwner) && 
Objects.equals(apiDesc, apiDTO.apiDesc)
+                && Objects.equals(apiSource, apiDTO.apiSource) && 
Objects.equals(document, apiDTO.document) && Objects.equals(documentMd5, 
apiDTO.documentMd5)
+                && Objects.equals(dateCreated, apiDTO.dateCreated) && 
Objects.equals(dateUpdated, apiDTO.dateUpdated) && Objects.equals(tagIds, 
apiDTO.tagIds);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, contextPath, apiPath, httpMethod, consume, 
produce, version,
+                rpcType, state, ext, apiOwner, apiDesc, apiSource, document, 
documentMd5, dateCreated, dateUpdated, tagIds);
+    }
+
 }
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/entity/ApiDO.java 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/entity/ApiDO.java
index b490cd05d..c85fce995 100644
--- a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/entity/ApiDO.java
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/entity/ApiDO.java
@@ -17,7 +17,14 @@
 
 package org.apache.shenyu.admin.model.entity;
 
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shenyu.admin.model.dto.ApiDTO;
+import org.apache.shenyu.common.utils.UUIDUtils;
+
+import java.sql.Timestamp;
 import java.util.Date;
+import java.util.Optional;
 
 /**
  * api.
@@ -66,7 +73,7 @@ public class ApiDO {
     /**
      * 0-unpublished1-published2-offline.
      */
-    private Byte state;
+    private Integer state;
 
     /**
      * extended fields.
@@ -240,7 +247,7 @@ public class ApiDO {
      * getState.
      * @return state
      */
-    public Byte getState() {
+    public Integer getState() {
         return state;
     }
 
@@ -248,7 +255,7 @@ public class ApiDO {
      * setState.
      * @param state state
      */
-    public void setState(final Byte state) {
+    public void setState(final Integer state) {
         this.state = state;
     }
 
@@ -379,4 +386,286 @@ public class ApiDO {
     public void setDateUpdated(final Date dateUpdated) {
         this.dateUpdated = dateUpdated;
     }
+
+    /**
+     * builder.
+     * @return ApiDOBuilder
+     */
+    public static ApiDOBuilder builder() {
+        return new ApiDOBuilder();
+    }
+
+    /**
+     * buildApiDO.
+     * @param apiDTO apiDTO
+     * @return ApiDO
+     */
+    public static ApiDO buildApiDO(final ApiDTO apiDTO) {
+
+        return Optional.ofNullable(apiDTO).map(item -> {
+            Timestamp currentTime = new Timestamp(System.currentTimeMillis());
+            ApiDO apiDO = ApiDO.builder()
+                    .contextPath(item.getContextPath())
+                    .apiPath(item.getApiPath())
+                    .httpMethod(item.getHttpMethod())
+                    .consume(item.getConsume())
+                    .produce(item.getProduce())
+                    .version(item.getVersion())
+                    .rpcType(item.getRpcType())
+                    .state(item.getState())
+                    .ext(item.getExt())
+                    .apiOwner(item.getApiOwner())
+                    .apiDesc(item.getApiDesc())
+                    .apiSource(item.getApiSource())
+                    .document(item.getDocument())
+                    .documentMd5(DigestUtils.md5Hex(item.getDocument()))
+                    .build();
+            if (StringUtils.isEmpty(item.getId())) {
+                apiDO.setId(UUIDUtils.getInstance().generateShortUuid());
+                apiDO.setDateCreated(currentTime);
+            } else {
+                apiDO.setId(item.getId());
+            }
+            return apiDO;
+        }).orElse(null);
+    }
+
+    public static final class ApiDOBuilder {
+
+        private String id;
+
+        private String contextPath;
+
+        private String apiPath;
+
+        private Integer httpMethod;
+
+        private String consume;
+
+        private String produce;
+
+        private String version;
+
+        private String rpcType;
+
+        private Integer state;
+
+        private String ext;
+
+        private String apiOwner;
+
+        private String apiDesc;
+
+        private Integer apiSource;
+
+        private String document;
+
+        private String documentMd5;
+
+        private Date dateCreated;
+
+        private Date dateUpdated;
+
+        /**
+         * Construct.
+         */
+        private ApiDOBuilder() {
+        }
+
+        /**
+         * builder id.
+         * @param id id
+         * @return ApiDOBuilder
+         */
+        public ApiDOBuilder id(final String id) {
+            this.id = id;
+            return this;
+        }
+
+        /**
+         * builder contextPath.
+         * @param contextPath contextPath
+         * @return ApiDOBuilder
+         */
+        public ApiDOBuilder contextPath(final String contextPath) {
+            this.contextPath = contextPath;
+            return this;
+        }
+
+        /**
+         * builder apiPath.
+         * @param apiPath apiPath
+         * @return ApiDOBuilder
+         */
+        public ApiDOBuilder apiPath(final String apiPath) {
+            this.apiPath = apiPath;
+            return this;
+        }
+
+        /**
+         * builder httpMethod.
+         * @param httpMethod httpMethod
+         * @return ApiDOBuilder
+         */
+        public ApiDOBuilder httpMethod(final Integer httpMethod) {
+            this.httpMethod = httpMethod;
+            return this;
+        }
+
+        /**
+         * builder httpMethod.
+         * @param consume consume
+         * @return ApiDOBuilder
+         */
+        public ApiDOBuilder consume(final String consume) {
+            this.consume = consume;
+            return this;
+        }
+
+        /**
+         * builder produce.
+         * @param produce produce
+         * @return ApiDOBuilder
+         */
+        public ApiDOBuilder produce(final String produce) {
+            this.produce = produce;
+            return this;
+        }
+
+        /**
+         * builder version.
+         * @param version version
+         * @return ApiDOBuilder
+         */
+        public ApiDOBuilder version(final String version) {
+            this.version = version;
+            return this;
+        }
+
+        /**
+         * builder rpcType.
+         * @param rpcType rpcType
+         * @return ApiDOBuilder
+         */
+        public ApiDOBuilder rpcType(final String rpcType) {
+            this.rpcType = rpcType;
+            return this;
+        }
+
+        /**
+         * builder state.
+         * @param state state
+         * @return ApiDOBuilder
+         */
+        public ApiDOBuilder state(final Integer state) {
+            this.state = state;
+            return this;
+        }
+
+        /**
+         * builder ext.
+         * @param ext ext
+         * @return ApiDOBuilder
+         */
+        public ApiDOBuilder ext(final String ext) {
+            this.ext = ext;
+            return this;
+        }
+
+        /**
+         * builder apiOwner.
+         * @param apiOwner apiOwner
+         * @return ApiDOBuilder
+         */
+        public ApiDOBuilder apiOwner(final String apiOwner) {
+            this.apiOwner = apiOwner;
+            return this;
+        }
+
+        /**
+         * builder apiDesc.
+         * @param apiDesc apiDesc
+         * @return ApiDOBuilder
+         */
+        public ApiDOBuilder apiDesc(final String apiDesc) {
+            this.apiDesc = apiDesc;
+            return this;
+        }
+
+        /**
+         * builder apiSource.
+         * @param apiSource apiSource
+         * @return ApiDOBuilder
+         */
+        public ApiDOBuilder apiSource(final Integer apiSource) {
+            this.apiSource = apiSource;
+            return this;
+        }
+
+        /**
+         * builder document.
+         * @param document document
+         * @return ApiDOBuilder
+         */
+        public ApiDOBuilder document(final String document) {
+            this.document = document;
+            return this;
+        }
+
+        /**
+         * builder documentMd5.
+         * @param documentMd5 documentMd5
+         * @return ApiDOBuilder
+         */
+        public ApiDOBuilder documentMd5(final String documentMd5) {
+            this.documentMd5 = documentMd5;
+            return this;
+        }
+
+        /**
+         * builder dateCreated.
+         * @param dateCreated dateCreated
+         * @return ApiDOBuilder
+         */
+        public ApiDOBuilder dateCreated(final Date dateCreated) {
+            this.dateCreated = dateCreated;
+            return this;
+        }
+
+        /**
+         * builder dateUpdated.
+         * @param dateUpdated dateUpdated
+         * @return ApiDOBuilder
+         */
+        public ApiDOBuilder dateUpdated(final Date dateUpdated) {
+            this.dateUpdated = dateUpdated;
+            return this;
+        }
+
+        /**
+         * builder.
+         * @return ApiDO
+         */
+        public ApiDO build() {
+            ApiDO apiDO = new ApiDO();
+            apiDO.setId(id);
+            apiDO.setContextPath(contextPath);
+            apiDO.setApiPath(apiPath);
+            apiDO.setHttpMethod(httpMethod);
+            apiDO.setConsume(consume);
+            apiDO.setProduce(produce);
+            apiDO.setVersion(version);
+            apiDO.setRpcType(rpcType);
+            apiDO.setState(state);
+            apiDO.setExt(ext);
+            apiDO.setApiOwner(apiOwner);
+            apiDO.setApiDesc(apiDesc);
+            apiDO.setApiSource(apiSource);
+            apiDO.setDocument(document);
+            apiDO.setDocumentMd5(documentMd5);
+            apiDO.setDateCreated(dateCreated);
+            apiDO.setDateUpdated(dateUpdated);
+            return apiDO;
+        }
+    }
 }
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/query/ApiQuery.java 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/query/ApiQuery.java
new file mode 100644
index 000000000..144fcba06
--- /dev/null
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/query/ApiQuery.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.admin.model.query;
+
+import org.apache.shenyu.admin.model.page.PageParameter;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * this is plugin query.
+ */
+public class ApiQuery implements Serializable {
+
+    private static final long serialVersionUID = 167659024501717438L;
+
+    /**
+     * apiPath.
+     */
+    private String apiPath;
+
+
+    /**
+     * state.
+     */
+    private Integer state;
+
+
+    /**
+     * tagId.
+     */
+    private String tagId;
+
+    /**
+     * page parameter.
+     */
+    private PageParameter pageParameter;
+
+    public ApiQuery() {
+    }
+
+    public ApiQuery(final String apiPath, final Integer state, final String 
tagId, final PageParameter pageParameter) {
+        this.apiPath = apiPath;
+        this.state = state;
+        this.tagId = tagId;
+        this.pageParameter = pageParameter;
+    }
+
+    /**
+     * get apiPath.
+     *
+     * @return apiPath
+     */
+    public String getApiPath() {
+        return apiPath;
+    }
+
+    /**
+     * set apiPath.
+     *
+     * @param apiPath apiPath
+     */
+    public void setApiPath(final String apiPath) {
+        this.apiPath = apiPath;
+    }
+
+    /**
+     * get state.
+     *
+     * @return state
+     */
+    public Integer getState() {
+        return state;
+    }
+
+    /**
+     * set state.
+     *
+     * @param state state
+     */
+    public void setState(final Integer state) {
+        this.state = state;
+    }
+
+    /**
+     * get tagId.
+     *
+     * @return tagId
+     */
+    public String getTagId() {
+        return tagId;
+    }
+
+    /**
+     * set tagId.
+     *
+     * @param tagId tagId
+     */
+    public void setTagId(final String tagId) {
+        this.tagId = tagId;
+    }
+
+    /**
+     * get pageParameter.
+     *
+     * @return pageParameter
+     */
+    public PageParameter getPageParameter() {
+        return pageParameter;
+    }
+
+    /**
+     * set pageParameter.
+     *
+     * @param pageParameter pageParameter
+     */
+    public void setPageParameter(final PageParameter pageParameter) {
+        this.pageParameter = pageParameter;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof ApiQuery)) {
+            return false;
+        }
+        ApiQuery apiQuery = (ApiQuery) o;
+        return apiPath.equals(apiQuery.apiPath) && 
state.equals(apiQuery.state) && tagId.equals(apiQuery.tagId) && 
pageParameter.equals(apiQuery.pageParameter);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(apiPath, state, tagId, pageParameter);
+    }
+}
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/query/TagRelationQuery.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/query/TagRelationQuery.java
index 6a69244f3..505027dd3 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/query/TagRelationQuery.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/query/TagRelationQuery.java
@@ -83,4 +83,59 @@ public class TagRelationQuery {
     public int hashCode() {
         return Objects.hash(apiId, tagId);
     }
+
+    /**
+     * builder.
+     * @return TagRelationQueryBuilder
+     */
+    public static TagRelationQueryBuilder builder() {
+        return new TagRelationQueryBuilder();
+    }
+
+    public static final class TagRelationQueryBuilder {
+
+        /**
+         * apiId.
+         */
+        private String apiId;
+
+        /**
+         * tagId.
+         */
+        private String tagId;
+
+        private TagRelationQueryBuilder() {
+        }
+
+        /**
+         * build apiId.
+         * @param apiId apiId
+         * @return TagRelationQueryBuilder
+         */
+        public TagRelationQueryBuilder apiId(final String apiId) {
+            this.apiId = apiId;
+            return this;
+        }
+
+        /**
+         * build tagId.
+         * @param tagId tagId
+         * @return TagRelationQueryBuilder
+         */
+        public TagRelationQueryBuilder tagId(final String tagId) {
+            this.tagId = tagId;
+            return this;
+        }
+
+        /**
+         * build.
+         * @return TagRelationQuery
+         */
+        public TagRelationQuery build() {
+            TagRelationQuery tagRelationQuery = new TagRelationQuery();
+            tagRelationQuery.setApiId(apiId);
+            tagRelationQuery.setTagId(tagId);
+            return tagRelationQuery;
+        }
+    }
 }
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/ApiVO.java 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/ApiVO.java
new file mode 100644
index 000000000..941515aa8
--- /dev/null
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/ApiVO.java
@@ -0,0 +1,790 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.admin.model.vo;
+
+import org.apache.shenyu.admin.model.entity.ApiDO;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * this is api view to web front.
+ */
+public class ApiVO implements Serializable {
+
+    private static final long serialVersionUID = 7944745026885343719L;
+
+    /**
+     * primary key id.
+     */
+    private String id;
+
+    /**
+     * the context_path.
+     */
+    private String contextPath;
+
+    /**
+     * the apiPath.
+     */
+    private String apiPath;
+
+    /**
+     * 0-get,1-head,2-post,3-put,4-patch,5-delete,6-options,7-trace.
+     */
+    private Integer httpMethod;
+
+    /**
+     * specify the submitted content type for processing requests, such as 
application/json, text/html;.
+     */
+    private String consume;
+
+    /**
+     * specify the content type to be returned. only when the (accept) type in 
the request header contains the specified type can it be returned;.
+     */
+    private String produce;
+
+    /**
+     * api version,for example V0.01.
+     */
+    private String version;
+
+    /**
+     * http,dubbo,sofa,tars,websocket,springCloud,motan,grpc.
+     */
+    private String rpcType;
+
+    /**
+     * 0-unpublished1-published2-offline.
+     */
+    private Integer state;
+
+    /**
+     * extended fields.
+     */
+    private String ext;
+
+    /**
+     * apiOwner.
+     */
+    private String apiOwner;
+
+    /**
+     * the api description.
+     */
+    private String apiDesc;
+
+    /**
+     * 0-swagger,1-annotation generation,2-create manuallym,3-import 
swagger,4-import yapi.
+     */
+    private Integer apiSource;
+
+    /**
+     * complete documentation of the api, including request parameters and 
response parameters.
+     */
+    private String document;
+
+    /**
+     * document_md5.
+     */
+    private String documentMd5;
+
+    /**
+     * create time.
+     */
+    private Date dateCreated;
+
+    /**
+     * update time.
+     */
+    private Date dateUpdated;
+
+    /**
+     * tags.
+     */
+    private List<TagVO> tags;
+
+    /**
+     * getId.
+     *
+     * @return id
+     */
+    public String getId() {
+        return id;
+    }
+
+    /**
+     * set id.
+     *
+     * @param id id
+     */
+    public void setId(final String id) {
+        this.id = id;
+    }
+
+    /**
+     * getContextPath.
+     *
+     * @return context path
+     */
+    public String getContextPath() {
+        return contextPath;
+    }
+
+    /**
+     * set context path.
+     *
+     * @param contextPath context path
+     */
+    public void setContextPath(final String contextPath) {
+        this.contextPath = contextPath;
+    }
+
+    /**
+     * getApiPath.
+     *
+     * @return apiPath
+     */
+    public String getApiPath() {
+        return apiPath;
+    }
+
+    /**
+     * setApiPath.
+     *
+     * @param apiPath apiPath
+     */
+    public void setApiPath(final String apiPath) {
+        this.apiPath = apiPath;
+    }
+
+    /**
+     * getHttpMethod.
+     *
+     * @return http method
+     */
+    public Integer getHttpMethod() {
+        return httpMethod;
+    }
+
+    /**
+     * setHttpMethod.
+     *
+     * @param httpMethod http method
+     */
+    public void setHttpMethod(final Integer httpMethod) {
+        this.httpMethod = httpMethod;
+    }
+
+    /**
+     * getConsume.
+     *
+     * @return consume
+     */
+    public String getConsume() {
+        return consume;
+    }
+
+    /**
+     * setConsume.
+     *
+     * @param consume consume
+     */
+    public void setConsume(final String consume) {
+        this.consume = consume;
+    }
+
+    /**
+     * getProduce.
+     *
+     * @return produce
+     */
+    public String getProduce() {
+        return produce;
+    }
+
+    /**
+     * setProduce.
+     *
+     * @param produce the produce
+     */
+    public void setProduce(final String produce) {
+        this.produce = produce;
+    }
+
+    /**
+     * getVersion.
+     *
+     * @return version
+     */
+    public String getVersion() {
+        return version;
+    }
+
+    /**
+     * setVersion.
+     *
+     * @param version the version
+     */
+    public void setVersion(final String version) {
+        this.version = version;
+    }
+
+    /**
+     * getRpcType.
+     *
+     * @return rpc type
+     */
+    public String getRpcType() {
+        return rpcType;
+    }
+
+    /**
+     * setRpcType.
+     *
+     * @param rpcType the rpc type
+     */
+    public void setRpcType(final String rpcType) {
+        this.rpcType = rpcType;
+    }
+
+    /**
+     * getState.
+     *
+     * @return state
+     */
+    public Integer getState() {
+        return state;
+    }
+
+    /**
+     * setState.
+     *
+     * @param state state
+     */
+    public void setState(final Integer state) {
+        this.state = state;
+    }
+
+    /**
+     * getExt.
+     *
+     * @return extension.
+     */
+    public String getExt() {
+        return ext;
+    }
+
+    /**
+     * setExt.
+     *
+     * @param ext extension
+     */
+    public void setExt(final String ext) {
+        this.ext = ext;
+    }
+
+    /**
+     * getApiOwner.
+     *
+     * @return apiOwner
+     */
+    public String getApiOwner() {
+        return apiOwner;
+    }
+
+    /**
+     * setApiOwner.
+     *
+     * @param apiOwner apiOwner
+     */
+    public void setApiOwner(final String apiOwner) {
+        this.apiOwner = apiOwner;
+    }
+
+    /**
+     * getApiDesc.
+     *
+     * @return apiDesc
+     */
+    public String getApiDesc() {
+        return apiDesc;
+    }
+
+    /**
+     * setApiDesc.
+     *
+     * @param apiDesc apiDesc
+     */
+    public void setApiDesc(final String apiDesc) {
+        this.apiDesc = apiDesc;
+    }
+
+    /**
+     * getApiSource.
+     *
+     * @return apiSource
+     */
+    public Integer getApiSource() {
+        return apiSource;
+    }
+
+    /**
+     * setSource.
+     *
+     * @param apiSource apiSource
+     */
+    public void setApiSource(final Integer apiSource) {
+        this.apiSource = apiSource;
+    }
+
+    /**
+     * getDocument.
+     *
+     * @return document
+     */
+    public String getDocument() {
+        return document;
+    }
+
+    /**
+     * setDocument.
+     *
+     * @param document document
+     */
+    public void setDocument(final String document) {
+        this.document = document;
+    }
+
+    /**
+     * getDocumentMd5.
+     *
+     * @return document md5
+     */
+    public String getDocumentMd5() {
+        return documentMd5;
+    }
+
+    /**
+     * setDocumentMd5.
+     *
+     * @param documentMd5 documentMd5
+     */
+    public void setDocumentMd5(final String documentMd5) {
+        this.documentMd5 = documentMd5;
+    }
+
+    /**
+     * getDateCreated.
+     *
+     * @return dateCreated
+     */
+    public Date getDateCreated() {
+        return dateCreated;
+    }
+
+    /**
+     * setDateCreated.
+     *
+     * @param dateCreated dateCreated
+     */
+    public void setDateCreated(final Date dateCreated) {
+        this.dateCreated = dateCreated;
+    }
+
+    /**
+     * getDateUpdated.
+     *
+     * @return dateUpdated
+     */
+    public Date getDateUpdated() {
+        return dateUpdated;
+    }
+
+    /**
+     * setDateUpdated.
+     *
+     * @param dateUpdated dateUpdated
+     */
+    public void setDateUpdated(final Date dateUpdated) {
+        this.dateUpdated = dateUpdated;
+    }
+
+
+    /**
+     * getTags.
+     *
+     * @return tags
+     */
+    public List<TagVO> getTags() {
+        return tags;
+    }
+
+    /**
+     * setTags.
+     *
+     * @param tags tags
+     */
+    public void setTags(final List<TagVO> tags) {
+        this.tags = tags;
+    }
+
+    /**
+     * builder.
+     *
+     * @return ApiVOBuilder
+     */
+    public static ApiVOBuilder builder() {
+        return new ApiVOBuilder();
+    }
+
+    /**
+     * buildApiVO.
+     *
+     * @param apiDO apiDO.
+     * @param tags  tags.
+     * @return ApiVO.
+     */
+    public static ApiVO buildApiVO(final ApiDO apiDO, final List<TagVO> tags) {
+        return ApiVO.builder()
+                .id(apiDO.getId())
+                .contextPath(apiDO.getContextPath())
+                .apiPath(apiDO.getApiPath())
+                .httpMethod(apiDO.getHttpMethod())
+                .consume(apiDO.getConsume())
+                .produce(apiDO.getProduce())
+                .version(apiDO.getVersion())
+                .rpcType(apiDO.getRpcType())
+                .state(apiDO.getState())
+                .ext(apiDO.getExt())
+                .apiOwner(apiDO.getApiOwner())
+                .apiDesc(apiDO.getApiDesc())
+                .apiSource(apiDO.getApiSource())
+                .document(apiDO.getDocument())
+                .documentMd5(apiDO.getDocumentMd5())
+                .dateCreated(apiDO.getDateCreated())
+                .dateUpdated(apiDO.getDateUpdated())
+                .tags(tags)
+                .build();
+    }
+
+    public static final class ApiVOBuilder {
+
+        /**
+         * id.
+         */
+        private String id;
+
+        /**
+         * contextPath.
+         */
+        private String contextPath;
+
+        /**
+         * apiPath.
+         */
+        private String apiPath;
+
+        /**
+         * httpMethod.
+         */
+        private Integer httpMethod;
+
+        /**
+         * consume.
+         */
+        private String consume;
+
+        /**
+         * produce.
+         */
+        private String produce;
+
+        /**
+         * version.
+         */
+        private String version;
+
+        /**
+         * rpcType.
+         */
+        private String rpcType;
+
+        /**
+         * status.
+         */
+        private Integer state;
+
+        /**
+         * ext.
+         */
+        private String ext;
+
+        /**
+         * apiOwner.
+         */
+        private String apiOwner;
+
+        /**
+         * apiDesc.
+         */
+        private String apiDesc;
+
+        /**
+         * apiSource.
+         */
+        private Integer apiSource;
+
+        /**
+         * document.
+         */
+        private String document;
+
+        /**
+         * documentMd5.
+         */
+        private String documentMd5;
+
+        /**
+         * dateCreated.
+         */
+        private Date dateCreated;
+
+        /**
+         * dateUpdated.
+         */
+        private Date dateUpdated;
+
+        /**
+         * tags.
+         */
+        private List<TagVO> tags;
+
+        private ApiVOBuilder() {
+
+        }
+
+        /**
+         * builder id.
+         * @param id id
+         * @return ApiVOBuilder
+         */
+        public ApiVOBuilder id(final String id) {
+            this.id = id;
+            return this;
+        }
+
+        /**
+         * builder contextPath.
+         * @param contextPath contextPath
+         * @return ApiVOBuilder
+         */
+        public ApiVOBuilder contextPath(final String contextPath) {
+            this.contextPath = contextPath;
+            return this;
+        }
+
+        /**
+         * build apiPath.
+         * @param apiPath apiPath
+         * @return ApiVOBuilder
+         */
+        public ApiVOBuilder apiPath(final String apiPath) {
+            this.apiPath = apiPath;
+            return this;
+        }
+
+        /**
+         * build httpMethod.
+         * @param httpMethod httpMethod
+         * @return ApiVOBuilder
+         */
+        public ApiVOBuilder httpMethod(final Integer httpMethod) {
+            this.httpMethod = httpMethod;
+            return this;
+        }
+
+        /**
+         * build consume.
+         * @param consume consume
+         * @return ApiVOBuilder
+         */
+        public ApiVOBuilder consume(final String consume) {
+            this.consume = consume;
+            return this;
+        }
+
+        /**
+         * build produce.
+         * @param produce produce
+         * @return ApiVOBuilder
+         */
+        public ApiVOBuilder produce(final String produce) {
+            this.produce = produce;
+            return this;
+        }
+
+        /**
+         * build version.
+         * @param version version
+         * @return ApiVOBuilder
+         */
+        public ApiVOBuilder version(final String version) {
+            this.version = version;
+            return this;
+        }
+
+        /**
+         * build rpcType.
+         * @param rpcType rpcType
+         * @return ApiVOBuilder
+         */
+        public ApiVOBuilder rpcType(final String rpcType) {
+            this.rpcType = rpcType;
+            return this;
+        }
+
+        /**
+         * build state.
+         * @param state state
+         * @return ApiVOBuilder
+         */
+        public ApiVOBuilder state(final Integer state) {
+            this.state = state;
+            return this;
+        }
+
+        /**
+         * build ext.
+         * @param ext ext
+         * @return ApiVOBuilder
+         */
+        public ApiVOBuilder ext(final String ext) {
+            this.ext = ext;
+            return this;
+        }
+
+        /**
+         * build apiOwner.
+         * @param apiOwner apiOwner
+         * @return ApiVOBuilder
+         */
+        public ApiVOBuilder apiOwner(final String apiOwner) {
+            this.apiOwner = apiOwner;
+            return this;
+        }
+
+        /**
+         * build apiDesc.
+         * @param apiDesc apiDesc
+         * @return ApiVOBuilder
+         */
+        public ApiVOBuilder apiDesc(final String apiDesc) {
+            this.apiDesc = apiDesc;
+            return this;
+        }
+
+        /**
+         * build apiSource.
+         * @param apiSource apiSource
+         * @return ApiVOBuilder
+         */
+        public ApiVOBuilder apiSource(final Integer apiSource) {
+            this.apiSource = apiSource;
+            return this;
+        }
+
+        /**
+         * build document.
+         * @param document document
+         * @return ApiVOBuilder
+         */
+        public ApiVOBuilder document(final String document) {
+            this.document = document;
+            return this;
+        }
+
+        /**
+         * build documentMd5.
+         * @param documentMd5 documentMd5
+         * @return ApiVOBuilder
+         */
+        public ApiVOBuilder documentMd5(final String documentMd5) {
+            this.documentMd5 = documentMd5;
+            return this;
+        }
+
+        /**
+         * build dateCreated.
+         * @param dateCreated dateCreated
+         * @return ApiVOBuilder
+         */
+        public ApiVOBuilder dateCreated(final Date dateCreated) {
+            this.dateCreated = dateCreated;
+            return this;
+        }
+
+        /**
+         * build dateUpdated.
+         * @param dateUpdated dateUpdated
+         * @return ApiVOBuilder
+         */
+        public ApiVOBuilder dateUpdated(final Date dateUpdated) {
+            this.dateUpdated = dateUpdated;
+            return this;
+        }
+
+        /**
+         * build tags.
+         * @param tags tags
+         * @return ApiVOBuilder
+         */
+        public ApiVOBuilder tags(final List<TagVO> tags) {
+            this.tags = tags;
+            return this;
+        }
+
+        /**
+         * build.
+         * @return ApiVO
+         */
+        public ApiVO build() {
+            ApiVO apiVO = new ApiVO();
+            apiVO.setId(id);
+            apiVO.setContextPath(contextPath);
+            apiVO.setApiPath(apiPath);
+            apiVO.setHttpMethod(httpMethod);
+            apiVO.setConsume(consume);
+            apiVO.setProduce(produce);
+            apiVO.setVersion(version);
+            apiVO.setRpcType(rpcType);
+            apiVO.setState(state);
+            apiVO.setExt(ext);
+            apiVO.setApiOwner(apiOwner);
+            apiVO.setApiDesc(apiDesc);
+            apiVO.setApiSource(apiSource);
+            apiVO.setDocument(document);
+            apiVO.setDocumentMd5(documentMd5);
+            apiVO.setDateCreated(dateCreated);
+            apiVO.setDateUpdated(dateUpdated);
+            apiVO.setTags(tags);
+            return apiVO;
+        }
+    }
+}
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/ApiService.java 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/ApiService.java
new file mode 100644
index 000000000..1440eb5a2
--- /dev/null
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/ApiService.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.admin.service;
+
+import org.apache.shenyu.admin.model.dto.ApiDTO;
+import org.apache.shenyu.admin.model.page.CommonPager;
+import org.apache.shenyu.admin.model.query.ApiQuery;
+import org.apache.shenyu.admin.model.vo.ApiVO;
+import org.apache.shenyu.admin.model.vo.PluginVO;
+
+import java.util.List;
+
+/**
+ * this is api service.
+ */
+public interface ApiService {
+
+    /**
+     * Create or update string.
+     *
+     * @param apiDTO the api dto
+     * @return the string
+     */
+    String createOrUpdate(ApiDTO apiDTO);
+
+    /**
+     * Delete string.
+     *
+     * @param ids the ids
+     * @return the string
+     */
+    String delete(List<String> ids);
+    
+    /**
+     * find api by id.
+     *
+     * @param id pk.
+     * @return {@linkplain PluginVO}
+     */
+    ApiVO findById(String id);
+
+
+    /**
+     * find page of api by query.
+     *
+     * @param apiQuery {@linkplain ApiQuery}
+     * @return {@linkplain CommonPager}
+     */
+    CommonPager<ApiVO> listByPage(ApiQuery apiQuery);
+}
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/ApiServiceImpl.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/ApiServiceImpl.java
new file mode 100644
index 000000000..3aa1cd07f
--- /dev/null
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/ApiServiceImpl.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.admin.service.impl;
+
+import com.google.common.collect.Lists;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shenyu.admin.mapper.ApiMapper;
+import org.apache.shenyu.admin.mapper.TagMapper;
+import org.apache.shenyu.admin.mapper.TagRelationMapper;
+import org.apache.shenyu.admin.model.dto.ApiDTO;
+import org.apache.shenyu.admin.model.entity.ApiDO;
+import org.apache.shenyu.admin.model.entity.TagDO;
+import org.apache.shenyu.admin.model.entity.TagRelationDO;
+import org.apache.shenyu.admin.model.page.CommonPager;
+import org.apache.shenyu.admin.model.page.PageResultUtils;
+import org.apache.shenyu.admin.model.query.ApiQuery;
+import org.apache.shenyu.admin.model.query.TagRelationQuery;
+import org.apache.shenyu.admin.model.vo.ApiVO;
+import org.apache.shenyu.admin.model.vo.TagVO;
+import org.apache.shenyu.admin.service.ApiService;
+import org.apache.shenyu.admin.utils.ListUtil;
+import org.apache.shenyu.admin.utils.ShenyuResultMessage;
+import org.apache.shenyu.common.constant.AdminConstants;
+import org.apache.shenyu.common.utils.UUIDUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.sql.Timestamp;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * Implementation of the {@link org.apache.shenyu.admin.service.ApiService}.
+ */
+@Service
+public class ApiServiceImpl implements ApiService {
+
+    private final ApiMapper apiMapper;
+
+    private final TagRelationMapper tagRelationMapper;
+
+    private final TagMapper tagMapper;
+
+    public ApiServiceImpl(final ApiMapper apiMapper, final TagRelationMapper 
tagRelationMapper,
+                          final TagMapper tagMapper) {
+        this.apiMapper = apiMapper;
+        this.tagRelationMapper = tagRelationMapper;
+        this.tagMapper = tagMapper;
+
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String createOrUpdate(final ApiDTO apiDTO) {
+        return StringUtils.isBlank(apiDTO.getId()) ? this.create(apiDTO) : 
this.update(apiDTO);
+    }
+
+    /**
+     * update.
+     *
+     * @param apiDTO apiDTO
+     * @return update message
+     */
+    private String update(final ApiDTO apiDTO) {
+        ApiDO apiDO = ApiDO.buildApiDO(apiDTO);
+        final int updateRows = apiMapper.updateByPrimaryKeySelective(apiDO);
+        if (CollectionUtils.isNotEmpty(apiDTO.getTagIds()) && updateRows > 0) {
+            List<String> tagIds = apiDTO.getTagIds();
+            List<TagRelationDO> tags = Lists.newArrayList();
+            Timestamp currentTime = new Timestamp(System.currentTimeMillis());
+            for (String tagId : tagIds) {
+                tags.add(TagRelationDO.builder()
+                        .id(UUIDUtils.getInstance().generateShortUuid())
+                        .apiId(apiDO.getId())
+                        .tagId(tagId)
+                        .dateCreated(currentTime)
+                        .dateUpdated(currentTime)
+                        .build());
+            }
+            tagRelationMapper.deleteByApiId(apiDO.getId());
+            tagRelationMapper.batchInsert(tags);
+        }
+        return ShenyuResultMessage.UPDATE_SUCCESS;
+    }
+
+    /**
+     * create.
+     *
+     * @param apiDTO apiDTO
+     * @return create message
+     */
+    private String create(final ApiDTO apiDTO) {
+        ApiDO apiDO = ApiDO.buildApiDO(apiDTO);
+        final int insertRows = apiMapper.insertSelective(apiDO);
+        if (CollectionUtils.isNotEmpty(apiDTO.getTagIds()) && insertRows > 0) {
+            List<String> tagIds = apiDTO.getTagIds();
+            List<TagRelationDO> tags = Lists.newArrayList();
+            Timestamp currentTime = new Timestamp(System.currentTimeMillis());
+            for (String tagId : tagIds) {
+                tags.add(TagRelationDO.builder()
+                        .id(UUIDUtils.getInstance().generateShortUuid())
+                        .apiId(apiDO.getId())
+                        .tagId(tagId)
+                        .dateCreated(currentTime)
+                        .dateUpdated(currentTime)
+                        .build());
+            }
+            tagRelationMapper.batchInsert(tags);
+        }
+        return ShenyuResultMessage.CREATE_SUCCESS;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String delete(final List<String> ids) {
+        // select api id.
+        List<ApiDO> apis = this.apiMapper.selectByIds(ids);
+        if (CollectionUtils.isEmpty(apis)) {
+            return AdminConstants.SYS_API_ID_NOT_EXIST;
+        }
+        // delete apis.
+        final List<String> apiIds = ListUtil.map(apis, ApiDO::getId);
+        final int deleteRows = this.apiMapper.deleteByIds(apiIds);
+        if (deleteRows > 0) {
+            tagRelationMapper.deleteByApiIds(apiIds);
+        }
+        return StringUtils.EMPTY;
+    }
+
+    @Override
+    public ApiVO findById(final String id) {
+        return Optional.ofNullable(apiMapper.selectByPrimaryKey(id)).map(item 
-> {
+            List<TagRelationDO> tagRelations = 
tagRelationMapper.selectByQuery(TagRelationQuery.builder().apiId(item.getId()).build());
+            List<String> tagIds = 
tagRelations.stream().map(TagRelationDO::getTagId).collect(Collectors.toList());
+            List<TagVO> tagVOS = Lists.newArrayList();
+            if (CollectionUtils.isNotEmpty(tagIds)) {
+                List<TagDO> tagDOS = tagMapper.selectByIds(tagIds);
+                for (TagDO tagDO : tagDOS) {
+                    tagVOS.add(TagVO.buildTagVO(tagDO));
+                }
+            }
+            return ApiVO.buildApiVO(item, tagVOS);
+        }).orElse(null);
+    }
+
+    @Override
+    public CommonPager<ApiVO> listByPage(final ApiQuery apiQuery) {
+        return PageResultUtils.result(apiQuery.getPageParameter(), () -> 
apiMapper.selectByQuery(apiQuery)
+                .stream().map(item -> {
+                    List<TagRelationDO> tagRelations = 
tagRelationMapper.selectByQuery(TagRelationQuery.builder().apiId(item.getId()).build());
+                    List<String> tagIds = 
tagRelations.stream().map(TagRelationDO::getTagId).collect(Collectors.toList());
+                    List<TagVO> tagVOS = Lists.newArrayList();
+                    if (CollectionUtils.isNotEmpty(tagIds)) {
+                        List<TagDO> tagDOS = tagMapper.selectByIds(tagIds);
+                        for (TagDO tagDO : tagDOS) {
+                            tagVOS.add(TagVO.buildTagVO(tagDO));
+                        }
+                    }
+                    return ApiVO.buildApiVO(item, tagVOS);
+                }).collect(Collectors.toList()));
+    }
+}
diff --git a/shenyu-admin/src/main/resources/mappers/api-sqlmap.xml 
b/shenyu-admin/src/main/resources/mappers/api-sqlmap.xml
index 43504947d..9468d0630 100644
--- a/shenyu-admin/src/main/resources/mappers/api-sqlmap.xml
+++ b/shenyu-admin/src/main/resources/mappers/api-sqlmap.xml
@@ -39,6 +39,7 @@
     <result column="date_created" jdbcType="TIMESTAMP" property="dateCreated" 
/>
     <result column="date_updated" jdbcType="TIMESTAMP" property="dateUpdated" 
/>
   </resultMap>
+
   <sql id="Base_Column_List">
     <[email protected]>
     id, context_path, api_path, http_method, consume, produce, version, 
rpc_type, `state`, 
@@ -257,4 +258,64 @@
       date_updated = #{dateUpdated,jdbcType=TIMESTAMP}
     where id = #{id,jdbcType=VARCHAR}
   </update>
+
+  <select id="existed" resultType="java.lang.Boolean">
+    select true
+    from api
+    where id = #{id} limit 1
+  </select>
+
+  <select id="selectByQuery" 
parameterType="org.apache.shenyu.admin.model.query.ApiQuery"
+          resultMap="BaseResultMap">
+    select api.id,
+           api.context_path,
+           api.api_path,
+           api.http_method,
+           api.consume,
+           api.produce,
+           api.version,
+           api.rpc_type,
+           api.`state`,
+           api.ext,
+           api.api_owner,
+           api.api_desc,
+           api.api_source,
+           api.document,
+           api.document_md5,
+           api.date_created,
+           api.date_updated
+    from api
+    <if test="tagId != null and tagId != ''">
+        inner join tag_relation on api.id = tag_relation.api_id and 
tag_relation.tag_id = #{tagId,jdbcType=VARCHAR}
+    </if>
+    <where>
+      <if test="apiPath != null and apiPath != ''">
+        <bind name="nameLike" value="('%' + apiPath + '%')"/>
+        and api_path LIKE #{nameLike, jdbcType=VARCHAR}
+      </if>
+      <if test="state != null">
+        and state = #{state, jdbcType=TINYINT}
+      </if>
+    </where>
+    order by api.date_created desc
+  </select>
+
+  <select id="selectByIds" parameterType="java.util.List" 
resultMap="BaseResultMap">
+    SELECT
+    <include refid="Base_Column_List"/>
+    FROM api
+    WHERE id IN
+    <foreach item="id" collection="list" open="(" separator="," close=")">
+      #{id, jdbcType=VARCHAR}
+    </foreach>
+  </select>
+
+  <delete id="deleteByIds" parameterType="java.util.List">
+    DELETE FROM api
+    WHERE id IN
+    <foreach item="id" collection="list" open="(" separator="," close=")">
+      #{id, jdbcType=VARCHAR}
+    </foreach>
+  </delete>
+
 </mapper>
\ No newline at end of file
diff --git a/shenyu-admin/src/main/resources/mappers/tag-relation-sqlmap.xml 
b/shenyu-admin/src/main/resources/mappers/tag-relation-sqlmap.xml
index 92b2cd906..6fd464c06 100644
--- a/shenyu-admin/src/main/resources/mappers/tag-relation-sqlmap.xml
+++ b/shenyu-admin/src/main/resources/mappers/tag-relation-sqlmap.xml
@@ -147,4 +147,35 @@
             #{id, jdbcType=VARCHAR}
         </foreach>
     </delete>
+
+    <insert id="batchInsert" parameterType="java.util.List">
+        INSERT INTO tag_relation
+        (id,
+         date_created,
+         date_updated,
+         api_id,
+         tag_id)
+        VALUES
+        <foreach collection="list" item="item" separator=",">
+            (#{item.id, jdbcType=VARCHAR},
+             #{item.dateCreated, jdbcType=TIMESTAMP},
+             #{item.dateUpdated, jdbcType=TIMESTAMP},
+             #{item.apiId, jdbcType=VARCHAR},
+             #{item.tagId, jdbcType=VARCHAR})
+        </foreach>
+    </insert>
+
+    <delete id="deleteByApiId">
+        delete
+        from tag_relation
+        where api_id = #{apiId,jdbcType=VARCHAR}
+    </delete>
+
+    <delete id="deleteByApiIds" parameterType="java.util.List">
+        DELETE FROM tag_relation
+        WHERE api_id IN
+        <foreach item="id" collection="apiIds" open="(" separator="," 
close=")">
+            #{id, jdbcType=VARCHAR}
+        </foreach>
+    </delete>
 </mapper>
diff --git a/shenyu-admin/src/main/resources/mappers/tag-sqlmap.xml 
b/shenyu-admin/src/main/resources/mappers/tag-sqlmap.xml
index 21974c641..f303e4ab2 100644
--- a/shenyu-admin/src/main/resources/mappers/tag-sqlmap.xml
+++ b/shenyu-admin/src/main/resources/mappers/tag-sqlmap.xml
@@ -62,7 +62,7 @@
 
     <select id="selectByParentTagIds" parameterType="java.util.List" 
resultMap="BaseResultMap">
         SELECT
-        <include refid="Base_Column_List"/>
+            <include refid="Base_Column_List"/>
         FROM tag
         WHERE parent_tag_id IN
         <foreach item="id" collection="list" open="(" separator="," close=")">
@@ -189,4 +189,14 @@
     <delete id="deleteAllData" >
         delete from tag
     </delete>
+
+    <select id="selectByIds" resultMap="BaseResultMap">
+        SELECT
+            <include refid="Base_Column_List"/>
+        FROM tag
+        WHERE id IN
+        <foreach item="id" collection="list" open="(" separator="," close=")">
+            #{id, jdbcType=VARCHAR}
+        </foreach>
+    </select>
 </mapper>
diff --git 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/controller/ApiControllerTest.java
 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/controller/ApiControllerTest.java
new file mode 100644
index 000000000..a6d24cf63
--- /dev/null
+++ 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/controller/ApiControllerTest.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.admin.controller;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shenyu.admin.exception.ExceptionHandlers;
+import org.apache.shenyu.admin.mapper.ApiMapper;
+import org.apache.shenyu.admin.model.dto.ApiDTO;
+import org.apache.shenyu.admin.model.page.CommonPager;
+import org.apache.shenyu.admin.model.page.PageParameter;
+import org.apache.shenyu.admin.model.query.ApiQuery;
+import org.apache.shenyu.admin.model.vo.ApiVO;
+import org.apache.shenyu.admin.service.ApiService;
+import org.apache.shenyu.admin.spring.SpringBeanUtils;
+import org.apache.shenyu.admin.utils.ShenyuResultMessage;
+import org.apache.shenyu.common.utils.GsonUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.core.Is.is;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static 
org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static 
org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+/**
+ * Test cases for ApiController.
+ */
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+public final class ApiControllerTest {
+
+    private MockMvc mockMvc;
+
+    @InjectMocks
+    private ApiController apiController;
+
+    @Mock
+    private ApiService apiService;
+
+    @Mock
+    private ApiMapper apiMapper;
+
+    private ApiVO apiVO;
+
+    @BeforeEach
+    public void setUp() {
+        this.mockMvc = MockMvcBuilders.standaloneSetup(apiController)
+                .setControllerAdvice(new ExceptionHandlers())
+                .build();
+        this.apiVO = ApiVO.builder()
+                .id("123")
+                .contextPath("string")
+                .apiPath("string")
+                .httpMethod(0)
+                .consume("string")
+                .produce("string")
+                .version("string")
+                .rpcType("/dubbo")
+                .state(0)
+                .apiSource(0)
+                .document("document")
+                .build();
+        
SpringBeanUtils.getInstance().setApplicationContext(mock(ConfigurableApplicationContext.class));
+    }
+
+    @Test
+    public void testQueryApis() throws Exception {
+        final PageParameter pageParameter = new PageParameter();
+        List<ApiVO> apiVOS = new ArrayList<>();
+        apiVOS.add(apiVO);
+        final CommonPager<ApiVO> commonPager = new CommonPager<>();
+        commonPager.setPage(pageParameter);
+        commonPager.setDataList(apiVOS);
+        final ApiQuery apiQuery = new ApiQuery("string", 0, "", pageParameter);
+        given(this.apiService.listByPage(apiQuery)).willReturn(commonPager);
+        this.mockMvc.perform(MockMvcRequestBuilders.get("/api")
+                .param("apiPath", "string")
+                .param("state", "0")
+                .param("tagId", "")
+                .param("currentPage", pageParameter.getCurrentPage() + "")
+                .param("pageSize", pageParameter.getPageSize() + ""))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.message", 
is(ShenyuResultMessage.QUERY_SUCCESS)))
+                .andExpect(jsonPath("$.data.dataList[0].contextPath", 
is(apiVO.getContextPath())))
+                .andReturn();
+    }
+
+    @Test
+    public void testDetailApi() throws Exception {
+        given(this.apiService.findById("123")).willReturn(apiVO);
+        this.mockMvc.perform(MockMvcRequestBuilders.get("/api/{id}", "123"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.message", 
is(ShenyuResultMessage.DETAIL_SUCCESS)))
+                .andExpect(jsonPath("$.data.id", is(apiVO.getId())))
+                .andReturn();
+    }
+
+    @Test
+    public void testCreateApi() throws Exception {
+        ApiDTO apiDTO = new ApiDTO();
+        apiDTO.setContextPath("string");
+        apiDTO.setApiPath("string");
+        apiDTO.setHttpMethod(0);
+        apiDTO.setConsume("string");
+        apiDTO.setProduce("string");
+        apiDTO.setVersion("string");
+        apiDTO.setRpcType("/dubbo");
+        apiDTO.setState(0);
+        apiDTO.setApiOwner("string");
+        apiDTO.setApiDesc("string");
+        apiDTO.setApiSource(0);
+        apiDTO.setDocument("document");
+        apiDTO.setExt("ext");
+        
given(this.apiService.createOrUpdate(apiDTO)).willReturn(ShenyuResultMessage.CREATE_SUCCESS);
+        this.mockMvc.perform(MockMvcRequestBuilders.post("/api")
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(GsonUtils.getInstance().toJson(apiDTO)))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.message", 
is(ShenyuResultMessage.CREATE_SUCCESS)))
+                .andReturn();
+    }
+
+    @Test
+    public void testUpdateApi() throws Exception {
+        ApiDTO apiDTO = new ApiDTO();
+        apiDTO.setContextPath("string");
+        apiDTO.setApiPath("string");
+        apiDTO.setHttpMethod(0);
+        apiDTO.setConsume("string");
+        apiDTO.setProduce("string");
+        apiDTO.setVersion("string");
+        apiDTO.setRpcType("/dubbo");
+        apiDTO.setState(0);
+        apiDTO.setApiOwner("string");
+        apiDTO.setApiDesc("string");
+        apiDTO.setApiSource(0);
+        apiDTO.setDocument("document");
+        apiDTO.setExt("ext");
+        apiDTO.setId("123");
+        
when(SpringBeanUtils.getInstance().getBean(ApiMapper.class)).thenReturn(apiMapper);
+        when(apiMapper.existed(apiDTO.getId())).thenReturn(true);
+        
given(this.apiService.createOrUpdate(apiDTO)).willReturn(ShenyuResultMessage.UPDATE_SUCCESS);
+        this.mockMvc.perform(MockMvcRequestBuilders.put("/api/{id}", 
apiDTO.getId())
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(GsonUtils.getInstance().toJson(apiDTO)))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.message", 
is(ShenyuResultMessage.UPDATE_SUCCESS)))
+                .andReturn();
+
+    }
+
+    @Test
+    public void testDeleteApis() throws Exception {
+        
given(this.apiService.delete(Collections.singletonList("123"))).willReturn(StringUtils.EMPTY);
+        this.mockMvc.perform(MockMvcRequestBuilders.delete("/api/batch")
+                .contentType(MediaType.APPLICATION_JSON)
+                .content("[\"123\"]"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.message", 
is(ShenyuResultMessage.DELETE_SUCCESS)))
+                .andReturn();
+
+    }
+
+}
diff --git 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/mapper/ApiMapperTest.java 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/mapper/ApiMapperTest.java
index 5ec9292cc..ce75f8dc6 100644
--- 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/mapper/ApiMapperTest.java
+++ 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/mapper/ApiMapperTest.java
@@ -74,7 +74,7 @@ public final class ApiMapperTest extends 
AbstractSpringIntegrationTest {
         this.apiDO.setHttpMethod(1);
         this.apiDO.setVersion("V0.02");
         this.apiDO.setRpcType("dubbo1");
-        this.apiDO.setState((byte) 1);
+        this.apiDO.setState(1);
         final int count = apiMapper.updateByPrimaryKeySelective(this.apiDO);
         assertEquals(1, count);
     }
@@ -88,7 +88,7 @@ public final class ApiMapperTest extends 
AbstractSpringIntegrationTest {
         this.apiDO.setHttpMethod(2);
         this.apiDO.setVersion("V0.03");
         this.apiDO.setRpcType("dubbo2");
-        this.apiDO.setState((byte) 2);
+        this.apiDO.setState(2);
         this.apiDO.setApiSource(3);
         final int count = apiMapper.updateByPrimaryKeySelective(this.apiDO);
         assertEquals(1, count);
@@ -111,7 +111,7 @@ public final class ApiMapperTest extends 
AbstractSpringIntegrationTest {
         apiDO.setProduce("accept");
         apiDO.setVersion("V0.01");
         apiDO.setRpcType("dubbo");
-        apiDO.setState((byte) 0);
+        apiDO.setState(0);
         apiDO.setExt("ext");
         apiDO.setApiOwner("admin");
         apiDO.setApiDesc("hello world api");
diff --git 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ApiServiceTest.java
 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ApiServiceTest.java
new file mode 100644
index 000000000..2ea6b411d
--- /dev/null
+++ 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ApiServiceTest.java
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.admin.service;
+
+import org.apache.shenyu.admin.mapper.ApiMapper;
+import org.apache.shenyu.admin.mapper.TagMapper;
+import org.apache.shenyu.admin.mapper.TagRelationMapper;
+import org.apache.shenyu.admin.model.dto.ApiDTO;
+import org.apache.shenyu.admin.model.entity.ApiDO;
+import org.apache.shenyu.admin.model.page.CommonPager;
+import org.apache.shenyu.admin.model.page.PageParameter;
+import org.apache.shenyu.admin.model.query.ApiQuery;
+import org.apache.shenyu.admin.model.vo.ApiVO;
+import org.apache.shenyu.admin.service.impl.ApiServiceImpl;
+import org.apache.shenyu.admin.utils.ShenyuResultMessage;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.platform.commons.util.StringUtils;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+
+import java.sql.Timestamp;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.when;
+
+/**
+ * Test cases for ApiService.
+ */
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+public final class ApiServiceTest {
+
+    @InjectMocks
+    private ApiServiceImpl apiService;
+
+    @Mock
+    private ApiMapper apiMapper;
+
+    @Mock
+    private TagRelationMapper tagRelationMapper;
+
+    @Mock
+    private TagMapper tagMapper;
+
+    @BeforeEach
+    public void setUp() {
+        apiService = new ApiServiceImpl(apiMapper, tagRelationMapper, 
tagMapper);
+    }
+
+    @Test
+    public void testCreateOrUpdate() {
+        testCreate();
+        testUpdate("123");
+    }
+
+    @Test
+    public void testDelete() {
+        List<ApiDO> apis = Collections.singletonList(buildApiDO("123"));
+        
when(apiMapper.selectByIds(Collections.singletonList("123"))).thenReturn(apis);
+        
when(apiMapper.deleteByIds(Collections.singletonList("123"))).thenReturn(1);
+        assertEquals(org.apache.commons.lang3.StringUtils.EMPTY, 
apiService.delete(Collections.singletonList("123")));
+    }
+
+    @Test
+    public void testFindById() {
+        String id = "123";
+        final ApiDO apiDO = buildApiDO(id);
+        given(this.apiMapper.selectByPrimaryKey(eq(id))).willReturn(apiDO);
+        ApiVO byId = this.apiService.findById(id);
+        assertNotNull(byId);
+    }
+
+    @Test
+    public void testListByPage() {
+        PageParameter pageParameter = new PageParameter();
+        pageParameter.setPageSize(5);
+        pageParameter.setTotalCount(10);
+        pageParameter.setTotalPage(pageParameter.getTotalCount() / 
pageParameter.getPageSize());
+        ApiQuery apiQuery = new ApiQuery(null, 0, "", pageParameter);
+        List<ApiDO> apiDOList = new ArrayList<>();
+        for (int i = 0; i < 10; i++) {
+            final ApiDO apiDO = buildApiDO("" + i);
+            apiDOList.add(apiDO);
+        }
+        given(this.apiMapper.selectByQuery(apiQuery)).willReturn(apiDOList);
+        final CommonPager<ApiVO> apiDOCommonPager = 
this.apiService.listByPage(apiQuery);
+        assertEquals(apiDOCommonPager.getDataList().size(), apiDOList.size());
+    }
+
+    private ApiDO buildApiDO(final String id) {
+        ApiDO apiDO = ApiDO.buildApiDO(buildApiDTO(id));
+        Timestamp now = Timestamp.valueOf(LocalDateTime.now());
+        apiDO.setDateCreated(now);
+        apiDO.setDateUpdated(now);
+        return apiDO;
+    }
+
+    private ApiVO buildApiVO(final String id) {
+        return ApiVO.builder()
+                .id(id)
+                .contextPath("string")
+                .apiPath("string")
+                .httpMethod(0)
+                .consume("string")
+                .produce("string")
+                .version("string")
+                .rpcType("string")
+                .state(0)
+                .apiOwner("string")
+                .apiDesc("string")
+                .apiSource(0)
+                .document("document")
+                .build();
+    }
+
+    private void testCreate() {
+        ApiDTO apiDTO = buildApiDTO("");
+        assertEquals(ShenyuResultMessage.CREATE_SUCCESS, 
this.apiService.createOrUpdate(apiDTO));
+    }
+
+    private ApiDTO buildApiDTO(final String id) {
+        ApiDTO apiDTO = new ApiDTO();
+        if (StringUtils.isNotBlank(id)) {
+            apiDTO.setId(id);
+        }
+        apiDTO.setContextPath("string");
+        apiDTO.setApiPath("string");
+        apiDTO.setHttpMethod(0);
+        apiDTO.setConsume("string");
+        apiDTO.setProduce("string");
+        apiDTO.setVersion("string");
+        apiDTO.setRpcType("string");
+        apiDTO.setState(0);
+        apiDTO.setApiOwner("string");
+        apiDTO.setApiDesc("string");
+        apiDTO.setApiSource(0);
+        apiDTO.setDocument("document");
+        return apiDTO;
+    }
+
+    private void testUpdate(final String id) {
+        ApiDTO apiDTO = new ApiDTO();
+        apiDTO.setId(id);
+        apiDTO.setApiPath("test");
+        apiDTO.setDocument("testDocument");
+        assertEquals(ShenyuResultMessage.UPDATE_SUCCESS, 
this.apiService.createOrUpdate(apiDTO));
+    }
+}
diff --git 
a/shenyu-common/src/main/java/org/apache/shenyu/common/constant/AdminConstants.java
 
b/shenyu-common/src/main/java/org/apache/shenyu/common/constant/AdminConstants.java
index 9457c89de..74ee4f5a6 100644
--- 
a/shenyu-common/src/main/java/org/apache/shenyu/common/constant/AdminConstants.java
+++ 
b/shenyu-common/src/main/java/org/apache/shenyu/common/constant/AdminConstants.java
@@ -57,6 +57,11 @@ public final class AdminConstants {
      */
     public static final String SYS_PLUGIN_ID_NOT_EXIST = "The plugin(s) does 
not exist!";
 
+    /**
+     * The constant SYS_API_ID_NOT_EXIST.
+     */
+    public static final String SYS_API_ID_NOT_EXIST = "The api(s) does not 
exist!";
+
     /**
      * The constant DATA_PATH_IS_EXIST.
      */

Reply via email to