Files
xiaoqu_mcp_service/xiaoqu-agent-service/doc/warehouse-stock-mcp-tool-design.md
T

11 KiB
Raw Blame History

库存物品分页查询 MCP 工具详细设计

需求:REQ-20260915-0011 工具:warehouse.stock.list.query 对齐接口:POST /warehouseStatis/listByPage 工具属性:warehouse / read 数据源:m2 warehouse_stock_manage

1. 背景与目标

老系统库存物品列表只能通过页面接口查询,Agent 无法在统一鉴权、工具作用域和审计体系下读取库存。 本需求将列表查询封装为只读 MCP 工具,使 Agent 能按仓库、所属公司和物品属性分页检索库存,并保持 老接口的代理商行级权限语义。

目标如下:

  1. 暴露稳定、可发现的库存分页查询契约。
  2. 保持总部与代理商的数据可见范围和老接口一致。
  3. 返回库存业务白名单字段,并补充可用库存派生值。
  4. 限制单次查询规模,避免 Agent 发起无界读取。

2. 范围与非目标

2.1 本期范围

  • 新增 warehouse.stock.list.query MCP 工具。
  • 新增 WarehouseStockManage 只读映射和 WarehouseStockManageMapper。
  • 在 PermissionService 新增库存专用行级权限过滤。
  • 支持组合筛选、分页、稳定排序、库存空值归零和可用库存计算。
  • 补充工具清单、详细设计和单元测试。

2.2 非目标

  • 不实现老系统 /warehouseStatis/statisticsQuery 的全量聚合;如需要,另建 warehouse.stock.summary.query 并单独评估权限和查询成本。
  • 不新增、扣减、预占或校正库存,不修改任何业务表。
  • 不返回库存变更流水、入出库单详情或 SN 级资产明细。
  • 不改变老系统接口行为,也不复用其 HTTP Controller。

3. 总体设计

调用链如下:

MCP Client
  -> Token scope 校验
  -> WarehouseStockTools.warehouseStockListQuery
  -> 规范化分页和筛选参数
  -> PermissionService.applyWarehouseStockRowFilter
  -> WarehouseStockManageMapper.selectCount/selectList
  -> 字段白名单映射及派生值计算
  -> total/page/rows/notice/records

工具直接查询库存快照表,避免跨服务调用和 N+1 富化。仓库、公司、物品名称等均使用表内快照字段, 因此结果反映库存记录最近一次维护时的业务快照。

4. 工具契约

4.1 输入参数

参数 类型 必填 默认值 规则 数据库条件
warehouseIds array<long> 否 - 空数组视为不筛选 warehouse_id IN (...)
warehouseOwnerId long 否 - 精确匹配仓库所属公司 warehouse_owner_id = ?
materialName string 否 - 去除首尾空格;空白不筛选 materiel_full_name LIKE
materialNo string 否 - 去除首尾空格;空白不筛选 material_no LIKE
materialCategoryIds array<long> 否 - 空数组视为不筛选 materiel_index_id IN (...)
specifications string 否 - 去除首尾空格;空白不筛选 specifications LIKE
page integer 否 1 小于 1 时归一为 1 SQL offset
rows integer 否 20 小于 1 时归一为 20;大于 100 截断为 100 SQL limit

所有业务筛选条件为 AND 关系;行级权限条件作为一个括号分组追加。查询始终包含 soft_delete = 0。

4.2 返回结构

顶层字段:

字段 类型 说明
total long 权限及筛选条件命中的总记录数
page integer 归一化后的当前页码
rows integer 实际采用的每页条数
notice string 仅在 rows > 100 时返回截断提示
records array<object> 当前页库存记录;无数据时为空数组

records 字段白名单:

字段 来源/计算 说明
id id 库存记录 ID
warehouseId / warehouseNo / warehouseName 同名仓库快照列 仓库信息
agentId / agentName 管理公司快照列 库存管理公司
warehouseOwnerId / warehouseOwnerName 所属公司快照列 仓库所属公司
materialDefinitionId materiel_definition_id 物料定义 ID
materialNo / materialName material_no / materiel_full_name 物品编号和名称
materialCategoryId / materialCategory materiel_index_id / materiel_sort 分类 ID 和分类快照
specifications / snCode 同名列 规格和库存表 SN 快照
currentInventory COALESCE(current_inventory, 0) 当前库存
outOfStock COALESCE(out_of_stock, 0) 出库中/预占库存
availableInventory currentInventory - outOfStock 可用库存;允许如实返回负数以暴露异常
batteryInventory COALESCE(battery_inventory, 0) 电池费用化库存
updatedAt modify_time 上海时区 yyyy-MM-dd HH:mm:ss;无值返回 null

不返回 create_id、create_time、modify_id、soft_delete 等内部维护字段。

4.3 排序与分页

  • 固定按 current_inventory DESC, id DESC 排序。
  • id DESC 作为同库存量下的稳定次序,减少翻页时顺序不确定。
  • offset 使用 (page - 1) * rows,计算过程使用 long,避免 int 乘法溢出。
  • count 与列表复用同一组过滤条件;排序和 limit 仅追加到列表查询。

返回示例:

{
  "total": 1,
  "page": 1,
  "rows": 20,
  "records": [
    {
      "id": 901,
      "warehouseId": 8,
      "warehouseNo": "WH-008",
      "warehouseName": "一号仓",
      "agentId": 10,
      "agentName": "管理公司A",
      "warehouseOwnerId": 10,
      "warehouseOwnerName": "所属公司A",
      "materialDefinitionId": 31,
      "materialNo": "MAT-031",
      "materialName": "擦手纸",
      "materialCategoryId": 3,
      "materialCategory": "纸品",
      "specifications": "A型",
      "snCode": null,
      "currentInventory": 20,
      "outOfStock": 4,
      "availableInventory": 16,
      "batteryInventory": 0,
      "updatedAt": "2026-09-15 17:30:00"
    }
  ]
}

