Files
linkmaster-node/cmd/agent/main.go
yoyo e0d97c4486 feat: 添加时间同步服务和北京时间支持
- 在主程序中集成时间同步服务,每30分钟同步一次时间。
- 在心跳报告中加载并使用北京时间,确保心跳在每分钟的第1秒发送。
- 增强了错误处理,确保在加载时区失败时使用默认时区。
2025-12-24 02:34:05 +08:00

168 lines
4.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 main
import (
"context"
"fmt"
"os"
"os/signal"
"path/filepath"
"syscall"
"time"
"linkmaster-node/internal/config"
"linkmaster-node/internal/heartbeat"
"linkmaster-node/internal/recovery"
"linkmaster-node/internal/server"
"linkmaster-node/internal/timesync"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var version = "1.1.0" // 编译时通过 -ldflags "-X main.version=xxx" 设置
func main() {
// 加载配置
cfg, err := config.Load()
if err != nil {
fmt.Printf("加载配置失败: %v\n", err)
os.Exit(1)
}
// 初始化日志
logger, err := initLogger(cfg)
if err != nil {
fmt.Printf("初始化日志失败: %v\n", err)
os.Exit(1)
}
defer logger.Sync()
logger.Info("节点服务启动", zap.String("version", version))
// 初始化错误恢复
recovery.Init()
// 如果配置中没有节点信息,先发送一次心跳获取节点信息
if cfg.Node.ID == 0 || cfg.Node.IP == "" {
logger.Info("节点信息未配置,发送心跳获取节点信息")
if err := heartbeat.RegisterNode(cfg); err != nil {
logger.Warn("注册节点失败,将在心跳时重试", zap.Error(err))
} else {
logger.Info("节点信息已获取并保存",
zap.Uint("node_id", cfg.Node.ID),
zap.String("node_ip", cfg.Node.IP),
zap.String("location", fmt.Sprintf("%s/%s/%s", cfg.Node.Country, cfg.Node.Province, cfg.Node.City)))
}
}
// 启动时间同步服务每30分钟同步一次
var timeSync *timesync.TimeSync
timeSync, err = timesync.NewTimeSync(logger)
if err != nil {
logger.Warn("创建时间同步器失败", zap.Error(err))
timeSync = nil
} else {
go timeSync.Start(context.Background(), 30*time.Minute)
logger.Info("时间同步服务已启动")
}
// 启动心跳上报
heartbeatReporter := heartbeat.NewReporter(cfg)
go heartbeatReporter.Start(context.Background())
// 启动HTTP服务器
httpServer := server.NewHTTPServer(cfg)
go func() {
if err := httpServer.Start(); err != nil {
logger.Fatal("HTTP服务器启动失败", zap.Error(err))
}
}()
// 等待中断信号
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
<-sigChan
logger.Info("收到停止信号,正在关闭服务...")
// 优雅关闭
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
httpServer.Shutdown(ctx)
heartbeatReporter.Stop()
if timeSync != nil {
timeSync.Stop()
}
logger.Info("服务已关闭")
}
func initLogger(cfg *config.Config) (*zap.Logger, error) {
// 确定日志级别
var level zapcore.Level
logLevel := cfg.Log.Level
if logLevel == "" {
if cfg.Debug {
logLevel = "debug"
} else {
logLevel = "info"
}
}
switch logLevel {
case "debug":
level = zapcore.DebugLevel
case "info":
level = zapcore.InfoLevel
case "warn":
level = zapcore.WarnLevel
case "error":
level = zapcore.ErrorLevel
default:
level = zapcore.InfoLevel
}
// 编码器配置
encoderConfig := zap.NewProductionEncoderConfig()
if cfg.Debug {
encoderConfig = zap.NewDevelopmentEncoderConfig()
}
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
// 确定输出目标
var writeSyncer zapcore.WriteSyncer
if cfg.Log.File != "" {
// 确保日志目录存在
logDir := filepath.Dir(cfg.Log.File)
if logDir != "." && logDir != "" {
if err := os.MkdirAll(logDir, 0755); err != nil {
return nil, fmt.Errorf("创建日志目录失败: %w", err)
}
}
// 打开日志文件(追加模式)
logFile, err := os.OpenFile(cfg.Log.File, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return nil, fmt.Errorf("打开日志文件失败: %w", err)
}
writeSyncer = zapcore.AddSync(logFile)
} else {
// 输出到标准错误(兼容原有行为)
writeSyncer = zapcore.AddSync(os.Stderr)
}
// 创建核心
core := zapcore.NewCore(
zapcore.NewJSONEncoder(encoderConfig),
writeSyncer,
level,
)
// 创建 logger
logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
return logger, nil
}