using System.Text.Json; namespace XFEEDToolBox.Core.Execution; public sealed record TradeJourneyCheckpoint(string Commander,long ShipId,DateTimeOffset StartedAt, ConfirmedTradeReceipt? Bought=null,ConfirmedTradeReceipt? Sold=null); /// Durable journey state; reloading never changes a confirmed order's identity or session. public static class TradeJourneyCheckpoints { private static readonly JsonSerializerOptions Json=new(JsonSerializerDefaults.Web){WriteIndented=true}; public static TradeJourneyCheckpoint Load(string path) { var value=JsonSerializer.Deserialize(File.ReadAllText(path),Json) ??throw new InvalidDataException("跑商恢复记录为空。"); Validate(value);return value; } public static void Save(string path,TradeJourneyCheckpoint value) { Validate(value);path=Path.GetFullPath(path);Directory.CreateDirectory(Path.GetDirectoryName(path)!); var temporary=path+"."+Guid.NewGuid().ToString("N")+".tmp"; try { using(var file=new FileStream(temporary,FileMode.CreateNew,FileAccess.Write,FileShare.Read,4096,FileOptions.WriteThrough)) {JsonSerializer.Serialize(file,value,Json);file.Flush(true);} File.Move(temporary,path,true); } finally{if(File.Exists(temporary))File.Delete(temporary);} } private static void Validate(TradeJourneyCheckpoint value) { if(string.IsNullOrWhiteSpace(value.Commander)||value.ShipId<0||value.StartedAt==default||value.Sold is not null&&value.Bought is null) throw new InvalidDataException("跑商恢复记录缺少原始指挥官、飞船或买入回执。"); void Receipt(ConfirmedTradeReceipt receipt,bool buy) { if(!Guid.TryParseExact(receipt.OperationKey,"N",out _)||!receipt.ServerConfirmed||!receipt.ClientApplied ||receipt.CommanderId!=value.Commander||receipt.ShipId!=value.ShipId||receipt.MarketId<=0||string.IsNullOrWhiteSpace(receipt.Commodity) ||(buy?receipt.QuantityDelta<=0:receipt.QuantityDelta>=0)||receipt.UnitPrice<=0||receipt.CreditsBefore<0||receipt.CreditsAfter<0 ||receipt.CargoBefore<0||receipt.CargoAfter<0||(long)receipt.CargoAfter-receipt.CargoBefore!=receipt.QuantityDelta ||(decimal)receipt.CreditsAfter-receipt.CreditsBefore!=-(decimal)receipt.UnitPrice*receipt.QuantityDelta ||receipt.SessionStartedAt==default||receipt.SessionStartedAt>receipt.ConfirmedAt||receipt.ConfirmedAt