feat: 添加时间同步配置功能至安装脚本
- 在 install.sh 中新增 sync_time 函数,配置系统时间同步,设置时区为 Asia/Shanghai,并安装 chrony。 - 配置 NTP 服务器为阿里云和腾讯云,确保时间同步的准确性。 - 更新主函数以调用时间同步配置,优化安装流程。
This commit is contained in:
@@ -13,7 +13,6 @@ import (
|
|||||||
"linkmaster-node/internal/heartbeat"
|
"linkmaster-node/internal/heartbeat"
|
||||||
"linkmaster-node/internal/recovery"
|
"linkmaster-node/internal/recovery"
|
||||||
"linkmaster-node/internal/server"
|
"linkmaster-node/internal/server"
|
||||||
"linkmaster-node/internal/timesync"
|
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
@@ -55,17 +54,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 启动时间同步服务(每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)
|
heartbeatReporter := heartbeat.NewReporter(cfg)
|
||||||
go heartbeatReporter.Start(context.Background())
|
go heartbeatReporter.Start(context.Background())
|
||||||
@@ -91,9 +79,6 @@ func main() {
|
|||||||
|
|
||||||
httpServer.Shutdown(ctx)
|
httpServer.Shutdown(ctx)
|
||||||
heartbeatReporter.Stop()
|
heartbeatReporter.Stop()
|
||||||
if timeSync != nil {
|
|
||||||
timeSync.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Info("服务已关闭")
|
logger.Info("服务已关闭")
|
||||||
}
|
}
|
||||||
|
|||||||
138
install.sh
138
install.sh
@@ -410,6 +410,119 @@ install_dependencies() {
|
|||||||
echo -e "${GREEN}✓ 系统依赖安装完成${NC}"
|
echo -e "${GREEN}✓ 系统依赖安装完成${NC}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 配置时间同步
|
||||||
|
sync_time() {
|
||||||
|
echo -e "${BLUE}配置时间同步...${NC}"
|
||||||
|
|
||||||
|
# 1. 设置时区为 Asia/Shanghai
|
||||||
|
echo -e "${BLUE}[1/6] 设置时区为 Asia/Shanghai${NC}"
|
||||||
|
if command -v timedatectl > /dev/null 2>&1; then
|
||||||
|
sudo timedatectl set-timezone Asia/Shanghai 2>/dev/null || {
|
||||||
|
echo -e "${YELLOW}⚠ timedatectl 设置时区失败,尝试其他方法${NC}"
|
||||||
|
# 尝试创建时区链接(适用于较老的系统)
|
||||||
|
if [ -f /usr/share/zoneinfo/Asia/Shanghai ]; then
|
||||||
|
sudo ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
elif [ -f /usr/share/zoneinfo/Asia/Shanghai ]; then
|
||||||
|
sudo ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. 安装 chrony
|
||||||
|
echo -e "${BLUE}[2/6] 安装 chrony${NC}"
|
||||||
|
if [ "$OS" = "ubuntu" ] || [ "$OS" = "debian" ]; then
|
||||||
|
if ! dpkg -l 2>/dev/null | grep -q "^ii.*chrony"; then
|
||||||
|
sudo apt-get install -y chrony
|
||||||
|
else
|
||||||
|
echo -e "${BLUE}chrony 已安装,跳过${NC}"
|
||||||
|
fi
|
||||||
|
elif [ "$OS" = "centos" ] || [ "$OS" = "rhel" ] || [ "$OS" = "rocky" ] || [ "$OS" = "almalinux" ]; then
|
||||||
|
if ! rpm -q chrony &>/dev/null; then
|
||||||
|
sudo yum install -y chrony
|
||||||
|
else
|
||||||
|
echo -e "${BLUE}chrony 已安装,跳过${NC}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠ 未知系统类型,跳过 chrony 安装${NC}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. 配置 NTP 服务器
|
||||||
|
echo -e "${BLUE}[3/6] 配置 NTP 服务器${NC}"
|
||||||
|
CONF="/etc/chrony.conf"
|
||||||
|
|
||||||
|
if [ -f "$CONF" ]; then
|
||||||
|
# 备份配置文件
|
||||||
|
if [ ! -f "${CONF}.backup.$(date +%Y%m%d)" ]; then
|
||||||
|
sudo cp "$CONF" "${CONF}.backup.$(date +%Y%m%d)" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 注释掉原有的 server 行
|
||||||
|
sudo sed -i 's/^server /#server /g' "$CONF" 2>/dev/null || true
|
||||||
|
|
||||||
|
# 添加中国 NTP 服务器(如果还没有)
|
||||||
|
if ! grep -q "ntp.aliyun.com" "$CONF"; then
|
||||||
|
sudo tee -a "$CONF" > /dev/null <<EOF
|
||||||
|
|
||||||
|
# China NTP servers (added by LinkMaster Node installer)
|
||||||
|
server ntp.aliyun.com iburst
|
||||||
|
server ntp.tencent.com iburst
|
||||||
|
server ntp1.aliyun.com iburst
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠ chrony.conf 不存在,跳过配置${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. 启动并启用 chronyd
|
||||||
|
echo -e "${BLUE}[4/6] 启动 chronyd${NC}"
|
||||||
|
if command -v systemctl > /dev/null 2>&1; then
|
||||||
|
sudo systemctl enable chronyd --now 2>/dev/null || {
|
||||||
|
# 如果 systemctl 失败,尝试使用 service 命令
|
||||||
|
if command -v service > /dev/null 2>&1; then
|
||||||
|
sudo service chronyd start 2>/dev/null || true
|
||||||
|
sudo chkconfig chronyd on 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
elif command -v service > /dev/null 2>&1; then
|
||||||
|
sudo service chronyd start 2>/dev/null || true
|
||||||
|
sudo chkconfig chronyd on 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 等待服务启动
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# 5. 立即强制同步
|
||||||
|
echo -e "${BLUE}[5/6] 强制同步系统时间${NC}"
|
||||||
|
if command -v chronyc > /dev/null 2>&1; then
|
||||||
|
sudo chronyc -a makestep 2>/dev/null || {
|
||||||
|
# 如果 makestep 失败,尝试使用 sources 和 sourcestats
|
||||||
|
sudo chronyc sources 2>/dev/null || true
|
||||||
|
sudo chronyc sourcestats 2>/dev/null || true
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 6. 写入硬件时间
|
||||||
|
echo -e "${BLUE}[6/6] 写入硬件时钟${NC}"
|
||||||
|
if command -v hwclock > /dev/null 2>&1; then
|
||||||
|
sudo hwclock --systohc 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 显示时间状态
|
||||||
|
echo -e "${BLUE}当前时间状态:${NC}"
|
||||||
|
if command -v timedatectl > /dev/null 2>&1; then
|
||||||
|
sudo timedatectl status 2>/dev/null || true
|
||||||
|
else
|
||||||
|
date
|
||||||
|
if command -v hwclock > /dev/null 2>&1; then
|
||||||
|
echo -e "${BLUE}硬件时钟:${NC}"
|
||||||
|
sudo hwclock 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✓ 时间同步配置完成${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
# 从官网下载安装 Go
|
# 从官网下载安装 Go
|
||||||
install_go_from_official() {
|
install_go_from_official() {
|
||||||
echo -e "${BLUE}从 Go 官网下载安装...${NC}"
|
echo -e "${BLUE}从 Go 官网下载安装...${NC}"
|
||||||
@@ -1515,26 +1628,29 @@ main() {
|
|||||||
echo -e "${BLUE}后端地址: ${BACKEND_URL}${NC}"
|
echo -e "${BLUE}后端地址: ${BACKEND_URL}${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
echo -e "${BLUE}[1/8] 检测系统类型...${NC}"
|
echo -e "${BLUE}[1/11] 检测系统类型...${NC}"
|
||||||
detect_system
|
detect_system
|
||||||
|
|
||||||
# 检查是否已安装,如果已安装则先卸载
|
# 检查是否已安装,如果已安装则先卸载
|
||||||
if check_installed; then
|
if check_installed; then
|
||||||
echo -e "${BLUE}[2/8] 卸载已存在的服务...${NC}"
|
echo -e "${BLUE}[2/11] 卸载已存在的服务...${NC}"
|
||||||
uninstall_service
|
uninstall_service
|
||||||
else
|
else
|
||||||
echo -e "${BLUE}[2/8] 检查已安装服务...${NC}"
|
echo -e "${BLUE}[2/11] 检查已安装服务...${NC}"
|
||||||
echo -e "${GREEN}✓ 未检测到已安装的服务${NC}"
|
echo -e "${GREEN}✓ 未检测到已安装的服务${NC}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${BLUE}[3/8] 检测并配置镜像源...${NC}"
|
echo -e "${BLUE}[3/11] 检测并配置镜像源...${NC}"
|
||||||
detect_fastest_mirror
|
detect_fastest_mirror
|
||||||
|
|
||||||
echo -e "${BLUE}[4/8] 安装系统依赖...${NC}"
|
echo -e "${BLUE}[4/11] 安装系统依赖...${NC}"
|
||||||
install_dependencies
|
install_dependencies
|
||||||
|
|
||||||
|
echo -e "${BLUE}[5/11] 配置时间同步...${NC}"
|
||||||
|
sync_time
|
||||||
|
|
||||||
# 优先尝试从 Releases 下载二进制文件
|
# 优先尝试从 Releases 下载二进制文件
|
||||||
echo -e "${BLUE}[5/8] 下载或编译二进制文件...${NC}"
|
echo -e "${BLUE}[6/11] 下载或编译二进制文件...${NC}"
|
||||||
if ! download_binary_from_releases; then
|
if ! download_binary_from_releases; then
|
||||||
echo -e "${BLUE}从 Releases 下载失败,开始从源码编译...${NC}"
|
echo -e "${BLUE}从 Releases 下载失败,开始从源码编译...${NC}"
|
||||||
build_from_source
|
build_from_source
|
||||||
@@ -1542,19 +1658,19 @@ main() {
|
|||||||
echo -e "${GREEN}✓ 使用预编译二进制文件,跳过编译步骤${NC}"
|
echo -e "${GREEN}✓ 使用预编译二进制文件,跳过编译步骤${NC}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${BLUE}[6/8] 创建 systemd 服务...${NC}"
|
echo -e "${BLUE}[7/11] 创建 systemd 服务...${NC}"
|
||||||
create_service
|
create_service
|
||||||
|
|
||||||
echo -e "${BLUE}[7/8] 配置防火墙规则...${NC}"
|
echo -e "${BLUE}[8/11] 配置防火墙规则...${NC}"
|
||||||
configure_firewall
|
configure_firewall
|
||||||
|
|
||||||
echo -e "${BLUE}[8/8] 登记节点到后端服务器...${NC}"
|
echo -e "${BLUE}[9/11] 登记节点到后端服务器...${NC}"
|
||||||
register_node
|
register_node
|
||||||
|
|
||||||
echo -e "${BLUE}[9/9] 启动服务...${NC}"
|
echo -e "${BLUE}[10/11] 启动服务...${NC}"
|
||||||
start_service
|
start_service
|
||||||
|
|
||||||
echo -e "${BLUE}[10/10] 验证安装...${NC}"
|
echo -e "${BLUE}[11/11] 验证安装...${NC}"
|
||||||
verify_installation
|
verify_installation
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ type Reporter struct {
|
|||||||
client *http.Client
|
client *http.Client
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
stopCh chan struct{}
|
stopCh chan struct{}
|
||||||
beijingTZ *time.Location
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReporter(cfg *config.Config) *Reporter {
|
func NewReporter(cfg *config.Config) *Reporter {
|
||||||
@@ -80,14 +79,6 @@ func NewReporter(cfg *config.Config) *Reporter {
|
|||||||
// 初始化节点信息(从配置文件读取)
|
// 初始化节点信息(从配置文件读取)
|
||||||
InitNodeInfo(cfg)
|
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{
|
return &Reporter{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
client: &http.Client{
|
client: &http.Client{
|
||||||
@@ -95,7 +86,6 @@ func NewReporter(cfg *config.Config) *Reporter {
|
|||||||
},
|
},
|
||||||
logger: logger,
|
logger: logger,
|
||||||
stopCh: make(chan struct{}),
|
stopCh: make(chan struct{}),
|
||||||
beijingTZ: beijingTZ,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,9 +94,8 @@ func (r *Reporter) Start(ctx context.Context) {
|
|||||||
r.sendHeartbeat()
|
r.sendHeartbeat()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// 获取当前北京时间
|
// 计算到下一分钟第1秒的时间
|
||||||
now := time.Now().In(r.beijingTZ)
|
now := time.Now()
|
||||||
// 计算到下一分钟第1秒的时间(基于北京时间)
|
|
||||||
nextMinute := now.Truncate(time.Minute).Add(time.Minute)
|
nextMinute := now.Truncate(time.Minute).Add(time.Minute)
|
||||||
nextHeartbeatTime := nextMinute.Add(1 * time.Second)
|
nextHeartbeatTime := nextMinute.Add(1 * time.Second)
|
||||||
durationUntilNext := nextHeartbeatTime.Sub(now)
|
durationUntilNext := nextHeartbeatTime.Sub(now)
|
||||||
@@ -122,7 +111,7 @@ func (r *Reporter) Start(ctx context.Context) {
|
|||||||
timer.Stop()
|
timer.Stop()
|
||||||
return
|
return
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
// 在每分钟的第1秒发送心跳(北京时间)
|
// 在每分钟的第1秒发送心跳
|
||||||
r.sendHeartbeat()
|
r.sendHeartbeat()
|
||||||
}
|
}
|
||||||
timer.Stop()
|
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
|
|
||||||
}
|
|
||||||
53
time.sh
Normal file
53
time.sh
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== CentOS 7 时间同步脚本开始 ==="
|
||||||
|
|
||||||
|
# 1. 检查是否 root
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
echo "请使用 root 用户执行"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. 设置时区
|
||||||
|
echo "[1/6] 设置时区为 Asia/Shanghai"
|
||||||
|
timedatectl set-timezone Asia/Shanghai
|
||||||
|
|
||||||
|
# 3. 安装 chrony
|
||||||
|
echo "[2/6] 安装 chrony"
|
||||||
|
if ! rpm -q chrony &>/dev/null; then
|
||||||
|
yum install -y chrony
|
||||||
|
else
|
||||||
|
echo "chrony 已安装,跳过"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. 配置 NTP 服务器
|
||||||
|
echo "[3/6] 配置 NTP 服务器"
|
||||||
|
CONF="/etc/chrony.conf"
|
||||||
|
|
||||||
|
sed -i 's/^server /#server /g' "$CONF"
|
||||||
|
|
||||||
|
grep -q "ntp.aliyun.com" "$CONF" || cat >> "$CONF" <<EOF
|
||||||
|
|
||||||
|
# China NTP servers
|
||||||
|
server ntp.aliyun.com iburst
|
||||||
|
server ntp.tencent.com iburst
|
||||||
|
server ntp1.aliyun.com iburst
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 5. 启动并启用 chronyd
|
||||||
|
echo "[4/6] 启动 chronyd"
|
||||||
|
systemctl enable chronyd --now
|
||||||
|
|
||||||
|
# 6. 立即强制同步
|
||||||
|
echo "[5/6] 强制同步系统时间"
|
||||||
|
chronyc -a makestep
|
||||||
|
|
||||||
|
# 7. 写入硬件时间
|
||||||
|
echo "[6/6] 写入硬件时钟"
|
||||||
|
hwclock --systohc
|
||||||
|
|
||||||
|
echo "=== 时间同步完成 ==="
|
||||||
|
echo
|
||||||
|
timedatectl status
|
||||||
Reference in New Issue
Block a user