2026-05-06-REQ-MOD-004.md 7.5 KB

req_id: REQ-MOD-004 date: 2026-05-06

module: module_mod

Spec: REQ-MOD-004 — 模块查询

目标

实现后端 GET /api/modules 接口:以树形结构返回所有未软删除的模块,可选按 sModuleNameZh 模糊匹配过滤;过滤命中时同时保留命中节点的所有祖先路径以便定位上下文。

输入 / 触发

接口GET /api/modules,无请求体。

Query parameters

字段 类型 必填 校验 / 取值
keyword String 长度 ≤ 50;非空时对 sModuleNameZhLIKE '%keyword%'(不区分大小写——MySQL utf8mb4_unicode_ci 默认行为)

鉴权:契约要求 Authorization: Bearer <accessToken> + 权限码 MOD:READ。沿用 SecurityConfig permitAll;Controller Javadoc:REQ-USR-004 完成后追加 @PreAuthorize("hasAuthority('MOD:READ')")

输出 / 结果

HTTP 200,响应体

{
  "code": 200,
  "message": "操作成功",
  "data": [
    {
      "iIncrement": 1,
      "sModuleNameZh": "系统配置",
      "sDisplayType": "系统配置",
      "sManageDeptEn": "IT",
      "iParentId": null,
      "iSortOrder": 0,
      "children": [
        {
          "iIncrement": 2,
          "sModuleNameZh": "用户管理",
          "sDisplayType": "前端业务",
          "sManageDeptEn": "IT",
          "iParentId": 1,
          "iSortOrder": 1,
          "children": []
        }
      ]
    }
  ],
  "timestamp": 1746528600000
}

data 是根节点数组(iParentId == null 的节点);每个节点带 children 数组(同结构递归)。

新增 VO ModuleTreeNodeVO:字段 iIncrement / sModuleNameZh / sDisplayType / sManageDeptEn / iParentId / iSortOrder / children: List<ModuleTreeNodeVO>

不返回的字段(避免泄露内部):sProcedureName / sModuleType / bShowPermission / sId / sBrandsId / sSubsidiaryId / tCreateDate / sCreatedBy / bDeleted / tDeletedDate / sDeletedBy。如未来 REQ 需要其中字段,再扩 VO。

业务规则

  1. 范围过滤bDeleted = 0(默认仅返回未软删除)。
  2. 空 keyword:返回所有未软删除模块构成的完整树。
  3. 非空 keyword
    • 步骤 a:在所有未软删除模块中找出 sModuleNameZh LIKE '%keyword%' 的命中集合 hits
    • 步骤 b:对每个 hit,沿 iParentId 链向上收集所有未软删除祖先(深度上限 5 与 docs/03 § tModule 一致),并入结果集合。
    • 步骤 c:用结果集合在内存中按 iParentId 组装树。
    • 命中节点本身的子孙不会被强制纳入(除非也命中);这样避免一次过滤拉出整棵子树。
  4. 排序:同级节点按 iSortOrder ASC 升序,iSortOrder 相同则按 iIncrement ASC(确定性排序)。
  5. children 字段:叶子节点为 [],不为 null(确保前端可直接 .map)。
  6. 空结果:keyword 无匹配 → data = []HTTP 200 + code=200,不返回 404 类错误。
  7. 只读:本接口不写库,无事务要求;标 @Transactional(readOnly = true)

边界与约束

鉴权策略

沿用 REQ-MOD-001/002/003 SecurityConfig permitAll。

错误码

场景 错误码 ErrorCode 枚举
keyword 长度 > 50 40010 PARAM_INVALID(已存在)
服务端兜底 50000 INTERNAL_ERROR

性能

  • 单次 selectList(LambdaQueryWrapper.eq(bDeleted, false)) 拉取所有未删除模块;spec § 性能上限 docs/03 注明"单次返回不超过 500 项 / 树深度上限 5 层",本期数据量低不分页。
  • 在内存里 O(N) 建索引(id → entity)+ O(N) 建子节点列表 + O(N) 排序 + O(K * D) 沿祖先链向上(K=hits 数,D=深度上限 5)。
  • 不引入 SQL 递归 CTE / 自连接;保持 mapper 层轻量。

