Files
mfd-monitor-agent/internal/reporter/reporter.go
codex de3fd88948 ✨ feat(agent): 添加 token 鉴权和流程文档
* 默认监听调整为 0.0.0.0:8080,并要求查询参数 token
* 支持首次 run 自动生成含随机 token 的配置文件
* 补充 HTTP 鉴权、配置校验和相关测试
* 完善代码注释,覆盖缓存、上报、服务和平台采集路径
* 新增架构与开发流程文档,补充一键构建脚本
2026-05-22 06:46:21 +00:00

106 lines
3.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}