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

191 lines
4.7 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 timesync
import (
"context"
"encoding/json"
"fmt"
"net/http"
"sync"
"time"
"go.uber.org/zap"
)
// TimeSync 时间同步器
type TimeSync struct {
logger *zap.Logger
stopCh chan struct{}
beijingTZ *time.Location
lastSyncTime time.Time
lastSyncError error
mu sync.RWMutex
}
// ChinaTimeAPIResponse 中国时间API响应结构type=1返回时间戳
type ChinaTimeAPIResponse struct {
Code int `json:"code"`
Msg string `json:"msg"` // 时间戳字符串(秒)
}
// NewTimeSync 创建时间同步器
func NewTimeSync(logger *zap.Logger) (*TimeSync, error) {
// 加载北京时间时区
beijingTZ, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
return nil, fmt.Errorf("加载时区失败: %w", err)
}
return &TimeSync{
logger: logger,
stopCh: make(chan struct{}),
beijingTZ: beijingTZ,
}, nil
}
// syncTime 同步时间从HTTP API获取北京时间
func (ts *TimeSync) syncTime() error {
// 使用中国时间API必须使用大陆无法访问海外API
chinaAPIs := []string{
"http://101.35.2.25/api/time/getapi.php",
"http://124.222.204.22/api/time/getapi.php",
"http://81.68.149.132/api/time/getapi.php",
"https://cn.apihz.cn/api/time/getapi.php",
}
var lastErr error
// 尝试所有中国时间API
for _, baseURL := range chinaAPIs {
apiURL := baseURL + "?id=88888888&key=88888888&type=1"
client := &http.Client{
Timeout: 5 * time.Second,
}
resp, err := client.Get(apiURL)
if err != nil {
lastErr = err
ts.logger.Debug("API请求失败", zap.String("api", baseURL), zap.Error(err))
continue
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
lastErr = fmt.Errorf("API返回状态码: %d", resp.StatusCode)
ts.logger.Debug("API返回错误状态码", zap.String("api", baseURL), zap.Int("status", resp.StatusCode))
continue
}
var result ChinaTimeAPIResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
resp.Body.Close()
lastErr = err
ts.logger.Debug("解析API响应失败", zap.String("api", baseURL), zap.Error(err))
continue
}
resp.Body.Close()
// 检查返回码
if result.Code != 200 {
lastErr = fmt.Errorf("API返回错误: %s", result.Msg)
ts.logger.Debug("API返回错误码", zap.String("api", baseURL), zap.Int("code", result.Code), zap.String("msg", result.Msg))
continue
}
// 从msg字段解析时间戳字符串
if result.Msg == "" {
lastErr = fmt.Errorf("API返回的时间戳为空")
continue
}
// 解析时间戳字符串为int64
var timestamp int64
if _, err := fmt.Sscanf(result.Msg, "%d", &timestamp); err != nil {
lastErr = fmt.Errorf("解析时间戳失败: %w", err)
ts.logger.Debug("解析时间戳失败", zap.String("api", baseURL), zap.String("msg", result.Msg), zap.Error(err))
continue
}
// 使用时间戳转换为北京时间
beijingTime := time.Unix(timestamp, 0).In(ts.beijingTZ)
// 计算时间差
localTime := time.Now()
timeDiff := beijingTime.Sub(localTime)
ts.mu.Lock()
ts.lastSyncTime = beijingTime
ts.lastSyncError = nil
ts.mu.Unlock()
ts.logger.Info("时间同步成功",
zap.String("api", baseURL),
zap.String("remote_time", beijingTime.Format("2006-01-02 15:04:05")),
zap.String("local_time", localTime.Format("2006-01-02 15:04:05")),
zap.Duration("time_diff", timeDiff),
zap.Int64("timestamp", timestamp))
return nil
}
ts.mu.Lock()
ts.lastSyncError = lastErr
ts.mu.Unlock()
return fmt.Errorf("所有中国时间API同步失败: %w", lastErr)
}
// Start 启动定期时间同步
func (ts *TimeSync) Start(ctx context.Context, interval time.Duration) {
// 立即同步一次
if err := ts.syncTime(); err != nil {
ts.logger.Warn("初始时间同步失败", zap.Error(err))
}
// 定期同步
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ts.stopCh:
return
case <-ticker.C:
if err := ts.syncTime(); err != nil {
ts.logger.Warn("时间同步失败", zap.Error(err))
}
}
}
}
// Stop 停止时间同步
func (ts *TimeSync) Stop() {
close(ts.stopCh)
}
// GetBeijingTime 获取当前北京时间
func (ts *TimeSync) GetBeijingTime() time.Time {
return time.Now().In(ts.beijingTZ)
}
// GetLocation 获取北京时区
func (ts *TimeSync) GetLocation() *time.Location {
return ts.beijingTZ
}
// GetLastSyncTime 获取最后一次同步的时间
func (ts *TimeSync) GetLastSyncTime() time.Time {
ts.mu.RLock()
defer ts.mu.RUnlock()
return ts.lastSyncTime
}
// GetLastSyncError 获取最后一次同步的错误
func (ts *TimeSync) GetLastSyncError() error {
ts.mu.RLock()
defer ts.mu.RUnlock()
return ts.lastSyncError
}