配套
report.md(自动生成的用例明细)与pii_report.md(脱敏专项)。 本文对 1 个 FAIL 和 73 个 SKIP 逐类给出根因分析、代码定位与修复建议。 分析基于源码阅读,非推测;关键行号已标注。
| 类别 | 数量 | 是否缺陷 | 结论 |
|---|---|---|---|
| FAIL | 1 | 是(真实缺陷) | service_feedback_history nil 指针 panic |
| SKIP-A 需真实样本参数 | 51 | 否 | 工具有必填参数,本轮无样本,未构造调用 |
| SKIP-B 依赖未初始化 | 约 8 | 是(配置缺陷) | MCP main.go 少调了 FWH / Plane / Queue 的初始化 |
| SKIP-C 数据为空 | 3 | 否 | 生产库当日无对应统计文档 |
| SKIP-D Plane 参数缺失被远端拒 | 3 | 次要(健壮性) | handler 忽略必填校验错误,导致 404 而非清晰报错 |
核心结论:真正需要修的是 MCP Server 的初始化不完整(cmd/mcp/main.go)。它只初始化了 DB/Cache/MinIO/Plane 底层 client,漏掉了 3 处初始化,直接导致 1 个 FAIL + 约 8 个本应 PASS 的 SKIP。
service_feedback_history — nil 指针 panic现象
internal error: panic recovered in service_feedback_history tool handler:
runtime error: invalid memory address or nil pointer dereference
(panic 被 server.WithRecovery() 捕获,进程未崩溃,但该工具完全不可用。)
根因(已定位)
调用链:handleFeedbackHistory → services.GetFeedbackHistory("") → repositories.Plane.ListIntakeIssues(...)。
repositories.Plane 是一个包级全局变量,只有 repositories.InitPlane() 才会给它赋值:
repositories/repositories.go:22,26-29
var Plane *plane_v2.FeedbackPlaneService
// InitPlane 初始化新架构 Plane(启动时调用)
func InitPlane(cfg *config.PlaneWebApiConfig) {
plane_v2.InitPlaneClient(cfg.Host, cfg.Email, cfg.Password)
Plane = plane_v2.NewFeedbackPlaneService(cfg.WorkspaceSlug, cfg.ProjectId, ...)
}
// 通用初始化接口(给MCP Server等外部模块使用)
var InitPlaneClient = plane_v2.InitPlaneClient // 只初始化底层 client,不赋值 Plane
而 MCP 的 cmd/mcp/main.go:48 只调用了 底层 的 InitPlaneClient:
repositories.InitPlaneClient(planeCfg.Host, planeCfg.Email, planeCfg.Password)
没有调用 repositories.InitPlane(...),所以 repositories.Plane 始终为 nil → 空指针解引用 panic。
修复建议(二选一)
方案 A(推荐,最小改动)——在 cmd/mcp/main.go 用 InitPlane 替换 InitPlaneClient:
// 原:repositories.InitPlaneClient(planeCfg.Host, planeCfg.Email, planeCfg.Password)
repositories.InitPlane(&cfg.PlaneWebApi)
InitPlane 内部已包含 InitPlaneClient,同时会给 repositories.Plane 赋值,data_plane_v2_* 与 service_feedback_* 都能正常工作。
方案 B(防御性,二者叠加)——保留底层 init 的同时补充:
repositories.InitPlaneClient(planeCfg.Host, planeCfg.Email, planeCfg.Password)
repositories.InitPlane(&cfg.PlaneWebApi) // 追加,给 Plane 全局赋值
附加健壮性建议:services.GetFeedbackHistory 入口应做 nil 校验,避免未初始化时 panic:
func GetFeedbackHistory(userName string) ([]FeedbackHistoryItem, error) {
if repositories.Plane == nil {
return nil, fmt.Errorf("Plane 未初始化")
}
...
}
这些工具连接失败/URL 非法,根因都是 MCP main.go 缺少初始化,而非依赖服务本身不可用。
http: no Host in request URL受影响用例:data_fwh_query_site_base_info_list、data_fwh_query_site_list、
data_fwh_query_alarm_statistics、data_fwh_query_fault_record_call_record_list、
data_fwh_query_tou_template_list、service_tou_template_list 等(凡走 FWH Pro API)。
现象
Post "https:///hes-gateway/manage/selectIotTouTemplateList": http: no Host in request URL
注意 https:// 后面 host 为空。
根因(已定位)
FWH 客户端的 host 来自包级变量 fwhEnergy,只有 FwhHttpInit 会赋值:
repositories/api/fwh_pro/fwhHttpClient.go:16-31
var fwhEnergy string // 未初始化时为 ""
func FwhHttpInit(cfg *config.FwhApiConfig) {
fwhEnergy = cfg.FwhEnergy // ← 关键赋值
...
}
func getFullEnergyUrl(relativeURL string) string {
return "https://" + fwhEnergy + relativeURL // fwhEnergy="" → "https:///..."
}
HTTP Server 的 main.go:61 有调用 repositories.FwhHttpInit(&cfg.FwhApi),
但 MCP 的 cmd/mcp/main.go 没有调用,因此 fwhEnergy 为空 → URL 无 host。
说明:
config.env里FWH_ENERGY="energy.franklinwh.com"是配置好的, 问题不在配置文件,而在 MCP 启动时没把它注入 FWH 客户端。
修复建议:在 cmd/mcp/main.go 初始化段补一行:
repositories.FwhHttpInit(&cfg.FwhApi)
(若还需测试环境 API,可按需追加 FwhTestApi。)
inspector not initialized受影响用例:service_background_task_list
根因(已定位)
task/queue/fwhQueue/fwhTaskQueue.go:98
return nil, fmt.Errorf("inspector not initialized")
Asynq 的 inspector 需在启动时初始化。MCP main.go 未初始化任务队列 inspector。
修复建议(按需):
main.go 补充任务队列 inspector 的初始化(参考 HTTP Server 启动流程中的队列初始化)。在 cmd/mcp/main.go 的初始化段(现为 database.Init → cache.Init → minioUtil.InitClient → InitPlaneClient)补齐:
repositories.FwhHttpInit(&cfg.FwhApi) // 修复 B-1:FWH API host
repositories.InitPlane(&cfg.PlaneWebApi) // 修复 FAIL-1 + Plane 相关
// (可选)初始化任务队列 inspector,修复 B-2
预计可将 FWH 相关 4~6 个 SKIP、feedback panic、以及后台任务查询一并转为可用。
受影响用例:plane_list_labels、plane_list_states(测试以 {} 调用),返回 404 Not Found。
根因(已定位)
这两个工具在 schema 中把 workspace_slug/project_id 声明为 Required,
但 handler 忽略了必填校验错误:
cmd/mcp/tools/data/plane_v2_api_handlers.go:164-177
func handlePlaneV2ListLabels(...) {
slug, _ := req.RequireString("workspace_slug") // ← 错误被丢弃
projectId, _ := req.RequireString("project_id") // ← 错误被丢弃
data, err := planeV2Svc.ListLabels(slug, projectId) // 用空串请求 → 远端 404
...
}
对比正确写法(如 handlePlaneV2GetWorkspace / handlePlaneV2ListProjects)是检查错误并 return helper.ParamError(...)。
影响:功能上这些工具需要真实 slug/project_id 才能用(属 SKIP-A 类), 但由于未做前置校验,缺参时返回的是含糊的远端 404,而不是清晰的"参数 workspace_slug 必填"。
修复建议:统一补齐必填校验:
slug, err := req.RequireString("workspace_slug")
if err != nil { return helper.ParamError("workspace_slug"), nil }
projectId, err := req.RequireString("project_id")
if err != nil { return helper.ParamError("project_id"), nil }
建议全量排查 plane_v2_api_handlers.go 中所有 := req.RequireString(...) 后紧跟 , _ 丢弃错误的写法。
备注:
plane_list_projects用例本轮同样因缺workspace_slug被判 SKIP, 但它 handler 有正确校验,返回了清晰的"参数 workspace_slug 必填",属正常行为。
受影响用例(约 51 个):所有含必填业务参数的只读工具,例如
service_site_query_info(sn)、service_sim_query(giccid)、
data_pgsql_query_snapshot_data(sn,date)、data_sim_*(iccid)、
plane_get_*(workspace_slug,project_id,...)、storage_minio_read_object(bucket,key) 等。
原因:这些工具需要真实存在的 SN / GICCID / 任务 ID / 文件名 / Plane UUID 才能构造有意义的调用;本轮测试无这些样本,驱动主动 SKIP(避免用假值产生无意义的错误)。
这不是缺陷。补测方法:提供样本后运行驱动的样本模式(见"如何补测")。这些工具的参数校验分支已在 L1 层抽样验证正确(缺参返回 参数 xx 必填)。
受影响用例:service_monitor_alarm_stats、service_dashboard_alarm_detail(mongo: no documents in result)。
原因:生产库 StatisticsAlarm 集合当日/最新无文档。工具行为正确(查不到返回错误/空),只是恰好无数据。
service_monitor_shutdown_stats、service_dashboard_shutdown_detail 同类工具有数据、已 PASS,可佐证逻辑正常。
建议(可选):对"无文档"场景,services 层可返回空结果 + 提示(helper.EmptyResult)而非 error,语义更友好;当前作为 error 也可接受。
| 优先级 | 问题 | 修复位置 | 预期收益 |
|---|---|---|---|
| P0 | FAIL-1 feedback panic | cmd/mcp/main.go 改用 InitPlane |
消除 panic,Plane/feedback 可用 |
| P0 | B-1 FWH host 为空 | cmd/mcp/main.go 补 FwhHttpInit |
恢复所有 data_fwh_* / TOU 远程查询 |
| P1 | SKIP-D Plane handler 忽略必填校验 | plane_v2_api_handlers.go 补错误检查 |
缺参时清晰报错而非 404 |
| P2 | B-2 后台任务 inspector | cmd/mcp/main.go 补队列初始化 或 友好提示 |
后台任务查询可用/语义清晰 |
| P2 | GetFeedbackHistory nil 防御 | services/feedbackServices.go |
纵深防御 |
| P3 | SKIP-C 空数据语义 | services 统计相关 |
语义友好(可选) |
一次性关键修复(cmd/mcp/main.go,覆盖 P0):
// 现有:
database.Init(&cfg.App)
cache.Init(cfg.App.RedisAddr)
minioUtil.InitClient(&cfg.App)
repositories.InitPlaneClient(planeCfg.Host, planeCfg.Email, planeCfg.Password)
// 建议调整为:
database.Init(&cfg.App)
cache.Init(cfg.App.RedisAddr)
minioUtil.InitClient(&cfg.App)
repositories.FwhHttpInit(&cfg.FwhApi) // 新增:FWH API host
repositories.InitPlane(&cfg.PlaneWebApi) // 替换:同时初始化 Plane 全局实例
main.go 修复并重新构建:go build -o fwh-mcp-server ./cmd/mcp/go run ./cmd/mcp/test/runner -bin <新二进制> -cwd /var/www/fwh-toolbox/server -out ./cmd/mcp/testrunL2 的 preset 段),将 SKIP-A 用例转为真实冒烟。已应用 P0 + P1 修复并重跑驱动,结果对比:
| 指标 | 修复前 | 修复后 |
|---|---|---|
| PASS | 50 | 57 |
| FAIL | 1 | 0 |
| SKIP | 73 | 67 |
已应用的修复:
cmd/mcp/main.go:新增 repositories.FwhHttpInit(&cfg.FwhApi);将 InitPlaneClient 替换为 repositories.InitPlane(&cfg.PlaneWebApi)。services/feedbackServices.go:GetFeedbackHistory / SubmitFeedback 增加 repositories.Plane == nil 防御。cmd/mcp/tools/data/plane_v2_api_handlers.go:新增 requireSlugProject 辅助函数,修复 15 个 handler 忽略 workspace_slug/project_id 必填校验的问题。验证要点:
service_feedback_history → PASS(返回 506B 合法结果)。data_fwh_query_site_list、data_fwh_query_tou_template_list、service_tou_template_list 等 FWH 远程查询 → PASS。plane_list_labels/states/projects 缺参时返回清晰的"参数 workspace_slug 必填",不再是 404。service_background_task_list(B-2,inspector 未初始化,属 P2 待定项,本次未改)。使用用户提供的样本(SN 10130095A00X25280030、GICCID 8931084524121848556、
Plane workspace fwh / project 55efdd5f-...)补测原 SKIP-A 用例,并做 MinIO 写 + 文件读闭环。
结果:149 用例 PASS 82 / FAIL 0 / SKIP 67(较上一轮 +25 用例、+25 PASS)。
storage_file_write(写入 MinIO)→ storage_file_list →
storage_file_read(读回内容一致)→ storage_minio_list_objects(确认对象落盘)。BUG-2 data_fwh_query_system_update_log 反序列化失败
json: cannot unmarshal number into Go struct field SystemUpdateRecordInfo.result.upgradeType of type stringdomain/fwhProDomain.go 中 SystemUpdateRecordInfo.UpgradeType 声明为 string,
但 FWH 上游 API 对该字段实际返回 number,导致整个响应反序列化失败、工具不可用。UpgradeType string 改为 UpgradeType int(上游该字段返回数值,用数值类型更明确、类型安全;该字段仅透传输出、无消费方,改动安全)。data_fwh_query_system_update_log → PASS。说明:该缺陷仅在特定 SN(
upgradeType为数字)时触发,第一轮因无样本被 SKIP 掩盖, 说明真实样本补测对发现"数据形态相关"的反序列化 bug 很有价值。建议排查同族结构 (如AHubUpdateRecordInfo、SystemUpdateDetailInfo等)是否也存在 number/string 混用字段。
在样本轮基础上新增两阶段自动补测,把"有必填参数但可从已 PASS 接口派生"的工具全部覆盖,无需额外外部样本。
jsonExtract(支持 data.list[0].iccid 路径)提取第一个 id/key/name,再喂给详情接口。覆盖:
auto_update get_package/get_config、tou detail(service + data_fwh)、site_analysis get_config/data、
batch_download path、site_monitor config_get、acceleronix card_info/data_pool_sim_info、
minio read_object(正确的大小校验判 PASS)、plane get_intake_issue。累计结果:173 用例 PASS 101 / FAIL 0 / SKIP 72(可补测工具已全部覆盖)。
排除各阶段已 PASS 后,剩 23 个工具,全部无法靠测试手段自行解决:
service_background_task_list 需在 MCP main.go 初始化 asynq inspector。每个剩余 SKIP 的"缺什么/如何补"已在
report.md的「SKIP 逐条分析」章节动态列出。
service_site_search/service_sim_search/data_mongo_find 返回均正确脱敏(account/手机/地址/经纬度/IMEI/IMSI/deviceIp 均 ******),保留字段(sn/giccid/_id)正常;helper 单测 21 项全 PASS。脱敏是本次测试重点,结论为安全达标。