商品分类查询接口

2023年 9月 29日 18.6k 0

本节我们继续开发商品分类剩下的查询接口,包括了后台管理的分类分页查询列表和门户端的分类查询。话不多说,开干!

定义openApi3文档

现在我们来定义分类的分页查询API,看下在mall.yml中新增的内容:

openapi: 3.0.1
...
paths:
  ...
  /mall-api/admin/categories:
    get:
      tags:
        - category
      summary: 查询分类列表
      description: 分页查询分类列表
      operationId: listCategories
      requestBody:
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/CategoryCondition'
      x-pageResult: true
      responses:
        200:
          description: 查询成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CategoryInfo'
    ...
  ...
components:
  schemas:
    ...
    PageQuery:
      description: 分页基类DTO
      type: object
      properties:
        pageNo:
          description: 当前页
          type: integer
        pageSize:
          description: 每页显示记录数
          type: integer
    CategoryCondition:
      description: 分类分页查询条件DTO类
      type: object
      allOf:
        - $ref: '#/components/schemas/PageQuery'
      properties:
        name:
          description: 分类名称(模糊匹配)
          type: string
    CategoryInfo:
      description: 分类信息DTO类
      type: object
      properties:
        id:
          description: 分类id
          type: integer
          format: int64
        pid:
          description: 父级分类id
          type: integer
          format: int64
        name:
          description: 分类名称
          type: string
        level:
          description: 分类层级
          type: integer
        orderNum:
          description: 排序
          type: integer
        createTime:
          description: 创建时间
          type: string
          format: date
        updateTime:
          description: 更新时间
          type: string
          format: date
        children:
          description: 子分类列表
          type: array
          items:
            $ref: '#/components/schemas/CategoryInfo'

    ...

注意,分页查询条件DTO,我们用openApi3文档规范中提供的allOf来实现对schema的重用,生成的类则会实现继承。

而返回类型将作为我们包装的一个分页结果PageResultDTO的泛型类型,因此这里也采用了扩展定义:x-pageResult: true

看下我们对分页信息类的设计:

package com.xiaojuan.boot.common.dto;

import ...

@Data
@AllArgsConstructor
public class PageResultDTO {
    private Long total;
    private List data;
}

这里还涉及到java.util.Date日期类型的字段,注意我们这里的字段声明形式:

createTime:
  type: string
  format: date

还要在生成器配置中设置dateLibrarycustom

image.png

生成的CategoryInfoDTO我们将用作两个用途,一个是给后台管理返回查询的分页列表,还有就是给门户端查询分类展示嵌套结构,因此这里我们多定义了一个List类型的children字段,注意下定义形式:

CategoryInfo:
  description: 分类信息DTO类
  type: object
  properties:
    ...
    children:
      description: 子分类列表
      type: array
      items:
        $ref: '#/components/schemas/CategoryInfo'

修改returnTypes模板

image.png

