Ghost 上下文地图(Context Map):Portal、Gift Subscriptions 与 Gift Links 的领域边界与术语契约

发布时间:2026/9/7 9:08:20
Ghost 上下文地图(Context Map):Portal、Gift Subscriptions 与 Gift Links 的领域边界与术语契约 Ghost 上下文地图Context MapPortal、Gift Subscriptions 与 Gift Links 的领域边界与术语契约【免费下载链接】GhostIndependent technology for modern publishing, memberships, subscriptions and newsletters.项目地址: https://gitcode.com/GitHub_Trending/gh/GhostCONTEXT-MAP.md 是 Ghost 仓库中用领域驱动设计DDD的“上下文映射”方法组织业务边界的导航文档它声明了三个业务上下文Portal、Gift Subscriptions、Gift Links各自的语言文档位置与职责范围并用两条关系描述了上下文之间的协作方式。阅读本文后你将掌握 Ghost 会员/赠礼体系的领域划分、每个上下文的关键术语含必须避免的歧义用语以及这些术语在 gifts 服务源码 中的落点便于在阅读代码或设计新功能时保持与官方术语一致。1. 上下文地图的作用与结构CONTEXT-MAP.md 由两部分构成Contexts上下文清单与Relationships上下文关系。每个上下文条目给出两样东西Path该上下文详细语言文档CONTEXT.md的相对路径一句话职责描述该上下文负责的业务范围。仓库中这三个上下文分别是上下文语言文档位置职责范围Portalapps/portal/CONTEXT.md嵌入在 Ghost 站点中的访客端会员组件modal负责会员注册、登录、付费订阅结账、优惠offers、账户管理以及访客端的礼品订阅旅程Gift Subscriptionsghost/core/core/server/services/gifts/CONTEXT.md预付、固定期限的会员访问权覆盖礼品购买、礼品兑换、礼品过期、礼品消耗以及向付费订阅的延续continuationGift Linksghost/core/core/server/services/gift-links/CONTEXT.md在不创建会员的前提下对单篇受保护文章/页面进行可分享的访问授权从源码结构看上下文与目录一一对应Portal 是独立前端应用apps/portal/Gift Subscriptions 是核心服务端服务模块ghost/core/core/server/services/gifts/Gift Links 是另一独立服务端模块ghost/core/core/server/services/gift-links/。这种“一个上下文、一个目录、一份 CONTEXT.md”的组织方式使术语文档始终贴近实现。上下文文档普遍采用Language语言章节 逐条术语定义的形式并且每条术语都附有Avoid应避免使用的同义词清单。这一机制是上下文地图的核心价值它把“同一个词在不同上下文里含义不同”的问题显式化防止团队以及 AI 助手在跨模块协作时用错术语。2. Portal 上下文访客端入口术语apps/portal/CONTEXT.md 定义了 9 个术语。Portal 的难点在于站点上有多种控件都能“打开 Portal”但它们打开的页面与生效条件各不相同。以下按原文档完整列出并结合关系小节说明。2.1 控件与链接类术语术语定义据原文档Portal buttonPortal 按钮站点上的环境式ambient控件允许访客在未跟随任何特定 Portal 链接的情况下打开 Portal。它不决定某个特定 Portal 链接是否打开 Portal。Offer link优惠链接用于查看并接受会员优惠的 Portal 链接。优惠链接总是打开优惠页——即使 Portal 按钮被隐藏——且仅对有资格接受优惠的访客生效。Portal 的优惠触发器就是优惠链接。对无资格的访客——已有付费会员不含免费/complimentary 会员、或过期/已归档/挽留retention优惠——链接被静默忽略Portal 不打开。Checkout button结账按钮直接为某个会员方案发起结账的站点控件把访客直接送入结账流程而不是打开优惠页。Checkout attempt结账尝试为某会员方案发起结账的一次尝试。结账尝试受到跨结账流量的请求限制保护并对针对同一邮箱地址的重复尝试施加限制。若因已存在活跃付费订阅而被拒绝已登出访客会被转入“登录邮箱流程”而已登录会员则被告知已拥有活跃订阅——不会发送登录邮件。从源码结构看Gift Subscriptions 服务的startCheckout接口见 gifts README负责时长目录duration catalogue、Portal 方案门槛、层级/计费周期校验与权威价格——这正是上下文文档中“结账尝试”这一语言概念在后端的落点。2.2 礼品订阅相关术语Portal 文档中一半术语与礼品订阅旅程相关它们刻意与“全局开关”解耦术语定义据原文档避免使用的词Gift checkout礼品结账Portal 中买家选择时长与会员层级、发起礼品订阅一次性购买的旅程。Gift purchase、gift pagePortal gift promotionPortal 礼品推广发布者可控制的、进入礼品结账的 Ghost 自有入口的呈现方式。注册页推广与账户页推广相互独立控制两者都不决定通过直接 Portal 链接访问礼品结账本身是否可用。“Gift subscriptions enabled”“gifting enabled”“global gift promotion”Signup gift entry point注册礼品入口注册旅程中的 Portal 推广面向想为他人购买会员的访客。其可见性独立于账户礼品入口。Gift signup、account gift entry pointAccount gift entry point账户礼品入口账户旅程中面向付费会员与 complimentary 会员的推广供其为他人购买礼品。不展示给免费会员或正在接受赠礼访问的会员。Gift continuation、gift-recipient cardGift redemption page礼品兑换页由兑换链接打开的 Portal 界面访客或已登录会员可在此查看并领取claim礼品订阅。Gift Link page这里的关键设计意图是三个开关维度——注册入口可见性、账户入口可见性、直接链接结账可用性——彼此正交。Avoid清单明确禁止把“入口推广可见”与“礼品订阅功能是否启用”混为一谈。3. Gift Subscriptions 上下文赠礼订阅的完整语言gifts/CONTEXT.md 是三个上下文中语言最庞大的一份按“角色 / 购买与领取 / 时间与生命周期”三组共定义 21 个术语。以下完整继承原文档骨架。3.1 角色Roles术语定义据原文档避免使用的词Buyer买家为礼品订阅付款的人。买家无需是会员若自身有资格也可成为兑换者。Purchaser、recipient、redeemerRedeemer兑换者领取礼品订阅并获得其访问权的会员。兑换者由兑换时而非购买时决定。Recipient、buyerRecipient受赠人礼品订阅所针对的人。其邮箱只用于投递路由不预留兑换权无需是会员在领取之前不会成为兑换者。Receiver、redeemer这三者的分离解释了“兑换者由兑换时决定”的语义受赠人邮箱只是投递通道任何人持链接均可领取。3.2 购买与领取Purchase and claim术语定义据原文档避免使用的词Gift subscription礼品订阅预付、不可续期的会员权益为某一会员层级提供固定期限的访问权。从领取时开始而非从购买时开始。Gift membership、recurring subscriptionGift purchase礼品购买创建一份可分享礼品订阅的一次性购买。购买完成不意味着礼品邮件已发出。Gift checkoutGiftable offering可赠售组合当前可同时购买的至少一个付费会员层级与赠礼时长的组合。其缺失意味着礼品结账无法提供可购买的礼品。“Gifting enabled”、“paid memberships enabled”Delivery method投递方式买家选择移交礼品订阅的方式由站点邮件发给受赠人或买家私下达成交换链接。Delivery modeRedemption link兑换链接单次使用、限时有效的链接可借此查看并领取已购买的礼品订阅。持链接者可看到买家姓名、指定受赠人姓名、受赠人邮箱与个人留言指定受赠人不预留兑换权。Gift LinkGift redemption礼品兑换领取一份有资格的礼品订阅的行为。领取者成为兑换者赠礼访问自该时刻开始。Gift activationGift delivery礼品投递通过邮件向受赠人传达兑换链接。投递本身不领取礼品也不开启赠礼访问。Gift redemption、gift activationScheduled delivery定时投递受赠人邮件计划在未来某日投递的礼品投递。排定投递时间不会推迟兑换的可领取性。Scheduled gift、scheduled redemptionDelivery date投递日期为定时投递选择的站点本地日历日期。在站点时区的09:00使受赠人邮件到期选择当天则立即投递。它不影响兑换可领取性。Delivery time、deliver-at datePersonal message个人留言买家的留言构成礼品的一部分可在礼品被展示的各处含邮件与兑换体验呈现。Delivery message、email messageGift sent礼品已发送受赠人邮件已被站点配置的发信通道mail transport接受。即使没有后续服务商回执也视为投递完成。“Gift delivered”、provider deliveryEmail delivery status邮件投递状态把受赠人邮件交给发信通道的当前进度pending、sending、sent、failed 或 cancelled。与后续服务商汇报的结果相互独立。Delivery outcomeDelivery outcome投递结果礼品邮件最近一次服务商汇报的结果如 delivered、temporarily failed、permanently failed。若发信通道不提供投递遥测结果保持未知。“Gift sent”、delivery state“邮件投递状态”与“投递结果”的切分对应两种可靠性事实前者是 Ghost 自己控制的“至少交付给通道一次”后者是服务商如 Mailgun异步回传的事件。gifts README 印证了这一点GiftDeliveryService.recordOutcome(...)仅保留最新的 Mailgun 投递结果而发信通道的接受acceptance才是权威的“已发送”事实新记录到的服务商永久失败会以尽力best-effort方式向买家发送包含礼品链接的事务性通知供其手动分享。3.3 时间与生命周期Time and lifecycle这是整个上下文最精密的部分四个时间概念严格分离术语定义据原文档Redemption availability兑换可领取性礼品订阅可被领取的时刻。自购买时即开始所有礼品皆如此——包括受赠人邮件被排期到未来的礼品。Claim window领取窗口从礼品购买到礼品过期、未兑换礼品可被领取的期间。当投递被排期到未来日期时领取窗口可能超过 365 天。Expiry anchor过期锚点计算礼品过期所用的站点本地日历日期。定时投递取所选投递日期其余情况取礼品购买日期。Gift duration礼品时长赠礼访问的总长度自兑换起算。Gift cadence礼品计费周期用于给礼品订阅定价的月付或年付基础。它不使礼品变为周期性recurring订阅。Gift expiration礼品过期未兑换礼品领取窗口的终点过期锚点之后365 个站点日历日后续邮件处理不会移动它。Gift consumption礼品消耗已兑换礼品提供访问这一角色的终点时长走完或付费订阅接管时。Consumption reminder消耗提醒发送给兑换者的邮件每份礼品至多一次、在礼品消耗前最多 7 天发出以便在赠礼访问结束前安排礼品延续。Gift continuation礼品延续从赠礼访问启动付费订阅并在开始计费前把剩余赠礼时长结转carry forward。这组术语与源码常量一一对应constants.ts 给出了精确数值export const GIFT_EXPIRY_DAYS 365; // 过期锚点后的领取窗口长度 export const GIFT_MAX_SCHEDULE_DAYS 365; // 投递排期的最远提前量 export const GIFT_SEND_HOUR 9; // 定时投递在站点本地 09:00 发送 export const GIFT_REMINDER_LEAD_DAYS 7; // 消耗提醒最早提前量 export const GIFT_REMINDER_FLOOR_DAYS 3; // 消耗提醒最晚提前量 export const GIFT_DELIVERY_STALE_AFTER_MS 60 * 60 * 1000; // 投递“进行中”超过 1 小时视为陈旧 export const GIFT_DELIVERY_EMAIL_TAG gift-delivery;值得注意的两处实现细节GIFT_MAX_SCHEDULE_DAYS的注释明确说明 Portal 端在 apps/portal 的 gift-page.tsx 中持有一份独立副本GIFT_MAX_SCHEDULE_DAYS因为 Portal 是独立分发的——两端修改必须同步。这正体现了上下文地图“上下文独立、契约需人工同步”的特征。GIFT_SEND_HOUR的注释说明 Portal 端从不推导发送时刻精确发送时刻通过成功 URL 中的gift_scheduled_at参数传递——与术语表中“Delivery date 在站点时区 09:00 使邮件到期”的定义严格一致。4. Gift Links 上下文与兑换链接的关键区分gift-links/CONTEXT.md 只有单一术语但它的存在恰恰是上下文地图最有价值的部分之一Gift Link一个可撤销revocable的链接持有者可借此访问单篇受保护的帖子或页面。它不创建或兑换礼品订阅。Avoid: Redemption link、gift-subscription linkGift Links 是一个独立的小型服务端模块源码见 gift-links 目录含 service.ts、actions.ts、models.ts 等。它与 Gift Subscriptions 的关系是名称极易混淆但机制完全不同一个 Gift Link 授予对单篇内容的一次性可撤销访问、不产生会员身份而一个 Redemption link 指向整段固定期限的会员访问权。上下文文档通过在两个上下文中互相写入Avoid条款Gift Subscriptions 的“Redemption link”避免使用“Gift Link”Gift Links 的“Gift Link”避免使用“Redemption link”来消除这一歧义。5. 上下文关系RelationshipsCONTEXT-MAP.md 在 Relationships 一节给出两条关系Portal ↔ Gift SubscriptionsPortal 呈现礼品订阅的购买与兑换旅程。Gift Subscriptions ↔ Gift Links礼品订阅的兑换链接声明claim固定期限的会员访问权Gift Link 则授予单篇受保护帖子或页面的访问权。第一条关系在代码中的体现是清晰的分层Portal 作为前端旅程发起请求Gift Subscriptions 服务的 capability-oriented 接口 承接能力调用——README 明确说明“调用方拿不到Gift实体、Bookshelf 模型、事务或 Stripe 对象”只获得稳定 DTO 与读模型。关键接口包括startCheckout(input)拥有时长目录、Portal 方案门槛、层级/周期校验、权威价格、token、成功参数、元数据、客户与一次性支付getRedeemable(input)/redeem(input)返回公共兑换 DTO对应 Portal 的 Gift redemption pagepreparePaidContinuation(input)校验活跃礼品并返回稳定 tier/cadence/trial 决策供正常订阅结账路径使用对应 Gift continuation 术语handlePaidSubscriptionActivation(memberId)付费订阅接管时消耗礼品时长processReminders()/processConsumed()/processExpired()拥有礼品生命周期处理reassignRedeemer(...)数据导入能力。第二条关系则如第 4 节所述本质是术语防混淆契约而非调用关系——两个模块互不调用只是产品上都需要被明确区分。6. 生命周期在源码中的落点调度、投递与恢复术语表中的生命周期概念scheduled delivery、consumption reminder、delivery at-least-once在 gifts/index.ts 的init()中有完整装配可作为理解上下文语言的实现证据投递调度GiftDeliveryService配合SignedFlushScheduler端点gifts/flush_deliveries调度名gift_delivery按 GiftDelivery 的scheduledAt排期立即投递在购买后经由进程内事件SendGiftDeliveryEvent触发定时投递在scheduledAt到达时进入同一条处理路径——印证术语表“Scheduled delivery 不推迟兑换可领取性”的定义。消耗提醒调度另一个SignedFlushScheduler端点gifts/flush_reminders以gift.reminderDueAt()为排期依据由StartGiftReminderFlushEvent触发processReminders()周期性任务通过 jobs含 send-gift-reminders-job.js 与 clean-gifts-job.ts注册。付费订阅接管DomainEvents订阅SubscriptionActivatedEvent触发giftService.handlePaidSubscriptionActivation(...)——即 Gift consumption 中“付费订阅接管”这一分支的实现。启动恢复recoverPendingDeliveries()在所有服务初始化完成后运行重新排期未来投递并恢复上次关闭时中断的发送README 说明投递领取是原子的、过期的进行中领取在崩溃后会被重试因此“发信通道接受”是**至少一次at least once**语义——对应术语表中 Email delivery status 的pending/sending状态存在原因。邮件通道选择init()中GiftEmailService同时装配事务性邮件器GhostMailer用于买家确认与失败通知与批量 Mailgun 客户端MailgunClient用于受赠人投递使投递结果可观测无 Mailgun 时回退到 provider 无关的事务邮件通道。7. 如何使用这份上下文地图跨模块沟通时先查术语涉及 Portal 入口、优惠链接或礼品功能时先到 CONTEXT-MAP.md 定位所属上下文再到对应 CONTEXT.md 使用规范用语并避开Avoid清单中的词例如不要说“gifting enabled”来表达入口推广。区分两个“link”Gift Linkgift-links≠ Redemption linkgifts前者是内容访问授权后者是会员权益兑换。时间问题按四个概念拆解谈赠礼时间线时先分清 redemption availability购买即可领、claim window至多锚点365 天、gift duration自兑换起算、gift expiration过期锚点365 站点日历日数值以 constants.ts 为准。以接口边界定位实现Gift Subscriptions 的能力入口在 gifts/README.md 列出的接口集合装配与事件订阅在 gifts/index.tsPortal 侧旅程代码位于 apps/portal/src。【免费下载链接】GhostIndependent technology for modern publishing, memberships, subscriptions and newsletters.项目地址: https://gitcode.com/GitHub_Trending/gh/Ghost创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考