大小写敏感

utf8mb4_unicode_ci collation 默认不区分大小写。MyBatis-Plus LambdaQueryWrapper.like(...) 走 SQL LIKE,行为与 collation 一致。无需额外处理。

keyword 中的特殊字符

% / _ 在 SQL LIKE 模式里是通配符。简化处理:本期不做转义(业务模块名通常不含这些字符);如未来需要,service 层先把 keyword 中的 % / _ / \ 替换为转义形式(MyBatis-Plus 5.xlike 已自带 escape,本期 3.5.7 需手动)。本 REQ 暂不处理转义,作为已知边界记录。

依赖的 schema 表 / 字段

读表tModule

字段 用途
iIncrement 节点 id;输出
sModuleNameZh 模糊匹配列;输出
sDisplayType 输出
sManageDeptEn 输出
iParentId 父子关系;输出
iSortOrder 排序;输出
bDeleted 过滤未删除(=0)

索引利用

  • idx_module_name_zhLIKE '%keyword%' 实际不走索引(左模糊),但本期数据量低可接受。
  • idx_parent:祖先链查询时按 iIncrement PK 走 selectById,不用 idx_parent。
  • idx_module_deleted(未单独建,但 bDeleted 在多个 idx 中已有):bDeleted 过滤时 MySQL 优化器自行选择。

外键:本接口只读,不触发 FK 检查。

依赖的接口

无(独立查询接口)。

验收标准

功能正确性

  1. 正向 — 空 keyword 返回完整树:DB 中 5 棵根 + 多层子模块;GET 返回 5 个根节点,子孙完整嵌套;同级按 iSortOrder 升序。
  2. 正向 — keyword 模糊匹配 + 祖先:DB 有 root("系统配置") → 子("用户管理") → 孙("登录");GET ?keyword=登录 返回 root → 子 → 孙 三层(即命中节点 + 全部祖先)。
  3. 正向 — keyword 部分匹配:keyword="管理",匹配多个节点;返回它们各自完整祖先链合并的树。
  4. 正向 — 软删除模块过滤:DELETE 一个模块后再查,结果不含该模块。
  5. 正向 — 空结果:keyword="不存在的关键词",返回 data=[] + code=200。
  6. 正向 — keyword 含中英混合:keyword="user 用户" 也走 LIKE '%user 用户%'(与 spec § 业务规则 3 一致;不拆词)。
  7. 同级排序:两根 iSortOrder=2/1,返回顺序 1 在前。
  8. children 字段:叶子节点 children 是 [] 而非 null(jsonPath 断言)。
  9. keyword 长度超限:keyword=51 字符,返回 code=40010
  10. 无登录也可访问(permitAll 阶段):直接 GET 不带 Authorization 返回 200。
  11. 响应字段精简:响应不含 sProcedureName / sModuleType / bShowPermission / 标准列等内部字段(jsonPath 断言)。

接口契约一致性

  • 响应格式 {code, message, data, timestamp}
  • 错误码 200 / 40010 / 50000。
  • 不回显堆栈。

测试覆盖

  • 单元测试 ModuleServiceImplTest 追加(mock ModuleMapper):

    • tree_emptyDb_returnsEmptyList
    • tree_singleRoot_returnsOneNodeWithEmptyChildren
    • tree_multiLevel_buildsNestedStructureSortedByISortOrder
    • tree_keywordHit_includesAncestorChain
    • tree_keywordNoMatch_returnsEmptyList
    • tree_softDeletedExcluded(mapper 已过滤;service 不重复过滤)
  • 集成测试 ModuleControllerIT 追加:

    • get_emptyKeyword_returnsAllUndeletedAsTree
    • get_keyword_filtersByModuleNameZhWithAncestors
    • get_keywordNoMatch_returnsEmptyArray
    • get_keywordTooLong_returns40010
    • get_softDeletedNotInResult
    • get_responseExcludesInternalFields
    • get_leafNodeChildrenIsEmptyArrayNotNull

代码与文档

  • // REQ-MOD-004 注释贴在 Controller / Service / VO。
  • 提交按 feat(mod): <subject> REQ-MOD-004 规范。