名称: 内容工作流 描述: 用于实现编辑工作流、审批链、定时发布或基于角色的内容权限。涵盖无头CMS的工作流状态、转换、通知和工作流API。 允许工具: 读取、全局、搜索、任务、技能
内容工作流
用于在无头CMS系统中实现编辑工作流、审批流程和定时发布的指南。
何时使用此技能
- 实现多阶段审批工作流
- 添加定时/禁发发布
- 构建内容审核系统
- 定义基于角色的发布权限
- 自动化工作流通知
工作流状态
基本工作流状态
public enum WorkflowState
{
Draft, // 初始创建,作者编辑
InReview, // 提交审核
Approved, // 已批准,准备发布
Published, // 实时内容
Unpublished, // 从实时移除
Archived, // 长期存储
Rejected // 审核拒绝,需要修订
}
扩展工作流状态
public enum ExtendedWorkflowState
{
// 创建
Draft,
// 审核阶段
PendingEditorialReview,
EditorialApproved,
PendingLegalReview,
LegalApproved,
PendingFinalApproval,
// 发布
Scheduled,
Published,
// 发布后
Unpublished,
Expired,
Archived
}
状态机实现
工作流定义
public class WorkflowDefinition
{
public string Name { get; set; } = string.Empty;
public List<WorkflowStateDefinition> States { get; set; } = new();
public List<WorkflowTransition> Transitions { get; set; } = new();
}
public class WorkflowStateDefinition
{
public string Name { get; set; } = string.Empty;
public bool IsInitial { get; set; }
public bool IsFinal { get; set; }
public List<string> AllowedRoles { get; set; } = new();
public List<string> RequiredFields { get; set; } = new();
}
public class WorkflowTransition
{
public string Name { get; set; } = string.Empty;
public string FromState { get; set; } = string.Empty;
public string ToState { get; set; } = string.Empty;
public List<string> AllowedRoles { get; set; } = new();
public bool RequiresComment { get; set; }
public List<string> Notifications { get; set; } = new();
}
状态机服务
public class WorkflowService
{
public async Task<bool> CanTransitionAsync(
ContentItem content,
string transition,
ClaimsPrincipal user)
{
var workflow = await GetWorkflowAsync(content.ContentType);
var transitionDef = workflow.Transitions
.FirstOrDefault(t => t.Name == transition
&& t.FromState == content.WorkflowState);
if (transitionDef == null)
return false;
// 检查角色权限
var userRoles = user.Claims
.Where(c => c.Type == ClaimTypes.Role)
.Select(c => c.Value);
return transitionDef.AllowedRoles
.Any(r => userRoles.Contains(r));
}
public async Task TransitionAsync(
Guid contentId,
string transition,
string? comment,
ClaimsPrincipal user)
{
var content = await _repository.GetAsync(contentId);
if (!await CanTransitionAsync(content!, transition, user))
throw new WorkflowException("转换不允许");
var workflow = await GetWorkflowAsync(content!.ContentType);
var transitionDef = workflow.Transitions
.First(t => t.Name == transition);
// 验证目标状态的必填字段
var targetState = workflow.States
.First(s => s.Name == transitionDef.ToState);
await ValidateRequiredFieldsAsync(content, targetState);
// 记录转换
var history = new WorkflowHistory
{
Id = Guid.NewGuid(),
ContentItemId = contentId,
FromState = content.WorkflowState,
ToState = transitionDef.ToState,
Transition = transition,
Comment = comment,
UserId = user.GetUserId(),
OccurredUtc = DateTime.UtcNow
};
content.WorkflowState = transitionDef.ToState;
content.ModifiedUtc = DateTime.UtcNow;
await _repository.UpdateAsync(content);
await _historyRepository.AddAsync(history);
// 发送通知
await SendNotificationsAsync(content, transitionDef);
}
}
定时发布
计划模型
public class PublishSchedule
{
public Guid ContentItemId { get; set; }
// 发布窗口
public DateTime? PublishAtUtc { get; set; }
public DateTime? UnpublishAtUtc { get; set; }
// 重复(可选)
public bool IsRecurring { get; set; }
public string? CronExpression { get; set; }
// 状态
public ScheduleStatus Status { get; set; }
public DateTime? LastExecutedUtc { get; set; }
}
public enum ScheduleStatus
{
Pending,
Published,
Completed,
Failed,
Cancelled
}
定时发布服务
public class ScheduledPublishingService
{
public async Task SchedulePublishAsync(
Guid contentId,
DateTime publishAtUtc,
DateTime? unpublishAtUtc = null)
{
var content = await _repository.GetAsync(contentId);
if (content == null)
throw new NotFoundException();
// 验证内容准备发布
await ValidateForPublishAsync(content);
var schedule = new PublishSchedule
{
ContentItemId = contentId,
PublishAtUtc = publishAtUtc,
UnpublishAtUtc = unpublishAtUtc,
Status = ScheduleStatus.Pending
};
await _scheduleRepository.AddAsync(schedule);
// 更新内容状态
content.WorkflowState = "Scheduled";
content.ScheduledPublishUtc = publishAtUtc;
await _repository.UpdateAsync(content);
}
// 由后台作业调用
public async Task ProcessScheduledItemsAsync()
{
var now = DateTime.UtcNow;
// 待发布项目
var toPublish = await _scheduleRepository
.GetPendingPublishAsync(now);
foreach (var schedule in toPublish)
{
try
{
await PublishContentAsync(schedule.ContentItemId);
schedule.Status = ScheduleStatus.Published;
schedule.LastExecutedUtc = now;
}
catch (Exception ex)
{
schedule.Status = ScheduleStatus.Failed;
_logger.LogError(ex, "发布失败 {ContentId}",
schedule.ContentItemId);
}
await _scheduleRepository.UpdateAsync(schedule);
}
// 待取消发布项目
var toUnpublish = await _scheduleRepository
.GetPendingUnpublishAsync(now);
foreach (var schedule in toUnpublish)
{
await UnpublishContentAsync(schedule.ContentItemId);
schedule.Status = ScheduleStatus.Completed;
await _scheduleRepository.UpdateAsync(schedule);
}
}
}
后台作业配置
// 使用Hangfire或类似
public class ScheduledPublishingJob
{
private readonly ScheduledPublishingService _service;
[AutomaticRetry(Attempts = 3)]
public async Task Execute()
{
await _service.ProcessScheduledItemsAsync();
}
}
// 注册
RecurringJob.AddOrUpdate<ScheduledPublishingJob>(
"scheduled-publishing",
job => job.Execute(),
"*/5 * * * *"); // 每5分钟
审批链
多阶段审批
public class ApprovalChain
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public List<ApprovalStage> Stages { get; set; } = new();
}
public class ApprovalStage
{
public int Order { get; set; }
public string Name { get; set; } = string.Empty;
public ApprovalType Type { get; set; }
public List<string> ApproverRoles { get; set; } = new();
public List<Guid>? SpecificApproverIds { get; set; }
public int RequiredApprovals { get; set; } = 1;
public TimeSpan? Timeout { get; set; }
}
public enum ApprovalType
{
AnyApprover, // 列表中任意一个
AllApprovers, // 所有人都必须批准
MajorityApprovers // 多数规则
}
审批跟踪
public class ApprovalRequest
{
public Guid Id { get; set; }
public Guid ContentItemId { get; set; }
public Guid ApprovalChainId { get; set; }
public int CurrentStage { get; set; }
public ApprovalStatus Status { get; set; }
public DateTime RequestedUtc { get; set; }
public DateTime? CompletedUtc { get; set; }
public List<ApprovalDecision> Decisions { get; set; } = new();
}
public class ApprovalDecision
{
public Guid Id { get; set; }
public Guid ApprovalRequestId { get; set; }
public int Stage { get; set; }
public string ApproverId { get; set; } = string.Empty;
public ApprovalDecisionType Decision { get; set; }
public string? Comment { get; set; }
public DateTime DecidedUtc { get; set; }
}
public enum ApprovalDecisionType
{
Pending,
Approved,
Rejected,
Delegated
}
通知
通知类型
public enum WorkflowNotificationType
{
// 审核请求
ReviewRequested,
ApprovalRequired,
// 决策
ContentApproved,
ContentRejected,
// 发布
ContentPublished,
ContentScheduled,
ContentExpiring,
// 提醒
ReviewOverdue,
ApprovalOverdue
}
通知服务
public class WorkflowNotificationService
{
public async Task SendNotificationAsync(
WorkflowNotificationType type,
ContentItem content,
IEnumerable<string> recipientIds,
Dictionary<string, string>? extraData = null)
{
var template = await _templateService.GetAsync(type.ToString());
foreach (var recipientId in recipientIds)
{
var recipient = await _userService.GetAsync(recipientId);
var notification = new Notification
{
Id = Guid.NewGuid(),
Type = type.ToString(),
RecipientId = recipientId,
Subject = template.RenderSubject(content, extraData),
Body = template.RenderBody(content, extraData),
ContentItemId = content.Id,
CreatedUtc = DateTime.UtcNow
};
await _notificationRepository.AddAsync(notification);
// 通过配置的渠道发送
if (recipient.EmailNotifications)
await _emailService.SendAsync(recipient.Email, notification);
if (recipient.SlackNotifications)
await _slackService.SendAsync(recipient.SlackId, notification);
}
}
}
基于角色的权限
内容权限
public class ContentPermission
{
public string ContentType { get; set; } = string.Empty;
public string Role { get; set; } = string.Empty;
public ContentPermissionLevel Level { get; set; }
}
[Flags]
public enum ContentPermissionLevel
{
None = 0,
Read = 1,
Create = 2,
Edit = 4,
Delete = 8,
Publish = 16,
Unpublish = 32,
Archive = 64,
ManageWorkflow = 128,
Full = Read | Create | Edit | Delete | Publish | Unpublish | Archive | ManageWorkflow
}
权限检查
public class ContentAuthorizationService
{
public async Task<bool> CanPerformActionAsync(
ClaimsPrincipal user,
ContentItem content,
ContentPermissionLevel action)
{
var userRoles = user.Claims
.Where(c => c.Type == ClaimTypes.Role)
.Select(c => c.Value);
var permissions = await _permissionRepository
.GetByContentTypeAsync(content.ContentType);
foreach (var role in userRoles)
{
var permission = permissions.FirstOrDefault(p => p.Role == role);
if (permission != null && permission.Level.HasFlag(action))
return true;
}
return false;
}
}
API设计
工作流端点
GET /api/content/{id}/workflow # 获取工作流状态
POST /api/content/{id}/workflow/transition # 执行转换
GET /api/content/{id}/workflow/history # 转换历史
GET /api/content/{id}/workflow/available # 可用转换
# 调度
POST /api/content/{id}/schedule # 安排发布
DELETE /api/content/{id}/schedule # 取消安排
GET /api/scheduled # 列出安排项目
# 审批
POST /api/content/{id}/submit-for-review # 开始审批
POST /api/approvals/{id}/approve # 批准
POST /api/approvals/{id}/reject # 拒绝
GET /api/approvals/pending # 我的待审批
相关技能
content-versioning- 带有工作流的版本控制content-type-modeling- 工作流支持的内容类型headless-api-design- 工作流API端点