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