ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Ghost 数据库结构完全指南:schema.js、各业务域表模型与变更流程解析

Ghost 数据库结构完全指南:schema.js、各业务域表模型与变更流程解析 Ghost 数据库结构完全指南schema.js、各业务域表模型与变更流程解析【免费下载链接】GhostIndependent technology for modern publishing, memberships, subscriptions and newsletters.项目地址: https://gitcode.com/GitHub_Trending/gh/Ghost本指南以 Ghost 官方文档《Database Structure》为骨架结合仓库内 schema.js 的源码级实现系统梳理 Ghost 发布平台博客内容、会员订阅、邮件投递、归属归因等的数据库组织方式。读完你将掌握Ghost 数据库的“唯一事实来源”在哪里、Posts/Members/Newsletters 等核心业务域的建表与关系逻辑、以及任何结构变更前必须同步维护的迁移与一致性约束。Ghost 数据库设计的两个出发点在深入每张表之前需要先建立两个核心认知否则很容易在阅读 schema 时产生误解。第一schema.js是数据库结构的唯一“目标形状”。Ghost 的数据库 schema 集中定义在 schema.js它描述的是所有迁移migration执行完毕之后数据库应有的最终形态。也就是说新装数据库通过初始化迁移直接建成该形状而升级数据库则通过版本化的迁移文件一步步逼近该形状。配套的 Schema 与默认数据指南 进一步指出目录内的三个“事实来源”各司其职schema.js所有迁移执行后的最终表与索引结构fixtures/fixtures.json一个新站点必需的持久化记录与关系角色、权限、Owner、起步内容、Tier、Newsletter 等default-settings/default-settings.json站点初始 settings按职责分组定义。第二不要只凭表结构推断业务规则。位于 models/ 的 Bookshelf 模型才定义表之间的关系与应用层行为——例如级联删除的时机、查询时的默认过滤、状态流转的约束。schema 中的references/cascadeDelete只表达数据库层外键真正的领域规则比如“哪些状态的文章才能进入 Content API”写在模型与序列化层。文档中每个小节本质上是业务域地图domain map它解释“为什么这些相关表会存在”以及“改动它们时该从哪里入手”而不是 schema 的替代品。schema.js 源码特征速览从源码层面看schema.js 有几个值得注意的全局约定字符串长度规范文件头注释明确给出短字符串 ≤ 50、中字符串 ≤ 191、大字符串 ≤ 2000191–2000 之间应通过校验设置软限制、Text 类型 6553564 KiB、Long text 上限 1,000,000,000。典型体现newsletters.name、tags.slug、members.email均为maxlength: 191正文内容posts.lexical、posts.html、emails.html均声明为fieldtype: long的 text。主键与时间戳约定几乎所有业务表的主键都是id: { type: string, maxlength: 24, nullable: false, primary: true }24 位字符串 ID并普遍携带created_at非空与updated_at可空时间戳。这与字符串长度规范中的 “Small strings length 50” 形成呼应——ID 独立取值 24。外键与级联策略差异同一张表的不同外键可能选择不同删除策略。例如事件表普遍对members.id使用cascadeDelete: truemember 删除即随删而automation_actions对automations.id使用restrictDelete: trueredirects对posts.id使用setNullDelete: true。这再次印证“随主记录删除什么、保留什么”属于模型层设计决策阅读时必须逐一留意。Posts 域一篇文章的完整生命周期posts表schema.js是内容域的中心。关键字段从 schema 校验中可以直接读出type只能是post或page默认poststatus只能是published、draft、scheduled、sent四种visibility默认public联合唯一约束[slug, type]同一 slug 可分别用于 post 与 page联合索引[type, status, updated_at]服务于后台列表按类型/状态/更新时间过滤。关于四种状态的语义文档做了关键澄清sent状态的文章只发送邮件、并不发布到站点而只有published的文章才会通过 Content API 对外暴露。因此“已发邮件但未上线”的内容在库中的标识就是status sent。围绕中心表还有若干卫星表posts_metapost_id一对一存 SEO/社交分享的 meta、email_subject、email_only标记等、posts_authors与posts_tags多对多关联表、posts_products文章与付费 Tier 的门槛关系。改动这一组表时必须连同模型、序列化器一起考虑因为“谁能看、何时可见”是由模型层决定的。Tags 域公开标签与内部标签tags表schema.js的visibility字段只允许两个值internal或public。内部标签不对外暴露常用于站点后台的过滤与组织例如标记某类投稿流程而公开标签会渲染在前台页面。tags还支持父子结构parent_id自引用、排序与富文本元数据og_image、twitter_*、meta_*、codeinjection_head/foot、accent_color等。多对多关联落在posts_tags带sort_order以控制标签在文章页的展示次序。Integrations 域四类集成的来源与可见性integrations表schema.js的type字段枚举了四种集成schema 校验与文档口径一致类型含义来自文档源码印证internal对 API 私有的集成integrations.type允许值之一builtinGhost 内置集成之一创建的集成同上core由 Ghost 核心创建并管理同上custom由用户创建同上且为默认值defaultTo: custom与之配套的是webhooks通过integration_id外键cascadeDelete绑定集成与api_keys。注意 schema.js 中的注释api_keys.integration_id允许为空正是为了容纳“不在 UI 展示的 internal API key”。Member attribution events归因事件的语义会员注册与订阅创建产生的归因事件会随其关联 member 一起被删除schema 中member_id均带cascadeDelete: true如members_created_events、members_subscription_created_events。以members_created_eventsschema.js为例事件字段清晰地分为几组schema 注释标注了它们的来源归因对象attribution_idattribution_type其中attribution_type只允许url、post、page、author、tag另有attribution_url来源 referrer由浏览器 referrer 经解析库处理而来referrer_source/referrer_medium/referrer_urlUTM 原始参数utm_source/utm_medium/utm_campaign/utm_term/utm_content取自 URL query未做加工创建途径source只允许member、import、system、api、admin——这正是文档所说“记录是什么创建了该 member”的字段。Members 域读者身份的中央记录与责任分表members表schema.js是一位读者的中央记录负责存储身份、状态、档案信息、邮件互动汇总与沟通偏好。源码可见email唯一且带isEmail校验status允许free、paid、comped、gift邮件互动汇总字段email_count、email_opened_count、email_open_rate、email_disabled直接冗余在行上另有last_seen_at、last_commented_at、commenting等行为字段。围绕这张中央表Ghost 按“职责分离”原则把相关数据拆分到不同表中文档给出的完整映射如下责任表标签Labelslabels、members_labels自定义字段members_custom_fields、members_custom_field_valuesNewsletter 订阅newsletters、members_newslettersTier 与访问权限products、members_products、subscriptionsStripe 状态members_stripe_customers、members_stripe_customers_subscriptions、members_current_subscription、stripe_products、stripe_prices优惠Offersoffers、offer_redemptions生命周期与归因历史members_*_events系列事件表几个重要的模式在源码中可以直接验证多对多标签labels与members通过members_labels连接member_id、label_id均cascadeDeleteNewsletter 订阅采用同样的模式即members_newsletters自定义字段的定义与取值分离字段定义只存一份于members_custom_fieldsschema 中衍生出members_custom_field_bindings做字段-产品绑定而members_custom_field_values存每位成员实际提供的值订阅的“双轨”设计subscriptions是与支付提供商无关的本体订阅记录而一组members_stripe_customers/members_stripe_customers_subscriptions/stripe_products/stripe_prices表缓存了同步付费状态所需的提供商侧记录。其中members_current_subscription采用member 一行一条的查找表member_id即主键用于在 Admin 与会员筛选中快速暴露“解析后的当前 Stripe 订阅”。文档明确提醒改动这些关系前除了 schema还应当阅读 member 模型、resolved-subscription 视图与 Stripe service。文档还给出了一条易被忽视的设计纪律事件表是面向追加append-only的历史记录服务于归因、分析与会员/订阅状态变更追踪它们不是当前会员记录的替代事实来源——当前的权威状态应始终来自members与subscriptions本身。Subscriptions 域的付费语义subscriptions表schema.js集中体现了文档所述的类型与状态语义类型typefree、comped赠送、paid状态statusactive、expired、canceled付费专有信息源码注释明确“type 非 paid 时这些为 null”cadencemonth/year、currency、amount以及支付提供方链接字段payment_provider如stripe、payment_subscription_url、payment_user_url。subscriptions通过tier_id指向products即 Tier 表offer_id可选指向offersmember_id与members.id之间是cascadeDelete。Newsletters 与邮件域一次发送拆成四张表文档指出一次 newsletter 发送被表示在四张主要表中其职责划分非常清晰表用途emails一篇文章对应的一次发送含渲染后的内容、收件人过滤条件、聚合计数、追踪选项与整体状态email_batches向邮件提供方的分批提交含会员分段、提供方标识、批次状态与批次级错误email_recipients某次发送选中的收件人所属批次、发送时的会员身份快照以及处理/投递/打开/失败时间戳email_recipient_failures面向单个收件人的结构化投递失败信息临时或永久这种拆分分别回答了三个问题emails记录“Ghost 发出了什么”email_batches记录“如何提交给提供方”email_recipients记录“谁被包含、每封投递发生了什么”。源码进一步印证这些语义emailsschema.jspost_id唯一status限于pending/submitting/submitted/failedrecipient_filter存 NQL 收件人过滤表达式email_count/delivered_count/opened_count/failed_count聚合计数track_opens/track_clicks/feedback_enabled追踪选项source_type标识内容来源是html/lexical/mobiledocemail_recipientsschema.js除批次外还冗余了member_uuid/member_email/member_name快照——这正是“发送时刻的记录”的证据并对(email_id, member_email)、(email_id, delivered_at/opened_at/failed_at)建了索引以便按投递状态查询email_recipient_failures的severity仅允许temporary/permanent。由此引出文档中的关键纪律不要用当前会员或分段状态去重建历史收件人列表email_recipients行才是发送时刻的唯一记录。Newsletter 身份由newsletters表持有。posts.newsletter_id与emails.newsletter_id分别把内容与发送关联回 newsletternewsletters表则拥有 newsletter 的身份、发件人配置、订阅默认值与呈现设置。从 schema.js 可见其字段之丰富sender_name/sender_email/sender_reply_to、subscribe_on_signup、visibility默认members、statusactive/archived以及title_font_category、button_style、background_color等大量邮件外观设置。互动数据的汇总与明细分层同样在此域出现email_spam_complaint_events记录与某会员某邮件关联的投诉事件schema.js 附近同时members行上缓存了email_count、email_opened_count、email_open_rate等聚合值供浏览与过滤使用——逐次发送、逐收件人的明细仍以email_recipients等明细表为准。自动化邮件是独立体系Ghost 的自动化邮件欢迎邮件等使用另一组automation_*、welcome_email_automation_*与automated_email_recipients表而不会创建 newsletter 体系的emails与email_recipients行。这一差异在 schema 中清晰可见automations/automation_actions/automation_action_revisions/automation_runs/automation_run_steps等表schema.js 区间且automation_actions.type只允许wait与send_email。这解释了为什么在数据库里追踪“欢迎邮件发送”要查的是automated_email_recipients而不是 newsletter 邮件表。Link redirects 与点击追踪被追踪链接的存储同样有明确分工redirectsschema.js保存被跟踪链接的源路径from、目标to及关联内容。其中post_id可空setNullDelete还可能关联到automation_action_revision_id即某个自动化邮件的发送行为to_hash用于目标去重表上有(automation_action_revision_id, to_hash)联合唯一约束members_click_eventsschema.js记录每一次会员点击包括同一会员的重复点击。每行由member_idredirect_idcreated_at组成redirect_id指向redirects。由于点击事件随会员级联删除、随重定向级联删除这一组合适合做“每次点击”粒度的转化分析而不适合作为 redirect 本身的状态表。变更数据库结构前必须做的事文档强调本节内容是领域地图而非 schema 替代品如果真要动手改这些结构请务必遵守 Ghost 的配套治理流程。改动结构前请确认以下三类文件保持同步schema 定义改动任何表结构必须同步更新 schema.js因为它是“迁移全部跑完后”的预期形状迁移文件仅改 schema.js 不会改变已装数据库。为既有安装创建版本化迁移新建文件用cd ghost/core pnpm migrate:create kebab-case-slug开发期用pnpm knex-migrator migrate/rollback迭代涉及 schema 完整性时运行pnpm test:single test/unit/server/data/schema/integrity.test.js完整规范参见 数据库迁移指南exporter 导出表清单与完整性测试新增表还需进入导出清单并保持 schema 完整性 hash 与测试一致初始数据与默认设置相关改动参见 Schema 与默认数据指南。阅读上述两份配套文档时可以重点关注几条贯穿性原则迁移必须幂等且最小化、禁止使用模型层模型来自新版而数据库还是旧版状态会破坏旧迁移、尽量复用core/server/data/migrations/utils/中经过测试的迁移工具、一旦合并进main迁移即不可再变。所有这些纪律的最终目的都是为了保证“schema.js 描述的最终形状”与任何一条升级路径上的实际数据库状态严格一致。总结阅读 Ghost 表结构的正确姿势回到本文开头提出的两个出发点可以把整篇文档浓缩为一张心智地图想确认“现在应该长什么样”→ 读 schema.js它是一切的权威目标形状想理解“业务上为什么这么拆、谁和谁是什么关系”→ 对照本文各业务域地图再回到 models/ 看 Bookshelf 模型的行为想判断“历史发过什么、谁当时收到了”→ 查明细表email_recipients、members_*_events、members_click_events这些追加型记录不可用当前状态重建想改动结构→ 严格走 数据库迁移指南 流程并把 schema、迁移、导出清单、完整性测试四者视为一个必须整体提交的变更单元。把握了“中央记录 责任分表 追加事件 冗余汇总”这套分层哲学Ghost 的数百个表定义就不再是散点而是一套围绕会员、内容与邮件生命周期的高度工程化的数据模型。【免费下载链接】GhostIndependent technology for modern publishing, memberships, subscriptions and newsletters.项目地址: https://gitcode.com/GitHub_Trending/gh/Ghost创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表