CMS后端前端插件系统【免费下载链接】emdashEmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress项目地址https://gitcode.com/gh_mirrors/emdas/emdash点击查看免费下载EmDash 是一套基于 Astro 的全栈 TypeScript CMS其插件系统将内容能力封装为一组按能力capability门控的 API供沙箱插件、原生插件在 Native、Cloudflare Worker Loader 与 Node/workerd 三种执行模式下共享使用。本篇指南以官方技能文档 content.md 为骨架结合 plugins/context.ts、plugins/content-access.ts 等核心源码系统讲解ctx.schema、ctx.content的读取、翻译、发布、恢复全流程以及对应的发布策略钩子与运行时测试方法。读完本文你将掌握如何在 EmDash 插件中安全地读写内容、创建多语言翻译、以修订号_rev为护栏执行发布与恢复动作并理解底层实现与测试边界。内容 API 的权限门控模型插件内容 API 的核心设计是声明即授权插件作者必须在emdash-plugin.jsonc中显式声明所需能力宿主在运行时按清单门控每一次调用。与内容直接相关的能力如下完整词汇表见 packages/plugin-types/src/index.ts 中的PluginCapability联合类型能力授予的操作schema:read公开的集合与字段定义ctx.schemacontent:read内容身份、翻译组与已发布的公开 URLctx.content读取content:revisions:read保留的修订数据隐含content:readcontent:write创建、更新、删除与翻译创建隐含content:readcontent:publish修订号保护的发布、取消发布、定时与取消定时隐含content:readcontent:restore修订号保护的回收站内容读取与恢复hooks.content-policy:register发布前、定时前、取消发布前策略钩子注册从源码看能力与钩子的绑定关系由 hooks.ts 中的映射表维护如content:beforePublish、content:beforeSchedule、content:beforeUnpublish均映射到hooks.content-policy:register而运行时通过createContentAccess/createContentAccessWithWritecontent-access.ts、context.ts按是否授予对应能力决定挂载哪些方法。一个值得注意的细节content:revisions:read、content:write、content:publish在 declared-access.ts 中都被设计为隐含content:read——例如capabilitiesToDeclaredAccess会在声明content:write时同时写入content.read {}。这意味着写权限永远不小于读权限避免了只能写不能读的荒谬状态。发现与读取schema:read与content:readSchema 发现schema:read暴露ctx.schema.listCollections()与ctx.schema.getCollection()用于批量获取公开的集合与字段定义。对应实现createSchemaAccesscontext.ts内部基于SchemaRegistry查询并转换为插件可见形态CollectionSchemaInfo包含集合身份slug、label、labelSingular、description路由信息urlPattern、routable是否可路由、hidden功能开关hasSeo、titleField、dateField字段定义数组fields每个字段含slug、label、type、required、unique、default、validation、widget、options、searchable、indexed、translatable、sortOrder等元数据这为插件在运行时动态了解站点内容结构而非硬编码集合名提供了基础。内容读取content:read暴露ctx.content上的四个读取方法get(collection, id)按 ID 取单条内容不存在返回nulllist(collection, options?)分页列表支持limit默认 50、cursor、orderBy、wheregetTranslations(collection, id)返回翻译组 ID 及该组下所有翻译的摘要ID、locale、slug、status、updatedAtgetPublicUrl(collection, id)解析内容对外可访问的公开 URL内容结果字段是插件读取的核心契约。从 content-access.ts 的实现可见每条ContentItem包含身份与状态id、type集合名、slug、status、locale数据与时间戳data字段值、createdAt、updatedAt、publishedAt、scheduledAt归属与分组authorId、translationGroup修订指针liveRevisionId、draftRevisionId线上/草稿版本指针版本号version行版本可选seo当集合启用 SEO 时附带公开 URL 解析规则非常严格getPublicUrl只有在站点配置了 URL、内容状态为published、存在 slug、且集合routable时才返回完整 URLsite.url 路由路径否则返回null。关键点是——它只返回已发布的、可路由的 URL绝不返回预览地址。这意味着插件拿到的永远是真实可对外访问的链接不会意外泄露草稿或预览路由。修订读取content:revisions:readcontent:revisions:read在只读访问之上追加listRevisions()与getRevision()。实现位于 content-access.ts通过RevisionRepository查询可见修订。两个语义要点修订快照可以保留后续被删除的字段值历史修订记录的是当时保存的完整字段快照即使当前条目已删除了某字段旧修订中仍可能持有该值——这是审计与回滚的价值所在。修订结果会剔除修订作者身份从源码可见listRevisions与getRevision在返回前都会通过解构authorId将其剥离const { authorId: _authorId, ...revision }即插件能看到修订内容与时间线但看不到谁改的这一身份信息保护用户隐私。写入与翻译content:writecontent:write追加create、update、delete三个写操作实现于createContentAccessWithWritecontext.ts。创建翻译创建翻译是插件最常用的写场景之一官方示例await ctx.content!.create(posts, data, { locale: fr, translationOf: sourceId });其中translationOf指向同一集合中的源条目 ID。从 types.ts 的ContentCreateOptions可见创建选项仅有两个locale默认为站点配置语言再退化为en与translationOf。翻译的继承与约束翻译创建的语义约束与ContentRepository.create的translationOf参数对应源必须是同一集合中的活跃条目不能跨集合引用也不能引用已删除/已回收的条目。新行加入源条目的翻译组并继承不可翻译字段、署名byline credits与分类taxonomy分配——翻译行只携带本语言的内容字段站点级归属信息保持一致。一个翻译组每个语言只允许一个活跃行同一语言重复创建会被拒绝这正是运行时测试要覆盖的 duplicate locales 场景。校验与保存钩子照常运行创建翻译同样走完整的校验管线且对发起创建的插件启用重入围栏re-entrancy fencing防止插件在自己的保存钩子中再次写同一内容造成死循环。写操作的其他细节update(collection, id, data)是草稿感知更新updateDraftAware会根据条目当前状态决定写入草稿还是直接更新字段且仅在有字段更新时才触碰updated_at与version——纯 SEO 更新不会无谓地制造修订噪音context.ts。data支持保留的seo键ContentWriteInputtypes.ts会被拆出并路由到_emdash_seo表与条目写入同一事务但对未启用 SEO 的集合传入seo会抛校验错误。delete是移入回收站软删除而非永久删除同时会释放条目锁并标记内容媒体用量缓存失效。稳定错误码写操作失败时插件应捕获以下稳定错误码宿主保证跨执行模式一致错误码含义CONFLICT修订冲突或同 locale 活跃行重复NOT_FOUND目标条目/集合不存在VALIDATION_ERROR字段校验失败含 SEO 未启用等约束SAVE_REJECTED保存钩子拒绝了本次写入发布策略hooks.content-policy:register发布策略是一组只读优先的钩子注册hooks.content-policy:register即可获得content:beforePublish、content:beforeSchedule、content:beforeUnpublish三个钩子而无需同时获得内容读取、写入或发布动作权限。也就是说一个仅声明策略能力的插件可以在不接触内容数据的前提下否决发布动作。拒绝机制在钩子中返回{ cancel: true, reason }即可拒绝对应动作// 示例拒绝包含特定字段值的条目发布 const plugin: SandboxedPlugin { hooks: { content:beforePublish: async (event) { if (event.content.data.missingCopyright) { return { cancel: true, reason: Copyright attribution is required before publication }; } return undefined; // 放行 }, }, };从 hooks.ts 的管线实现可见策略钩子以决策decision形式返回decision.kind cancel时携带{ pluginId, reason }记录取消来源钩子异常则按错误策略continue/abort处理并记录告警。content:beforeSchedule还要求事件必须携带scheduledAt否则直接报错。动作来源与定时的二次校验发布策略事件会标识动作来源覆盖API、MCP、可视化编辑器visual-editor、插件、调度器scheduler、系统system。对于经过认证的人类操作事件还会携带actor 身份与来源——这让策略可以根据是谁、通过什么入口做差异化判断。调度发布有特殊语义定时任务真正执行发布时发布策略会再次运行。如果此时策略拒绝则取消该条目的定时unschedule并把有界的原因bounded reason即截断后的拒绝理由记录给管理员查看。这避免了定时时放行、执行时反悔导致条目悬空。发布与恢复动作content:publish与content:restore修订号保护的发布动作content:publish在读取之上追加四个动作方法与一个版本化读取方法getVersioned(collection, id)返回带_rev不透明修订号的VersionedContentItempublish(collection, id, { _rev })发布unpublish(collection, id, { _rev })取消发布schedule(collection, id, { _rev, scheduledAt })定时发布unschedule(collection, id, { _rev })取消定时类型定义见 types.ts。使用铁律先读后写每次变更必须传递不透明的_rev。_rev是乐观并发控制的凭证——如果条目在你读取后被他人修改你的_rev已过期宿主会拒绝本次变更这正是测试过期修订stale revisions要覆盖的场景。成功的动作会返回下一修订并完整执行常规行为链发布策略policy→ 同步synchronization→ 媒体用量media-usage→ 缓存失效cache-invalidation→ 后置钩子after-hook。也就是说插件发起的发布与后台 UI 发起的发布走完全相同的生产路径不会绕过任何环节。恢复动作content:restore是刻意收窄的能力它只追加getTrashedVersioned()与restore()用于读取回收站中条目的版本化快照并恢复既不授予普通内容读取权限也不授予永久删除权限——插件永远无法通过该能力彻底抹除内容。动作溯源与重入拒绝所有插件动作都会报告{ source: plugin, pluginId }让下游策略、审计、钩子事件能识别动作来自哪个插件。同时同一插件对同一 canonical entry 再次进入同一动作会被拒绝。运行时有对应集成测试plugin-action-settlement.test.ts 演示了在content:afterPublish钩子中嵌套调用unpublish会得到CONTENT_ACTION_REENTRANT错误码——这就是跨动作重入围栏的行为验证。此外该测试文件还覆盖了发布期间媒体用量激活进行中时的MEDIA_USAGE_ACTIVATION_IN_PROGRESS拒绝、完整内容契约字段authorId、translationGroup、liveRevisionId、draftRevisionId、version的保留等边界。运行时测试策略文档最后给出针对内容能力的测试方法论核心是区分三种测试工具运行时夹具runtime fixtures用于建立初始条目、翻译、署名bylines与分类taxonomy分配——只造状态不触发钩子适合搭建测试前置条件。运行时动作runtime actions用于走发布路径等生产边界——每次动作都真实执行策略、同步、媒体用量与缓存逻辑。检查器inspectors用于读取持久化状态验证动作后的真实落库结果。当以下行为对你重要时务必显式覆盖测试过期修订stale revisions、策略拒绝policy rejection、重复 localeduplicate locales、重入re-entrancy、重启restart与缓存失效cache invalidation。完整的两级测试宿主说明见技能主文档 SKILL.mdcreatePluginTestHost()用于快速的钩子/路由/清单/能力/KV/设置/存储传输测试createPluginRuntimeTestHost()用于需要真实内容动作、插件激活、媒体、评论、重定向、调度、重启、授权、CSRF、缓存、Block Kit 校验或已保存条目扩展的测试。注意每次使用后都要释放dispose宿主。小结EmDash 的插件内容 API 通过能力声明 → 运行时门控 → 修订号护栏三层设计在沙箱与原生插件之间提供了统一且安全的内容访问契约读schema:read发现结构content:read读取内容与公开 URLcontent:revisions:read读取修订快照但剥离作者身份写content:write支持创建、更新、删除与翻译创建翻译继承不可翻译字段、署名与分类同 locale 唯一且全程带重入围栏策略hooks.content-policy:register以最小权限提供发布前/定时前/取消发布前的否决权定时执行会二次校验动作content:publish/content:restore强制先读后写、_rev防并发成功即走完整生产行为链插件动作全程可溯源。设计插件时请始终以最小能力声明为原则按需组合上述能力并用运行时测试宿主覆盖失败路径——这既是安全边界也是生产级插件的质量底线。更多相邻能力可继续阅读 hooks.md钩子管线与 publishing.md发布机制等参考文档。赞分享CMS后端前端插件系统【免费下载链接】emdashEmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress项目地址https://gitcode.com/gh_mirrors/emdas/emdash点击查看免费下载相关推荐EmDash CMS 插件内容 API 完全指南schema、翻译、发布策略与修订栅栏EmDash CMS 插件内容 API 完全指南schema、翻译、发布策略与修订栅栏 EmDash基于 Astro 的全栈 TypeScript CMSCMS后端前端插件系统Spring IoC 容器初始化源码解析一BeanDefinition 的资源定位过程 —— 以 FileSystemXmlApplicationContext 为例Spring IoC 容器初始化源码解析一BeanDefinition 的资源定位过程 —— 以 FileSystemXmlApplicationContCMS后端前端插件系统三步接入 Surface Pen 压感用 windows-rs 让 Rust 应用听懂笔尖轻重三步接入 Surface Pen 压感用 windows rs 让 Rust 应用听懂笔尖轻重 在绘画应用里写字如果笔触粗细永远是固定值画出来的线条像 PCMS后端前端插件系统上一篇OmenSuperHub终极指南解锁惠普暗影精灵笔记本隐藏性能的完整教程下一篇【终极指南】Stable Diffusion模型训练硬件需求与时间估算stable-diffusion-webui-docker配置详解创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考