using OrderSupportTicket.Models; using OrderSupportTicket.Models.Api; using System; using System.Threading.Tasks; namespace OrderSupportTicket.Services; /// /// 工单业务服务,聚合工单 API 客户端与 SSE 实时服务。 /// public sealed class TicketService : IDisposable { private readonly TicketApiService _api; private readonly EventSourceTicketRealtimeService _realtime; private bool _disposed; /// /// 创建使用默认依赖的工单业务服务。 /// public TicketService(bool ownsRealtime = true) : this( new TicketApiService(), new EventSourceTicketRealtimeService(), ownsRealtime) { } /// /// 创建工单业务服务。 /// /// 工单 API 客户端。 /// 工单实时通知服务。 /// /// 是否由当前实例负责实时连接的停止与释放;共享连接应传入 false。 /// public TicketService( TicketApiService api, EventSourceTicketRealtimeService realtime, bool ownsRealtime = true) { _api = api ?? throw new ArgumentNullException(nameof(api)); _realtime = realtime ?? throw new ArgumentNullException(nameof(realtime)); OwnsRealtime = ownsRealtime; _realtime.NotificationReceived += OnNotificationReceived; _realtime.StateChanged += OnStateChanged; } /// /// 收到工单变更摘要通知时触发。 /// public event EventHandler NotificationReceived; /// /// SSE 连接状态变化时触发。 /// public event EventHandler StateChanged; /// /// 当前 SSE 连接状态。 /// public TicketRealtimeState RealtimeState => _realtime.State; /// /// 是否由当前服务实例管理实时连接的生命周期。 /// public bool OwnsRealtime { get; } /// /// 校验工单页运行参数。 /// public void ValidateOptions(TicketClientOptions options) { ThrowIfDisposed(); TicketApiService.ValidateOptions(options); } /// /// 查询工单摘要列表。 /// public Task GetWorkOrdersAsync( TicketClientOptions options, string wechatOrderId = null, string status = null, int limit = 20, string cursor = null) { ThrowIfDisposed(); return _api.GetWorkOrdersAsync( options, wechatOrderId, status, limit, cursor); } /// /// 查询指定订单的公开留言板。 /// public Task GetMessagesAsync( TicketClientOptions options, string appId, string wechatOrderId, string before = null, int limit = 50) { ThrowIfDisposed(); return _api.GetMessagesAsync( options, appId, wechatOrderId, before, limit); } /// /// 提交问题或创建工单。 /// public Task CreateWorkOrderAsync( TicketClientOptions options, TicketOrderOption order, string description, string clientRequestId = null) { ThrowIfDisposed(); return _api.CreateWorkOrderAsync( options, order, description, clientRequestId); } /// /// 提交客户留言。 /// public Task SendMessageAsync( TicketClientOptions options, WorkOrderSummaryDto workOrder, string text, string clientMessageId, IList attachments = null) { ThrowIfDisposed(); return _api.SendMessageAsync( options, workOrder, text, clientMessageId, attachments); } /// /// 申请单张图片的短时直传凭证。 /// public Task CreateImageUploadTicketAsync( TicketClientOptions options, string appId, string wechatOrderId, string fileName, string mimeType, long size, string requestId = null) { ThrowIfDisposed(); return _api.CreateImageUploadTicketAsync( options, appId, wechatOrderId, fileName, mimeType, size, requestId); } /// /// 将图片二进制直接上传到 COS,返回留言接口需要的回执。 /// public Task UploadImageAsync( ImageUploadTicketDto upload, string filePath) { ThrowIfDisposed(); return _api.UploadImageAsync(upload, filePath); } /// /// 标记当前订单留言已读。 /// public Task MarkReadAsync( TicketClientOptions options, string appId, string wechatOrderId, int throughSequenceNo) { ThrowIfDisposed(); return _api.MarkReadAsync( options, appId, wechatOrderId, throughSequenceNo); } /// /// 使用指定工单参数建立 SSE 连接。 /// public async Task StartRealtime(TicketClientOptions options) { ThrowIfDisposed(); ValidateOptions(options); await _realtime.Start(options); } /// /// 停止 SSE 连接;共享连接由宿主管理时,调用方应先检查 。 /// public Task StopRealtimeAsync() { ThrowIfDisposed(); return _realtime.StopAsync(); } /// /// 停止并重新建立 SSE 连接。 /// public async Task RestartRealtimeAsync(TicketClientOptions options) { ThrowIfDisposed(); await _realtime.StopAsync(); ThrowIfDisposed(); await StartRealtime(options); } public void Dispose() { if (_disposed) return; _disposed = true; _realtime.NotificationReceived -= OnNotificationReceived; _realtime.StateChanged -= OnStateChanged; if (OwnsRealtime) _realtime.Dispose(); } private void OnNotificationReceived( object sender, TicketRealtimeNotificationEventArgs args) { NotificationReceived?.Invoke(this, args); } private void OnStateChanged( object sender, TicketRealtimeStateChangedEventArgs args) { StateChanged?.Invoke(this, args); } private void ThrowIfDisposed() { if (_disposed) throw new ObjectDisposedException(nameof(TicketService)); } }