feat: 添加时间同步配置功能至安装脚本
- 在 install.sh 中新增 sync_time 函数,配置系统时间同步,设置时区为 Asia/Shanghai,并安装 chrony。 - 配置 NTP 服务器为阿里云和腾讯云,确保时间同步的准确性。 - 更新主函数以调用时间同步配置,优化安装流程。
This commit is contained in:
@@ -67,11 +67,10 @@ func GetNodeLocation() (country, province, city, isp string) {
|
||||
}
|
||||
|
||||
type Reporter struct {
|
||||
cfg *config.Config
|
||||
client *http.Client
|
||||
logger *zap.Logger
|
||||
stopCh chan struct{}
|
||||
beijingTZ *time.Location
|
||||
cfg *config.Config
|
||||
client *http.Client
|
||||
logger *zap.Logger
|
||||
stopCh chan struct{}
|
||||
}
|
||||
|
||||
func NewReporter(cfg *config.Config) *Reporter {
|
||||
@@ -80,22 +79,13 @@ func NewReporter(cfg *config.Config) *Reporter {
|
||||
// 初始化节点信息(从配置文件读取)
|
||||
InitNodeInfo(cfg)
|
||||
|
||||
// 加载北京时间时区
|
||||
beijingTZ, err := time.LoadLocation("Asia/Shanghai")
|
||||
if err != nil {
|
||||
// 如果加载失败,使用UTC+8手动创建
|
||||
beijingTZ = time.FixedZone("CST", 8*60*60)
|
||||
logger.Warn("加载时区失败,使用UTC+8", zap.Error(err))
|
||||
}
|
||||
|
||||
return &Reporter{
|
||||
cfg: cfg,
|
||||
client: &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
},
|
||||
logger: logger,
|
||||
stopCh: make(chan struct{}),
|
||||
beijingTZ: beijingTZ,
|
||||
logger: logger,
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,9 +94,8 @@ func (r *Reporter) Start(ctx context.Context) {
|
||||
r.sendHeartbeat()
|
||||
|
||||
for {
|
||||
// 获取当前北京时间
|
||||
now := time.Now().In(r.beijingTZ)
|
||||
// 计算到下一分钟第1秒的时间(基于北京时间)
|
||||
// 计算到下一分钟第1秒的时间
|
||||
now := time.Now()
|
||||
nextMinute := now.Truncate(time.Minute).Add(time.Minute)
|
||||
nextHeartbeatTime := nextMinute.Add(1 * time.Second)
|
||||
durationUntilNext := nextHeartbeatTime.Sub(now)
|
||||
@@ -122,7 +111,7 @@ func (r *Reporter) Start(ctx context.Context) {
|
||||
timer.Stop()
|
||||
return
|
||||
case <-timer.C:
|
||||
// 在每分钟的第1秒发送心跳(北京时间)
|
||||
// 在每分钟的第1秒发送心跳
|
||||
r.sendHeartbeat()
|
||||
}
|
||||
timer.Stop()
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
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
|
||||
}
|
||||
|
||||
// K780TimeAPIResponse K780时间API响应结构
|
||||
type K780TimeAPIResponse struct {
|
||||
Success string `json:"success"`
|
||||
Msgid string `json:"msgid"`
|
||||
Msg string `json:"msg"`
|
||||
Result struct {
|
||||
Timestamp string `json:"timestamp"` // 时间戳(秒)
|
||||
TimestampMs string `json:"timestamp_ms"` // 时间戳(毫秒)
|
||||
Datetime1 string `json:"datetime_1"` // 日期时间格式1
|
||||
Datetime2 string `json:"datetime_2"` // 日期时间格式2
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// 使用K780时间API
|
||||
apiURL := "https://sapi.k780.com/?app=life.time&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json"
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
// 创建请求,添加浏览器请求头
|
||||
req, err := http.NewRequest("GET", apiURL, nil)
|
||||
if err != nil {
|
||||
ts.mu.Lock()
|
||||
ts.lastSyncError = err
|
||||
ts.mu.Unlock()
|
||||
return fmt.Errorf("创建请求失败: %w", err)
|
||||
}
|
||||
|
||||
// 添加浏览器请求头,模拟浏览器访问
|
||||
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
|
||||
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
|
||||
req.Header.Set("Cache-Control", "max-age=0")
|
||||
req.Header.Set("Connection", "keep-alive")
|
||||
req.Header.Set("Referer", "https://www.nowapi.com/")
|
||||
req.Header.Set("Sec-Fetch-Dest", "document")
|
||||
req.Header.Set("Sec-Fetch-Mode", "navigate")
|
||||
req.Header.Set("Sec-Fetch-Site", "cross-site")
|
||||
req.Header.Set("Sec-Fetch-User", "?1")
|
||||
req.Header.Set("Upgrade-Insecure-Requests", "1")
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36")
|
||||
req.Header.Set("sec-ch-ua", `"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"`)
|
||||
req.Header.Set("sec-ch-ua-mobile", "?0")
|
||||
req.Header.Set("sec-ch-ua-platform", `"macOS"`)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
ts.mu.Lock()
|
||||
ts.lastSyncError = err
|
||||
ts.mu.Unlock()
|
||||
return fmt.Errorf("API请求失败: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err := fmt.Errorf("API返回状态码: %d", resp.StatusCode)
|
||||
ts.mu.Lock()
|
||||
ts.lastSyncError = err
|
||||
ts.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
var result K780TimeAPIResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
ts.mu.Lock()
|
||||
ts.lastSyncError = err
|
||||
ts.mu.Unlock()
|
||||
return fmt.Errorf("解析API响应失败: %w", err)
|
||||
}
|
||||
|
||||
// 检查返回状态
|
||||
if result.Success != "1" {
|
||||
errMsg := fmt.Sprintf("API返回失败状态: success=%s", result.Success)
|
||||
if result.Msg != "" {
|
||||
errMsg += fmt.Sprintf(", msg=%s", result.Msg)
|
||||
}
|
||||
if result.Msgid != "" {
|
||||
errMsg += fmt.Sprintf(", msgid=%s", result.Msgid)
|
||||
}
|
||||
err := fmt.Errorf(errMsg)
|
||||
ts.mu.Lock()
|
||||
ts.lastSyncError = err
|
||||
ts.mu.Unlock()
|
||||
ts.logger.Warn("时间API返回失败", zap.String("success", result.Success), zap.String("msgid", result.Msgid), zap.String("msg", result.Msg))
|
||||
return err
|
||||
}
|
||||
|
||||
// 从result.timestamp字段解析时间戳字符串
|
||||
if result.Result.Timestamp == "" {
|
||||
err := fmt.Errorf("API返回的时间戳为空")
|
||||
ts.mu.Lock()
|
||||
ts.lastSyncError = err
|
||||
ts.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// 解析时间戳字符串为int64
|
||||
var timestamp int64
|
||||
if _, err := fmt.Sscanf(result.Result.Timestamp, "%d", ×tamp); err != nil {
|
||||
err := fmt.Errorf("解析时间戳失败: %w", err)
|
||||
ts.mu.Lock()
|
||||
ts.lastSyncError = err
|
||||
ts.mu.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// 使用时间戳转换为北京时间
|
||||
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", apiURL),
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user