{{#returnContainer}}{{#isMapContainer}}Map{{/isMapContainer}}{{#isListContainer}}List{{/isListContainer}}{{/returnContainer}}{{^returnContainer}}{{#vendorExtensions.x-pageResult}}PageResultDTO{{/vendorExtensions.x-pageResult}}{{^vendorExtensions.x-pageResult}}{{{returnType}}}{{/vendorExtensions.x-pageResult}}{{/returnContainer}}

这里我们对扩展的定义进行了判断:

{{#vendorExtensions.x-pageResult}}PageResultDTO{{/vendorExtensions.x-pageResult}}{{^vendorExtensions.x-pageResult}}{{{returnType}}}{{/vendorExtensions.x-pageResult}}

修改生成器源码

为了在API接口头部按需导入PageResultDTO类型,我们还需要修改swagger生成器源码。

image.png

这样就可以在api.mustache中按需导入了:

{{#needImportPageResultDTO}}
import com.xiaojuan.boot.common.dto.PageResultDTO;
{{/needImportPageResultDTO}}

最后生成的是我们想要的代码:

image.png

image.png

开发分页查询接口

接下来我们将开发分页查询的mapper接口,因为之前我们已经完成了mybatis与spring boot的整合,现在我们只专注相关模块的开发流程。

因为涉及到动态查询条件的传入,我们将通过xml的mapper查询语句块的形式来实现动态查询条件,我们先新建一个category-mapper.xml




    

        select t.id, t.pid, t.name, t.level, t.order_num, t.create_time, t.update_time
            from TB_CATEGORY t
        
            
                t.NAME like concat('%', #{name}, '%')
            
        
    

对应的mapper接口:

package com.xiaojuan.boot.dao.mapper;

import ...

public interface CustomCategoryMapper {

    List listCategories(CategoryConditionDTO conditionDTO);

}

Service接口:

package com.xiaojuan.boot.service;

import ...

public interface CategoryService {

    ...

    PageResultDTO listCategoriesByPage(CategoryConditionDTO conditionDTO);
}

注意service接口的方法名要尽量起的见名知意。

Service接口实现:

package com.xiaojuan.boot.service.impl;

import ...

@RequiredArgsConstructor
@Service
public class CategoryServiceImpl implements CategoryService {

    ...

    private final CustomCategoryMapper customCategoryMapper;

    ...

    @Override
    public PageResultDTO listCategoriesByPage(CategoryConditionDTO conditionDTO) {
        PageHelper.startPage(conditionDTO.getPageNo(), conditionDTO.getPageSize());
        List categories = customCategoryMapper.listCategories(conditionDTO);
        PageInfo pageInfo = new PageInfo(categories);
        return new PageResultDTO(pageInfo.getTotal(), pageInfo.getList());
    }
}

分页API前面我们已经实践过,这里派上了用场。

Controller组件调用service则更简单了:

package com.xiaojuan.boot.web.controller.admin;

import ...

@RequiredArgsConstructor
@RestController
public class CategoryController implements CategoryApi {

    private final CategoryService categoryService;

    ...

    @Override
    public PageResultDTO listCategories(CategoryConditionDTO body) {
        return categoryService.listCategoriesByPage(body);
    }
}

最后的测试我们用http client的方式,调用下:

image.png

相应的日志输出:

image.png

开发门户端分类查询

完成了后台管理的分类分页查询,现在我们再回到门户网站的分类展示功能。我们将开发一个以嵌套结构展示的分类查询功能。

先来定义api:

  /mall-api/portal/category/listTree:
    get:
      summary: 门户端查询分类树接口
      description: 查询分类嵌套结构
      operationId: listCategories
      responses:
        200:
          description: 查询成功
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CategoryInfo'

当我们生成API接口后,我们发现命名为:

image.png

这种命名规则其实是通过我们在openApi3中设置的tag来生成的。

tag定义与api接口名

当我们这样定义tag时:

...
paths:
  /mall-api/admin/users:
    get:
      tags:
        - admin
        - user
      summary: 用户列表查询接口
      ...
  /mall-api/user/profile:
    get:
      tags:
        - user
      summary: 用户信息查询接口
      ...
  /mall-api/user/admin/login:
    post:
      tags:
        - user
      summary: 管理员登录接口
      ...
  /mall-api/user/login:
    post:
      tags:
        - user
      summary: 普通用户登录接口
      ...
  /mall-api/user/signature:
    post:
      tags:
        - user
      summary: 更新个性签名接口
      ...
  /mall-api/user/logout:
    post:
      tags:
        - user
      summary: 退出登录接口
      ...
  /mall-api/user/register:
    post:
      tags:
        - user
      summary: 用户注册接口
      ...
  /mall-api/admin/categories:
    get:
      tags:
        - admin
        - category
      summary: 查询分类列表
      ...
    post:
      tags:
        - admin
        - category
      summary: 新增分类
      ...
    put:
      tags:
        - admin
        - category
      summary: 更新分类
      ...
  /mall-api/admin/categories/{id}:
    delete:
      tags:
        - admin
        - category
      summary: 删除分类
      ...
  /mall-api/portal/category/listTree:
    get:
      tags:
        - portal
        - category
      summary: 门户端查询分类树接口
      ...
components:
  schemas:
    ...

这里我们只是粗略的把接口分成了后台管理、门户和用户认证相关三大类(这里是以第一个tag来生成API名称):

image.png

而对于多个标签的api接口,我们更希望能在命名API接口时都体现出来,对接口进行细粒度的划分,因此我们可以这样定义组合的标签:

...
paths:
  /mall-api/admin/users:
    get:
      tags:
        - userAdmin
      summary: 用户列表查询接口
      ...
  ...    
  /mall-api/admin/categories:
    get:
      tags:
        - categoryAdmin      
      summary: 查询分类列表
      ...
    post:
      tags:
        - categoryAdmin      
      summary: 新增分类
      ...
    put:
      tags:
        - categoryAdmin      
      summary: 更新分类
      ...
  /mall-api/admin/categories/{id}:
    delete:
      tags:
        - categoryAdmin
      summary: 删除分类
      ...
  /mall-api/portal/category/listTree:
    get:
      tags:
        - categoryPortal
      summary: 门户端查询分类树接口
      ...
components:
  schemas:
    ...

我们为多标签生成一个组合标签名。

这样再看生成的API接口的命名,就划分更合理了:

image.png

api分组

现在我们调整下controller的包和类:

image.png

这里我们将Rest API分三组,对应的swagger分组配置进行调整:

springdoc:
  group-configs:
    - group: '用户认证模块'
      packagesToScan: com.xiaojuan.boot.web.controller.user
      pathsToMatch: /**
    - group: '管理模块'
      packagesToScan: com.xiaojuan.boot.web.controller.admin
      pathsToMatch: /**
    - group: '门户模块'
      packagesToScan: com.xiaojuan.boot.web.controller.portal
      pathsToMatch: /**

这样我们看一个模块的API就会比较清晰:

image.png

开发接口

这次我们按照从调用层到接口定义的方向来做,然后用idea帮助我们根据修复建议自动生成接口定义。

controller调用:

package com.xiaojuan.boot.web.controller.portal;

import ...

@RequiredArgsConstructor
@RestController
public class CategoryPortalController implements CategoryPortalApi {

    private final CategoryService categoryService;

    @Override
    public List listCategories() {
        return categoryService.listCategoryTree();
    }
}

生成service接口:

package com.xiaojuan.boot.service;

import ...

public interface CategoryService {

    ...

    List listCategoryTree();
}

完成service接口实现:

package com.xiaojuan.boot.service.impl;

import ...

@RequiredArgsConstructor
@Service
public class CategoryServiceImpl implements CategoryService {

    ...

    private final CustomCategoryMapper customCategoryMapper;

    ...

    @Override
    public List listCategoryTree() {
        List children = new ArrayList();
        loadSubcategories(0, children);
        return children;
    }

    private void loadSubcategories(long pid, List children) {
        CategoryConditionDTO conditionDTO = new CategoryConditionDTO();
        conditionDTO.setPid(pid);
        List subCategories = customCategoryMapper.listCategories(conditionDTO);
        if (!CollectionUtils.isEmpty(subCategories)) {
            for (CategoryInfoDTO subCategory : subCategories) {
                children.add(subCategory);
                if (subCategory.getChildren() == null) {
                    subCategory.setChildren(new ArrayList());
                }
                loadSubcategories(subCategory.getId(), subCategory.getChildren());
            }
        }
    }
}

这里我们采用了递归的调用方式从pid为0的分类开始依次加载其子分类列表。而mapper层我们并没有新开发接口,而是重用了之前为分页提供的接口。只不过我们要为查询条件DTO额外增加一个pid的字段,在openApi3文档中定义即可,并在category-mapper.xml的中新增基于pid查询的动态条件定义。这里代码就省略了。

最后通过swagger测试,该API无需用户登录,直接调用:

image.png

本节我们边开发分类查询接口,边对swagger生成器进行进一步修改源码的完善。在实现门户端的查询时我们采用了递归方式,每次递归都要按照父级id查询子列表。下一节我们将来实践spring缓存,对从数据库加载的分类信息进行缓存,大家加油!

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论