Files
5.24BuYu/Assets/Framework/Tool/BeijingTimeManager.cs
T

251 lines
9.9 KiB
C#
Raw Normal View History

2026-03-18 17:26:14 +08:00
using Newtonsoft.Json.Linq;
2026-07-27 23:30:25 +08:00
using System;
using System.Collections;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Networking;
2026-03-09 09:25:01 +08:00
/// <summary>
2026-07-25 16:13:21 +08:00
/// 北京时间管理器 - 同步时记录服务器时间与本地系统时间的差值,获取时返回本地时间+差值
2026-03-09 09:25:01 +08:00
/// </summary>
public class BeijingTimeManager : MonoSingleton<BeijingTimeManager>
{
[Header("时间状态")]
2026-03-18 17:26:14 +08:00
[SerializeField] private bool _isTimeSynced = false;
2026-07-25 16:13:21 +08:00
[SerializeField] private TimeSpan _serverTimeOffset; // 服务器时间 - 本地系统时间
2026-03-09 09:25:01 +08:00
2026-07-27 23:30:25 +08:00
[Header("定时同步")]
[Tooltip("定时同步间隔(秒),本地系统时钟会漂移,需要定期校正")]
[SerializeField] private float _syncInterval = 30f;
[Tooltip("offset 平滑系数(0~1),越大越偏向新测量值,抗 RTT 抖动")]
[SerializeField] private float _smoothFactor = 0.3f;
private Coroutine _autoSyncCoroutine;
2026-03-18 17:26:14 +08:00
/// <summary>
/// 获取当前北京时间
/// </summary>
2026-03-09 09:25:01 +08:00
public DateTime CurrentBeijingTime
{
get
{
if (_isTimeSynced)
{
2026-07-25 16:13:21 +08:00
return DateTime.Now + _serverTimeOffset;
2026-03-09 09:25:01 +08:00
}
2026-03-18 17:26:14 +08:00
return DateTime.Now; // 未同步时返回本地时间
2026-03-09 09:25:01 +08:00
}
}
/// <summary>
2026-03-18 17:26:14 +08:00
/// 获取时间戳(秒)- 1970-01-01 08:00:00 到现在的秒数
/// </summary>
public long CurrentTimestamp
{
get
{
DateTime beijingTime = CurrentBeijingTime;
DateTime epoch = new DateTime(1970, 1, 1, 8, 0, 0); // 北京时间起点
return (long)(beijingTime - epoch).TotalSeconds;
}
}
/// <summary>
/// 获取时间戳(毫秒)
/// </summary>
public long CurrentTimestampMs
{
get
{
DateTime beijingTime = CurrentBeijingTime;
DateTime epoch = new DateTime(1970, 1, 1, 8, 0, 0); // 北京时间起点
return (long)(beijingTime - epoch).TotalMilliseconds;
}
}
public bool IsTimeSynced => _isTimeSynced;
public event Action<DateTime> OnTimeSynced;
private void Start()
{
SyncBeijingTime();
2026-07-27 23:30:25 +08:00
// 启动定时同步,修正本地系统时钟漂移
if (_autoSyncCoroutine == null)
_autoSyncCoroutine = StartCoroutine(AutoSyncLoop());
}
/// <summary>
/// 定时同步循环
/// </summary>
private IEnumerator AutoSyncLoop()
{
while (true)
{
yield return new WaitForSeconds(_syncInterval);
SyncBeijingTime();
}
}
private void OnDestroy()
{
if (_autoSyncCoroutine != null)
StopCoroutine(_autoSyncCoroutine);
2026-03-18 17:26:14 +08:00
}
/// <summary>
/// 同步北京时间
2026-03-09 09:25:01 +08:00
/// </summary>
2026-07-27 23:30:25 +08:00
public void SyncBeijingTime(Action action=null)
2026-03-09 09:25:01 +08:00
{
2026-07-27 23:30:25 +08:00
StartCoroutine(FetchBeijingTime(action));
2026-03-09 09:25:01 +08:00
}
2026-07-27 23:30:25 +08:00
private IEnumerator FetchBeijingTime(Action action = null)
2026-03-09 09:25:01 +08:00
{
string[] timeServers = new string[]
{
2026-07-26 22:00:38 +08:00
"http://47.109.132.12/api/Login/GetDateTime",
2026-05-01 21:59:16 +08:00
"https://timeapi.io/api/Time/current/zone?timeZone=Asia/Shanghai",
"http://worldtimeapi.org/api/timezone/Asia/Shanghai",
"https://quan.suning.com/getSysTime.do"
2026-03-09 09:25:01 +08:00
};
foreach (string url in timeServers)
{
2026-07-27 16:59:22 +08:00
// 记录请求开始时间,用于 RTT 补偿
DateTime requestStartTime = DateTime.Now;
2026-05-01 21:59:16 +08:00
using (UnityWebRequest request = UnityWebRequest.Get(url + "?_=" + DateTime.Now.Ticks))
2026-03-09 09:25:01 +08:00
{
2026-03-18 17:26:14 +08:00
request.timeout = 5;
2026-03-09 09:25:01 +08:00
yield return request.SendWebRequest();
2026-07-27 16:59:22 +08:00
// 收到响应的本地时间
DateTime requestEndTime = DateTime.Now;
2026-03-09 09:25:01 +08:00
2026-03-18 17:26:14 +08:00
if (request.result == UnityWebRequest.Result.Success)
2026-03-09 09:25:01 +08:00
{
2026-05-01 21:59:16 +08:00
string jsonText = request.downloadHandler.text;
Debug.Log($"返回原始数据:{jsonText}");
if (!jsonText.TrimStart().StartsWith("{"))
{
Debug.LogWarning("返回的不是 JSON,跳过该源");
continue;
}
2026-03-18 17:26:14 +08:00
try
2026-03-09 09:25:01 +08:00
{
2026-05-01 21:59:16 +08:00
JObject json = JObject.Parse(jsonText);
2026-07-27 16:59:22 +08:00
DateTime? serverTime = null;
2026-05-01 21:59:16 +08:00
2026-07-26 22:00:38 +08:00
if (url.Contains("47.109.132.12"))
{
2026-07-27 16:59:22 +08:00
// 返回格式:{"data":{"dateTime":"2026-07-26 20:02:48","milliSeconds":330,...},"code":0,"info":null}
// dateTime 只精确到秒,需要补 milliSeconds 字段
2026-07-26 22:00:38 +08:00
string timeStr = json["data"]?["dateTime"]?.ToString();
2026-07-27 16:59:22 +08:00
int ms = json["data"]?["milliSeconds"]?.Value<int>() ?? 0;
Debug.Log($"47.109.132.12 提取到的时间字符串:{timeStr} 毫秒:{ms}");
2026-07-26 22:00:38 +08:00
2026-07-27 23:30:25 +08:00
// 把服务器时间变成毫秒级时间戳打印
2026-07-26 22:00:38 +08:00
if (!string.IsNullOrEmpty(timeStr) &&
2026-07-27 16:59:22 +08:00
DateTime.TryParseExact(timeStr, "yyyy-MM-dd HH:mm:ss", null, System.Globalization.DateTimeStyles.None, out DateTime t))
2026-07-26 22:00:38 +08:00
{
2026-07-27 23:30:25 +08:00
DateTime serverTimeWithMs = t.AddMilliseconds(ms);
DateTime epoch = new DateTime(1970, 1, 1, 8, 0, 0);
long timestampMs = (long)(serverTimeWithMs - epoch).TotalMilliseconds;
Debug.Log($"47.109.132.12 服务器时间毫秒级时间戳:{timestampMs}");
}
if (!string.IsNullOrEmpty(timeStr) &&
DateTime.TryParseExact(timeStr, "yyyy-MM-dd HH:mm:ss", null, System.Globalization.DateTimeStyles.None, out DateTime t2))
{
serverTime = t2.AddMilliseconds(ms);
2026-07-26 22:00:38 +08:00
}
else
{
Debug.LogWarning($"无法解析时间字符串:{timeStr}");
}
}
else if (url.Contains("timeapi.io"))
2026-05-01 21:59:16 +08:00
{
string timeStr = json["dateTime"]?.ToString();
Debug.Log(timeStr);
if (!string.IsNullOrEmpty(timeStr))
{
2026-07-27 16:59:22 +08:00
serverTime = DateTime.Parse(timeStr);
2026-05-01 21:59:16 +08:00
}
}
else if (url.Contains("worldtimeapi"))
{
string timeStr = json["datetime"]?.ToString();
Debug.Log(timeStr);
if (!string.IsNullOrEmpty(timeStr))
{
2026-07-27 16:59:22 +08:00
serverTime = DateTimeOffset.Parse(timeStr)
2026-05-01 21:59:16 +08:00
.ToOffset(TimeSpan.FromHours(8))
.DateTime;
}
}
else if (url.Contains("suning"))
2026-03-09 09:25:01 +08:00
{
2026-03-18 17:26:14 +08:00
string timeStr = json["sysTime2"]?.ToString();
2026-05-01 21:59:16 +08:00
Debug.Log(timeStr);
2026-03-18 17:26:14 +08:00
if (!string.IsNullOrEmpty(timeStr))
{
2026-07-27 16:59:22 +08:00
serverTime = DateTime.ParseExact(timeStr, "yyyy-MM-dd HH:mm:ss", null);
2026-03-18 17:26:14 +08:00
}
2026-03-09 09:25:01 +08:00
}
2026-07-27 16:59:22 +08:00
if (serverTime.HasValue)
{
// RTT 补偿:服务器生成响应到客户端接收耗时约为 RTT/2(假设网络对称)
// 补偿后服务器时间 = 服务器响应时间 + RTT/2
TimeSpan rtt = requestEndTime - requestStartTime;
DateTime compensatedServerTime = serverTime.Value + TimeSpan.FromTicks(rtt.Ticks / 2);
2026-07-27 23:30:25 +08:00
TimeSpan measuredOffset = compensatedServerTime - requestEndTime;
// offset 平滑(低通滤波):抗 RTT 抖动,同时跟随系统时钟漂移
// 首次同步直接采用,后续按平滑系数加权
if (_isTimeSynced)
{
double smoothed = _serverTimeOffset.TotalMilliseconds * (1 - _smoothFactor)
+ measuredOffset.TotalMilliseconds * _smoothFactor;
_serverTimeOffset = TimeSpan.FromMilliseconds(smoothed);
}
else
{
_serverTimeOffset = measuredOffset;
_isTimeSynced = true;
}
Debug.Log($"北京时间同步成功:{CurrentBeijingTime:yyyy-MM-dd HH:mm:ss.fff},偏移量:{_serverTimeOffset.TotalSeconds:F3}s,本次测量:{measuredOffset.TotalSeconds:F3}s,RTT:{rtt.TotalSeconds:F3}s");
2026-07-27 16:59:22 +08:00
OnTimeSynced?.Invoke(CurrentBeijingTime);
2026-07-27 23:30:25 +08:00
action?.Invoke();
2026-07-27 16:59:22 +08:00
yield break;
}
2026-03-09 09:25:01 +08:00
}
2026-03-18 17:26:14 +08:00
catch (Exception e)
{
Debug.LogWarning($"解析失败:{e.Message}");
continue;
}
2026-03-09 09:25:01 +08:00
}
}
}
2026-03-18 17:26:14 +08:00
Debug.LogError("所有时间服务器都失败了,使用本地时间");
2026-07-25 16:13:21 +08:00
_serverTimeOffset = TimeSpan.Zero;
2026-03-18 17:26:14 +08:00
_isTimeSynced = false;
2026-03-09 09:25:01 +08:00
}
2026-05-01 21:59:16 +08:00
2026-03-09 09:25:01 +08:00
/// <summary>
2026-03-18 17:26:14 +08:00
/// 获取格式化的北京时间
2026-03-09 09:25:01 +08:00
/// </summary>
2026-03-18 17:26:14 +08:00
public string GetFormattedTime(string format = "yyyy-MM-dd HH:mm:ss")
2026-03-09 09:25:01 +08:00
{
2026-03-18 17:26:14 +08:00
return CurrentBeijingTime.ToString(format);
2026-03-09 09:25:01 +08:00
}
}