using SimpleCore; using SimpleCore.Library; using StandardScene.Utils; using System; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; using StandardScene.Model; namespace StandardScene.Chained { /// /// 统一管理 TransportDelivery 相关的任务回调方法,并通过回调注册表提供可恢复性。 /// public static class TransportDeliveryCallbacks { public const string KeyOnStarted = "Transport.OnMissionStarted"; public const string KeyOnFetched = "Transport.OnFetched"; public const string KeyOnPut = "Transport.OnPut"; public const string KeyOnFinished = "Transport.OnMissionFinished"; public const string KeyOnFailed = "Transport.OnMissionFailed"; public const string KeyOnTerminated = "Transport.OnMissionTerminated"; private static bool _initialized; private static readonly HttpClient _httpClient = new HttpClient(); private static string _callbackUrl = "http://127.0.0.1:20101/api/v1/MDCS/State"; static TransportDeliveryCallbacks() { if (_initialized) return; _initialized = true; DeliveryCallbackRegistry.RegisterOnStart (KeyOnStarted, OnMissionStarted); DeliveryCallbackRegistry.RegisterDoneFetch (KeyOnFetched, OnFetched); DeliveryCallbackRegistry.RegisterDonePut (KeyOnPut, OnPut); DeliveryCallbackRegistry.RegisterDoneMission(KeyOnFinished, OnMissionFinished); DeliveryCallbackRegistry.RegisterFailed (KeyOnFailed, OnMissionFailed); DeliveryCallbackRegistry.RegisterOnTerminated(KeyOnTerminated, OnMissionTerminated); } /// /// 显式调用以确保静态构造函数已执行(注册所有默认回调)。 /// public static void EnsureInitialized() { // 访问本类,确保静态构造已执行 if (!_initialized) { // 触发 static ctor(CLR 保证线程安全) System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(TransportDeliveryCallbacks).TypeHandle); } } /// /// 配置任务状态回调的完整 URL(例如 http://ip:port/api/v1/MDCS/State)。 /// 建议在 TransportMission 启动或构造时调用一次。 /// public static void ConfigureCallbackUrl(string url) { if (!string.IsNullOrWhiteSpace(url)) { _callbackUrl = url; } } private static void DispatchMissionState(TransportDelivery d, MissionState.MissionStateEnum state) { var ms = new MissionState { MissionId = d.TaskId, CarCode = d.UsingCar?.id.ToString() ?? "0", TriggerTime = DateTime.Now, State = state }; MissionStatePost(ms); } private static async void MissionStatePost(MissionState ms) { var retryCount = 10; var retryDelay = TimeSpan.FromSeconds(1); var content = new StringContent(ms.ToJson()); content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); for (int i = 0; i < retryCount; i++) { try { var resp = await _httpClient.PostAsync(_callbackUrl, content); Diagnosis.Post($"{ms.State}回调结果 => {resp.Content.ReadAsStringAsync().Result}"); var body = resp.Content.ReadAsStringAsync().Result.JsonTo(); var result = body.Code == 200 ? "成功" : "失败"; Diagnosis.Post($"车辆编号:{ms.CarCode} 任务id:{ms.MissionId} 状态回调执行{result}"); break; } catch (Exception ex) { Diagnosis.Log($"{ms.State}状态回调失败 => ex:{ex}"); if (i == retryCount - 1) { Diagnosis.Log($"StringContent:{content}", "apiError"); } await Task.Delay(retryDelay); } } } public static async void OnMissionStarted(ChainedDeliveryMission.Delivery delivery) { var d = (TransportDelivery)delivery; Diagnosis.Log($"task(#{d.TaskId}) started"); if (!string.IsNullOrWhiteSpace(d.TaskIdsString)) { Commons.AddOrUpdateTag(d.UsingCar.tags, "taskIdsString", d.TaskIdsString); Diagnosis.Log( $"holdCarTagAddOrUpdate--TaskId:{d.TaskId},task:[{d.ToJson()}].taskIdsString:{d.TaskIdsString}", "holdCarTag", true); } Commons.DeleteTag(d.UsingCar.tags, "holdCar"); if (!string.IsNullOrEmpty(d.HoldCarSite)) { Commons.AddOrUpdateTag(d.UsingCar.tags, "holdCar", d.HoldCarSite); Diagnosis.Log( $"holdCarTagAddOrUpdate--TaskId:{d.TaskId},task:[{d.ToJson()}].holdCar:{d.HoldCarSite}", "holdCarTag", true); } DispatchMissionState(d, MissionState.MissionStateEnum.Started); } public static async void OnFetched(ChainedDeliveryMission.Delivery delivery) { var d = (TransportDelivery)delivery; if (d.Src == d.Dst) return; Diagnosis.Log($"task(#{d.TaskId}) fetched"); DispatchMissionState(d, MissionState.MissionStateEnum.Fetched); } public static async void OnPut(ChainedDeliveryMission.Delivery delivery) { var d = (TransportDelivery)delivery; if (d.Src == d.Dst) return; Diagnosis.Log($"task(#{d.TaskId}) put"); DispatchMissionState(d, MissionState.MissionStateEnum.Put); } public static async void OnMissionFailed(ChainedDeliveryMission.Delivery delivery) { var d = (TransportDelivery)delivery; Diagnosis.Log($"task(#{d.TaskId}) failed"); DispatchMissionState(d, MissionState.MissionStateEnum.Failed); } public static async void OnMissionFinished(ChainedDeliveryMission.Delivery delivery) { var d = (TransportDelivery)delivery; Diagnosis.Log($"task(#{d.TaskId}) finished"); if (!string.IsNullOrWhiteSpace(d.MGTaskCode!) && d.MGTaskCode.Contains("-")) { var phase = d.MGTaskCode.Split('-')[1]; var last = int.Parse(phase) - 1; if (last > 0 && string.IsNullOrWhiteSpace(d.TaskIdsString)) { var holdCar = SimpleLib.GetAllCars() .FirstOrDefault(e => e.tags.TryGetValue("taskIdsString", out var v) && v.Contains(d.TaskId)); if (holdCar != null) { Commons.DeleteTag(holdCar.tags, "taskIdsString"); Diagnosis.Log( $"holdCarTagDelete-2:OnMissionFinished--TaskId:{d.TaskId},task:[{d.ToJson()}].taskIdsString:{d.TaskIdsString}", "holdCarTag", true); } } } DispatchMissionState(d, MissionState.MissionStateEnum.Finished); } public static async Task OnMissionTerminated(ChainedDeliveryMission.Delivery delivery, string ISRelease) { try { using var hc = new HttpClient(); var task = await hc.GetAsync($"http://{delivery.UsingCar.address}:8008/car/startOrPause?ISRelease={ISRelease}"); var resp = task.Content.ReadAsStringAsync().Result.JsonTo(); var result = resp.Code == 200 ? "成功" : "失败"; return 1; } catch (Exception ex) { Diagnosis.Log($"暂停恢复失败 => ex:{ExceptionFormatter.FormatEx(ex)}"); return 0; } } } }