| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- package tools
- import (
- "context"
- "fmt"
- "sort"
- "strings"
- "time"
- "unicode/utf8"
- "github.com/mark3labs/mcp-go/mcp"
- "fwh-toolbox-server/agentmcp/kit"
- "fwh-toolbox-server/domain"
- "fwh-toolbox-server/repositories"
- "fwh-toolbox-server/services"
- )
- func parseCountry(v string) (domain.CountryType, error) {
- c, err := kit.Enum("country", v, "all", "all", "us", "au")
- if err != nil {
- return 0, err
- }
- switch c {
- case "us":
- return domain.CTRY_US, nil
- case "au":
- return domain.CTRY_AU, nil
- }
- return domain.CTRY_ALL, nil
- }
- // ---- alarm_current ----
- type snArgs struct {
- SN string `json:"sn" jsonschema:"站点 SN(aGate 序列号)"`
- }
- var AlarmCurrent = kit.Def[snArgs]{
- Name: "alarm_current",
- Title: "查询站点当前告警",
- Description: "查询单个站点当前未结束的告警列表(FWH 平台实时告警)。" +
- "返回字段:alarmEqSn(告警设备 SN)、alarmCode / commonAlarmCode(告警码)、level(等级)、time(发生时间)、logName / logNameEn(告警名称)。",
- ReadOnly: true,
- Handler: func(ctx context.Context, a snArgs) (*mcp.CallToolResult, error) {
- sn, err := kit.Required("sn", a.SN)
- if err != nil {
- return kit.Fail(err), nil
- }
- res, err := repositories.QuerySiteAlarmList(sn)
- if err != nil {
- return kit.Upstream("FWH 平台实时告警接口", err), nil
- }
- if res == nil || res.Result == nil || len(*res.Result) == 0 {
- return kit.Empty("该站点当前没有未结束的告警。"), nil
- }
- return kit.List(*res.Result, -1, 200, ""), nil
- },
- }
- // ---- alarm_events ----
- type alarmEventsArgs struct {
- SN string `json:"sn" jsonschema:"站点 SN(aGate 序列号)"`
- From string `json:"from" jsonschema:"开始时间,yyyy-MM-dd HH:mm:ss,站点当地时间"`
- To string `json:"to" jsonschema:"结束时间,同上;与开始时间相差不超过 31 天"`
- Kind string `json:"kind,omitempty" jsonschema:"alarm(只看告警)/ event(只看事件)/ all(默认)"`
- Limit int `json:"limit,omitempty" jsonschema:"最多返回条数,1~500,默认 100"`
- }
- // EventRow alarm_events 的精简行
- type EventRow struct {
- Kind string `json:"kind"` // 告警 / 事件
- LogTime string `json:"logTime"` // 设备时间
- StartTime string `json:"startTime,omitempty"`
- EndTime string `json:"endTime,omitempty"`
- Code string `json:"code,omitempty"`
- Name string `json:"name,omitempty"`
- Cmd string `json:"cmd,omitempty"`
- Data string `json:"data,omitempty"` // 原始 logData,超过 500 字截断
- }
- var AlarmEvents = kit.Def[alarmEventsArgs]{
- Name: "alarm_events",
- Title: "查询站点告警/事件记录",
- Description: "查询单个站点在一段时间内的告警和事件记录(FWH 平台日志),按时间倒序。用于排查某次故障前后发生了什么。" +
- "返回字段:kind(告警/事件)、logTime(设备当地时间)、startTime、endTime(告警结束时间,未结束为空)、code(告警码,事件为 0)、name、cmd、data(原始日志数据,超过 500 字截断)。" +
- "超过 limit 时 truncated=true,请缩短时间范围。时间范围较大时查询较慢。",
- ReadOnly: true,
- Handler: func(ctx context.Context, a alarmEventsArgs) (*mcp.CallToolResult, error) {
- sn, err := kit.Required("sn", a.SN)
- if err != nil {
- return kit.Fail(err), nil
- }
- from, to, err := kit.ParseRange(a.From, a.To, 31*24*time.Hour)
- if err != nil {
- return kit.Fail(err), nil
- }
- kind, err := kit.Enum("kind", a.Kind, "all", "all", "alarm", "event")
- if err != nil {
- return kit.Fail(err), nil
- }
- t := repositories.EventAndAlarm
- switch kind {
- case "alarm":
- t = repositories.Alarm
- case "event":
- t = repositories.Event
- }
- items, err := repositories.QueryEventOrAlarmListOnTimeRange(sn, from, to, t)
- if err != nil {
- return kit.Upstream("FWH 平台事件告警日志接口", err), nil
- }
- rows := MapEventRows(items)
- return kit.List(rows, -1, kit.Limit(a.Limit, 100, 500), "结果已截断,请缩短时间范围或改用 kind 过滤"), nil
- },
- }
- // MapEventRows 转为精简行并按设备时间倒序。
- func MapEventRows(items []domain.EventAlarmItem) []EventRow {
- rows := make([]EventRow, 0, len(items))
- for _, it := range items {
- r := EventRow{
- Kind: "事件",
- LogTime: it.LogTime,
- StartTime: it.StartTime,
- Code: it.AlarmCode,
- Name: it.LogName,
- Cmd: strings.TrimSpace(it.CmdName + " " + it.CmdType),
- Data: clip(it.LogData, 500),
- }
- if it.LogType == "1" {
- r.Kind = "告警"
- }
- if it.EndTime != nil {
- r.EndTime = *it.EndTime
- }
- if r.Code == "0" {
- r.Code = ""
- }
- rows = append(rows, r)
- }
- sort.SliceStable(rows, func(i, j int) bool { return rows[i].LogTime > rows[j].LogTime })
- return rows
- }
- func clip(s string, n int) string {
- if utf8.RuneCountInString(s) <= n {
- return s
- }
- return string([]rune(s)[:n]) + "…"
- }
- // ---- alarm_snapshot ----
- type alarmSnapshotArgs struct {
- SN string `json:"sn" jsonschema:"站点 SN(aGate 序列号)"`
- Date string `json:"date" jsonschema:"日期,yyyy-MM-dd"`
- }
- var AlarmSnapshot = kit.Def[alarmSnapshotArgs]{
- Name: "alarm_snapshot",
- Title: "查询站点告警快照",
- Description: "查询单个站点某一天的告警快照记录(告警发生时设备上传的快照文件信息)。" +
- "返回字段:id、fileName、fileUrl、时间等(以实际返回为准)。",
- ReadOnly: true,
- Handler: func(ctx context.Context, a alarmSnapshotArgs) (*mcp.CallToolResult, error) {
- sn, err := kit.Required("sn", a.SN)
- if err != nil {
- return kit.Fail(err), nil
- }
- day, err := kit.ParseDate("date", a.Date, false)
- if err != nil {
- return kit.Fail(err), nil
- }
- items, err := repositories.QueryAlarmSnapshotInfo(sn, day.Format(kit.DateLayout))
- if err != nil {
- return kit.Upstream("FWH 平台告警快照接口", err), nil
- }
- return kit.List(items, -1, 100, ""), nil
- },
- }
- // ---- alarm_stats ----
- type alarmStatsArgs struct {
- Source string `json:"source,omitempty" jsonschema:"daily(默认):ToolBox 每日告警统计,按告警码汇总站点数;realtime:FWH 平台当前告警统计"`
- Date string `json:"date,omitempty" jsonschema:"daily 时的统计日期 yyyy-MM-dd(北京时间),不填取最新"`
- Country string `json:"country,omitempty" jsonschema:"realtime 时的国家:all(默认)、us(美国)、au(澳大利亚)"`
- Page int `json:"page,omitempty" jsonschema:"realtime 时的页码,默认 1"`
- Top int `json:"top,omitempty" jsonschema:"daily 时返回告警码数量最多的前 N 个,1~200,默认 50"`
- }
- // CodeCount 告警码计数
- type CodeCount struct {
- Code string `json:"code"`
- Count uint `json:"count"`
- }
- var AlarmStats = kit.Def[alarmStatsArgs]{
- Name: "alarm_stats",
- Title: "查询全站告警统计",
- Description: "查询全量站点的告警统计。source=daily(默认):ToolBox 每日统计,返回 date、siteStatistics(有告警的站点数)、codes(按触发站点数倒序的告警码列表 code/count);" +
- "source=realtime:FWH 平台当前告警统计(按国家、分页),返回 total 与 records。" +
- "查看某个告警码涉及哪些站点用 alarm_sites_by_code。",
- ReadOnly: true,
- Handler: func(ctx context.Context, a alarmStatsArgs) (*mcp.CallToolResult, error) {
- src, err := kit.Enum("source", a.Source, "daily", "daily", "realtime")
- if err != nil {
- return kit.Fail(err), nil
- }
- if src == "realtime" {
- country, err := parseCountry(a.Country)
- if err != nil {
- return kit.Fail(err), nil
- }
- res, err := repositories.QueryAlarmStatistics(country, kit.Page(a.Page))
- if err != nil {
- return kit.Upstream("FWH 平台告警统计接口", err), nil
- }
- if res == nil || res.Data == nil {
- return kit.Empty(""), nil
- }
- return kit.List(res.Data.Records, res.Data.Total, 200, fmt.Sprintf("共 %d 页,可用 page 翻页", res.Data.PageTotal)), nil
- }
- day, err := kit.ParseDate("date", a.Date, true)
- if err != nil {
- return kit.Fail(err), nil
- }
- var datePtr *string
- if day != nil {
- s := day.Format(kit.DateLayout)
- datePtr = &s
- }
- dash, err := services.GetAlarmStatistics(datePtr)
- if err != nil {
- return kit.Upstream("ToolBox 告警统计", err), nil
- }
- if dash == nil {
- return kit.Empty("没有该日期的告警统计。"), nil
- }
- codes := SortCodeCounts(dash.CodeStatistics)
- top := kit.Limit(a.Top, 50, 200)
- total := len(codes)
- if len(codes) > top {
- codes = codes[:top]
- }
- return kit.Object(map[string]any{
- "date": dash.Date,
- "siteStatistics": dash.SiteStatistics,
- "codeTotal": total,
- "codes": codes,
- }), nil
- },
- }
- // SortCodeCounts 按计数倒序、告警码升序排列。
- func SortCodeCounts(m map[string]uint) []CodeCount {
- out := make([]CodeCount, 0, len(m))
- for k, v := range m {
- out = append(out, CodeCount{Code: k, Count: v})
- }
- sort.Slice(out, func(i, j int) bool {
- if out[i].Count != out[j].Count {
- return out[i].Count > out[j].Count
- }
- return out[i].Code < out[j].Code
- })
- return out
- }
- // ---- alarm_sites_by_code ----
- type alarmSitesByCodeArgs struct {
- Code string `json:"code" jsonschema:"告警码"`
- Country string `json:"country,omitempty" jsonschema:"国家:all(默认)、us(美国)、au(澳大利亚)"`
- Page int `json:"page,omitempty" jsonschema:"页码,默认 1"`
- }
- var AlarmSitesByCode = kit.Def[alarmSitesByCodeArgs]{
- Name: "alarm_sites_by_code",
- Title: "查询触发某告警码的站点",
- Description: "查询当前触发指定告警码的站点列表(FWH 平台实时告警,按国家、分页)。返回 total 与 items(站点 SN、告警时间等,以实际返回为准)。",
- ReadOnly: true,
- Handler: func(ctx context.Context, a alarmSitesByCodeArgs) (*mcp.CallToolResult, error) {
- code, err := kit.Required("code", a.Code)
- if err != nil {
- return kit.Fail(err), nil
- }
- country, err := parseCountry(a.Country)
- if err != nil {
- return kit.Fail(err), nil
- }
- res, err := repositories.QueryAlarmStatisticsFromCode(country, code, kit.Page(a.Page))
- if err != nil {
- return kit.Upstream("FWH 平台告警统计接口", err), nil
- }
- if res == nil || res.Data == nil {
- return kit.Empty(""), nil
- }
- return kit.List(res.Data.Records, res.Data.Total, 200, fmt.Sprintf("共 %d 页,可用 page 翻页", res.Data.PageTotal)), nil
- },
- }
|