timesync.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. // ============================================================================
  2. // timesync.go — 系统时间同步
  3. //
  4. // 从 NTP 服务器读取标准时间,调用平台实现校准系统时钟(Windows 用 SetLocalTime)。
  5. // ============================================================================
  6. package main
  7. import (
  8. "encoding/binary"
  9. "fmt"
  10. "net"
  11. "time"
  12. "github.com/gen2brain/beeep"
  13. )
  14. // ntpEpoch NTP 时间戳起始时间(1900-01-01)
  15. var ntpEpoch = time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC)
  16. // queryNTP 向 NTP 服务器查询,返回服务器 UTC 时间
  17. func queryNTP(server string) (time.Time, error) {
  18. conn, err := net.DialTimeout("udp", server+":123", 3*time.Second)
  19. if err != nil {
  20. return time.Time{}, err
  21. }
  22. defer conn.Close()
  23. conn.SetDeadline(time.Now().Add(3 * time.Second))
  24. // 48 字节请求包: LI=0, VN=4, Mode=3(client)
  25. req := make([]byte, 48)
  26. req[0] = 0x1B
  27. if _, err := conn.Write(req); err != nil {
  28. return time.Time{}, err
  29. }
  30. resp := make([]byte, 48)
  31. if _, err := conn.Read(resp); err != nil {
  32. return time.Time{}, err
  33. }
  34. secs := binary.BigEndian.Uint32(resp[40:44])
  35. frac := binary.BigEndian.Uint32(resp[44:48])
  36. return ntpEpoch.Add(time.Duration(secs)*time.Second +
  37. time.Duration(frac)*1e9/(1<<32)), nil
  38. }
  39. // syncSystemTime 从 NTP 获取 UTC 时间,按系统当前时区换算后校准本地时钟(校准需管理员权限)
  40. func syncSystemTime() error {
  41. before := time.Now()
  42. fmt.Fprintf(apiLogRing, "[SYS] 开始同步系统时间 | 当前 %s\n", before.Format("2006-01-02 15:04:05.000"))
  43. var serverTime time.Time
  44. var lastErr error
  45. for _, server := range []string{"ntp.aliyun.com", "ntp.tencent.com", "time.windows.com"} {
  46. t, err := queryNTP(server)
  47. if err == nil {
  48. serverTime = t
  49. fmt.Fprintf(apiLogRing, "[SYS] NTP 服务器 %s 响应成功\n", server)
  50. break
  51. }
  52. lastErr = err
  53. }
  54. if serverTime.IsZero() {
  55. fmt.Fprintf(apiLogRing, "[SYS] 所有 NTP 服务器均不可达: %v\n", lastErr)
  56. return fmt.Errorf("无法连接 NTP 服务器: %v", lastErr)
  57. }
  58. // 关键:按系统当前时区(如 China Standard Time UTC+8)换算为本地时间
  59. serverLocal := serverTime.In(before.Location())
  60. drift := serverLocal.Sub(before)
  61. absDrift := drift
  62. if absDrift < 0 {
  63. absDrift = -absDrift
  64. }
  65. if absDrift < time.Second {
  66. fmt.Fprintf(apiLogRing, "[SYS] 时钟偏差 %v,无需校准\n", absDrift.Round(time.Millisecond))
  67. _ = beeep.Notify("🕐 时间已精准", fmt.Sprintf("时钟偏差 %v,无需校准", absDrift.Round(time.Millisecond)), "")
  68. return nil
  69. }
  70. if err := setLocalTime(serverLocal); err != nil {
  71. fmt.Fprintf(apiLogRing, "[SYS] 校准失败: %v\n", err)
  72. return err
  73. }
  74. after := time.Now()
  75. fmt.Fprintf(apiLogRing, "[SYS] 校准成功 | 偏差 %v | %s → %s\n",
  76. drift.Round(time.Millisecond),
  77. before.Format("15:04:05"), after.Format("15:04:05"))
  78. _ = beeep.Notify("🕐 时间同步成功",
  79. fmt.Sprintf("偏差: %v\n校准前: %s\n校准后: %s",
  80. drift.Round(time.Millisecond),
  81. before.Format("15:04:05"), after.Format("15:04:05")), "")
  82. return nil
  83. }