事件現(xiàn)象
在進行 公司 asp.net core 項目開發(fā)的時候, 出現(xiàn)一個有趣的bug, 有的接口返回的 datetime 類型為后綴帶Z eg `2021-11-24T12:31:50.2246113Z` 的 UTC時間, 有的接口返回的是 正常的 YYYY:MM:DD HH:mm:ss eg:2021-11-24 20:31:56. 這種現(xiàn)象導致前端進行適配的時候 非常困難.
但是因為業(yè)務中對時分秒的要求沒有太高, 所以這個現(xiàn)象一直沒有引起太大的注意, 直到最近開始注意到這個現(xiàn)象
調(diào)查過程
- 一開始很自然的以為是 Newtonsoft.json 或者 System.Text.json 序列化的問題. 因為是公司封裝了一個 Controller base類型. 替換了 asp.net core ActionResult. 但是經(jīng)過幾番排查和試錯發(fā)現(xiàn)這種現(xiàn)象還是存在. 事情進入死胡同
- 然后就發(fā)現(xiàn)經(jīng)過ef core 加載出來的數(shù)據(jù)的 Datetime 類型的 DatetimeKind 是Utc. 這里應該是local 才對. 然后就懷疑是不是 ef core 和 MSSQL 交互的時候出現(xiàn)了什么問題.
- 但是翻了翻 efcore 的源碼 發(fā)現(xiàn)沒有什么地方 明確改變了Datetime 的Kind
- 于是只能去看這個接口的實現(xiàn)代碼, 發(fā)現(xiàn)有一層緩存. 那么很自然的懷疑到這一層緩存上面.
- 公司緩存數(shù)據(jù) 用的是 MessagePack 做格式化, 和 redis做存儲.
- 那么嫌疑人只能是 MessagePack 和 redis了
測試代碼
[Route("type")]
[ApiController]
public class MyDatetimeController : AmiController
{
private readonly ICache cache;
public MyDatetimeController(ICache cache)
{
this.cache = cache;
}
[Route("no-cache")]
public async Task<AmiResult> Get()
{
return Ok(new MyDatetime { Mydate = DateTime.Now, MydateUtc = DateTime.UtcNow });
}
[Route("cache")]
public async Task<AmiResult> GetCache()
{
var m = new MyDatetime { Mydate = DateTime.Now, MydateUtc = DateTime.UtcNow };
cache.Cache(CacheKey.DV, "testd", m);
cache.TryGetValue(CacheKey.DV, "testd", out m);
return Ok(m);
}
}
[MessagePack.MessagePackObject]
public class MyDatetime
{
[MessagePack.Key(1)]
public DateTime MydateUtc { get; set; }
[MessagePack.Key(2)]
public DateTime Mydate { get; set; }
}
測試結果
http://localhost:8011/amiapicore/api/type/no-cache
{
"content": {
"mydateUtc": "2021-11-24T12:31:56.1622685Z",
"mydate": "2021-11-24T20:31:56.1622681+08:00"
},
"errorCode": 0
}
http://localhost:8011/amiapicore/api/type/cache
{
"content": {
"mydateUtc": "2021-11-24T12:31:50.2246113Z",
"mydate": "2021-11-24T12:31:50.2245647Z"
},
"errorCode": 0
}
結論
- 很自然的看出, 是MessagePack 在序列化的時候有點不一樣,事實也確實如此 找到了 MessagePack的官方文檔, 上面寫著
DateTime is serialized to MessagePack Timestamp format, it serialize/deserialize UTC and loses Kind info and requires that MessagePackWriter.OldSpec == false. If you use the NativeDateTimeResolver, DateTime values will be serialized using .NET's native Int64 representation, which preserves Kind info but may not be interoperable with non-.NET platforms.
- 簡單翻譯一下就是 DatetTime 被序列化成了 MessagePack Timestamp 格式, 并去掉了 Datetime 的 Kind 信息 以便和其他的系統(tǒng)交流.
- 如果你想保留Kind 信息,只能使用 NativeDateTimeResolver, 但是這樣就只能與 .Net 平臺交互.
### 解決方案
```c#
[MessagePack.MessagePackObject]
public class MyDatetime
{
[MessagePack.Key(1)]
public DateTime MydateUtc { get; set; }
// 簡單的話就加上下面的Atrribute, 讓MessagePack 先不做格式轉(zhuǎn)換, 復雜的話就要和公司的業(yè)務結合起來考慮了;
[MessagePack.Key(2)]
[MessagePack.MessagePackFormatter(typeof(MessagePack.Formatters.NativeDateTimeFormatter))]
public DateTime Mydate { get; set; }
}
引用
MessagePack-Csharp