Skip to content
bitzorcas
中EN

Concept

Guidance 引导计划、分配与进度

源码校验 Guidance Campaigns:租户引导计划聚合根、Draft/Active/Paused/Archived 状态机、用户分配与进度、受众与排期、服务端权威完成与豁免,以及分析、错误码与持久化边界。

Last updated

引导计划(Campaign)把已发布的 GuidanceContent 组织成一条有序、有受众、有排期的多步引导,并为每个用户跟踪独立进度。它和单纯的上下文指南不是一回事:上下文指南是”用户走到某个页面时展示一段帮助”,引导计划是”运营主动投放一组步骤,引导某类用户完成一条路径”。

1. 两个聚合根

有序步骤受众 + 排期

GuidanceCampaign
租户引导计划

已发布 GuidanceContent

激活时预分配
首次交互时隐式分配

GuidanceCampaignAssignment
用户分配与进度

NotStarted → InProgress
→ Completed / Dismissed

GuidanceCampaign 是租户级聚合根,组合一段有序的已发布 GuidanceContent 步骤 ID 列表,加上受众定向规则和排期窗口。GuidanceCampaignAssignment 是用户级的分配与进度聚合根,按 (TenantId, CampaignId, UserId) 唯一(索引 UX_GuidanceCampaignAssignment_Tenant_Campaign_User)。

2. 计划状态机

GuidanceCampaignStatus 有四态:

状态含义
Draft编辑中,对终端用户不可见
Active已激活,符合受众与排期的用户可见
Paused暂停投放,已分配的进度保留
Archived归档,不再投放

状态迁移由聚合严格守卫,违反时返回对应的 Guidance.Campaign.* 错误:

  • Create 产生 Draft;
  • Configure(更新步骤、受众、排期)只允许 Draft 或 Paused,否则 Guidance.Campaign.MustBeEditable;
  • Activate 只允许从 Draft 或 Paused,且会拒绝已过期排期(Guidance.Campaign.ScheduleExpired);
  • Pause 只允许从 Active(Guidance.Campaign.TransitionInvalid);
  • Archive 允许从任何非归档态。

每次生命周期变更都必须带 LastChangeReason(最长 512),作为有界审计字段。

3. 受众与排期

受众定向不依赖单一角色字符串,而是组合以下字段:

字段限制语义
TargetRoleNames最多 50 个命中任一角色即符合
TargetUserIds最多 200 个显式点名用户
TargetsAllUsers计算字段两者皆空时为 true,即面向全租户

IsEligible(userId, roles, now) 把状态、排期窗口和角色/用户成员关系合并判定。排期由 StartsAt(空表示立即生效)和 EndsAt(空表示不自动过期)控制,数据库 CHECK 约束 CK_GuidanceCampaign_Schedule 保证 StartsAt < EndsAt。激活时若窗口已经结束,会被 Guidance.Campaign.ScheduleExpired 拒绝。

激活一个计划时,系统会为显式点名的目标用户预创建分配(EnsureExplicitAssignmentsAsync);面向全租户或按角色的计划则在用户首次交互时隐式创建分配(CreateAssignmentAsync)。

4. 有序多步与进度状态机

步骤是有序的 ContentIds 列表,上限 StepLimit = 20,每个标识符最长 64 字符。用户进度由 GuidanceProgressStatus 描述:

状态含义
NotStarted已分配但未开始
InProgress完成至少一步但未全部完成
Completed全部步骤完成
Dismissed用户主动豁免

进度推进是服务端权威的,不是客户端自行声称:

  • CompleteStep 幂等。首次完成任意一步会把 NotStarted 推进到 InProgress;全部步骤完成后自动推进到 Completed。重复完成同一步不报错;重复完成整个计划返回 Guidance.Campaign.AlreadyCompleted。
  • Dismiss 幂等,但受计划级 AllowDismiss 控制。AllowDismiss 为 false 时返回 Guidance.Campaign.DismissDenied;已经豁免再豁免返回 Guidance.Campaign.AlreadyDismissed。
  • 找不到步骤返回 Guidance.Campaign.StepNotFound。

这条”服务端权威 + 幂等 + AllowDismiss”的链路保证进度可信:客户端不能伪造完成,也不能绕过豁免策略。

5. HTTP 路由

计划路由集中在 /api/v1/guidance/campaigns,由 Source Generator 生成:

