在企业级客户支持与内部 IT 协作场景中,工单系统(Ticketing Subsystem)面临复杂的业务流转与履约考核挑战:
- SLA 履约违约风险(SLA Breach):高优先级工单(如 P0 故障)若在 15 分钟内未响应,必须自动触发升级调度与多渠道催办通知;
- 并发指派与工单状态冲突(Concurrent Assignment Conflict):多名客服同时点击“抢单”或转派给不同工程师,若无并发版本控制会导致状态混乱;
- 多租户数据穿透防护:租户 A 的工单及其内部附件绝不能被租户 B 查询或下载。
BitzOrcas.Modern Ticketing 模块以“统一聚合根金样板 + SLA 状态机 + 事件驱动通知”构建了现代化服务台底座。
工单生命周期流转与 SLA 考核拓扑
第一步:核心领域聚合根 Ticket
using System;using System.ComponentModel;using BitzOrcas.Domain.Entities;using BitzOrcas.Domain.Results;using BitzOrcas.Persistence.Metadata;
namespace BitzOrcas.Ticket.Domain;
public static class TicketErrors{ public static readonly Error AlreadyClosed = Error.Conflict("Ticket.AlreadyClosed", "已关闭的工单无法指派。");
public static readonly Error NotFound = Error.NotFound("Ticket.NotFound", "工单不存在。");}
[BitzTable("SysTicket", IsTenant = true, IsSoftDelete = true, Description = "业务工单主表")][BitzIndex("IX_SysTicket_Status_Tenant", nameof(Status), nameof(TenantId))]public sealed class Ticket : TenantAggregateRoot<string>{ [BitzColumn(Length = 200, IsRequired = true)] public string Title { get; private set; } = string.Empty;
[BitzColumn(Length = 4000, IsRequired = true)] public string Description { get; private set; } = string.Empty;
[BitzColumn(IsRequired = true)] public TicketPriority Priority { get; private set; } = TicketPriority.Medium;
[BitzColumn(IsRequired = true)] public TicketStatus Status { get; private set; } = TicketStatus.Open;
[BitzColumn(Length = 64)] public string? AssigneeUserId { get; private set; }
[BitzColumn(IsRequired = true)] public DateTimeOffset SlaResponseDueAt { get; private set; }
[Obsolete("仅供 ORM 持久化物化使用。请使用 Create 工厂方法。", error: true)] [EditorBrowsable(EditorBrowsableState.Never)] public Ticket() : base("0") { }
// 领域方法:指派工单并进入处理中 public Result AssignTo(string engineerUserId, string operatorId, DateTimeOffset now) { if (Status == TicketStatus.Closed || Status == TicketStatus.Resolved) { return Result.Failure(TicketErrors.AlreadyClosed); }
AssigneeUserId = engineerUserId; Status = TicketStatus.InProgress;
// 产生指派领域事件 AddDomainEvent(new TicketAssignedDomainEvent(Id, TenantId, engineerUserId, operatorId, now));
return Result.Success(); }}第二步:指派工单命令垂直切片(AssignTicketCommandHandler)
using System.Threading;using System.Threading.Tasks;using BitzOrcas.Application.Abstractions.Security;using BitzOrcas.Domain.Abstractions;using BitzOrcas.Domain.Results;using BitzOrcas.Ticket.Domain;
public sealed class AssignTicketCommandHandler( ICommandRepository<Ticket, string> ticketRepo, ICurrentUser currentUser, IAppClock clock){ public async ValueTask<Result> Handle(AssignTicketCommand command, CancellationToken ct) { // 1. 加载目标工单聚合 var ticketResult = await ticketRepo.FindAsync(command.TicketId, ct); if (ticketResult.IsFailure) { return Result.Failure(TicketErrors.NotFound); }
var ticket = ticketResult.Value;
// 2. 执行领域指派 var assignResult = ticket.AssignTo(command.AssigneeUserId, currentUser.UserId, clock.UtcNow); if (assignResult.IsFailure) return assignResult;
// 3. 持久化聚合(自动检查乐观并发版本,由 TransactionPipeline 提交) await ticketRepo.UpdateAsync(ticket, ct);
return Result.Success(); }}总结
Ticketing 模块为客服与售后赋能:
- 乐观锁防并发抢单:并发修改自动检测版本冲突并返回 409;
- 全链路 SLA 监控:基于时钟端口精准计算超时升级;
- 事件驱动多渠道通知:状态变更毫秒级通知客户与工程师。