5. 行级权限设计

权限对齐老系统 WrapperUtils.getWarehouseAuth,但在 Agent 服务中独立实现,避免依赖老系统静态工具。

当前身份 可见范围 SQL 行过滤
总部(isagent=0) 全部未删除库存 不追加公司过滤
代理(isagent=1) 本公司,以及当前用户在 warehouse_agent_auth 中获授权代管的公司 agent_id IN (ids) OR warehouse_owner_id IN (ids)
代理但 user.agent_id 为空 无 fail closed,抛出拒绝访问异常

授权公司集合计算方式:

visibleAgentIds = { currentUser.agentId }
visibleAgentIds += SELECT manager_agent_id
                   FROM warehouse_agent_auth
                   WHERE user_id = currentUser.id AND soft_delete = 0

最终条件必须保留括号:

AND (agent_id IN (:visibleAgentIds)
     OR warehouse_owner_id IN (:visibleAgentIds))

直属下级代理不会因层级关系自动加入;只有本公司和显式 warehouse_agent_auth 授权公司可见。这一点 与老接口实际实现一致。MCP 层还会先校验 Token 是否绑定该工具 scope,行级权限不能替代 scope 校验。

6. 查询与数据处理

逻辑伪代码:

pageNo = page is null or page < 1 ? 1 : page
pageSize = rows is null or rows < 1 ? 20 : min(rows, 100)

conditions = [soft_delete = 0]
append non-empty business filters
append warehouse stock row-level permission

total = count(conditions)
records = select(conditions)
          order by current_inventory desc, id desc
          limit offset, pageSize

map records to whitelist fields
normalize nullable inventory values to 0
calculate availableInventory

字符串仅在 StringUtils.hasText 时参与查询,并使用 MyBatis-Plus 参数绑定生成 LIKE 条件;ID 集合为空时 不生成 IN ()。分页 limit 只使用规范化后的整数计算结果。

7. 异常、边界与一致性

  • 未传筛选条件:在权限范围内正常分页,不执行全量无分页返回。
  • 页码超出末页:total 保持真实值,records=[]。
  • rows > 100:成功返回,实际 rows=100,同时返回 notice。
  • 库存列为 null:按 0 输出;不回写数据库。
  • availableInventory < 0:不钳制,保留原始业务异常信号。
  • 权限基础数据缺失:代理用户缺少公司 ID 时拒绝查询,不能降级为总部权限。
  • 查询期间库存发生变化:count 和 list 可能反映不同瞬时快照;工具定位为运营查询,不提供事务级快照保证。

8. 性能与安全

  • 单页上限 100,避免大结果占用数据库、MCP 序列化和模型上下文。
  • 查询仅访问单表,无远程调用、循环查询或逐条富化。
  • 所有筛选值由查询构造器参数化;仅 limit 使用归一化后的数值拼接。
  • 固定过滤软删数据,输出采用显式白名单。
  • 典型过滤列为 warehouse_id、warehouse_owner_id、materiel_index_id;现有生产数据量较小时无需 本需求新增索引。若慢查询监控显示退化,再依据执行计划单独提交索引变更。

9. 代码改动

模块 文件/组件 职责
entity WarehouseStockManage m2 库存表字段映射
mapper WarehouseStockManageMapper MyBatis-Plus 单表查询
tool WarehouseStockTools MCP 契约、参数处理、分页及返回映射
permission PermissionService.applyWarehouseStockRowFilter 库存专用行级权限
test WarehouseStockToolsTest 筛选、分页、排序、映射和 schema
test PermissionServiceWarehouseStockRowFilterTest 总部、代理授权和 fail-closed
docs README、工具参考、本文 工具发现和维护说明

10. 测试与验收

10.1 自动化测试

  1. 组合筛选生成仓库、所属公司、名称、编号、分类和规格条件。
  2. page < 1 归一为 1,rows > 100 截断并产生 notice。
  3. 固定排序包含 current_inventory DESC, id DESC,limit 正确。
  4. 可用库存计算正确,电池库存 null 输出 0,时间按上海时区格式化。
  5. MCP schema 发布工具名及全部输入参数。
  6. 总部不追加权限过滤,也不查询代管授权。
  7. 代理权限覆盖本公司和代管公司,并同时作用于管理公司、所属公司两列。
  8. 代理用户缺少 agentId 时 fail closed。

10.2 验收标准

  • tools/list 可发现 warehouse.stock.list.query,分类为 warehouse、类型为 read。
  • 有 scope 的总部 Token 可分页查询全部未删除库存,并正确使用所有筛选条件。
  • 代理 Token 不能读取权限集合之外公司的库存;授权代管后可读取对应记录。
  • 返回字段、空值处理、可用库存公式、排序和分页符合第 4 节契约。
  • rows=101 时按 100 返回并带提示;空结果返回空数组。
  • 工具调用不产生库存及其他业务数据写入。
  • 相关单测和项目全量 Java 测试通过。

11. 发布与回滚

发布后由工具同步机制注册元数据;新工具默认未绑定 Token scope,管理员需显式授权后才可调用。 上线验证依次检查 tools/list、总部样例查询、代理权限正反例和分页上限。

如需回滚,先解除 Token 与工具 scope 的绑定以立即停止外部调用,再回滚服务版本。由于本需求无 DDL、 无数据迁移且全程只读,不需要业务数据回滚。