* 默认监听调整为 0.0.0.0:8080,并要求查询参数 token * 支持首次 run 自动生成含随机 token 的配置文件 * 补充 HTTP 鉴权、配置校验和相关测试 * 完善代码注释,覆盖缓存、上报、服务和平台采集路径 * 新增架构与开发流程文档,补充一键构建脚本
106 lines
3.0 KiB
Go
106 lines
3.0 KiB
Go
package reporter
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"log/slog"
|
||
"net/http"
|
||
"time"
|
||
|
||
"mfd-monitor-agent/internal/status"
|
||
)
|
||
|
||
// defaultRetryDelay 是单次上报失败后重试前等待的默认时长。
|
||
const defaultRetryDelay = 2 * time.Second
|
||
|
||
// Provider 提供用于外发上报的缓存 HostStatus。
|
||
type Provider interface {
|
||
// Get 返回最新快照及快照是否已就绪。
|
||
Get() (status.HostStatus, bool)
|
||
}
|
||
|
||
// Client 将 HostStatus 快照 POST 到配置的中心端地址。
|
||
type Client struct {
|
||
endpoint string
|
||
client *http.Client
|
||
}
|
||
|
||
// NewClient 使用给定端点与请求超时创建上报客户端。
|
||
func NewClient(endpoint string, timeout time.Duration) *Client {
|
||
return &Client{
|
||
endpoint: endpoint,
|
||
client: &http.Client{Timeout: timeout},
|
||
}
|
||
}
|
||
|
||
// Report 将一份 HostStatus 以 JSON POST 上报;非 2xx 响应作为错误返回。
|
||
// 请求超时由 Client 内部的 http.Client.Timeout 控制。
|
||
func (c *Client) Report(snapshot status.HostStatus) error {
|
||
body, err := json.Marshal(snapshot)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
req, err := http.NewRequest(http.MethodPost, c.endpoint, bytes.NewReader(body))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
req.Header.Set("Content-Type", "application/json")
|
||
resp, err := c.client.Do(req)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer resp.Body.Close()
|
||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||
return fmt.Errorf("report returned HTTP %d", resp.StatusCode)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// Run 在 ctx 取消前按间隔 POST 最新缓存快照。
|
||
func Run(ctx context.Context, provider Provider, client *Client, interval time.Duration, logger *slog.Logger) {
|
||
runWithRetryDelay(ctx, provider, client, interval, defaultRetryDelay, logger)
|
||
}
|
||
|
||
// runWithRetryDelay 运行上报循环;单次失败后按 retryDelay 重试一次。
|
||
func runWithRetryDelay(ctx context.Context, provider Provider, client *Client, interval time.Duration, retryDelay time.Duration, logger *slog.Logger) {
|
||
ticker := time.NewTicker(interval)
|
||
defer ticker.Stop()
|
||
for {
|
||
select {
|
||
case <-ctx.Done():
|
||
return
|
||
case <-ticker.C:
|
||
// 上报只读取缓存,避免与 HTTP 查询之外再触发一轮昂贵采集。
|
||
snapshot, ok := provider.Get()
|
||
if !ok {
|
||
// 首次快照未就绪时跳过本轮,上报循环继续等待下一周期。
|
||
continue
|
||
}
|
||
if err := reportWithRetry(ctx, client, snapshot, retryDelay); err != nil {
|
||
// 上报失败只写日志,不影响本地 HTTP 服务与后续采集。
|
||
logger.Warn("status report failed", "error", err)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// reportWithRetry 上报快照;失败时在可取消的等待后重试一次。
|
||
func reportWithRetry(ctx context.Context, client *Client, snapshot status.HostStatus, retryDelay time.Duration) error {
|
||
err := client.Report(snapshot)
|
||
if err == nil {
|
||
return nil
|
||
}
|
||
// 重试等待受 ctx 控制,agent 退出时不会被固定退避时间拖住。
|
||
select {
|
||
case <-ctx.Done():
|
||
return ctx.Err()
|
||
case <-time.After(retryDelay):
|
||
}
|
||
if retryErr := client.Report(snapshot); retryErr != nil {
|
||
return retryErr
|
||
}
|
||
return nil
|
||
}
|