2025-05-13 09:29:13 +08:00
|
|
|
|
package handlers
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2025-12-01 12:42:51 +08:00
|
|
|
|
"net/netip"
|
|
|
|
|
|
"platform/pkg/env"
|
2025-12-22 17:31:31 +08:00
|
|
|
|
"platform/web/auth"
|
2025-12-05 18:57:52 +08:00
|
|
|
|
"platform/web/core"
|
2025-12-22 17:31:31 +08:00
|
|
|
|
"platform/web/globals"
|
2025-12-01 12:42:51 +08:00
|
|
|
|
s "platform/web/services"
|
2025-05-24 12:37:16 +08:00
|
|
|
|
"time"
|
2025-05-13 18:47:30 +08:00
|
|
|
|
|
2025-05-27 15:08:18 +08:00
|
|
|
|
"github.com/gofiber/fiber/v2"
|
2025-05-13 09:29:13 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-01 12:42:51 +08:00
|
|
|
|
func DebugRegisterProxyBaiYin(c *fiber.Ctx) error {
|
|
|
|
|
|
if env.RunMode != env.RunModeDev {
|
|
|
|
|
|
return fiber.ErrNotFound
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 18:57:52 +08:00
|
|
|
|
err := s.Proxy.RegisterBaiyin("1a:2b:3c:4d:5e:6f", netip.AddrFrom4([4]byte{127, 0, 0, 1}), "test", "test")
|
2025-12-01 12:42:51 +08:00
|
|
|
|
if err != nil {
|
2025-12-05 18:57:52 +08:00
|
|
|
|
return core.NewServErr("注册失败", err)
|
2025-12-01 12:42:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-22 17:31:31 +08:00
|
|
|
|
// 注册白银代理网关
|
|
|
|
|
|
func ProxyRegisterBaiYin(c *fiber.Ctx) error {
|
|
|
|
|
|
_, err := auth.GetAuthCtx(c).PermitOfficialClient()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2025-05-13 15:26:40 +08:00
|
|
|
|
|
2025-12-22 17:31:31 +08:00
|
|
|
|
req := new(RegisterProxyBaiyinReq)
|
|
|
|
|
|
err = globals.Validator.ParseBody(c, req)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
addr, err := netip.ParseAddr(req.IP)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return core.NewServErr("IP地址格式错误", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
err = s.Proxy.RegisterBaiyin(req.Name, addr, req.Username, req.Password)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return core.NewServErr("注册失败", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
2025-05-13 09:29:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-22 17:31:31 +08:00
|
|
|
|
type RegisterProxyBaiyinReq struct {
|
|
|
|
|
|
Name string `json:"name" validate:"required"`
|
|
|
|
|
|
IP string `json:"ip" validate:"required"`
|
|
|
|
|
|
Username string `json:"username" validate:"required"`
|
|
|
|
|
|
Password string `json:"password" validate:"required"`
|
2025-05-14 17:45:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-22 17:31:31 +08:00
|
|
|
|
// region 报告上线
|
2025-05-27 15:08:18 +08:00
|
|
|
|
func ProxyReportOnline(c *fiber.Ctx) (err error) {
|
2025-11-24 18:44:06 +08:00
|
|
|
|
return c.JSON(map[string]any{
|
|
|
|
|
|
"error": "接口暂不可用",
|
2025-05-14 17:45:14 +08:00
|
|
|
|
})
|
2025-11-24 18:44:06 +08:00
|
|
|
|
|
|
|
|
|
|
// // 检查接口权限
|
|
|
|
|
|
// _, err = auth2.GetAuthCtx(c).PermitSecretClient()
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// // 验证请求参数
|
|
|
|
|
|
// var req = new(ProxyReportOnlineReq)
|
|
|
|
|
|
// err = g.Validator.Validate(c, req)
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// // 创建代理
|
|
|
|
|
|
// var ip = c.Context().RemoteIP()
|
|
|
|
|
|
|
|
|
|
|
|
// var secretBytes = make([]byte, 16)
|
|
|
|
|
|
// if _, err := rand.Read(secretBytes); err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
// var secret = base32.StdEncoding.
|
|
|
|
|
|
// WithPadding(base32.NoPadding).
|
|
|
|
|
|
// EncodeToString(secretBytes)
|
|
|
|
|
|
// slog.Debug("生成随机密钥", "ip", ip, "secret", secret)
|
|
|
|
|
|
|
|
|
|
|
|
// var proxy = &m.Proxy{
|
|
|
|
|
|
// Mac: req.Name,
|
|
|
|
|
|
// Version: int32(req.Version),
|
|
|
|
|
|
// Type: m.ProxyTypeSelfHosted,
|
|
|
|
|
|
// IP: ip,
|
|
|
|
|
|
// Secret: &secret,
|
|
|
|
|
|
// Status: 1,
|
|
|
|
|
|
// }
|
|
|
|
|
|
// err = q.Proxy.
|
|
|
|
|
|
// Clauses(clause.OnConflict{
|
|
|
|
|
|
// UpdateAll: true,
|
|
|
|
|
|
// Columns: []clause.Column{
|
|
|
|
|
|
// {Name: q.Proxy.Mac.ColumnName().String()},
|
|
|
|
|
|
// },
|
|
|
|
|
|
// }).
|
|
|
|
|
|
// Create(proxy)
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// // 获取边缘节点信息
|
|
|
|
|
|
// data, err := q.Edge.Where(
|
|
|
|
|
|
// q.Edge.ProxyID.Eq(proxy.ID),
|
|
|
|
|
|
// ).Find()
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
// edges := make([]*ProxyEdge, len(data))
|
|
|
|
|
|
// for i, edge := range data {
|
|
|
|
|
|
// edges[i] = &ProxyEdge{
|
|
|
|
|
|
// Id: edge.ID,
|
|
|
|
|
|
// Port: edge.ProxyPort,
|
|
|
|
|
|
// Prov: &edge.Prov,
|
|
|
|
|
|
// City: &edge.City,
|
|
|
|
|
|
// Isp: u.P(edge2.ISP(edge.Isp).String()),
|
|
|
|
|
|
// Status: &edge.Status,
|
|
|
|
|
|
// Loss: edge.Loss,
|
|
|
|
|
|
// Rtt: edge.Rtt,
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// // 获取许可配置
|
|
|
|
|
|
// channels, err := q.Channel.Where(
|
|
|
|
|
|
// q.Channel.ProxyID.Eq(proxy.ID),
|
|
|
|
|
|
// q.Channel.Expiration.Gt(orm.LocalDateTime(time.Now())),
|
|
|
|
|
|
// ).Find()
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// var permits = make([]*ProxyPermit, len(channels))
|
|
|
|
|
|
// for i, channel := range channels {
|
|
|
|
|
|
// if channel.EdgeID == nil {
|
|
|
|
|
|
// return core.NewBizErr(fmt.Sprintf("权限解析异常,通道缺少边缘节点ID %d", channel.ID))
|
|
|
|
|
|
// }
|
|
|
|
|
|
// permits[i] = &ProxyPermit{
|
|
|
|
|
|
// Id: *channel.EdgeID,
|
|
|
|
|
|
// Expire: time.Time(channel.Expiration),
|
|
|
|
|
|
// Whitelists: u.P(strings.Split(u.Z(channel.Whitelists), ",")),
|
|
|
|
|
|
// Username: channel.Username,
|
|
|
|
|
|
// Password: channel.Password,
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// slog.Debug("注册转发服务", "ip", ip, "id", proxy.ID)
|
|
|
|
|
|
// return c.JSON(&ProxyReportOnlineResp{
|
|
|
|
|
|
// Id: proxy.ID,
|
|
|
|
|
|
// Secret: secret,
|
|
|
|
|
|
// Edges: edges,
|
|
|
|
|
|
// Permits: permits,
|
|
|
|
|
|
// })
|
2025-05-13 09:29:13 +08:00
|
|
|
|
}
|
2025-05-13 15:26:40 +08:00
|
|
|
|
|
2025-12-22 17:31:31 +08:00
|
|
|
|
type ProxyReportOnlineReq struct {
|
|
|
|
|
|
Name string `json:"name" validate:"required"`
|
|
|
|
|
|
Version int `json:"version" validate:"required"`
|
|
|
|
|
|
}
|
2025-05-13 15:26:40 +08:00
|
|
|
|
|
2025-12-22 17:31:31 +08:00
|
|
|
|
type ProxyReportOnlineResp struct {
|
|
|
|
|
|
Id int32 `json:"id"`
|
|
|
|
|
|
Secret string `json:"secret"`
|
|
|
|
|
|
Permits []*ProxyPermit `json:"permits"`
|
|
|
|
|
|
Edges []*ProxyEdge `json:"edges"`
|
2025-05-13 15:26:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-22 17:31:31 +08:00
|
|
|
|
// region 报告下线
|
2025-05-27 15:08:18 +08:00
|
|
|
|
func ProxyReportOffline(c *fiber.Ctx) (err error) {
|
2025-11-24 18:44:06 +08:00
|
|
|
|
return c.JSON(map[string]any{
|
|
|
|
|
|
"error": "接口暂不可用",
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// // 检查接口权限
|
|
|
|
|
|
// _, err = auth2.GetAuthCtx(c).PermitSecretClient()
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// // 验证请求参数
|
|
|
|
|
|
// var req = new(ProxyReportOfflineReq)
|
|
|
|
|
|
// err = g.Validator.Validate(c, req)
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// // 下线转发服务
|
|
|
|
|
|
// _, err = q.Proxy.
|
|
|
|
|
|
// Where(q.Proxy.ID.Eq(req.Id)).
|
|
|
|
|
|
// UpdateSimple(q.Proxy.Status.Value(0))
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// // 下线所有相关的边缘节点
|
|
|
|
|
|
// _, err = q.Edge.
|
|
|
|
|
|
// Where(q.Edge.ProxyID.Eq(req.Id)).
|
|
|
|
|
|
// UpdateSimple(q.Edge.Status.Value(0))
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// return nil
|
2025-05-13 15:26:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-22 17:31:31 +08:00
|
|
|
|
type ProxyReportOfflineReq struct {
|
|
|
|
|
|
Id int32 `json:"id" validate:"required"`
|
2025-05-14 17:45:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-22 17:31:31 +08:00
|
|
|
|
// region 报告更新
|
2025-05-27 15:08:18 +08:00
|
|
|
|
func ProxyReportUpdate(c *fiber.Ctx) (err error) {
|
2025-11-24 18:44:06 +08:00
|
|
|
|
return c.JSON(map[string]any{
|
|
|
|
|
|
"error": "接口暂不可用",
|
2025-05-27 15:08:18 +08:00
|
|
|
|
})
|
2025-05-14 17:45:14 +08:00
|
|
|
|
|
2025-11-24 18:44:06 +08:00
|
|
|
|
// // 检查接口权限
|
|
|
|
|
|
// _, err = auth2.GetAuthCtx(c).PermitSecretClient()
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// // 验证请求参数
|
|
|
|
|
|
// var req = new(ProxyReportUpdateReq)
|
|
|
|
|
|
// err = g.Validator.Validate(c, req)
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// // 更新节点信息
|
|
|
|
|
|
// var idsActive = make([]int32, 0, len(req.Edges))
|
|
|
|
|
|
// var idsInactive = make([]int32, 0, len(req.Edges))
|
|
|
|
|
|
// var idsIspUnknown = make([]int32, 0, len(req.Edges))
|
|
|
|
|
|
// var idsIspTelecom = make([]int32, 0, len(req.Edges))
|
|
|
|
|
|
// var idsIspUnicom = make([]int32, 0, len(req.Edges))
|
|
|
|
|
|
// var idsIspMobile = make([]int32, 0, len(req.Edges))
|
|
|
|
|
|
// var otherEdges = make([]*ProxyEdge, 0, len(req.Edges))
|
|
|
|
|
|
// for _, edge := range req.Edges {
|
|
|
|
|
|
|
|
|
|
|
|
// // 检查更新ISP
|
|
|
|
|
|
// if edge.Isp != nil {
|
|
|
|
|
|
// switch edge2.ISPFromStr(*edge.Isp) {
|
|
|
|
|
|
// case edge2.IspUnknown:
|
|
|
|
|
|
// idsIspUnknown = append(idsIspUnknown, edge.Id)
|
|
|
|
|
|
// case edge2.IspChinaTelecom:
|
|
|
|
|
|
// idsIspTelecom = append(idsIspTelecom, edge.Id)
|
|
|
|
|
|
// case edge2.IspChinaUnicom:
|
|
|
|
|
|
// idsIspUnicom = append(idsIspUnicom, edge.Id)
|
|
|
|
|
|
// case edge2.IspChinaMobile:
|
|
|
|
|
|
// idsIspMobile = append(idsIspMobile, edge.Id)
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// // 检查更新状态
|
|
|
|
|
|
// if edge.Status != nil {
|
|
|
|
|
|
// if *edge.Status == 1 {
|
|
|
|
|
|
// idsActive = append(idsActive, edge.Id)
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
// idsInactive = append(idsInactive, edge.Id)
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// // 无法分类更新
|
|
|
|
|
|
// if edge.Host != nil || edge.Port != nil || edge.Prov != nil || edge.City != nil {
|
|
|
|
|
|
// otherEdges = append(otherEdges, edge)
|
|
|
|
|
|
// continue
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// slog.Debug("更新边缘节点信息",
|
|
|
|
|
|
// "active", len(idsActive),
|
|
|
|
|
|
// "inactive", len(idsInactive),
|
|
|
|
|
|
// "isp_unknown", len(idsIspUnknown),
|
|
|
|
|
|
// "isp_telecom", len(idsIspTelecom),
|
|
|
|
|
|
// "isp_unicom", len(idsIspUnicom),
|
|
|
|
|
|
// "isp_mobile", len(idsIspMobile),
|
|
|
|
|
|
// "other_edges", len(otherEdges),
|
|
|
|
|
|
// )
|
|
|
|
|
|
|
|
|
|
|
|
// err = q.Q.Transaction(func(q *q.Query) error {
|
|
|
|
|
|
// // 更新边缘节点状态
|
|
|
|
|
|
// if len(idsActive) > 0 {
|
|
|
|
|
|
// _, err = q.Edge.Debug().
|
|
|
|
|
|
// Where(q.Edge.ID.In(idsActive...)).
|
|
|
|
|
|
// UpdateSimple(q.Edge.Status.Value(1))
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// if len(idsInactive) > 0 {
|
|
|
|
|
|
// _, err = q.Edge.Debug().
|
|
|
|
|
|
// Where(q.Edge.ID.In(idsInactive...)).
|
|
|
|
|
|
// UpdateSimple(q.Edge.Status.Value(0))
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// // 更新边缘节点ISP
|
|
|
|
|
|
// if len(idsIspUnknown) > 0 {
|
|
|
|
|
|
// _, err = q.Edge.Debug().
|
|
|
|
|
|
// Where(q.Edge.ID.In(idsIspUnknown...)).
|
|
|
|
|
|
// UpdateSimple(q.Edge.Isp.Value(int32(edge2.IspUnknown)))
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// if len(idsIspTelecom) > 0 {
|
|
|
|
|
|
// _, err = q.Edge.Debug().
|
|
|
|
|
|
// Where(q.Edge.ID.In(idsIspTelecom...)).
|
|
|
|
|
|
// UpdateSimple(q.Edge.Isp.Value(int32(edge2.IspChinaTelecom)))
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// if len(idsIspUnicom) > 0 {
|
|
|
|
|
|
// _, err = q.Edge.Debug().
|
|
|
|
|
|
// Where(q.Edge.ID.In(idsIspUnicom...)).
|
|
|
|
|
|
// UpdateSimple(q.Edge.Isp.Value(int32(edge2.IspChinaUnicom)))
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// if len(idsIspMobile) > 0 {
|
|
|
|
|
|
// _, err = q.Edge.Debug().
|
|
|
|
|
|
// Where(q.Edge.ID.In(idsIspMobile...)).
|
|
|
|
|
|
// UpdateSimple(q.Edge.Isp.Value(int32(edge2.IspChinaMobile)))
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// // 更新其他边缘节点信息
|
|
|
|
|
|
// for _, edge := range otherEdges {
|
|
|
|
|
|
// do := q.Edge.Debug().Where(q.Edge.ID.Eq(edge.Id))
|
|
|
|
|
|
|
|
|
|
|
|
// var assigns = make([]field.AssignExpr, 0, 5)
|
|
|
|
|
|
// if edge.Host != nil {
|
|
|
|
|
|
// assigns = append(assigns, q.Edge.Host.Value(*edge.Host))
|
|
|
|
|
|
// }
|
|
|
|
|
|
// if edge.Port != nil {
|
|
|
|
|
|
// assigns = append(assigns, q.Edge.ProxyPort.Value(*edge.Port))
|
|
|
|
|
|
// }
|
|
|
|
|
|
// if edge.Prov != nil {
|
|
|
|
|
|
// assigns = append(assigns, q.Edge.Prov.Value(*edge.Prov))
|
|
|
|
|
|
// }
|
|
|
|
|
|
// if edge.City != nil {
|
|
|
|
|
|
// assigns = append(assigns, q.Edge.City.Value(*edge.City))
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// // 更新边缘节点
|
|
|
|
|
|
// _, err := do.UpdateSimple(assigns...)
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return fmt.Errorf("更新边缘节点 %d 失败: %w", edge.Id, err)
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
// return nil
|
|
|
|
|
|
// })
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// return nil
|
2025-05-14 17:45:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-22 17:31:31 +08:00
|
|
|
|
type ProxyReportUpdateReq struct {
|
|
|
|
|
|
Id int32 `json:"id" validate:"required"`
|
|
|
|
|
|
Edges []*ProxyEdge `json:"edges" validate:"required"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-24 12:37:16 +08:00
|
|
|
|
type ProxyPermit struct {
|
|
|
|
|
|
Id int32 `json:"id"`
|
|
|
|
|
|
Expire time.Time `json:"expire"`
|
|
|
|
|
|
Whitelists *[]string `json:"whitelists"`
|
|
|
|
|
|
Username *string `json:"username"`
|
|
|
|
|
|
Password *string `json:"password"`
|
|
|
|
|
|
}
|
2025-05-27 15:08:18 +08:00
|
|
|
|
|
|
|
|
|
|
type ProxyEdge struct {
|
|
|
|
|
|
Id int32 `json:"id"`
|
|
|
|
|
|
Host *string `json:"host,omitempty"` // 边缘节点地址
|
|
|
|
|
|
Port *int32 `json:"port,omitempty"` // 边缘节点代理端口
|
|
|
|
|
|
Prov *string `json:"prov,omitempty"`
|
|
|
|
|
|
City *string `json:"city,omitempty"`
|
|
|
|
|
|
Isp *string `json:"isp,omitempty"`
|
|
|
|
|
|
Status *int32 `json:"status,omitempty"`
|
|
|
|
|
|
Loss *int32 `json:"loss,omitempty"` // 丢包率
|
|
|
|
|
|
Rtt *int32 `json:"latency,omitempty"` // 延迟
|
|
|
|
|
|
}
|