Files
xiaoqu_mcp_service/xiaoqu-agent-service/doc/electricity-message-mcp-tool-design.md

5.1 KiB
Raw Permalink Blame History

设备电量消息记录 MCP 工具设计

需求:REQ-20260921(hardware.electricity.message.list.query)

背景

老系统 BatteryAnalyseController.batteryAnalyseList(xiaoqu-franchisee :63-150)消费 ES 索引 electricitymessage_yyyyMM(按月分索引,索引名 = "electricitymessage_" + 开始时间月份 yyyyMM), 文档结构 ElectricAndSignAndNetworkLogEntity:id、createTime、snCode、districtId、propertyId、 agentId、messageType(0电量/1信号/2温度/3充电循环次数/4电压/5电流)、value(六类共用 Integer)。

老接口的语义与坑:

  1. 只按开始时间的月份选索引,跨月范围静默丢数据;前端限制只能查一天,后端没校验。
  2. snCode 用 matchQuery(BaseSearchSupportImpl2 type=0 非数值分支),经 standard analyzer 分词后 AB-123 会被切成 ab/123,存在误配风险。
  3. messageType 默认值 0:不传参数时查询语义被默认值悄悄变成"只查电量",存在歧义。
  4. 接口完全无权限校验(鉴权代码被注释掉)。
  5. scroll 拉全量后在内存里等间隔抽样(count>300 时 i%limit>0 continue),导出接口同样无分页。
  6. 查询异常被 catch 吞掉,返回空结果。

设计决策(用户拍板)

决策 说明
不做任何权限校验 对齐老系统;访问控制由 MCP ak/scope 层兜底
只使用 esLocal 客户端 索引只建在本地集群,不走 esCloud;Gateway 只注入 esLocal bean
全量分页返回 不做老系统的内存抽样;from+size 分页,一次请求一次查询(track_total_hits 取 total)
snCode 用 match_phrase 精确短语匹配,修掉 standard analyzer 分词误配坑
单日强校验 startTime/endTime 必须为同一天(yyyy-MM-dd 部分相等),跨天直接抛 IllegalArgumentException;startTime 不得晚于 endTime;时间格式严格 yyyy-MM-dd HH:mm:ss(ResolverStyle.STRICT)
不传 messageType = 查全部类型 不加 term 过滤;与老系统默认值 0 的歧义语义不同,是刻意的显式设计
ES 异常 fail fast 不复制老系统 catch 吞异常返回空的语义,抛 IllegalStateException 由 GuardedToolCallback 收敛

参数契约

  • snCode:String,必填非空,match_phrase 精确短语匹配。
  • startTime / endTime:String,必填,严格 yyyy-MM-dd HH:mm:ss;两者必须为同一天,startTime ≤ endTime。
  • messageType:Integer,可选,0-5(0电量/1信号/2温度/3充电循环次数/4电压/5电流),越界拒绝;不传=全部类型。
  • page:Integer,默认 1(<1 归一为 1)。
  • rows:Integer,默认 500、上限 1000;>1000 截断为 1000 并在 notice 提示(与项目现有工具惯例一致)。

索引名:electricitymessage_ + startTime 月份(yyyyMM),与老系统取法一致但跨天已被拒绝, 因此不存在跨月静默丢数据问题。

查询构造

  • bool filter:match_phrase(snCode);messageType 传了才 term(messageType);range(createTime) gte/lte 闭区间。
  • 排序:createTime ASC(与老系统一致)。
  • track_total_hits=true,total 取精确值。
  • 深分页防御:from+size 不得超 ES max_result_window(10000)。from ≥ 10000 时截断为窗口内最后可用完整页; from < 10000 但 from+size > 10000 时截断本页条数;两种情况都在 notice 说明。

返回契约

snCode / index / total / page / rows / notice? / records[]
record: createTime / messageType / messageTypeText / value
  • createTime:yyyy-MM-dd HH:mm:ss;ES 文档若存 epoch 毫秒数则按 Asia/Shanghai 归一格式化。
  • messageTypeText:服务端映射 0电量/1信号/2温度/3充电循环次数/4电压/5电流;未知/空为 null。
  • value:原始 Integer 原样返回,不加单位标注(六类语义各自的单位老系统未定义,不做推断)。
  • rows 返回截断后的生效值;多条 notice 用";"拼接。

代码结构

  • es/ElectricityMessageEsGateway:接口,月索引名作为参数传入,保持可 mock;内嵌 Result/Record。
  • es/ElectricityMessageEsGatewayImpl:只注入 @Qualifier("esLocalClient");ES 异常抛 IllegalStateException。
  • mcp/tool/ElectricityMessageTools:@XqTool(category="hardware", kind="read") + @Tool(name="hardware.electricity.message.list.query");参数校验、索引名构造、分页与窗口截断、notice 拼接、文案映射。

验证

  • ElectricityMessageToolsTest(mock Gateway):跨天/倒置/非法格式时间、空 snCode、非法 messageType 拒绝且零 Gateway 交互; 默认分页与 rows 截断 notice;from+size 透传;max_result_window 整页/部分截断 notice;索引月份;返回映射含 messageTypeText;Spring AI schema。
  • ElectricityMessageEsGatewayImplTest(mock ElasticsearchClient,捕获请求构造 lambda 断言 SearchRequest): match_phrase/term 可选/range 闭区间/createTime ASC 排序/from+size/track_total_hits;命中映射含 epoch 毫秒 createTime 归一;ES 异常包装。
  • 全量 Maven 测试确认无回归。