内容工作流Skill content-workflow

这个技能提供在无头内容管理系统(CMS)中实现编辑工作流、多阶段审批、定时发布和基于角色的权限管理的全面指南。涵盖状态机、API设计、通知服务和角色权限等关键组件,适合后端开发和系统架构,关键词包括编辑工作流、审批链、定时发布、角色权限、CMS、状态机、API。

后端开发 0 次安装 0 次浏览 更新于 3/11/2026

名称: 内容工作流 描述: 用于实现编辑工作流、审批链、定时发布或基于角色的内容权限。涵盖无头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端点