方法与路由用途权限
POST /api/v1/guidance/campaigns创建计划guidance.campaign.manage
GET /api/v1/guidance/campaigns管理列表guidance.campaign.read
GET /api/v1/guidance/campaigns/{campaignId}计划详情guidance.campaign.read
PUT /api/v1/guidance/campaigns/{campaignId}更新步骤/受众/排期guidance.campaign.manage
POST /api/v1/guidance/campaigns/{campaignId}/activate激活guidance.campaign.manage
POST /api/v1/guidance/campaigns/{campaignId}/pause暂停guidance.campaign.manage
POST /api/v1/guidance/campaigns/{campaignId}/archive归档guidance.campaign.manage
GET /api/v1/guidance/campaigns/{campaignId}/analytics完成率与分布分析guidance.campaign.read
GET /api/v1/guidance/campaigns/me当前用户可见计划guidance.campaign.use
POST /api/v1/guidance/campaigns/me/{campaignId}/steps/{contentId}/complete完成一步guidance.campaign.use
POST /api/v1/guidance/campaigns/me/{campaignId}/dismiss豁免计划guidance.campaign.use

管理类操作(创建、更新、状态变更、读取、分析)走 guidance.campaign.read / guidance.campaign.manage;终端用户操作(查看自己的计划、完成步骤、豁免)走 guidance.campaign.use。三类权限各司其职,运营不会误用终端权限,终端用户也不会获得管理能力。

6. 分析口径

GET /api/v1/guidance/campaigns/{campaignId}/analytics 返回 GuidanceCampaignAnalytics,给出真实的(非估算的)进度分布计数:NotStarted、InProgress、Completed、Dismissed,以及 CompletionRate。这些数字来自分配聚合的实际状态,不是采样或估算。控制台可以直接据此渲染漏斗。

7. 持久化

SQL Server 迁移 202607300004-guidance-campaigns.sql 创建两张表:

  • dbo.GuidanceCampaign,带 CK_GuidanceCampaign_Status('Draft','Active','Paused','Archived')和 CK_GuidanceCampaign_Schedule,索引按 Tenant/Status 和 Tenant/Language。
  • dbo.GuidanceCampaignAssignment,带 CK_GuidanceCampaignAssignment_Status('NotStarted','InProgress','Completed','Dismissed')和唯一索引 UX_GuidanceCampaignAssignment_Tenant_Campaign_User,查询索引按 Tenant/Campaign/Status。

GuidanceCampaignStore 在 Infrastructure 层实现 IGuidanceCampaignStore,支撑上述命令与查询。Guidance Campaign 与 GuidanceContent、Session 一样有 SqlSugar/EF Core parity 基础。

8. 与上下文指南的区别

容易混淆的两个概念:

  • 上下文指南(GuidanceContent + Contextual Query):被动展示,用户走到某个路由/组件时按四级匹配取一段已发布帮助,受众只靠单个 RequiredRole 字符串。
  • 引导计划(GuidanceCampaign):主动投放,运营把多个已发布内容串成有序步骤,按角色/用户/全租户定向,带排期和服务端权威进度。

两者共享 GuidanceContent 作为内容来源,但计划只是引用已发布内容的 ID,不复制正文。因此内容的发布生命周期(Draft/Published)和计划的生命周期(Draft/Active/Paused/Archived)是独立的:一个计划引用的内容被撤销发布,会影响该计划步骤的可读性。

9. 错误码一览

计划相关的错误码集中在 Guidance.Campaign.* 命名空间,代表性的包括:

错误码触发场景
Guidance.Campaign.Invalid输入校验失败
Guidance.Campaign.TenantRequired缺少可信租户上下文
Guidance.Campaign.NotFound计划不存在
Guidance.Campaign.ContentInvalid步骤内容引用非法
Guidance.Campaign.MustBeEditable在非可编辑态执行 Configure
Guidance.Campaign.TransitionInvalid非法状态迁移
Guidance.Campaign.ScheduleExpired激活已过期排期
Guidance.Campaign.NotEligible用户不符合受众
Guidance.Campaign.StepNotFound步骤不存在
Guidance.Campaign.AlreadyDismissed重复豁免
Guidance.Campaign.DismissDenied计划不允许豁免
Guidance.Campaign.AlreadyCompleted重复完成
Guidance.Campaign.VersionConflict乐观并发冲突

10. 源码核查

Terminal window
# 计划聚合、状态机、分配与进度。
rg -n "GuidanceCampaign|GuidanceCampaignStatus|GuidanceProgressStatus|GuidanceCampaignAssignment" \
src/Platform/Guidance -g '*.cs'
# 计划权限码与路由。
rg -n "guidance.campaign|CampaignResource|HttpRoute" \
src/Platform/Guidance -g '*.cs'
# 物理表与约束。
rg -n "GuidanceCampaign|CK_GuidanceCampaign" \
src/Hosts/BitzOrcas.Api/SchemaMigrations/SqlServer/202607300004-guidance-campaigns.sql

Guidance 总览 · 内容生命周期与持久化 · 上下文选择与授权

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%