Ever Gauzy legal-ui 插件改造实录:法律文档从运行时远程拉取改为构建期捆绑
后端前端企业应用MCP 服务【免费下载链接】ever-gauzyEver® Gauzy™ - Open Business Management Platform (ERP/CRM/HRM/ATS/PM) - https://gauzy.co项目地址https://gitcode.com/GitHub_Trending/ev/ever-gauzy点击查看免费下载导读本文基于 packages/plugins/legal-ui/CHANGELOG.md 的 [Unreleased] 变更记录深入剖析 Ever Gauzy 的 Legal UI 插件如何将服务条款Terms of Service、隐私政策Privacy Policy与 Cookie 政策Cookie Policy从运行时调用第三方 REST API 注入页面重构为构建期从ever-co/legal语料库 vendored 并随应用打包的架构。读完本文你将掌握该插件的文档来源机制、LegalService.getDocument()同步查询 API、ILegalDocument数据模型、内容刷新脚本sync-legal-content.mjs的用法以及对应的路由、组件与测试保障能够独立理解并维护 Gauzy 中的法律页面。背景运行时远程拉取的旧模式与空白页故障在本次变更之前Gauzy 应用内的法律页面存在一个根深蒂固的可靠性隐患服务条款、隐私政策、Cookie 政策三份文档在运行时通过 HTTP 从第三方 REST API 拉取然后用[innerHtml]注入到页面中。该模式的问题在于每次渲染法律页面都会发起网络请求依赖第三方服务可用性与订阅状态唯一的错误处理是console.error一旦远程服务宕机、请求被拦截或订阅过期法律页面就会渲染成空白页用户连基本的法律文本都看不到页面内容不可在代码评审中审查也无法精确追踪线上到底渲染的是哪个版本。CHANGELOG 明确记录了这一故障模式及其后果Previously the three documents were fetched from a third-party REST API at runtime and injected with[innerHtml]. The only error handling was aconsole.error, so any failure — an outage, a blocked request, or a lapsed subscription — rendered the legal pagesblank.本次 [Unreleased] 变更彻底消除了这一失败模式三份文档改为从ever-co/legal语料库 vendored 进src/lib/content/*.generated.ts随构建捆绑运行时零网络请求。新架构核心语料库 vendored 构建期捆绑新的文档来源机制体现在 packages/plugins/legal-ui/src/lib/content/index.ts 中。该文件头部明确标注DO NOT EDIT BY HAND由scripts/sync-legal-content.mjs从ever-co/legal0.1.0生成并导出以下常量常量值含义LEGAL_CORPUS_PACKAGEever-co/legal法律文本的来源 npm 包LEGAL_CORPUS_VERSION0.1.0捆绑文本对应的语料库版本LEGAL_CORPUS_NAMEEver语料库归属如ever/ever.coLEGAL_PRODUCTgauzy文档渲染面向的产品LEGAL_DEFAULT_LOCALEen无本地化文档时的兜底 locale语料库当前仅发布英文LEGAL_CORPUSreadonly ILegalDocument[]捆绑进应用的每一份法律文档LEGAL_CORPUS由三个生成文件组成tos.en.generated.ts、privacy.en.generated.ts、cookies.en.generated.ts。这种架构带来三个关键收益README 与脚本注释中均有阐述渲染法律页面不发起任何 HTTP 请求因此远程服务宕机时页面不可能空白无第三方依赖、无订阅依赖运行时与构建都不需要ever-co/legal包文本在 diff 中可审查且每个文档由语料库的sha256固定版本线上渲染的内容可精确追踪。页面头部header现在会展示文档标题、产品、版本与生效日期这些元数据直接来自语料库的index.json由页面组件读取并渲染。新增 API 与数据模型同步查询的 LegalService本次变更新增了核心服务方法LegalService.getDocument(document, locale?)实现在 packages/plugins/legal-ui/src/lib/providers/legal.service.ts。它是针对内存常量的同步查找不发起 HTTP 请求getDocument(document: LegalDocumentSlug, locale: string LEGAL_DEFAULT_LOCALE): ILegalDocument | null { const candidates LEGAL_CORPUS.filter((entry) entry.document document); if (!candidates.length) { return null; } // Exact locale, then the base language (en-GB - en), then the default locale. const normalized (locale || LEGAL_DEFAULT_LOCALE).toLowerCase(); const language normalized.split(/[-_]/)[0]; return ( candidates.find((entry) entry.locale.toLowerCase() normalized) ?? candidates.find((entry) entry.locale.toLowerCase() language) ?? candidates.find((entry) entry.locale.toLowerCase() LEGAL_DEFAULT_LOCALE) ?? candidates[0] ); }查找策略非常稳健执行三条回退链先精确匹配完整 locale如en-GB再匹配基础语言截断-/_前缀后的en最后回退到LEGAL_DEFAULT_LOCALEen。若语料库完全不包含该文档则返回null。README 给出了典型用法示例const terms this.legalService.getDocument(tos); terms.title; // Terms of Service terms.version; // 1.0.0 terms.effectiveDate; // 2026-08-02 terms.html; // rendered HTML bodyILegalDocument 与 LegalDocumentSlug 模型新增的ILegalDocument接口定义在 packages/plugins/legal-ui/src/lib/models/legal-document.model.ts每个字段均为只读描述一份捆绑文档的完整元数据字段类型说明documentLegalDocumentSlug文档 id如tosproductstring文档面向的产品如gauzyproductNamestring人类可读的产品名如Ever Gauzydomainstring文档指向的规范域名如gauzy.coentitystring发布文档的法律实体entityIdstring法律实体的稳定标识localestring文本的 BCP-47 风格 locale如enversionstring文档语义化版本如1.0.0effectiveDatestring版本生效日期格式YYYY-MM-DDsha256string语料库固定该文档修订版的 SHA-256titlestring文档标题取自其首个标题htmlstring渲染后的 HTML 正文LegalDocumentSlug类型则限定为tos | privacy | cookies与ever-co/legal语料库使用的文档 id 保持一致。内容刷新机制sync-legal-content.mjsever-co/legal被有意设计为不依赖本仓库——构建与运行时都不需要它只有负责刷新文本的维护者需要。刷新脚本位于 packages/plugins/legal-ui/scripts/sync-legal-content.mjs支持三种用法# 1. 使用已安装到本地的语料库副本 node packages/plugins/legal-ui/scripts/sync-legal-content.mjs # 2. 指向已解压的语料库包的 dist 目录 node packages/plugins/legal-ui/scripts/sync-legal-content.mjs --corpus /path/to/ever-co/legal/dist # 3. 让脚本把发布的 tarball 拉到临时目录需要网络 node packages/plugins/legal-ui/scripts/sync-legal-content.mjs --fetch node packages/plugins/legal-ui/scripts/sync-legal-content.mjs --fetch --corpus-version 0.1.0命令行参数说明参数作用--corpus dir指定本地已解压的语料库dist目录--fetch通过npm pack下载发布 tarball 到临时目录并解压--corpus-version version与--fetch配合指定 npm 版本或 dist-tag默认latest脚本会依次执行以下关键步骤对应源码main()与各辅助函数解析参数parseArgs未知参数直接抛错解析语料库目录resolveCorpusDir按--corpus--fetchrequire.resolve(ever-co/legal)的优先级确定来源若三处都不可用会输出一段友好的提示说明该包故意不是本仓库依赖并建议--fetch用法读取index.json并尝试从语料库外层package.json读取真实版本号覆盖命令行传入的版本逐文档校验并生成对tos/privacy/cookies三个 slug在语料库索引中查找product gauzy且locale en的条目若缺失、或publishable false、或index.json中的sha256与文档 sidecar JSON 不一致脚本都会**大声失败fails loudly**而不是静默产出错误文本剥离表现性 front matterstripFrontMattertos和privacy开头有三行重复元数据的 HTMLH1 标题、产品/域名行、版本/生效日期行会被精确匹配后剔除并提升到页面头部渲染cookies文档从 h2 开始、没有顶层标题因此使用脚本中定义的显示标题兜底。每条剥离行都要求精确匹配语料库一旦变化退化行为是front matter 显示两次而绝不会静默丢弃正文生成 TypeScript 源文件renderDocumentModule/renderIndexModule将正文 HTML 转义后嵌入模板字符串保留换行以便 diff 可审查元数据写入单引号字符串字面量最终写出 3 个*.generated.ts与 1 个index.ts共 4 个文件。脚本在控制台会输出每个文档的版本、生效日期、标题、剥离的 front matter 行数与源文件 sha256 摘要前 12 位方便维护者核对 diff。执行完毕后维护者的标准动作是审查 diff、提交再生成的 4 个文件。页面渲染与路由两个挂载点复用同一组件插件提供两个挂载点README 中的路由表背后由同一组组件支撑路由模块布局#/legal/terms、#/legal/privacyLegalModule公开页位于NbAuthComponent内#/pages/legal/terms、#/pages/legal/privacyPageLegalModule认证后的应用外壳路由定义在 packages/plugins/legal-ui/src/lib/legal.routes.ts其中值得注意的设计是Cookie 政策路由复用隐私政策组件privacy与cookies两条路由都指向PrivacyPolicyComponent通过路由data.documents区分渲染哪个文档避免复制页面标记{ path: privacy, component: PrivacyPolicyComponent, data: { documents: [privacy] } }, { // The Cookie Policy is its own page. PrivacyPolicyComponent already renders the // cookie document from the bundled corpus, so the route reuses it and selects the // section through data.documents rather than duplicating the markup. path: cookies, component: PrivacyPolicyComponent, data: { documents: [cookies] } }PrivacyPolicyComponent源码在ngOnInit中先通过resolveSections()读取路由快照的data.documents决定显示哪些区块未声明documents时默认两者都显示保持旧行为兼容再通过loadPolicies()同步调用getDocument(privacy, locale)与getDocument(cookies, locale)。TermsAndConditionsComponent源码同样在loadTerms()中同步获取tos文档并把terms.html赋给term_and_policy。页面模板 terms-and-conditions.component.html 的 header 区块渲染标题、产品名、版本与生效日期header classlegal-doc__header *ngIfterms h1 classlegal-doc__title{{ terms.title }}/h1 p classlegal-doc__meta span classlegal-doc__product{{ terms.productName }}/span span classlegal-doc__separator aria-hiddentruemiddot;/span span classlegal-doc__version{{ LEGAL.DOCUMENT_VERSION | translate: { version: terms.version } }}/span ... /p /header div classlegal-doc__body [innerHtml]term_and_policy/div正文仍通过[innerHtml]注入但注入的是构建期捆绑的本地 HTML而非运行时远程响应。新增的表单样式_legal-document.scss覆盖了语料库内容中的表格——旧提供方从不输出表格因此这是本次变更顺带补上的视觉能力。测试保障把零网络请求固化为契约单元测试 packages/plugins/legal-ui/src/lib/providers/legal.service.spec.ts 把新架构的可靠性承诺变成了可回归的测试契约。测试中最醒目的设计是注入了一个任何get调用都会抛错的假 HttpClientconst http { get: () { throw new Error(Rendering a bundled legal document must not perform an HTTP request); } } as unknown as HttpClient;测试断言覆盖了以下关键契约无网络可渲染对tos、privacy、cookies三个 sluggetDocument均返回有效文档且product为LEGAL_PRODUCT、locale为LEGAL_DEFAULT_LOCALE正文非空每个文档html.length 1000头部元数据完整title非空、version匹配^\d\.\d\.\d$、effectiveDate匹配^\d{4}-\d{2}-\d{2}$、sha256匹配 64 位十六进制无任何可执行标记HTML 不含script|iframe|object|embed|style也不含on*事件属性——这直接约束了语料库文本的注入安全性每份文档恰好捆绑一次LEGAL_CORPUS中document:locale键无重复数量等于 3locale 回退请求不存在的语言如de-DE、空字符串或undefined时均回退到默认 locale未知文档返回nullgetDocument(nope)为null。这套测试从工程上锁定了法律页面不能因网络而空白这一核心诉求任何未来回归都会在 CI 中立即暴露。移除内容与兼容保留CHANGELOG 明确列出了被移除的硬编码第三方 API 端点TERM_AND_POLICY_ENDPOINTPRIVACY_POLICY_ENDPOINTCOOKIE_PRIVACY_POLICY_ENDPOINT以及使用它们的运行时拉取逻辑。同时保留了LegalService.getContentFromFromUrl()源码——它通过HttpClient.get加载任意返回{ content: string }的 JSON URL保留给仍需渲染远程托管文档的调用方但应用内法律页面已不再调用它getContentFromFromUrl(url: string) { return firstValueFrom(this.http.get(url)); }这一保留但不使用的策略既满足了可能存在的远程文档渲染场景又确保 Gauzy 内建法律页面走上零网络依赖的新路径。总结从可能空白到不可能空白的架构演进本次 [Unreleased] 变更的本质是把法律文档的获取时机从运行时移到构建期并借助 vendored 生成文件将外部依赖从运行时链路中彻底剔除。收益可概括为四点可靠性法律页面零 HTTP 请求第三方宕机、请求被拦截、订阅过期等故障模式全部消失可审计性线上渲染的文本就是仓库中提交的生成文件版本与生效日期由语料库index.json驱动sha256固定修订版diff 可完全审查可维护性刷新文本只需运行sync-legal-content.mjs --fetch脚本对语料库缺失、不可发布、版本不一致等情况都会大声失败可测试性单元测试以渲染捆绑文档不得发起 HTTP 请求为契约覆盖 locale 回退、元数据格式与注入安全。对于 Ever Gauzy 的部署者与开发者而言这套模式同样适用于其他内容应在构建期固定、运行时不得依赖第三方的页面场景。相关参考文件CHANGELOG 见 packages/plugins/legal-ui/CHANGELOG.md使用说明见 packages/plugins/legal-ui/README.md核心服务实现见 legal.service.ts刷新脚本见 sync-legal-content.mjs测试契约见 legal.service.spec.ts。赞分享后端前端企业应用MCP 服务【免费下载链接】ever-gauzyEver® Gauzy™ - Open Business Management Platform (ERP/CRM/HRM/ATS/PM) - https://gauzy.co项目地址https://gitcode.com/GitHub_Trending/ev/ever-gauzy点击查看免费下载相关推荐在 Ever Gauzy 中内嵌渲染法律文档gauzy/plugin-legal-ui 插件深度解析在 Ever Gauzy 中内嵌渲染法律文档gauzy/plugin legal ui 插件深度解析 导读 gauzy/plugin legal ui 是后端前端企业应用MCP 服务Ever Gauzy 插件化 UI 开发指南基于 gauzy/plugin-ui 构建可插拔前端模块Ever Gauzy 插件化 UI 开发指南基于 gauzy/plugin ui 构建可插拔前端模块 gauzy/plugin ui 是 Ever Gau后端前端企业应用MCP 服务Ever Gauzy 文档中心 UI 插件gauzy/plugin-docs-ui深度解析功能架构、注册方式与源码实现Ever Gauzy 文档中心 UI 插件gauzy/plugin docs ui深度解析功能架构、注册方式与源码实现 gauzy/plugin do后端前端企业应用MCP 服务创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考