Ticketing 域统一了客服与运维工单系统的工单管理能力。Zendesk 和禅道(Zentao)两家供应商共享同一个 ITicketService 接口,但鉴权机制差异显著:Zendesk 用 Basic Auth(email/token:apiToken),禅道用 Session Token。
域结构
Bitzsoft.Integrations.Ticketing ← 抽象层├── Bitzsoft.Integrations.Ticketing.Zendesk ← Zendesk(Basic Auth)├── Bitzsoft.Integrations.Ticketing.Zentao ← 禅道(Session Token)└── Bitzsoft.Integrations.Ticketing.All ← 聚合包标准三层包模式。
统一接口(7 方法)
ITicketService 围绕”工单(Ticket)“这一核心概念组织,共 7 个方法:
public interface ITicketService{ string ProviderName { get; }
// 工单生命周期 Task<TicketInfo> CreateTicketAsync(CreateTicketRequest request, CancellationToken cancellationToken = default); Task<TicketInfo> GetTicketAsync(string ticketId, CancellationToken cancellationToken = default); Task<TicketInfo> UpdateTicketAsync(string ticketId, UpdateTicketRequest request, CancellationToken cancellationToken = default); Task AssignTicketAsync(string ticketId, string assigneeId, CancellationToken cancellationToken = default); Task ChangeTicketStatusAsync(string ticketId, TicketStatus status, CancellationToken cancellationToken = default); Task AddCommentAsync(string ticketId, AddCommentRequest request, CancellationToken cancellationToken = default); Task<List<TicketInfo>> ListTicketsAsync(ListTicketsRequest request, CancellationToken cancellationToken = default);}查询类方法(Create / Get / Update / List)返回 TicketInfo,操作类方法(Assign / ChangeStatus / AddComment)为无返回 Task,失败抛 TicketException。TicketInfo.Status 反映工单在供应商侧的当前状态。
鉴权模型
两家供应商使用截然不同的鉴权机制:
| 供应商 | 鉴权机制 | 凭证注入 | Token 刷新 |
|---|---|---|---|
| Zendesk | Basic Auth(email/token + apiToken) | Authorization: Basic {base64(email/token:apiToken)} 头 | 静态 API Token,不刷新 |
| 禅道 | 账号密码换 Session Token | Session Token(请求头或 query 参数) | POST /api.php/v1/tokens 换取,可缓存 |
供应商清单
| 供应商 | Provider ID | API 基地址 | Stability | DI 方法 |
|---|---|---|---|---|
| Zendesk | zendesk | {subdomain}.zendesk.com/api/v2 | Preview | AddZendeskTicket() |
| 禅道 | zentao | {your-host}/api.php/v1 | Preview | AddZentaoTicket() |
Zendesk 是国际主流客服工单平台,禅道是国内主流研发项目管理与 Bug 工单系统。
注册
单厂商
// ① Zendesk(Basic Auth:email/token + apiToken)builder.Services.AddZendeskTicket(builder.Configuration.GetSection("Ticketing:Zendesk"));
// ② 禅道(Session Token)builder.Services.AddZentaoTicket(builder.Configuration.GetSection("Ticketing:Zentao"));全量聚合
builder.Services.AddBitzsoftTicketingAll(builder.Configuration, "Ticketing");按配置节存在性自动注册,appsettings.json 只需写出启用的供应商:
{ "Ticketing": { "Zendesk": { "Subdomain": "yourcompany", "Email": "admin@example.com", "ApiToken": "..." }, "Zentao": { "BaseUrl": "https://zentao.example.com", "Username": "...", "Password": "..." } }}消费
创建工单
public class TicketCreateService(IIntegrationProviderResolver<ITicketService> providers){ public async Task<TicketInfo> OpenAsync(string vendor, string subject, string description, string requesterEmail) { var ticketing = providers.GetRequired(vendor); // "zendesk" / "zentao" return await ticketing.CreateTicketAsync(new CreateTicketRequest { Subject = subject, Description = description, RequesterEmail = requesterEmail, Priority = TicketPriority.Normal, }); }}分配、状态变更与评论
// 分配处理人await ticketing.AssignTicketAsync(ticketId, assigneeId: "user-123");
// 变更状态(如标记为已解决)await ticketing.ChangeTicketStatusAsync(ticketId, TicketStatus.Resolved);
// 追加内部备注或公开回复await ticketing.AddCommentAsync(ticketId, new AddCommentRequest{ Body = "已联系客户确认,问题复现已修复。", IsPublic = true,});查询工单
// 查询单份工单详情var detail = await ticketing.GetTicketAsync(ticketId);// detail.Status: New / Open / Pending / Resolved / Closed
// 按条件列表查询var list = await ticketing.ListTicketsAsync(new ListTicketsRequest{ Status = TicketStatus.Open, AssigneeId = "user-123", PageIndex = 1, PageSize = 20,});多厂商路由
public class TicketRouter(IIntegrationProviderResolver<ITicketService> providers){ public async Task<TicketInfo> GetAsync(string source, string ticketId) { var ticketing = providers.GetRequired(source); // "zendesk" / "zentao" return await ticketing.GetTicketAsync(ticketId); }}