11 KiB
库存物品分页查询 MCP 工具详细设计
需求:
REQ-20260915-0011工具:warehouse.stock.list.query对齐接口:POST /warehouseStatis/listByPage工具属性:warehouse/read数据源:m2warehouse_stock_manage
1. 背景与目标
老系统库存物品列表只能通过页面接口查询,Agent 无法在统一鉴权、工具作用域和审计体系下读取库存。 本需求将列表查询封装为只读 MCP 工具,使 Agent 能按仓库、所属公司和物品属性分页检索库存,并保持 老接口的代理商行级权限语义。
目标如下:
- 暴露稳定、可发现的库存分页查询契约。
- 保持总部与代理商的数据可见范围和老接口一致。
- 返回库存业务白名单字段,并补充可用库存派生值。
- 限制单次查询规模,避免 Agent 发起无界读取。
2. 范围与非目标
2.1 本期范围
- 新增
warehouse.stock.list.queryMCP 工具。 - 新增
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 自动化测试
- 组合筛选生成仓库、所属公司、名称、编号、分类和规格条件。
page < 1归一为 1,rows > 100截断并产生 notice。- 固定排序包含
current_inventory DESC, id DESC,limit 正确。 - 可用库存计算正确,电池库存 null 输出 0,时间按上海时区格式化。
- MCP schema 发布工具名及全部输入参数。
- 总部不追加权限过滤,也不查询代管授权。
- 代理权限覆盖本公司和代管公司,并同时作用于管理公司、所属公司两列。
- 代理用户缺少
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、 无数据迁移且全程只读,不需要业务数据回滚。