<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>优维科技 - 文档中心 Blog</title>
        <link>http://uwintech.cn/next-docs/blog</link>
        <description>优维科技 - 文档中心 Blog</description>
        <lastBuildDate>Wed, 11 May 2022 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[EasyMABuilder 系列知识 - 构件开发 Step by Step]]></title>
            <link>http://uwintech.cn/next-docs/blog/2022/05/11/brick-development</link>
            <guid>http://uwintech.cn/next-docs/blog/2022/05/11/brick-development</guid>
            <pubDate>Wed, 11 May 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[上一篇文章我们讲述了在 EasyMABuilder 中如何通过表达式和微应用函数来进行数据处理，让低代码平台也获得了与专业代码媲美的编写代码的能力和体验，不过这些代码仅限于数据加工，它无法参与 UI 界面相关的工作，虽然 EasyMABuilder 提供了数百个开箱即用的构件，并提供了在微应用层面封装模板的能力，但对于 UI 界面，总有更个性化的场景和需求，对此，我们提供了用户编写新构件的能力，并提供配套的脚手架工具来方便用户更快捷的编写新构件。]]></description>
            <content:encoded><![CDATA[<p>上一篇文章我们讲述了在 EasyMABuilder 中如何通过表达式和微应用函数来进行数据处理，让低代码平台也获得了与专业代码媲美的编写代码的能力和体验，不过这些代码仅限于数据加工，它无法参与 UI 界面相关的工作，虽然 EasyMABuilder 提供了数百个开箱即用的构件，并提供了在微应用层面封装模板的能力，但对于 UI 界面，总有更个性化的场景和需求，对此，我们提供了用户编写新构件的能力，并提供配套的脚手架工具来方便用户更快捷的编写新构件。</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="准备">准备<a href="#准备" class="hash-link" aria-label="Direct link to 准备" title="Direct link to 准备">​</a></h2><p>开始前，我们需要准备本地开发环境。</p><ol><li><a href="https://nodejs.org/" target="_blank" rel="noopener noreferrer">NodeJS</a> <!-- -->(<!-- -->&gt;=14<!-- -->)<!-- -->；</li><li><a href="https://classic.yarnpkg.com/en/docs/install" target="_blank" rel="noopener noreferrer">Yarn</a> <!-- -->(<!-- -->1.x<!-- -->)<!-- -->，安装方式：执行 <code>npm install -g yarn</code>；</li><li><a href="https://github.com/lerna/lerna" target="_blank" rel="noopener noreferrer">Lerna</a>，安装方式：执行 <code>npm install -g lerna</code>。</li></ol><div class="theme-admonition theme-admonition-note alert alert--secondary admonition_LlT9"><div class="admonitionHeading_tbUL"><span class="admonitionIcon_kALy"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_S0QG"><p>Windows 用户请使用 Subsystem for Linux。</p></div></div><h2 class="anchor anchorWithStickyNavbar_LWe7" id="创建一个新的构件项目">创建一个新的构件项目<a href="#创建一个新的构件项目" class="hash-link" aria-label="Direct link to 创建一个新的构件项目" title="Direct link to 创建一个新的构件项目">​</a></h2><p>开发者可以按需创建自己的构件项目，每个项目中可以包含多个构件包，每个构件包又可以定义多个构件。</p><p>使用优维官方提供的 CLI 工具即可一键生成新的构件项目：</p><div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># 指定项目名称，例如 `my-repo`，将在当前目录创建一个子目录：</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">npx @next-core/create-next-repo my-repo</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># 项目创建后，进入项目并安装 Node 模块。</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token builtin class-name">cd</span><span class="token plain"> my-repo </span><span class="token operator" style="color:#393A34">&amp;&amp;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">yarn</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-create-new-repo-243602c83dddd2ba4c3d8f1ffc6da991.png" width="634" height="199" class="img_ev3q"></p><p>这个新生成的项目已经初始化提供了构建、测试、打包、代码静态检查等现代化的前端工程化配置，如果我们将项目托管在 GitHub 上，还可以开箱即用地使用包括持续集成和依赖更新等在内的自动化工作流。</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="创建一个新的构件包">创建一个新的构件包<a href="#创建一个新的构件包" class="hash-link" aria-label="Direct link to 创建一个新的构件包" title="Direct link to 创建一个新的构件包">​</a></h2><p>初始化的项目还没有任何构件包，我们先使用项目中内置的脚手架工具创建一个：</p><ol><li>运行 <code>yarn yo</code>；</li><li>选择 <code>a new package of bricks</code>；</li><li>输入构件包的名称，例如 <code>my-bricks</code>；</li><li>然后，我们可以选择同时创建一个构件，本文为了演示我们先选择跳过。</li></ol><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-new-brick-package-1-2d6619e2bca062ed5929f5078b8039e3.png" width="581" height="213" class="img_ev3q"></p><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-new-brick-package-2-ecfbf5058ebbd6321a02438886c58997.png" width="581" height="398" class="img_ev3q"></p><p>新的构件包的代码就已经初始化完成，其中的各种工程配置文件可以按需自由调整。</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="创建一个新构件">创建一个新构件<a href="#创建一个新构件" class="hash-link" aria-label="Direct link to 创建一个新构件" title="Direct link to 创建一个新构件">​</a></h2><p>构件包有了，我们再创建一个新的构件：</p><ol><li>运行 <code>yarn yo</code>；</li><li>选择 <code>a new brick</code>；</li><li>输入构件的名称，例如 <code>hello-world</code>。</li></ol><p><img loading="lazy" src="/next-docs/assets/images/screen-record-new-brick-8a1100952c79591668ceef11d3df7d12.gif" width="600" height="330" class="img_ev3q"></p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="编写构件">编写构件<a href="#编写构件" class="hash-link" aria-label="Direct link to 编写构件" title="Direct link to 编写构件">​</a></h2><p>初始化的构件主要包含以下文件：</p><div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">bricks/my-bricks/src/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  hello-world/           </span><span class="token comment" style="color:#999988;font-style:italic"># 构件目录</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    HelloWorld.spec.tsx  </span><span class="token comment" style="color:#999988;font-style:italic"># React 组件单元测试</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    HelloWorld.tsx       </span><span class="token comment" style="color:#999988;font-style:italic"># React 组件代码</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    index.spec.ts        </span><span class="token comment" style="color:#999988;font-style:italic"># Custom Elements 单元测试</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    index.spec.tsx       </span><span class="token comment" style="color:#999988;font-style:italic"># Custom Elements 代码</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>这个构件已经可以使用，我们先启动开发版本的构建：</p><div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">yarn</span><span class="token plain"> start --scope @next-bricks/my-bricks</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>然后打开一个新的终端来启动开发服务：</p><div class="language-shell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-shell codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">yarn</span><span class="token plain"> serve --local-bricks</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">my-bricks</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>接着就可以在我们的微应用中使用该构件了：</p><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-brick-development-1-aae665a2c8f3834bcfca274929a6b31f.png" width="672" height="487" class="img_ev3q"></p><p>不过这个构件还只是一个空壳，我们接下来完善它。假设我们的需求是做一个按钮构件，简单做下修改：</p><div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_Ktv7">HelloWorld.tsx</div><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"react"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Button</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"antd"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">HelloWorld</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ReactElement</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">Hello World</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>保存后，本地页面将立即更新：</p><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-brick-development-2-8e78a830df3f22adbaf1831615a862b4.png" width="672" height="487" class="img_ev3q"></p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="属性">属性<a href="#属性" class="hash-link" aria-label="Direct link to 属性" title="Direct link to 属性">​</a></h3><p>我们再让构件支持一个“按钮类型”的属性：</p><div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_Ktv7">HelloWorld.tsx</div><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"react"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Button</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"antd"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token maybe-class-name">ButtonType</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"antd/lib/button"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">HelloWorldProps</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  buttonType</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">ButtonType</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">HelloWorld</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  buttonType</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">HelloWorldProps</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ReactElement</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">type</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">buttonType</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text">Hello World</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_Ktv7">index.tsx</div><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">HelloWorldElement</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">UpdatingElement</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  @</span><span class="token function" style="color:#d73a49">property</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  buttonType</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">ButtonType</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">protected</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">_render</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">isConnected</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token maybe-class-name">ReactDOM</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">render</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">BrickWrapper</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">HelloWorld</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">buttonType</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript keyword" style="color:#00009f">this</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript property-access" style="color:#00009f">buttonType</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">BrickWrapper</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">this</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>最后修改我们的应用编排，设置一个按钮类型：</p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">brick</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"my-bricks.hello-world"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">properties</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">buttonType</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"primary"</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-brick-development-3-940934deee2028b0ec29aca83eb8e3f4.png" width="672" height="487" class="img_ev3q"></p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="事件">事件<a href="#事件" class="hash-link" aria-label="Direct link to 事件" title="Direct link to 事件">​</a></h3><p>我们再为它添加一个点击事件：</p><div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_Ktv7">HelloWorld.tsx</div><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token class-name">HelloWorldProps</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  buttonType</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">ButtonType</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">onButtonClick</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">greeting</span><span class="token operator" style="color:#393A34">?</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">HelloWorld</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  buttonType</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  onButtonClick</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">HelloWorldProps</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">ReactElement</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> handleClick </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">useCallback</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">onButtonClick</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"Hello World"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">onButtonClick</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">type</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">buttonType</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">handleClick</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">      Hello World</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Button</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_Ktv7">index.tsx</div><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:#393A34">{</span><span class="token imports"> event</span><span class="token imports punctuation" style="color:#393A34">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">EventEmitter</span><span class="token imports"> </span><span class="token imports punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@next-core/brick-kit"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">HelloWorldElement</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">UpdatingElement</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  @</span><span class="token function" style="color:#d73a49">event</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"button.click"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> _buttonClickEvent</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token maybe-class-name">EventEmitter</span><span class="token operator" style="color:#393A34">&lt;</span><span class="token builtin">string</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> _handleButtonClick </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">greeting</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">_buttonClickEvent</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">emit</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">greeting</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">protected</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">_render</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">isConnected</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token maybe-class-name">ReactDOM</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">render</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">BrickWrapper</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">HelloWorld</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">            </span><span class="token tag attr-name" style="color:#00a4db">buttonType</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript keyword" style="color:#00009f">this</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript property-access" style="color:#00009f">buttonType</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line" style="color:#393A34"><span class="token tag" style="color:#00009f">            </span><span class="token tag attr-name" style="color:#00a4db">onButtonClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript keyword" style="color:#00009f">this</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript property-access" style="color:#00009f">_handleButtonClick</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token tag" style="color:#00009f">          </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">BrickWrapper</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token keyword" style="color:#00009f">this</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>最后修改我们的应用编排，绑定一个按钮事件：</p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">brick</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"my-bricks.hello-world"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">properties</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">buttonType</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"primary"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">events</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">button.click</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">action</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"message.info"</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">args</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"&lt;% EVENT.detail %&gt;"</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>点击该按钮，将弹出提示信息：</p><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-brick-development-4-4ee241d8a6f8e62d1a39bdd9a21e9fc0.png" width="672" height="487" class="img_ev3q"></p><div class="theme-admonition theme-admonition-note alert alert--secondary admonition_LlT9"><div class="admonitionHeading_tbUL"><span class="admonitionIcon_kALy"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_S0QG"><p>由于我们的构件即是 HTML 元素，因此，鼠标点击等原生事件可以直接在构件中使用，无需进行编程，这里我们仅用作编写构件事件的示例。</p></div></div><p>至此，我们完成了基本的构件能力的开发。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[EasyMABuilder 系列知识 - 数据处理：表达式和微应用函数]]></title>
            <link>http://uwintech.cn/next-docs/blog/2022/04/14/expressions-and-functions</link>
            <guid>http://uwintech.cn/next-docs/blog/2022/04/14/expressions-and-functions</guid>
            <pubDate>Thu, 14 Apr 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[上一篇文章我们讲述了在 EasyMABuilder 中通过 Context 和 State 在编排中管理数据，其中使用到了形如 `` 的表达式，和无代码平台不同，低代码平台为了能解决更丰富的应用场景、满足更个性化的需求，仍然离不开代码的编写，包括简单的逻辑计算和数据加工处理，而对于高阶开发者搭建更复杂的应用时，甚至需要不逊色于专业代码的开发体验，对此 EasyMABuilder 提供了对应的代码开发能力：表达式和微应用函数。]]></description>
            <content:encoded><![CDATA[<p>上一篇文章我们讲述了在 EasyMABuilder 中通过 Context 和 State 在编排中管理数据，其中使用到了形如 <code>&lt;% CTX.saving %&gt;</code> 的表达式，和无代码平台不同，低代码平台为了能解决更丰富的应用场景、满足更个性化的需求，仍然离不开代码的编写，包括简单的逻辑计算和数据加工处理，而对于高阶开发者搭建更复杂的应用时，甚至需要不逊色于专业代码的开发体验，对此 EasyMABuilder 提供了对应的代码开发能力：<em>表达式</em>和<em>微应用函数</em>。</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="表达式">表达式<a href="#表达式" class="hash-link" aria-label="Direct link to 表达式" title="Direct link to 表达式">​</a></h2><p><em>表达式</em>用于在编排中引用动态数据并进行简单的加工处理，可以在编排中随处使用，包括属性、事件、回调等等。</p><p>这里的表达式是 JavaScript 的表达式子集，对于有少量 Web 编程、或者 C/Java 编程经验的同学，上手都不会太难。</p><ul><li>引用数据： <code>&lt;% CTX.saving %&gt;</code></li><li>逻辑判断：<code>&lt;% CTX.saving ? "Saving..." : "Save" %&gt;</code></li><li>数学运算: <code>&lt;% CTX.rate * 100 %&gt;</code></li><li>字符串组装：<code>&lt;% `/project/${DATA.projectId}` %&gt;</code></li><li>数据过滤：<code>&lt;% DATA.list.filter(item =&gt; item.active) %&gt;</code></li></ul><p>表达式还支持最新的 JavaScript 的语言特性，例如在 ES2020 中引入的<em>可选链 Optional Chaining</em> 和<em>空值转换 Nullish Coalescing</em> 语法：</p><ul><li><code>&lt;% a?.b %&gt;</code></li><li><code>&lt;% data.settings ?? defaultSettings %&gt;</code></li></ul><p>我们还在表达式中集成了 <a href="https://lodash.com/" target="_blank" rel="noopener noreferrer">Lodash</a> 和 <a href="https://momentjs.com/" target="_blank" rel="noopener noreferrer">Moment</a>，对于有经验的开发者可以更得心应手的进行数据加工。此外还集成了 EasyMABuilder 官方提供的管道加工函数 <a href="https://easyops-cn.github.io/brick-next-pipes/brick-next-pipes.html" target="_blank" rel="noopener noreferrer">brick-next-pipes</a>，进行特定应用场景的数据加工。</p><ul><li><code>&lt;% _.findLastIndex(list, "active") %&gt;</code></li><li><code>&lt;% moment(date).format() %&gt;</code></li><li><code>&lt;% PIPES.yamlStringify(input) %&gt;</code></li></ul><p>而为了使流式的、连续的数据加工可以更流畅的编写、并具有更好的可读性，我们加入了 JavaScript 目前还在提案阶段的 <a href="https://github.com/tc39/proposal-pipeline-operator" target="_blank" rel="noopener noreferrer">Pipeline Operator</a> 的特性，并采用了其 <a href="https://github.com/tc39/proposal-pipeline-operator/tree/1ac09f375fa1553992a3bb257b57173a630bc172" target="_blank" rel="noopener noreferrer">minimal 草案</a>。</p><ul><li><code>&lt;% DATA.source |&gt; JSON.parse |&gt; PIPES.yamlStringify %&gt;</code></li></ul><p>另一方面，由于表达式的定位是处理简单的数据加工，除了不能使用 JS 语句类语法外，也限制了修改操作类的 JS 表达式语法，例如：赋值 <code>a = 1</code>、更新值 <code>++i</code>、删除 <code>delete a.b</code> 等，同时也无法访问 DOM 对象。</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="微应用函数">微应用函数<a href="#微应用函数" class="hash-link" aria-label="Direct link to 微应用函数" title="Direct link to 微应用函数">​</a></h2><p>表达式使用简单的语法就可以完成常见的数据加工，而当面对更加复杂的数据处理的需求时，它就显得力不从心了，同时越复杂的逻辑越需要自动化测试来保障其健壮性和可维护性，这些都是表达式不具备的，因此，我们提供了<em>微应用函数</em>。</p><p>微应用函数直接在 Visual Builder 中相关的项目中编写，使用 JavaScript 或 TypeScript：</p><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-say-hello-705577e133336073f466e13c8632db31.png" width="2400" height="1098" class="img_ev3q"></p><p>编写好的函数可以在该微应用的编排表达式中使用：<code>&lt;% FN.sayHello("World") %&gt;</code>。</p><p>微应用函数中可以使用上文<em>表达式</em>中提到的内置对象，例如 <code>_</code>、 <code>moment</code>、 <code>PIPES</code> 等。同时函数也可以使用同一个微应用下的其它函数。而无论使用 JavaScript 或 TypeScript ，函数编辑器都具有类型提示、自动补全、代码检查等能力，其体验向桌面 IDE 看齐。</p><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-intelli-sense-1-5e25088c57e407c51f048d0a3749bd54.png" width="1105" height="247" class="img_ev3q"></p><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-intelli-sense-2-37a5c770908ea8e4286246246fdcb1b3.png" width="2400" height="984" class="img_ev3q"></p><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-intelli-sense-3-6fc102e6f8c8bcebc2f9fb99bf9d92f6.png" width="1141" height="250" class="img_ev3q"></p><p>对函数进行调试也非常简单，只需给出输入参数即可运行并查看返回结果：</p><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-function-debug-26fffab2afe5faa7379c081504cb2f16.png" width="2400" height="984" class="img_ev3q"></p><p>微应用函数为解决复杂问题而生，虽然它直接在 Visual Builder 界面中编写，但仍需要手段来保障其质量、以及健壮性和可维护性。对此，我们提供了对函数进行单元测试的能力，并提供覆盖率统计。</p><p>与编写普通代码的单元测试不同，由于微应用函数专注于处理数据，不依赖副作用也不产生副作用，因此，我们的测试用例可以直接来自 Debug 的输入参数和输出结果。编写单元测试后，可以直接统计覆盖率，并在代码编辑器中给出未覆盖的代码部分的相关提示。</p><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-function-test-cfb3154742392d6df6a6e4d44d728911.png" width="2400" height="984" class="img_ev3q"></p><p>而在维护微应用函数时，修改代码并保存前，系统会自动运行单元测试，并对比测试预期结果，以便发现可能的问题：</p><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-function-test-failed-12fb0790fcdf25329d9ad971a602b076.png" width="2390" height="974" class="img_ev3q"></p><p>至此，我们在低代码平台中获得了与专业代码媲美的编写代码的能力和体验，不过这些代码仅限于数据加工，它无法参与 UI 界面相关的工作，虽然 EasyMABuilder 提供了数百个开箱即用的构件，并提供了在微应用层面封装模板的能力，但对于 UI 界面，总有更个性化的场景和需求，对此，我们提供了用户编写新构件的能力，并提供配套的脚手架工具来方便用户更快捷的编写新构件，下一期我们将讲述《构件开发 Step by Step》。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[EasyMABuilder 系列知识 - Context 和 State]]></title>
            <link>http://uwintech.cn/next-docs/blog/2022/03/23/context-and-state</link>
            <guid>http://uwintech.cn/next-docs/blog/2022/03/23/context-and-state</guid>
            <pubDate>Wed, 23 Mar 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[上一篇文章我们讲述了 EasyMABuilder 中的自定义模板，它是对构件组合的封装，和构件一样是组织 UI 界面结构的可复用单元。]]></description>
            <content:encoded><![CDATA[<p>上一篇文章我们讲述了 EasyMABuilder 中的自定义模板，它是对构件组合的封装，和构件一样是组织 UI 界面结构的可复用单元。</p><p>而在低代码平台中，除了组织基本的 UI 界面结构外，还有一项重要的工作是维护和管理数据，特别是来自远端的异步数据、或者动态变更的状态数据。</p><p>对此，EasyMABuilder 提供了 <em>Context</em> 和 <em>State</em> 两种数据管理方式。</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="context">Context<a href="#context" class="hash-link" aria-label="Direct link to Context" title="Direct link to Context">​</a></h2><p>Context 在 EasyMABuilder 中用于管理全局的状态数据，在一个路由页面的生命周期中有效，其数据的初始化可以来自异步的远端请求的结果，也可以是静态声明的，这些数据可以在构件的属性或事件中引用，另一方面，它的值可以在页面的交互过程中按需动态更新，同时引用了相关数据的构件的属性也可以实时地获得更新。</p><p>例如，假设我们的页面需要一个显示主机列表的表格，该数据来自 CMDB 中的主机模型，我们可以在页面中声明一个 Context 数据：</p><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-context-1-75f1a543b82e069f7cdce8634f4cf76b.png" width="651" height="453" class="img_ev3q"></p><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-context-3-82ffa674a40b59dc6bfc11e35ab4efbc.png" width="286" height="232" class="img_ev3q"></p><p>然后在该页面的构件中就可以通过<em>表达式</em>来引用这些数据，例如赋值给表格的数据源属性：</p><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-context-2-75884e4763cc1432b8db91a9570db993.png" width="391" height="197" class="img_ev3q"></p><p>另一方面，该状态数据可以在界面的交互过程中按需更新，例如通过某个特定事件来更改其状态数据。假设我们页面中有一个按钮，点击它将保存当前表单中的数据，同时我们希望点击它后立即禁用它，以防止连续点击发生，并在请求完成后恢复按钮状态。我们可以先定义一个 <code>saving</code> 的状态数据，并赋值给按钮的 <code>disabled</code> 属性：</p><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-context-4-cf803210f20c2d75993b5a8becf9ae3c.png" width="337" height="147" class="img_ev3q"></p><p>注意我们使用了<em>逗号表达式</em>并前置了字符串 <code>"track context"</code>，它提示系统在相关数据变更时自动同步该属性。</p><p>接着，我们为按钮添加一个点击事件，并使用 <code>context.replace</code> 的动作，将 <code>CTX.saving</code> 更新为 <code>true</code>：</p><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-context-5-6fa0f30e0f7506f55f8b5e19ad26b049.png" width="442" height="239" class="img_ev3q"></p><p>最后在请求完成的事件中设置回调动作，将 <code>CTX.saving</code> 重新设置为 <code>false</code>。</p><p>这样在我们的页面中，点击该按钮后，系统就会自动禁用它，直到请求完成再将按钮状态恢复。</p><p>除了上述基本的能力外，Context 还支持对请求返回的数据进行转换、请求的参数来自其他的 Context 等等。</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="state">State<a href="#state" class="hash-link" aria-label="Direct link to State" title="Direct link to State">​</a></h2><p>State 的能力和 Context 几乎完全一致，不同的是，Context 的作用域是整个页面、是全局的，而上一篇文章我们讲到了自定义模板，同一个模板在页面中可能有多个实例，如果直接使用 Context，则多个实例间的数据会互相影响，另外，使用全局的 Context 也会破坏模板的封装，削弱应用的可维护性，并带来潜在的问题。</p><p>因此 State 正是为了解决这个问题，它用于管理自定义模板内的数据，其作用域是模板的实例，多个模板实例之间的数据互相隔离，同时，在能力上完全与 Context 对等。State 与 Context 的区别有点像 JavaScript 中的 <code>let/const</code> 与 <code>var</code>。</p><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-context-6-652423c3872cdd80aff89a9083ce22c9.png" width="349" height="153" class="img_ev3q"></p><p>上文中我们多次使用了「表达式」，和无代码平台不同，低代码平台为了能解决更丰富的应用场景、满足更个性化的需求，仍然离不开代码的编写，包括简单的逻辑计算和数据加工处理，而对于高阶开发者搭建更复杂的应用时，甚至需要不逊色于专业代码的开发体验，对此 EasyMABuilder 提供了对应的代码开发能力，本系列下一篇将讲述《数据处理：表达式和微应用函数》。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[EasyMABuilder 系列知识 - 对构件组合的封装：自定义模板]]></title>
            <link>http://uwintech.cn/next-docs/blog/2022/03/15/custom-templates</link>
            <guid>http://uwintech.cn/next-docs/blog/2022/03/15/custom-templates</guid>
            <pubDate>Tue, 15 Mar 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[上一篇文章我们讲述了基于 Web Components 的构件，可复用的构件是 EasyMABuilder 的基础。]]></description>
            <content:encoded><![CDATA[<p>上一篇文章我们讲述了基于 Web Components 的构件，可复用的构件是 EasyMABuilder 的基础。</p><p>现代软件的开发追求最大化的可复用性，因为软件复用带来诸多好处：提升研发效率、降低研发成本、降低过程风险、增加可靠性、有利于标准化等。</p><p>构件就是 EasyMABuilder 中可复用的单元，通过组合现有的构件就可以搭建出完整的 Web 应用。</p><p>而随着应用规模的增长，开发者可能需要更上层的复用，即：将一套构件的组合封装成一个新的可复用的单元，并且希望它能像普通构件那样使用。例如我们可能需要一个<em>显示趋势图的卡片</em>，它由一个卡片构件及一个趋势图构件组成，我们希望将这个定制卡片能复用在多个页面中。</p><p>EasyMABuilder 对此提供了对构件组合的封装：自定义模板，以下我们简称为模板。</p><p>创建模板的界面与编排一个普通页面的界面完全一致，因为它们都是编排一套构件的组合。</p><p>不同的是，模板将内部的构件封装了起来，并且可以按需对外暴露属性、事件、方法和插槽，并将这些信息映射到内部的构件，这样，模板也有了普通构件的几大要素，因此它就可以像普通构件那样在各个地方复用。</p><p>例如，上文提到的趋势图卡片，我们可以先编排好模板的构件结构：</p><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-tpl-trend-card-769cb3d7dabbf0742c27b149f6295ce3.png" width="1602" height="772" class="img_ev3q"></p><p>为了提升这个模板的可复用性，我们扩展一下它的能力，支持由外部传入卡片的标题和趋势图的数据源，进行一下属性映射的配置：</p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"tpl-trend-card"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">properties</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">chartTitle</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic"># 通过引用 ID `ref` 来查找内部构件</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"my-card"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">refProperty</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"cardTitle"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">dataSource</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"trend-chart"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">refProperty</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"dataSource"</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>这样，我们的模板就有了两个属性 <code>chartTitle</code> 和 <code>dataSource</code>，设置或读取它们等同于读取内部构件的对应属性。</p><p>例如，我们可以在页面中同时编排两个趋势图卡片，分别显示 CPU 使用率和内存使用率：</p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">path</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/my-page"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">bricks</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">brick</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"tpl-trend-chart"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">properties</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">chartTitle</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"CPU Usage"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">dataSource</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'&lt;% ... %&gt;'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">brick</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"tpl-trend-chart"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">properties</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">chartTitle</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Memory Usage"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">dataSource</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'&lt;% ... %&gt;'</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>事件、方法和插槽的映射方式与属性类似，其中，插槽的映射是系统在运行时通过模板的展开来实现的。</p><p>例如，假设我们为上面的模板再添加一个附加按钮的插槽 <code>addon</code>，映射到卡片构件的对应插槽上，然后我们为上面的 CPU 使用率卡片添加一个查看详细信息的附加按钮。</p><p>在模板被展开之前的页面 storyboard 中，构件树大约长这样：</p><div></div><p>展开后：</p><div></div><p>每个模板在运行时将被分别展开，其中的插槽下的子构件也会被移植到指定的内部构件的对应插槽上。例如上面的查看详情的附加按钮 <code>view-detail</code> 就被移植到了 CPU 使用率所在模板内部的卡片构件的 <code>addon</code> 插槽中。</p><p>自定义模板可以在每个应用内维护，也可以选择在多个应用之间连接共享，还可以单独打包并纳入平台的构件资源库中，就像普通的构件包一样。</p><p>由于模板就是构件组合的编排，因此可以轻易地将现有的页面编排的某个部分转换成模板，以便在别处复用，这样可以更加平滑地进行应用的迭代研发。</p><p>以上我们讲诉了自定义模板在兼容普通构件的基本能力的前提下，通过对构件组合的封装，在 EasyMABuilder 中提供了更上层的可复用单元，从而让开发者以更高的效率和更好的可靠性完成应用的编排。</p><p>在低代码平台中，除了组织基本的 UI 界面结构外，还有一项重要的工作是维护和管理数据，特别是来自远端的异步数据和动态变更的状态数据，本系列下一篇将讲述 EasyMABuilder 中的数据管理：Context 和 State。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[EasyMABuilder 系列知识 - 基于 Web Components 的构件]]></title>
            <link>http://uwintech.cn/next-docs/blog/2022/03/02/web-components-as-bricks</link>
            <guid>http://uwintech.cn/next-docs/blog/2022/03/02/web-components-as-bricks</guid>
            <pubDate>Wed, 02 Mar 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[上一篇文章我们讲到了优维科技的前端低代码平台 EasyMABuilder 的基础部分--构件，构件用于渲染特定的 UI 界面部分，具有属性、事件、方法和插槽等要素。熟悉传统 Web 开发的同学会觉得有些熟悉，这和 HTML 元素看起来很像。]]></description>
            <content:encoded><![CDATA[<p>上一篇文章我们讲到了优维科技的前端低代码平台 EasyMABuilder 的基础部分--构件，构件用于渲染特定的 UI 界面部分，具有属性、事件、方法和插槽等要素。熟悉传统 Web 开发的同学会觉得有些熟悉，这和 HTML 元素看起来很像。</p><p>其实，构件就是 HTML 元素。</p><p>但构件并不是 HTML 标准里已经定义好的那些元素（比如 <code>body</code>、<code>div</code> 等），而是开发者自定义的元素。这是一套 Web 标准技术 -- Web Components，它允许开发者使用 JavaScript API 来创建新的自定义的、可重用的、封装的 HTML 标签，并且可以像普通的 HTML 标签那样使用它们。这套技术可以追溯到 2011 年的 Fronteers Conference <sup><a href="https://fronteers.nl/congres/2011/sessions/web-components-and-model-driven-views-alex-russell" target="_blank" rel="noopener noreferrer">[1]</a></sup>，并且在 2016 年就开始逐渐获得主流浏览器的支持（Chrome 54+ <sup><a href="https://developer.chrome.com/blog/new-in-chrome-54/#custom-elements" target="_blank" rel="noopener noreferrer">[2]</a></sup>, Firefox 63+）。</p><p>因此，EasyMABuilder 正是利用了原生的 Web 标准技术、结合组件化的开发思想，来搭建开发者熟悉的 Web 应用界面。</p><p>这些基于 Web Components 的构件，可以直接使用原生的 JS/CSS/HTML 来创建，或者结合你熟悉的任意主流第三方技术（例如 React/Vue/Angular）来创建<sup><a href="https://custom-elements-everywhere.com/" target="_blank" rel="noopener noreferrer">[3]</a></sup>。这使得 EasyMABuilder 的用户除了使用官方提供的构件资源外，也可以轻易地创建自己的个性化的构件。</p><p>Web Components 主要包含两项内容：Custom elements，和 Shadow DOM。</p><p>Custom elements 定义了一套 JavaScript API，允许开发者定义新的 HTML 标签以及它们的行为，或者加强已有的 HTML 标签，或者扩展其他开发者创作的组件。</p><p>而 Shadow DOM 则为元素提供了封装的能力，将样式、DOM 等行为都封装在元素内部，而不必担心它与外部的 ID 或者样式类名等产生冲突。</p><p>接下来，我们尝试结合上面的技术创建一个卡片元素。</p><p>首先，我们可以使用 <code>&lt;template&gt;</code> 来定义这个元素内部的结构：</p><div class="language-html codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-html codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">template</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">id</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">tpl-my-card</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">class</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">card</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">div</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">class</span><span class="token tag attr-value punctuation attr-equals" style="color:#393A34">=</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag attr-value" style="color:#e3116c">title</span><span class="token tag attr-value punctuation" style="color:#393A34">"</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">My Card</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">&lt;!-- 使用插槽来放置外部的子元素 --&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">slot</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">slot</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">div</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">style</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token style language-css"></span><br></span><span class="token-line" style="color:#393A34"><span class="token style language-css">    </span><span class="token style language-css selector class" style="color:#00009f">.card</span><span class="token style language-css"> </span><span class="token style language-css punctuation" style="color:#393A34">{</span><span class="token style language-css"></span><br></span><span class="token-line" style="color:#393A34"><span class="token style language-css">      </span><span class="token style language-css property" style="color:#36acaa">border</span><span class="token style language-css punctuation" style="color:#393A34">:</span><span class="token style language-css"> </span><span class="token style language-css number" style="color:#36acaa">1</span><span class="token style language-css unit">px</span><span class="token style language-css"> solid </span><span class="token style language-css color">gray</span><span class="token style language-css punctuation" style="color:#393A34">;</span><span class="token style language-css"></span><br></span><span class="token-line" style="color:#393A34"><span class="token style language-css">    </span><span class="token style language-css punctuation" style="color:#393A34">}</span><span class="token style language-css"></span><br></span><span class="token-line" style="color:#393A34"><span class="token style language-css">    </span><span class="token style language-css selector class" style="color:#00009f">.title</span><span class="token style language-css"> </span><span class="token style language-css punctuation" style="color:#393A34">{</span><span class="token style language-css"></span><br></span><span class="token-line" style="color:#393A34"><span class="token style language-css">      </span><span class="token style language-css property" style="color:#36acaa">border-bottom</span><span class="token style language-css punctuation" style="color:#393A34">:</span><span class="token style language-css"> </span><span class="token style language-css number" style="color:#36acaa">1</span><span class="token style language-css unit">px</span><span class="token style language-css"> solid </span><span class="token style language-css color">gray</span><span class="token style language-css punctuation" style="color:#393A34">;</span><span class="token style language-css"></span><br></span><span class="token-line" style="color:#393A34"><span class="token style language-css">    </span><span class="token style language-css punctuation" style="color:#393A34">}</span><span class="token style language-css"></span><br></span><span class="token-line" style="color:#393A34"><span class="token style language-css">  </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">style</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">template</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>接着，我们可以创建这个元素：</p><div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">MyCard</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">HTMLElement</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">constructor</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">super</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// 使用 Shadow DOM 封装内部结构。</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">let</span><span class="token plain"> shadowRoot </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">attachShadow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token literal-property property" style="color:#36acaa">mode</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"open"</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    shadowRoot</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">appendChild</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token dom variable" style="color:#36acaa">document</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">querySelector</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"#tpl-my-card"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">content</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">cloneNode</span><span class="token punctuation" style="color:#393A34">(</span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token dom variable" style="color:#36acaa">window</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">customElements</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">define</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"my-card"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token maybe-class-name">MyCard</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>然后我们就可以使用这个元素了：</p><div class="language-html codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-html codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">my-card</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain">Hello World!</span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">p</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag" style="color:#00009f">my-card</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p><img loading="lazy" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAACPCAYAAADUSI02AAAAAXNSR0IArs4c6QAAFEFJREFUeF7t3QmUjfUfx/HvbOjIki0nOeIkDVI00shyhGxZR9kiW7YsWSaDLClMEVKUqJQsLTTZQimyr5ViOCLqUNmaw8GMGTOd7+//f+4Zs7gzz+O6d+59/87phPv8nud3X7/H8/Fb7tygtLS0NPl/SU5OltTUVOu3/B8BBBBAAAGXQHBwsISFhbl+H6QBoqGh4UFBAAEEEEDAnYCGiIaJCZCkpCR3x/M6AggggAACLoH8+fNL0NWrV80IhIIAAggggEBOBcwIJDEx0bUGktOKHIcAAggggAABwj2AAAIIIGBLgACxxUYlBBBAAAEChHsAAQQQQMCWAAFii41KCCCAAAIECPcAAggggIAtAQLEFhuVEEAAAQQIEO4BBBBAAAFbAgSILTYqIYAAAggQINwDCCCAAAK2BAgQW2xUQgABBBC4aQGyc+dOiY+PlzNnzsi1a9eQRQABBBDwIYGQkBApWbKkhIeHS61atW5KyxwHSEJCgsTFxUmJEiUkIiJCypQpI6GhoTelcZwEAQQQQODmCKSkpMjJkydlz549cvbsWWnTpo0ULVrU0ckdB8iCBQukWrVqEhkZ6aghVEYAAQQQuDUC27dvl/3790v37t0dXdBRgOi0lU5ZRUVFOWoElRFAAAEEbq3AsmXLzJSWk+ksRwGio4+mTZtKuXLlbu0752oIIIAAAo4ETpw4IWvXrnU0CnEUIFOnTpWYmBjWPBx1I5URQACBWy+gayKxsbESHR1t++KOAkQvPn78eNsXpyICCCCAgPcEXn75ZTMIsFsIELty1EMAAQTyuAABksc7kOYjgAAC3hIgQLwlz3URQACBPC5AgOTxDqT5CCCAgLcECBBvyXNdBBBAII8LECB5vANpPgIIIOAtAQLEW/JcFwEEEMjjAgRIHu9Amo8AAgh4S4AA8ZY810UAAQTyuAABksc7kOYjgAAC3hIgQLwlz3URQACBPC5AgOTxDqT5CCCAgLcE/DJAlixZIqmpqVK7dm0pX758lrY//PCDHDt2TCpXriyPPPKII3+91qFDh0S/3+Tnn3+WChUqmC/JqlevngQHBzs6940qd+rUSfbt22eu6/SbwTzWSE6MAAJ+K+CXAVKpUiXTYW3btjU/bjhjSUxMNOFy6dIl6dq1q7z00ku2O1i/v33QoEGyYcOGTOdo0KCB6I+sL1SokO3z5yRAduzYIXfccYdHrsFJEUAAgewE/DJAatSoYcJBS1b/Ol+5cqWMGDHCvO4kQNLS0mTMmDGi38yloaXnfOCBB+TUqVMyc+ZM0VFOs2bNzK89UawRCAHiCV3OiQAC7gT8PkDGjh0rzzzzzHUO1oM3fYAoxN69e0X/X716ddfxEyZMMNNEr7/+utx///3XnUe/YP7xxx+XggULyqpVq+Suu+5yvZ6cnCz169eXc+fOyffff29eO3/+vMyYMUN27dplvsq3atWq0rFjR2nevLmpt2XLFnMdbd/hw4dl/fr10q5dOxNM2rZFixbJxo0bzRTZwIEDZe7cuaZtBIi725zXEUDAEwJ+GyDFihUTfYgXKFDAfO1iUFCQ8Tty5Ig8+eST8uijj5oHrzUCWbp0qflyqx49eri+IOXy5csmTDQgdu/eLSEhIdf1gQZDv379pH379jJp0qRM/XP69GkzErrzzjtNO3Q0cvz4cTNKKVKkiAkMLfPmzTPrJWvWrJGhQ4ea62m9e++9V1q2bClNmjQxX/2rpXTp0uZceh7rOALEE381OCcCCLgT8NsA0S971wf7tGnTZPHixfLwww8bC10T+fDDD2XWrFkyePBgV4Do6CAyMtI8oDdt2mSO/e6776R///7mO39HjRqVyXL27NnmPKNHj5Znn332htZ//fWXGTGULVtWevXqZY61ptI6d+5swssKkOLFi5vRhrUBQEcgemz6dsyfP9+sr2ghQNzd5ryOAAKeEPDrAFm4cKHUrVtXWrdubaaGrMXz8PBwmThxopk6Sr8GoqMJHVWsWLHCrGno+sYXX3whn376qTz00EOZ/KdMmSILFiyQyZMnS1RUVI76Rxfd9cvoL1y4IAcOHDDtaNiwocyZM8cVIBpGGkpW0RGIjjh0FFS4cGHzx7rzS98HAZIjdg5CAAEPCPh1gKxbt06GDBliprB0MX3z5s1mPWH69OlmPSNjgOhxevywYcOkd+/eUrNmTbODStcdrCmw9H2wfPlyMzLp2bOnjBw58obdo8GhIxb9L2PJGCB6/b59+5rDtJ5uNdbpKl3vSF90fURDiBGIB/5mcEoEEHAr4PcBousMOmWkW3V1ikgXp7dt2ya6AJ4xQK5cuWJGGrpGoaMPXeDWxWrdpptVOXjwoNkqXKVKFTNSyfiZj08++cSMHLp16ybx8fFmykyP1XDQhXBdSNc1lxsFiF5XtxzrYrx+xkTXP6xi7TYjQNze5xyAAAIeEPD7ANGpHv08xsWLF83CtI4soqOj5ejRo5kCRH11RKEjixYtWsjq1avN7qqKFStmSZ+UlGTOrQ93HbkMGDDAdZzumtK1DR056OhH10ree+89s8tLg0mLrs3o790FiJ5XP2eiO8J0h5YW/b11PQLEA38zOCUCCLgV8PsAUQHd5aSL6Vq+/vpr86//7ALEGrHosboOoushNyo6omnVqpU5REcXderUMZ8D0UVvLePGjZMuXbqYaTQNGV2k79Chg9nSq2s0WtwFiAaQjmK06E6usLCw69pFgLi9zzkAAQQ8IBAQAaLbaXUxPSIiwuxu0qI/xkQfxvpg1ukqq+iag6596GhF1zV0fcNd0Qe4ji62bt3qOlSDQqfNGjdubP5Mz6u7pnQHmBbdaaWBogFjBYgVMsOHD5c+ffpcd9m4uDh55513zJSYFm3bN998w48ycdc5vI4AAh4T8MsAcaJlbefVc+iie6lSpXJ8Ot3lpSMbDQ8NiKxKSkqKJCQkiH5Oxc7PydL26U6s0NDQHLeLAxFAAAFPCBAg6VR1d5auLfz222+ZRiaewOecCCCAQF4WIEDS9Z7uatKRQaNGjcxW3nz58uXlvqXtCCCAgEcFCBCP8nJyBBBAwH8FCBD/7VveGQIIIOBRAQLEo7ycHAEEEPBfAQLEf/uWd4YAAgh4VIAA8SgvJ0cAAQT8V4AA8d++5Z0hgAACHhUgQDzKy8kRQAAB/xUgQPy3b3lnCCCAgEcFCBCP8nJyBBBAwH8FCBD/7VveGQIIIOBRAQLEo7ycHAEEEPBfAQLEf/uWd4YAAgh4VIAA8SgvJ0cAAQT8V4AA8d++5Z0hgAACHhUgQDzKy8kRQAAB/xUgQPy3b3lnCCCAgEcFvB4gHn13nBwBBBBAwKMCMTExts8flJiYmGa3dmxsrIwfP95udeohgAACCHhRwOsjEALEi73PpRFAAAEHAgSIAzyqIoAAAoEsQIAEcu/z3hFAAAEHAgSIAzyqIoAAAoEsQIAEcu/z3hFAAAEHAgSIAzyqIoAAAoEsQIAEcu/z3hFAAAEHAgSIAzyqIoAAAoEsQIAEcu/z3hFAAAEHAgSIAzyqIoAAAoEsQIAEcu/z3hFAAAEHAgSIAzyqIoAAAoEsQIAEcu/z3hFAAAEHAgSIAzyqIoAAAoEsQIAEcu/z3hFAAAEHAgSIAzyqIoAAAoEsQIAEcu/z3hFAAAEHAgSIAzyqIoAAAoEsQIAEcu/z3hFAAAEHAgSIAzyqIoAAAoEsQIAEcu/z3hFAAAEHAgSIAzyqIoAAAoEsQIAEcu/z3hFAAAEHAn4ZINeuXZOgoCAJDg7ORJOSkiIhISHmdXdl+fLlUqVKFalUqZIcPXpU9u7dK08//bS7aple1/Zo0etaJS0tTfTPM7Ylq2NzesGLFy/KihUrpEWLFlK0aNEsq61atUruueceqVq1qnn96tWrEhoamqVVTq/LcQggEJgCfhkg7dq1k9q1a8uIESOu69Vjx45Js2bN5Msvv5TKlSu77XE9x6BBg6RTp04SFxcnEydOlH379rmtl/GAV155RbZs2SLr1q1zvbRx40bp27evzJgxQ5o3b+768/bt20vFihVlypQpub7OH3/8IY0bN5aVK1fKfffdl2V9DZfWrVtLnz595MKFC1KzZk159dVX5amnnsr19aiAAAKBLUCA3KD/b1aAfPvtt/L888/L1q1bpUSJEuaKkyZNko8//liioqJk8uTJ5s90BBERESFTp06VVq1a5frOzG2AWNfTsNLQpSCAAAK5EQj4APnqq6/ks88+k99//10ee+wxGThwoJQrV84Y3ihATp8+LW+++aYZWdx+++3SoEEDGTJkiISFhWXyt/6l/9Zbb8kTTzxhXm/UqJGUKVNGjhw5YoJFp9T0XL169ZLNmzdLqVKl5Mcff5T3339ftm3bJuHh4dKmTRvXSEGn13Q6Stu4cOFCM7XWsmXL60YgiYmJJow0wLQMHjxYPvjgA9cIRF9/8MEHbQdWbm40jkUAAf8TCKgA0XUMnS6yprA2bNggAwYMkN69e5t1Dh0R6L/K9eFcsGDBbANEH7ydO3eWS5cumakgrTNr1iyz9qDTVVmVDh06mIf16NGjxRopaDvatm1r1i30+hpI69evl9WrV5tA09GJTjHpaOTXX381D/9p06aZoPjoo4/MyEXr6RRbtWrVpFChQtcFyKhRo8x7iYmJMSE3d+5c+fPPP2X48OGm3cnJyWYtZPr06abtFAQQQCA3An4bIAcOHMjWwQoQfajrA1jXNrScO3fOhMa7775rRhTZjUCskcLatWulfPnypq6GQHR0tOzevVsKFy6c6dqzZ8+WNWvWmHDQEY8GgP5aH/46KunRo4f5dY0aNcx5Zs6caUYY+l+BAgXM+caMGSPx8fEmFKwA0dFJ8eLFzevpp7BKly5twkenynRdRcvBgwdNYFkBkpqaakY2Gn5NmjTJzX3DsQgggID4bYDoAzTjwvCpU6dMWGiAaHBYC+n64LbKkiVL5IUXXpD+/ftnGyDz588300abNm1y1fv777+lfv36smjRIrOOkbHodFTHjh1lx44dMnbsWBM8+iDXsNq1a5e8/fbbUr16dTNlVadOHXnuuefMNJYGgFU0cIYNGya//PKLaDt1RJK+DekDREdHej0dZd19992uc2hA9evXz4xAtKjDnDlzpGHDhvx1QAABBHIl4LcB4m4XVoUKFcyUUtmyZa9bsNaHsE4Z1atXL9sA0dGEhpC1tqDi58+fl8jISFcAZOwF3T6sW4J1mkrXSnS6rFatWiYMdISgD3GdTvvpp5/ktttuky5dupidVOPHj3edylqM1zD6/PPPTYilb0P6APn333+lW7durvUU6yTq0r17d1eA6DSYTpVpkFAQQACB3AgEbIDo6EPn/XXqRheXraLTWLr+odNG2U1hWWsnO3fudH3ewprW0v+XLFkyyz7QBXp9yB8+fNgER758+USnkXTEoqME3aGlowotujNKp8OWLVvm+syKbvnVaTPdDqxTWDcKkGLFiplNAdZ0nJ7zn3/+McFoTWHl5kbhWAQQQCCjQEAHyLx588yitO5U0tGIridMmDBBdIqqbt262QZIQkKCWYzXEYWGz+XLl836hO6q0gd7dmXp0qVmRKHTRTrisMqLL74ouhts5MiR0rNnT/PH+nkTnVrTBX7dfXXo0CHzuRZres1dgOjoRUcgJ06ckNdee82Mat544w3R0LMCRMOsa9euZkor/TQef00QQACBnAgEdIDop8H1obp48WKzo0pHHkOHDjUPVS032saro4hx48aZKSctGgqxsbFZLqBbHXH8+HEz4tF1GF3At4p+8E/DIeMHHHXRXae8tJ62TR/yepxu+dUpMP0vqyksXXjXDyOePXtWNJx0m7AW/eCijp40kHSNxVpU1/UeDSYKAgggkBsBvwyQ3ADosRokOqooUqRIrn+kx5UrV8yPAsnq8x+5bUd2x+vnSHSLbk5+/EpW59ARkv5YF2s3V/pjtP06OqEggAACuRUgQHIrxvEIIIAAAkaAAOFGQAABBBCwJUCA2GKjEgIIIIAAAcI9gAACCCBgS4AAscVGJQQQQAABAoR7AAEEEEDAlgABYouNSggggAACBAj3AAIIIICALQECxBYblRBAAAEECBDuAQQQQAABWwIEiC02KiGAAAIIECDcAwgggAACtgQIEFtsVEIAAQQQIEC4BxBAAAEEbAkQILbYqIQAAgggQIBwDyCAAAII2BIgQGyxUQkBBBBAgADhHkAAAQQQsCVAgNhioxICCCCAAAHCPYAAAgggYEvAqwEydepUiYmJkdDQUFuNpxICCCCAgHcEUlJSJDY2VqKjo203ICgxMTHNbu0FCxZI06ZNpVy5cnZPQT0EEEAAAS8InDhxQtauXSvdu3e3fXVHAbJz5045c+aMREVF2W4AFRFAAAEEbr3AsmXLpGTJklKrVi3bF3cUIHpVHYVUq1ZNIiMjbTeCiggggAACt05g+/btsn//fkejD22t4wBJSEiQuLg4KVGihEREREiZMmVYE7l19wFXQgABBHIkoGseJ0+elD179sjZs2elTZs2UrRo0RzVze4gxwFinVins+Lj482U1rVr1xw1isoIIIAAAjdXICQkxExZhYeHO5q2St+qmxYgN/etcjYEEEAAAV8XIEB8vYdoHwIIIOCjAgSIj3YMzUIAAQR8XYAA8fUeon0IIICAjwoQID7aMTQLAQQQ8HUBAsTXe4j2IYAAAj4qQID4aMfQLAQQQMDXBQgQX+8h2ocAAgj4qAAB4qMdQ7MQQAABXxcgQHy9h2gfAggg4KMCBIiPdgzNQgABBHxdIOjq1atpqampvt5O2ocAAggg4EMCwcHBEpSWlpaWlJTkQ82iKQgggAACvi6QP3/+/wWIjkCSk5N9vb20DwEEEEDABwTCwsLENQKx2qMhwnSWD/QOTUAAAQR8UEBDQ8PDKv8BVBGGyLjuCPMAAAAASUVORK5CYII=" width="400" height="143" class="img_ev3q"></p><p>元素即构件，我们也可以在 EasyMABuilder 中使用它：</p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">brick</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"my-card"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">slots</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">""</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">brick</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"p"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">properties</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">textContent</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Hello World!"</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>开发者除了可以像上面那样从头开始编写一个元素/构件外，平台还提供了一些脚手架工具和 API 来帮助开发者更高效地编写构件：</p><p><img loading="lazy" src="/next-docs/assets/images/yarn-yo-a-new-brick-3b2c93f45f40cea3d0ca2e95da2c18c2.gif" width="783" height="398" class="img_ev3q"></p><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> UpdatingElement</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> property </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@next-core/brick-kit"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">class</span><span class="token plain"> </span><span class="token class-name">MyCard</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">extends</span><span class="token plain"> </span><span class="token class-name">UpdatingElement</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">property</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  cardTitle</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">event</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    type</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"card.collapse"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">private</span><span class="token plain"> _collapseEvent</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> EventEmitter</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token decorator at operator" style="color:#393A34">@</span><span class="token decorator function" style="color:#d73a49">method</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">collapse</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><blockquote><p>关于 Web Components 更多的细节建议阅读谷歌 Web Fundamentals 系列的相关文章<sup><a href="https://developers.google.com/web/fundamentals/web-components/customelements" target="_blank" rel="noopener noreferrer">[4]</a></sup>或 MDN 相关文档<sup><a href="https://developer.mozilla.org/zh-CN/docs/Web/Web_Components" target="_blank" rel="noopener noreferrer">[5]</a></sup>。</p></blockquote><p>我们通过属性、方法和事件来灵活地定制每个构件的外观和行为，然后借助插槽、通过组合的方式来建立构件之间的层次，这和现代 Web 开发等组件化开发的思想相契合，是广受采纳和经受考验的最佳实践。</p><p>下一期我们将继续讲述对构件组合的封装：自定义模板。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[EasyMABuilder 系列知识 - 构件]]></title>
            <link>http://uwintech.cn/next-docs/blog/2022/02/22/about-bricks</link>
            <guid>http://uwintech.cn/next-docs/blog/2022/02/22/about-bricks</guid>
            <pubDate>Tue, 22 Feb 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[优维科技的前端低代码平台 EasyMABuilder 的架构大致如下图：]]></description>
            <content:encoded><![CDATA[<p>优维科技的前端低代码平台 EasyMABuilder 的架构大致如下图：</p><p><img loading="lazy" src="/next-docs/assets/images/low-code-arch-7b62a7f53092b85e448073646b8f6b0a.png" width="1106" height="590" class="img_ev3q"></p><p>其中主要包含以下三个基础部分：</p><ul><li><p>微应用：即 Web 应用程序，由多个路由页面组成，每个页面由构件组装而来，构件如何组装定义在 Storyboard 中。Storyboard 是结构化数据，可以通过可视化工具编排出来，也可以直接用 YAML 或 JSON 配置。</p></li><li><p>构件：和前端开发者常说的「组件」类似，主要用于在页面上渲染特定的 UI 内容。开发者可以很容易地通过声明的方式使用构件，无需编程。</p></li><li><p>EasyMABuilder Core：负责解析微应用的 storyboard，按照其定义加载路由并装载相应的构件，完成页面的渲染。</p></li></ul><p>整个架构和乐高很像，用一块块积木、按特定的方式组合在一起，就可以拼搭出各式各样的东西出来。</p><p>EasyMABuilder 的基石就是这些积木--构件，正是一个个构件的组合搭建起了最终的应用。我们已经提供了数百个开箱即用的构件，包括功能完整的 UI 构件，以及包含特定业务属性的业务构件。同时开发者也可以开发自己的构件，以满足更加个性化的场景。</p><p>构件主要包含以下几大要素：</p><ul><li><p>属性：构件的属性通常用于对该构件进行基本的参数设置。例如对于一个按钮构件，它可能会对外提供：按钮的文字内容、文字颜色等属性，这样应用在编排时可以按需设置属性，以实现不同的 UI 效果：</p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">brick</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"my-button"</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">properties</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">text</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Search"</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">color</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"blue"</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>构件的属性可以是任意类型的数据，包括字符串、数字、布尔值、对象、数组等。</p></li><li><p>事件：构件可以在特定的用户交互等行为发生时触发特定的事件。例如对于一个表单构件，如果用户点击其中的提交按钮时，会发起一个表单提交的事件，并同时提供已填写的表单数据，那么开发者就可以按需配置提交后的动作：</p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">brick</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"my-form"</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">events</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">submit</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">action</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"console.log"</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>平台提供了多种形式的事件处理方式，包括页面跳转、更新构件属性或调用构件方法、发起远端请求、打印日志等等。</p></li><li><p>方法：构件可以对外提供特定的方法，以供别的地方按需调用。例如对于一个对话框构件，它会提供一个「打开对话框」的方法，可以在用户点击按钮时调用：</p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">brick</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"my-button"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">events</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">click</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">target</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"my-dialog"</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">method</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"open"</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>注意，构件的方法可以接收任意的参数。</p></li><li><p>插槽：构件通过插槽可以让其他构件能以特定的方式容纳在该构件之内。例如表单构件可以提供一个插槽，允许其他的构件例如文本框、单选框等插入到表单中，同时表单构件还可以统一管理这些输入项的数据：</p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">brick</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"my-form"</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">slots</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">items</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">brick</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"my-input"</span><span class="token plain"></span><br></span><span class="token-line theme-code-block-highlighted-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">brick</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"my-radio"</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p><img loading="lazy" src="/next-docs/assets/images/screen-shot-my-form-78ac9313f309ace53ce54a706378eb26.png" width="785" height="304" class="img_ev3q"></p><p>插槽实际上定义了父子构件的关系，通过层层的构件组成一棵构件树，最终渲染成完整的页面。</p></li></ul><p>这些要素使得各个构件可以灵活地适应不同的应用场景，开发者可以通过这些构件的组合编排出一个完整的微应用。</p><p>属性、事件、构件树，这些概念对于有了解 HTML 的同学会觉得有些熟悉：HTML 元素的属性和事件、以及由这些元素组成的 DOM 树。那么构件与 HTML 元素有什么关系呢，敬请期待本系列下一篇：《基于 WebComponents 的构件》。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[调试 Brick Next]]></title>
            <link>http://uwintech.cn/next-docs/blog/2020/05/09/debugging-in-brick-next</link>
            <guid>http://uwintech.cn/next-docs/blog/2020/05/09/debugging-in-brick-next</guid>
            <pubDate>Sat, 09 May 2020 00:00:00 GMT</pubDate>
            <description><![CDATA[为了方便 Brick Next 框架下的界面开发和调试，我们提供了一个 Chrome 开发者工具的扩展程序：Brick Next Developer Tools。]]></description>
            <content:encoded><![CDATA[<p>为了方便 Brick Next 框架下的界面开发和调试，我们提供了一个 Chrome 开发者工具的扩展程序：<a href="https://chrome.google.com/webstore/detail/brick-next-developer-tool/imfbjbfcldgkdbfgeoppalofbjfihpdp" target="_blank" rel="noopener noreferrer">Brick Next Developer Tools</a>。</p><p><img loading="lazy" alt="Screen Shot" src="/next-docs/assets/images/devtools-screen-shot-1-760d48b634e5cd158137ed9da459b8c8.png" width="640" height="400" class="img_ev3q"></p><p><img loading="lazy" alt="Screen Shot" src="/next-docs/assets/images/devtools-screen-shot-2-b1d554dbab6c656f8750c79c9446775f.png" width="640" height="400" class="img_ev3q"></p><p>该扩展程序可以为基于 Brick Next 框架下的页面对应的开发者工具中添加一个 “Bricks” 面板，其功能如下：</p><ul><li>左上角选择 Bricks 时：<ul><li>查看页面当前渲染的构件树；</li><li>构件内部使用的 <code>useBrick</code> 将被收集在 <code>#internal</code> 的标签中；</li><li>已废弃或未定义等异常的构件将会在旁边显示对应的提示信息；</li><li>当页面路由切换时，构件树会自动刷新，点击 🔄 可强制刷新构件树（用于路由未切换的构件更新等场景）；</li><li>鼠标悬浮在一个构件上时，可以在页面上显示该构件的轮廓；</li><li>点击选中一个构件时，右侧显示该构件当前的属性、绑定的事件等；</li><li>选中构件后，可以点击右上角工具栏的 👁 唤起 Elements 面板并审查该构件对应的元素，或点击 <code>&lt;/&gt;</code> 唤起 Sources 面板查看构件对应的源码；</li></ul></li><li>左上角选择 Evaluations 时：<ul><li>监视当前页面发生的 <a href="/next-docs/docs/brick-next/evaluate-placeholders">Evaluate Placeholders 求值占位符</a>的运算记录；</li><li>可以输入 Expression 的关键字进行过滤；</li><li>可以勾选 “String Wrap” 对字符串类型的值自动折行；</li></ul></li><li>左上角选择 Transformations 时：<ul><li>监视当前页面发生的 <a href="/next-docs/docs/brick-next/transform">Transform 数据转换</a>的运算记录；</li><li>可以勾选 “String Wrap” 对字符串类型的值自动折行；</li></ul></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Brick Next 的依赖管理]]></title>
            <link>http://uwintech.cn/next-docs/blog/2019/11/12/dependencies-in-brick-next</link>
            <guid>http://uwintech.cn/next-docs/blog/2019/11/12/dependencies-in-brick-next</guid>
            <pubDate>Tue, 12 Nov 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[在模块化开发中，我们常常与各种依赖打交道：dependencies、devDependencies、peerDependencies 等等。错误的声明可能导致流水线失败，或者是偶现的、难以调查的 bug，甚至导致整个平台界面空白。]]></description>
            <content:encoded><![CDATA[<p>在模块化开发中，我们常常与各种依赖打交道：<code>dependencies</code>、<code>devDependencies</code>、<code>peerDependencies</code> 等等。错误的声明可能导致流水线失败，或者是偶现的、难以调查的 bug，甚至导致整个平台界面空白。</p><p>本文尝试说明 Brick Next 架构下的依赖管理方法及其背后的原因。在阅读本文前，建议先阅读 npm 官方<a href="https://docs.npmjs.com/files/package.json#dependencies" target="_blank" rel="noopener noreferrer">关于依赖声明的文档</a>。</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="tldr">TL;DR<a href="#tldr" class="hash-link" aria-label="Direct link to TL;DR" title="Direct link to TL;DR">​</a></h2><ul><li>在所有 Lerna 管理的项目中：<ul><li>开发设施依赖（包括测试、构建等工具）都声明在项目根目录的 <code>package.json</code> 的 <code>devDependencies</code>。</li></ul></li><li>在由 <code>create-next-repo</code> 新建的仓库中：<ul><li>如果有某个包依赖了其它仓库（例如 <code>brick-next</code>）中的构件包、模板包，需要在仓库根目录中的 <code>devDependencies</code> 中额外声明它们。</li></ul></li><li>在 <code>@bricks/*</code> 包中：<ul><li>Custom Templates 依赖的其它构件包声明在 <code>peerDependencies</code>，版本号需要按 semver 正确声明；</li><li>对 <code>@next-dll/*</code> 的依赖声明在 <code>peerDependencies</code>，并且版本号只需要填写 <code>*</code>；</li><li>其它依赖声明在 <code>devDependencies</code>。</li></ul></li><li>在 <code>@micro-apps/*</code> 或 <code>@templates/*</code> 包中：<ul><li><code>@bricks/*</code> 和 <code>@templates/*</code> 依赖声明在 <code>peerDependencies</code>；</li><li>其他依赖声明在 <code>devDependencies</code>。</li></ul></li><li>其他包（例如 <code>@libs/*</code>）中：<ul><li>对 <code>@next-dll/*</code> 的依赖声明在 <code>peerDependencies</code>，并且版本号只需要填写 <code>*</code>；</li><li>其他依赖声明在 <code>dependencies</code>。</li></ul></li><li>所有包不需要声明依赖 <code>@next-core/bricks-dll</code> 和 <code>@next-core/bricks-types</code>，它被声明在项目根目录中。</li><li>DLL（包括 <code>@next-core/bricks-dll</code> 和 <code>@next-dll/*</code>）中已包含的依赖不需要再显示声明了。</li></ul><h2 class="anchor anchorWithStickyNavbar_LWe7" id="why">Why<a href="#why" class="hash-link" aria-label="Direct link to Why" title="Direct link to Why">​</a></h2><p>我们先看不同类型的依赖有什么不同的表现：</p><ul><li><code>dependencies</code> 会被递归安装，其它类型的依赖则不会。例如 <code>A</code> 依赖了 <code>B</code>，<code>B</code> 依赖了 <code>C</code>，那么在 <code>A</code> 包中执行 <code>yarn</code> 时，<code>B</code> <code>C</code> 都会被安装。</li><li><code>peerDependencies</code> 不会被自动安装。</li><li><code>lerna version</code> 发布版本时，会自动更新内部子包之间的 <code>dependencies</code> 及 <code>devDependencies</code> 的版本。</li></ul><p>结合上述内容和我们的需求，就不难理解我们如何管理 Brick Next 的依赖了。</p><p>Q: 为什么 DLL 包含的依赖不需要再显示声明了？<br>
<!-- -->A: <code>dependencies</code> 会被递归安装，冗余的声明一方面难以维护，另一方面容易造成版本差异导致问题。</p><p>Q: 为什么 <code>@micro-apps/*</code> 依赖的 <code>@bricks/*</code> 声明在 <code>peerDependencies</code> 并且还需要手动更新其依赖版本？<br>
<!-- -->A: 小产品对构件库、模板库的依赖需要同步到 EasyOps 的包依赖关系中（<code>package.conf.yaml</code>），因此需要声明该依赖（我们的打包工具会扫描这些依赖生成一份 <code>package.conf.yaml</code>）。而我们期望构件库能保持兼容性，小产品对构件库的依赖版本不能始终跟随版本发布而更新，因此只有声明在 <code>peerDependencies</code> 中才不会被 Lerna 自动更新。也因此在小产品需要使用构件的新特性时，需要主动更新其依赖版本。</p><p>Q: 为什么 <code>@bricks/*</code> 的 Custom Templates 依赖的其它构件包要声明在 <code>peerDependencies</code> 中？
A: 如果 Custom Templates 中使用到了其它构件包的构件，那么这些依赖需要同步到 EasyOps 的包依赖关系中（<code>package.conf.yaml</code>），更多详情请参考上一条 Q/A。</p><p>Q: 为什么 <code>@bricks/*</code> 的其它依赖声明在 <code>devDependencies</code>？<br>
<!-- -->A: <code>@bricks/*</code> 不会像 <code>@libs/*</code> 一样被 <code>import</code> 使用，我们只消费它生成的制品文件，因此它的依赖只在开发时有用。</p><p>Q: 为什么 <code>@bricks/*</code> 需要主动声明对 <code>@next-dll/*</code> 的依赖并放在 <code>peerDependencies</code> 中？
A: <code>@bricks/*</code> 被加载前需要按需加载用到的 <code>@next-dll/*</code>，所以需要有地方声明它们；另一方面，我们不希望 <code>@next-dll/*</code> 更新时也触发 <code>@bricks/*</code> 更新，所以放置到 <code>peerDependencies</code> 中。</p><p>Q: 为什么 <code>@libs/*</code> 的普通依赖都声明在 <code>dependencies</code>？
A: <code>@libs/*</code> 和我们日常使用的普通第三方库一样，会被 <code>import</code> 并在打包时整体编译，因此需要它的依赖及其依赖的依赖都被正确地安装在 <code>node_modules</code> 中。</p><p>Q: 为什么在由 <code>create-next-repo</code> 新建的仓库中，需要在仓库根目录中的 <code>devDependencies</code> 中额外声明构件包、模板包的依赖？
A: 因为 <code>@micro-apps/*</code> 对构件包、模板包的依赖基于这些包声明的构件列表、模板列表，所以需要在仓库中安装这些包才能完成相关校验和检查。而之前的 <code>brick-next</code> 大仓库模式下这些包都在本地，所以不需要声明及安装。</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[EasyCore 介绍]]></title>
            <link>http://uwintech.cn/next-docs/blog/2019/07/29/easy-core-introduction</link>
            <guid>http://uwintech.cn/next-docs/blog/2019/07/29/easy-core-introduction</guid>
            <pubDate>Mon, 29 Jul 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[EasyCore 是一个图数据库，具备一致性、高可用、内存占用小等特点。接下来，一步步解开EasyCore的面纱。]]></description>
            <content:encoded><![CDATA[<p><code>EasyCore</code> 是一个图数据库，具备一致性、高可用、内存占用小等特点。接下来，一步步解开<code>EasyCore</code>的面纱。</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="架构">架构<a href="#架构" class="hash-link" aria-label="Direct link to 架构" title="Direct link to 架构">​</a></h2><p><img loading="lazy" alt="architecture" src="/next-docs/assets/images/easy-core-arc-18961912af27950864aa591696eb3ed4.jpg" width="576" height="946" class="img_ev3q"></p><p><code>EasyCore</code>是一个单体的架构，数据完整的存储在一个机器上，采用<code>Raft</code>协议将数据复制到多台机器，通过多副本的机制确保数据的安全和可靠。<code>Client</code>通过<code>grpc</code>协议与<code>EasyCore</code>进行通信，提供了图<code>API</code>供<code>Client</code>操作图数据库。</p><p>采用这种架构的好处是：</p><ul><li>组件单一，降低部署和维护的门槛，理论上可以秒级启动一个图数据库</li><li>采用<code>Rust</code>开发，整个系统的内存占用低</li></ul><h2 class="anchor anchorWithStickyNavbar_LWe7" id="存储">存储<a href="#存储" class="hash-link" aria-label="Direct link to 存储" title="Direct link to 存储">​</a></h2><p>既然要把数据存储到磁盘，那就需要一个存储引擎。存储引擎的作用是把数据落盘，有组织有格式的存储在磁盘上。</p><p>自研一个存储引擎的工作量很大，而且目前开源有不少优秀的存储引擎，因此我们选用了开源的<code>RocksDB</code>作为存储引擎。</p><p><code>RocksDB</code>是<code>Facebook</code>开源的一个<code>Key-Value</code>存储引擎，采用<code>LSM Tree</code>来组织文件，根据<code>Key</code>顺序存储在磁盘上，采用顺序写来替换随机写，极大的提升了写的性能。<code>RocksDB</code>对外提供的<code>API</code>只有简单的<code>Get</code> <code>Put</code> <code>Delete</code> <code>Scan</code>操作。</p><p><code>EasyCore</code>存储的是图相关的结构化数据，<code>RocksDB</code>接收<code>Key-Value</code>数据，因此需要做一层编码转换<code>Struct Data</code> -&gt; <code>Key-Value</code>。</p><p>基于<code>Key</code>有序的特性，我们使用以下原则来组织<code>EasyCore</code>的数据：</p><ul><li>每个模型分配一个<code>tid</code>，模型的<code>Schema</code>以<code>tid</code>为<code>Key</code>进行存储，即<code>tid</code> -&gt; <code>Object Schema</code></li><li>实例<code>tid_instanceId</code> -&gt; <code>Instance</code></li></ul><p>基于以上原则，也就是每类数据的<code>Key</code>都会包含一个命名空间的前缀，因此能快速查到模型下的实例。</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="graph">Graph<a href="#graph" class="hash-link" aria-label="Direct link to Graph" title="Direct link to Graph">​</a></h2><p><code>EasyCore</code>是一个图数据库，需要具备图查询的能力，也就是能够根据一些点、一些边，查询出其所连接的点。</p><p>在<code>EasyCore</code>内部，实时维护了一个图结构，添加实例时，会往<code>Graph</code>中存储顶点，添加边时，会局部调整<code>Graph</code>的结构，确保顶点之间的关系是正确的。当需要查询某些边相关的点时，只需要在<code>Graph</code>应用一次<code>Depth first search</code>即可快速查询出结果。</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="raft">Raft<a href="#raft" class="hash-link" aria-label="Direct link to Raft" title="Direct link to Raft">​</a></h2><p><img loading="lazy" alt="raft" src="/next-docs/assets/images/easy-core-raft-cb5b93429c5e2cba3a463f1e2a042ef9.jpg" width="627" height="389" class="img_ev3q"></p><p><code>Raft</code> 是一个强 <code>Leader</code> 的共识算法，只有 Leader 能处理客户端的请求，集群的数据（<code>Log</code>）的流向是从 <code>Leader</code> 流向 <code>Follower</code>。</p><p>基于<code>Raft</code>算法，将<code>EasyCore</code>的数据安全的复制到多个节点上，保证了<code>EasyCore</code>是分布式高可用。</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="grpc">Grpc<a href="#grpc" class="hash-link" aria-label="Direct link to Grpc" title="Direct link to Grpc">​</a></h2><p><code>EasyCore</code>的协议层，采用<code>Grpc</code>协议。这一层，提供了图操作的相关<code>API</code>。</p><p>...未完待续</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[EasyOps API 定义规范 V2.0]]></title>
            <link>http://uwintech.cn/next-docs/blog/2019/07/14/apiv2-specification</link>
            <guid>http://uwintech.cn/next-docs/blog/2019/07/14/apiv2-specification</guid>
            <pubDate>Sun, 14 Jul 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[EAML(EasyOps API Modeling Language) 是参考了 grpc, swagger, raml 等现有业界方案后定制化的 API 描述语言。]]></description>
            <content:encoded><![CDATA[<p>EAML(EasyOps API Modeling Language) 是参考了 grpc, swagger, raml 等现有业界方案后定制化的 API 描述语言。</p><p>1.0 vs 2.0</p><ul><li>大大简化了消息定义方式，减少 50%左右的重复消息定义</li><li>以领域模型对象为核心定义 model, 并附带校验规则，上层接口消费</li><li>消息内置于 request 和 response 内部，不需要显式声明 message, 定义更自然</li><li>将原先分散于各个组件的接口定义统一到契约中心</li></ul><h2 class="anchor anchorWithStickyNavbar_LWe7" id="定义规范的目的">定义规范的目的<a href="#定义规范的目的" class="hash-link" aria-label="Direct link to 定义规范的目的" title="Direct link to 定义规范的目的">​</a></h2><ul><li>标准化消息结构定义</li><li>标准化接口定义</li><li>增加消息约束校验能力</li><li>支撑 API 全面测试</li><li>支撑 RPC 框架功能强化</li></ul><p>接口间用结构化的消息来进行远程调用。</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="目录结构">目录结构<a href="#目录结构" class="hash-link" aria-label="Direct link to 目录结构" title="Direct link to 目录结构">​</a></h2><div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">contract</span><span class="token operator" style="color:#393A34">-</span><span class="token plain">center</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── CONTRIBUTING</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">md</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── README</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">md</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── easyops</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   ├── api     </span><span class="token comment" style="color:#999988;font-style:italic"># api 接口契约</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   ├── README</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">md</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   └── cd  </span><span class="token comment" style="color:#999988;font-style:italic"># 持续交付产品 cd</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │       ├── package </span><span class="token comment" style="color:#999988;font-style:italic"># 包模型</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │       │   ├── batch_update_package_permission</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │       │   ├── create</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │       │   ├── delete</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │       │   ├── get_package_detail</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │       │   ├── get_package_list</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │       │   ├── get_package_permission</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │       │   ├── get_package_user_variable</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │       │   ├── init_package_permission</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │       │   ├── search</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │       │   ├── update</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │       │   ├── update_package_permission</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │       │   └── upsert_package_user_variables</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │       └── version </span><span class="token comment" style="color:#999988;font-style:italic"># 版本模型</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │           ├── create_version_with_sign</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │           ├── delete_version</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │           ├── get_version_detail</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │           ├── get_version_list</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │           ├── get_version_permission</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │           ├── update</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │           ├── update_version_env_type</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │           └── update_version_permission</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   ├── brick  </span><span class="token comment" style="color:#999988;font-style:italic"># 构件契约</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   ├── README</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">md</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   ├── cd</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   └── common</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │       ├── creator</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │       ├── deleter</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │       ├── editor</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │       ├── explorer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │       ├── selector</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │       └── viewer</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   ├── model  </span><span class="token comment" style="color:#999988;font-style:italic"># 模型定义</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   ├── cd </span><span class="token comment" style="color:#999988;font-style:italic"># 持续交付 cd 产品</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   │   ├── instance</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   │   ├── package</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   │   ├── package_ext</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   │   ├── version</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   │   └── white_permission_user</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   └── common</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   └── </span><span class="token builtin">type</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># 类型定义</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│       ├── datetime</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│       ├── email</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│       ├── env_type</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│       ├── file_path</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│       ├── guid</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│       ├── ip</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│       ├── page</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│       ├── page_size</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│       ├── password</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│       ├── user_name</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│       ├── version</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│       └── white_permisson_type</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">yaml</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">└── schema  </span><span class="token comment" style="color:#999988;font-style:italic"># json-schema, 对 brick, interface, model, type 的定义进行约束</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ├── brick</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">json</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ├── interface</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">json</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ├── model</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">json</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    └── </span><span class="token builtin">type</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">json</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h2 class="anchor anchorWithStickyNavbar_LWe7" id="模型定义">模型定义<a href="#模型定义" class="hash-link" aria-label="Direct link to 模型定义" title="Direct link to 模型定义">​</a></h2><p>模型是将我们产品中核心对象抽象出来，按核心对象进行定义，可以基本按数据库的设计，也可以有一些扩展模型。
一个示例定义(model/cd/package.yaml)如下：</p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">_version_</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2.0</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 框架协议版本</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">_kind_</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> model </span><span class="token comment" style="color:#999988;font-style:italic"># 契约类型为 model</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package </span><span class="token comment" style="color:#999988;font-style:italic"># 契约名称</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 包模型 </span><span class="token comment" style="color:#999988;font-style:italic"># 模型简要描述</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">fields</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 模型字段定义</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> packageId </span><span class="token comment" style="color:#999988;font-style:italic"># 字体名称</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> guid </span><span class="token comment" style="color:#999988;font-style:italic"># 字段定义</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 包 ID </span><span class="token comment" style="color:#999988;font-style:italic"># 字段描述</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> string</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 包名称</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">validate</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 字段校验规则</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">gte</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">lte</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">45</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> type</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> env_type</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 版本类型 1 开发</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> 3 测试</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> 7 预发布</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> 15 生产</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> cId</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> int</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 包分类</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> source</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> string</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 包文件源</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> repoId</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> string</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> repoId</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> repoPath</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> string</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> repoPath</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> memo</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> string</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 备注说明</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">validate</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">gte</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> creator</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> user_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 创建者</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> org</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> int</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> org</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> category</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> string</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 包分类标签</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> icon</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> string</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 包图标</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> style</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> string</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 包图标样式(颜色)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> ctime</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> datetime</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> ctime</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> mtime</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> datetime</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> mtime</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> authUsers</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> user_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> authUsers</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> installPath</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> file_path</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 安装路径</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> platform</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> string</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 平台</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p><a href="https://git.easyops.local/anyclouds/contract-center/blob/master/schema/model.json" target="_blank" rel="noopener noreferrer">模型定义约束</a></p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="模型文件命名规范">模型文件命名规范<a href="#模型文件命名规范" class="hash-link" aria-label="Direct link to 模型文件命名规范" title="Direct link to 模型文件命名规范">​</a></h3><p>命名规则</p><ul><li>小写字母、下划线</li><li>模型的定义上线后，只能修改字段的描述，如果有字段类型的调整或者添加字段(不允许删除字段)，需要增加新模型文件，如 package_2.yaml</li></ul><p>示例: <code>model/cd/package.yaml</code></p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="name---消息名称">name - 消息名称<a href="#name---消息名称" class="hash-link" aria-label="Direct link to name - 消息名称" title="Direct link to name - 消息名称">​</a></h3><p>命名规则</p><ul><li>大写驼峰 <code>name: Package</code></li></ul><h3 class="anchor anchorWithStickyNavbar_LWe7" id="description---消息描述">description - 消息描述<a href="#description---消息描述" class="hash-link" aria-label="Direct link to description - 消息描述" title="Direct link to description - 消息描述">​</a></h3><p>示例: <code>description: 包模型</code></p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="imports---引用的模型">imports - 引用的模型<a href="#imports---引用的模型" class="hash-link" aria-label="Direct link to imports - 引用的模型" title="Direct link to imports - 引用的模型">​</a></h3><p>模型定义可以引用其它模型的定义，需要显式 import 到文件， 路径使用 / 分隔
示例：model/package_ext.yaml, 引用同级下的 Version 模型字段</p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">_version_</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2.0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">_kind_</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> model</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> PackageExt</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 包模型</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">import</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> easyops/model/cd/version</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">fields</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> lastVersionInfo</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> object</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">fields</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Version.ctime</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Version.name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Version.versionId</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 最新版本信息</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> instanceCount</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> int</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 包实例数量</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h3 class="anchor anchorWithStickyNavbar_LWe7" id="fields---字段定义">fields - 字段定义<a href="#fields---字段定义" class="hash-link" aria-label="Direct link to fields - 字段定义" title="Direct link to fields - 字段定义">​</a></h3><ul><li><p>name：字段名
命名规范：小写驼峰
原因是 Go 和 JavaScript 的命名风格都是驼峰为主流。见 MixedCaps</p></li><li><p>type：字段类型， 有三种来源, 基本内置类型、自定义类型、自定义模型</p><ul><li>a. 目前内置支持的字段类型</li></ul></li></ul><table><thead><tr><th>字段类型</th><th>对应到 go 的类型</th><th>对应到 typescript 的类型</th><th>备注</th></tr></thead><tbody><tr><td>int</td><td>int32</td><td>number</td><td></td></tr><tr><td>int64</td><td>int64</td><td>number</td><td>契约版本 2.2 支持</td></tr><tr><td>string</td><td>string</td><td>string</td><td></td></tr><tr><td>float</td><td>float</td><td>number</td><td></td></tr><tr><td>bool</td><td>bool</td><td>boolean</td><td></td></tr><tr><td>file</td><td>-</td><td>File</td><td>上传的文件</td></tr><tr><td>map</td><td><code>protobuf.types.Struct</code> (key 是 string， 值是 protobuf<!-- -->.<!-- -->types<!-- -->.<!-- -->Value 的结构体)</td><td><code>Record&lt;string,any&gt;</code></td><td>固定结构的返回下不推荐使用，不然会写得比较恶心，慎用，如果想用，这里有一些便捷的方式来做 Struct 和 map<!-- -->[string]<!-- -->interface{}的互转： <a href="https://git.easyops.local/easyops-go/kit/tree/master/gogoprotobuf/protostruct" target="_blank" rel="noopener noreferrer">https://git.easyops.local/easyops-go/kit/tree/master/gogoprotobuf/protostruct</a></td></tr><tr><td>(type)[]</td><td>type 类型的数组，type 可以是上面说的的任意类型，定义方式可参考示例</td><td>(type)[]</td><td>-</td></tr></tbody></table><ul><li><p>b. 在 easyops/type 下自定义的变量</p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">_version_</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2.0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">_kind_</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> type</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> email</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> string</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 电子邮箱地址</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">validate</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">pattern</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"^([A-Za-z0-9_\-\.\u4e00-\u9fa5])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,8})$/"</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div></li><li><p>c. 新的组合类型，object</p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">_version_</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2.0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">_kind_</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> model</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> PackageExt</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 包模型</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">fields</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> lastVersionInfo</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> object</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">fields</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Version.ctime</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Version.name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Version.versionId</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 最新版本信息</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> instanceCount</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> int</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 包实例数量</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div></li></ul><ul><li><p>ref
只能引用 model 中已经定义的类型
如果引用 model 中的所有字段， 可以是 Package.<!-- -->*</p></li><li><p>enum
枚举，只作为字段的约束手段，不再是独立类型。<code> 特别注意，自定义类型的时候，至少要有 enum 或者 validate 一种约束。</code></p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> platform</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> string</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">enum</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"linux"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"windows"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"others"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 平台</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div></li><li><p>validate - 框架当前支持的校验方法</p></li></ul><table><thead><tr><th>pattern</th><th>正则表达式</th><th>正则匹配</th></tr></thead><tbody><tr><td>gte</td><td>大于等于的值</td><td>如果是字符串，就是字符串长度大于等于。如果是数字，就是数字大于等于。 如果是数组， 就是数组长度大于等于</td></tr><tr><td>lte</td><td>小于等于的值</td><td>如果是字符串，就是字符串长度小于等于。 如果是数字，就是数字小于等于。 如果是数组， 就是数组长度小于等于</td></tr><tr><td>gt</td><td>大于的值</td><td>如果是字符串，就是字符串长度大于。如果是数字，就是数字大于。如果是数组， 就是数组长度大于</td></tr><tr><td>lt</td><td>小于的值</td><td>如果是字符串，就是字符串长度小于。 如果是数字，就是数字小于。如果是数组， 就是数组长度小于</td></tr></tbody></table><ul><li>自定义 type<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">_version_</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2.0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">_kind_</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> type</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> white_permission_type</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> string</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">enum</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">deleteAuthorizers</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> readAuthorizers</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> updateAuthorizers</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 白色单权限类型</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div></li></ul><h2 class="anchor anchorWithStickyNavbar_LWe7" id="接口定义">接口定义<a href="#接口定义" class="hash-link" aria-label="Direct link to 接口定义" title="Direct link to 接口定义">​</a></h2><p>一个示例接口定义(api/cd/create.yaml)如下：</p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">_version_</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2.0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">_kind_</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"interface"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">version</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1.0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Create"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"创建包"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">import</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> easyops/model/cd/package</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">endpoint</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">method</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"POST"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">uri</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/package"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">request</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> object</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">fields</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.type</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.cId</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.memo</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.installPath</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.platform</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.source</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.category</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.icon</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.style</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">required</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.type</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.cId</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.memo</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.installPath</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.platform</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">response</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">required</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.packageId</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.type</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.cId</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.memo</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.installPath</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.platform</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.source</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.category</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.icon</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.style</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p><a href="https://git.easyops.local/anyclouds/contract-center/blob/master/schema/interface.json" target="_blank" rel="noopener noreferrer">接口的定义约束</a></p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="name---接口名">name - 接口名<a href="#name---接口名" class="hash-link" aria-label="Direct link to name - 接口名" title="Direct link to name - 接口名">​</a></h3><p>接口名命名规则</p><ul><li>大写字母开头驼峰命名</li><li>CRUD 对应的命名</li></ul><table><thead><tr><th>HTTP VERB</th><th>命名</th><th>备注</th></tr></thead><tbody><tr><td>GET 列表</td><td>ListXxx</td><td></td></tr><tr><td>GET 详情</td><td>GetXxx</td><td></td></tr><tr><td>PUT</td><td>UpdateXxx</td><td></td></tr><tr><td>DELETE</td><td>DeleteXxx</td><td>不能为 Delete, Delete 为 js 关键字</td></tr><tr><td>POST</td><td>CreateXxx</td><td></td></tr><tr><td>PATCH</td><td>PatchXx</td><td></td></tr></tbody></table><h3 class="anchor anchorWithStickyNavbar_LWe7" id="接口文件名规范">接口文件名规范<a href="#接口文件名规范" class="hash-link" aria-label="Direct link to 接口文件名规范" title="Direct link to 接口文件名规范">​</a></h3><p>将 4.1 介绍的接口名转成小写下划线.yaml ， 如 <code>list_process_log.yaml</code></p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="version-接口版本">version: 接口版本<a href="#version-接口版本" class="hash-link" aria-label="Direct link to version: 接口版本" title="Direct link to version: 接口版本">​</a></h3><h3 class="anchor anchorWithStickyNavbar_LWe7" id="description-接口描述">description: 接口描述<a href="#description-接口描述" class="hash-link" aria-label="Direct link to description: 接口描述" title="Direct link to description: 接口描述">​</a></h3><h3 class="anchor anchorWithStickyNavbar_LWe7" id="endpoint-http-资源">endpoint: HTTP 资源<a href="#endpoint-http-资源" class="hash-link" aria-label="Direct link to endpoint: HTTP 资源" title="Direct link to endpoint: HTTP 资源">​</a></h3><table><thead><tr><th>字段名</th><th>备注</th><th>是否必填</th></tr></thead><tbody><tr><td>method</td><td>http 方法名 (get, post, put, delete)，另外提供一个 list 的方法，给拉取列表使用</td><td>是</td></tr><tr><td>uri</td><td>http uri， 中间有变量的话用 <!-- -->[:变量名]<!-- -->表示， 如 /v1/task/:task_id</td><td>是</td></tr><tr><td>ext_fields</td><td>ext_fields 指定 request 的 fields 里面的字段名来指定作为 POST 请求 body 或 GET 请求 url params 的字段定义。 如果不填，POST 请求默认用整个 request 作为 body 的数据结构定义, GET 请求默认用整个 request 作为 url params 的数据结构定义。这个字段的示例在 https://git<!-- -->.<!-- -->easyops<!-- -->.<!-- -->local/snippets/13 特殊场景下（比如请求类型是 map，uri 里面又带有参数）才会用到。</td><td>否</td></tr></tbody></table><h3 class="anchor anchorWithStickyNavbar_LWe7" id="request---http-请求定义">request - HTTP 请求定义<a href="#request---http-请求定义" class="hash-link" aria-label="Direct link to request - HTTP 请求定义" title="Direct link to request - HTTP 请求定义">​</a></h3><table><thead><tr><th>字段名</th><th>备注</th><th>是否必填</th></tr></thead><tbody><tr><td>type</td><td>请求体类型， 可以为 object, model 中定义的类型、file</td><td>是</td></tr><tr><td>fields</td><td>type 为 object 时，需要定义 fields 字段</td><td>否</td></tr><tr><td>required</td><td>字段是否必须</td><td>否</td></tr><tr><td>default</td><td>字段的默认值，仅在这个字段是可选时生效</td><td>否</td></tr></tbody></table><h4 class="anchor anchorWithStickyNavbar_LWe7" id="request-为空则为-">request 为空，则为 ~<a href="#request-为空则为-" class="hash-link" aria-label="Direct link to request 为空，则为 ~" title="Direct link to request 为空，则为 ~">​</a></h4><h4 class="anchor anchorWithStickyNavbar_LWe7" id="type">type<a href="#type" class="hash-link" aria-label="Direct link to type" title="Direct link to type">​</a></h4><p>type 代表请求消息的类型， 可以是 model 中的定义的具体类型 ，可以是自定义的 object 类型</p><ul><li><p>a. 当请求等于 model 中定义的所有字段，type 的类型为 model 中定义的具体类型 （大部分情况下都是 object）
示例:</p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">request</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">fields</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.type</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.cId</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.memo</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.installPath</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.platform</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.source</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.category</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.icon</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.style</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">required</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.type</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.cId</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.memo</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.installPath</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.platform</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div></li><li><p>b. 当返回的所有字段不是在同一个 model 中定义的，请求的 type 类型为 object 自定义类型</p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">request</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> object</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">fields</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.packageId</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> permissionType</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> white_permission_type</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 包权限类型</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">required</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.packageId</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div></li></ul><h3 class="anchor anchorWithStickyNavbar_LWe7" id="fields">fields<a href="#fields" class="hash-link" aria-label="Direct link to fields" title="Direct link to fields">​</a></h3><p>当 type 类型为 object 时，需要自定义 fields 字段，定义请求的所有字段，这里的字段类型可以是自定义类型， 也可以引用模型中已定义的字段</p><p>当类型为 object 时，定义的详情参见 model 中的 object 定义. 示例如下:</p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">request</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> object</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">fields</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.packageId </span><span class="token comment" style="color:#999988;font-style:italic"># 引用 Package 模型的 packageId 字段</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> permissionType </span><span class="token comment" style="color:#999988;font-style:italic"># 自定义字段</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> white_permission_type</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 包权限类型</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">required</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.packageId</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h3 class="anchor anchorWithStickyNavbar_LWe7" id="response---http-返回定义">response - HTTP 返回定义<a href="#response---http-返回定义" class="hash-link" aria-label="Direct link to response - HTTP 返回定义" title="Direct link to response - HTTP 返回定义">​</a></h3><ul><li>字段同 request 定义</li><li>框架或 sdk 会默认在 response 的 object 外封装一层</li></ul><div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"code"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"error"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"message"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token property" style="color:#36acaa">"data"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> response </span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><ul><li>如果想自定义整个 response_message， 可以在 response 里面加上<code>wrapper: false</code>参数， wrapper 默认为 true。 eaml 版本 2.1 带有这个特性。</li></ul><h3 class="anchor anchorWithStickyNavbar_LWe7" id="custom_controller">custom_controller<a href="#custom_controller" class="hash-link" aria-label="Direct link to custom_controller" title="Direct link to custom_controller">​</a></h3><p>这种一般都是接收了框架不支持的参数， 比如直接 post 一个数组过来，这时候需要配置这个设置</p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">_version_</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2.0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">_kind_</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"interface"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">version</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1.0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"GetPackageList"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"批量获取包信息"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">import</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> easyops/model/artifact/package</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> easyops/model/artifact/package_ext</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">endpoint</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">method</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"POST"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">uri</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/package/list"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">request</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> string</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">response</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> object</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">fields</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Package.* </span><span class="token comment" style="color:#999988;font-style:italic"># reference package model</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ref</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> PackageExt.*</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">required</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> Package.*</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> PackageExt</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> PackageExt.*</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">custom_controller</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean important" style="color:#36acaa">true</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h3 class="anchor anchorWithStickyNavbar_LWe7" id="private">private<a href="#private" class="hash-link" aria-label="Direct link to private" title="Direct link to private">​</a></h3><p>接口是否私有，默认为 false. 如果是私有，不会向外部生成 api 文档</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="deprecated">deprecated<a href="#deprecated" class="hash-link" aria-label="Direct link to deprecated" title="Direct link to deprecated">​</a></h3><p>接口是否被废弃，默认为 false</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="接口定义与-http-rest-的转换">接口定义与 HTTP REST 的转换<a href="#接口定义与-http-rest-的转换" class="hash-link" aria-label="Direct link to 接口定义与 HTTP REST 的转换" title="Direct link to 接口定义与 HTTP REST 的转换">​</a></h2><p>新的后台框架采用消息契约编程模式，所以 HTTP REST API 的一些字段（body, uri 带的资源参数, query_parameter) 都要在通讯的消息定义中能找得到，以下是转换规则</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="post">POST<a href="#post" class="hash-link" aria-label="Direct link to POST" title="Direct link to POST">​</a></h3><p>不支持 http 的 query parameter(查询参数）</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="put">PUT<a href="#put" class="hash-link" aria-label="Direct link to PUT" title="Direct link to PUT">​</a></h3><p>同 5.1，注意，PUT 方法只支持对象全量更新操作，不要提供只更新部分属性的功能，否则会有坑。</p><p>实在要提供只更新部分属性的功能， 参考 google 的 fieldmask 设计。</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="delete">DELETE<a href="#delete" class="hash-link" aria-label="Direct link to DELETE" title="Direct link to DELETE">​</a></h3><p>uri 中的参数名称必须在消息中定义
http 的 query parameter(查询参数）名称必须在接口中定义
返回时消息为 null</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="get-列表">GET 列表<a href="#get-列表" class="hash-link" aria-label="Direct link to GET 列表" title="Direct link to GET 列表">​</a></h3><p>http 的 query parameter(查询参数）名称必须在消息中定义
返回时建议的消息结构为</p><table><thead><tr><th>字段名</th><th>字段类型</th><th>校验规则</th><th>是否必填</th><th>备注</th></tr></thead><tbody><tr><td>page</td><td>int</td><td>gte: 1</td><td>是</td><td>要取的 page 数字</td></tr><tr><td>page_size</td><td>int</td><td>gte: 1, lte: 3000</td><td>是</td><td>要取的 page_size</td></tr><tr><td>total</td><td>int</td><td></td><td>否</td><td>数据总数，不用取总数的时候就不用</td></tr><tr><td>list</td><td>消息数组，如 Book[]</td><td></td><td>是</td><td>资源的列表</td></tr></tbody></table><h3 class="anchor anchorWithStickyNavbar_LWe7" id="get-详情">GET 详情<a href="#get-详情" class="hash-link" aria-label="Direct link to GET 详情" title="Direct link to GET 详情">​</a></h3><p>uri 中的参数名称必须在消息中定义
http 的 query parameter(查询参数）名称必须在消息中定义</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="api-版本号">API 版本号<a href="#api-版本号" class="hash-link" aria-label="Direct link to API 版本号" title="Direct link to API 版本号">​</a></h3><p>API 版本号要在 uri 里面体现出来， 如 <code>/v1/xxx /v2/xxx</code></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[EeasyOps 名字服务 1.0 介绍]]></title>
            <link>http://uwintech.cn/next-docs/blog/2019/07/13/ens-introduction</link>
            <guid>http://uwintech.cn/next-docs/blog/2019/07/13/ens-introduction</guid>
            <pubDate>Sat, 13 Jul 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[在一个分布式微服务系统里面，服务之间的调用会非常频繁和复杂，大规模集群服务会经常发生故障，应用的上下线也是常态。这时需要一个调度的"大脑"来给微服务指挥服务之间应该要如何做调用的。]]></description>
            <content:encoded><![CDATA[<p>在一个分布式微服务系统里面，服务之间的调用会非常频繁和复杂，大规模集群服务会经常发生故障，应用的上下线也是常态。这时需要一个调度的"大脑"来给微服务指挥服务之间应该要如何做调用的。</p><p>EasyOps 名字服务（以下简称 ens）扮演了 EasyOps 平台中这个指挥的角色。本文会从系统架构、用户使用的角度介绍 EasyOps 的名字服务。</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="功能">功能<a href="#功能" class="hash-link" aria-label="Direct link to 功能" title="Direct link to 功能">​</a></h2><p>ens 实现了以下功能：</p><ul><li>服务注册</li></ul><p>服务提供方需要向外提供服务的时候，会对外宣称自己提供的服务名和端口有哪些，启动的时候会自动把这个信息注册到名字服务里面，便于调用方能够发现这个服务。</p><p>在 EasyOps 平台里面，每个程序包里面的 package.conf.yaml 的描述文件里面会记录了这个程序包提供的服务</p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># 名字服务列表</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">service_list</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> logic.dc_console</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">port</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">8087</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><ul><li>服务路由</li></ul><p>调用方需要知道被调方的地址时，通过 ens 提供的不同语言 sdk(c++, go, rust, py, java 等)获取被调服务的地址（ip+端口)。</p><ul><li>负载均衡</li></ul><p>当有多台主机提供相同的服务时，ens 会通过每台机器设置的权重来随机分配一个地址给到调用方。</p><ul><li>自动容灾</li></ul><p>当服务或者主机发生故障时，ens 会自动踢掉有问题主机提供的服务，实现自动容灾的功能。</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="系统架构">系统架构<a href="#系统架构" class="hash-link" aria-label="Direct link to 系统架构" title="Direct link to 系统架构">​</a></h2><h3 class="anchor anchorWithStickyNavbar_LWe7" id="总体架构">总体架构<a href="#总体架构" class="hash-link" aria-label="Direct link to 总体架构" title="Direct link to 总体架构">​</a></h3><p>总体的系统架构图如下：</p><p><img loading="lazy" src="/next-docs/assets/images/ens-architecture-33da162df44f7464d746e806eb2cb963.png" width="625" height="628" class="img_ev3q"></p><ul><li>mongodb</li></ul><p>mongodb 集群作为名字服务的中心存储，用于记录服务名与主机地址的对应关系。</p><ul><li>ens_root</li></ul><p>缓存名字信息，分担 mongodb 压力。</p><ul><li>ens_client</li></ul><p>在每台 EasyOps 服务端的主机都会装上 ens_client。</p><p>ens_client 会定时从 ens_root 同步名字信息下来。ens_client 提供缓存名字信息、服务节点发现功能。</p><p>此外， ens_client 还会定时上报主机心跳信息， 当心跳过期时，心跳过期的主机会被自动剔除。</p><ul><li>ens_sdk</li></ul><p>缓存名字信息，提供不同语言的 sdk 给服务调用方使用。</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="高可用">高可用<a href="#高可用" class="hash-link" aria-label="Direct link to 高可用" title="Direct link to 高可用">​</a></h3><p>ens_root, ens_client, ens_sdk 都会有自己的缓存。任何一点故障，不会影响服务的正常调用。</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="ens-管理">ens 管理<a href="#ens-管理" class="hash-link" aria-label="Direct link to ens 管理" title="Direct link to ens 管理">​</a></h2><p>日常维护 EasyOps 平台时，我们经常需要管理名字服务。ens 提供了两个方法来对名字服务做读写管理。</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="控制台查看服务">控制台查看服务<a href="#控制台查看服务" class="hash-link" aria-label="Direct link to 控制台查看服务" title="Direct link to 控制台查看服务">​</a></h3><p>访问页面 http://\${EasyOps 平台 IP}/admin/ens 我们会看到所有的服务所在的地址</p><p><img loading="lazy" src="/next-docs/assets/images/ens-console-6464b1eab4b7edd9cbd434d4f8f9e098.png" width="2672" height="1134" class="img_ev3q"></p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="命令行工具查看新增删除服务">命令行工具查看、新增、删除服务<a href="#命令行工具查看新增删除服务" class="hash-link" aria-label="Direct link to 命令行工具查看、新增、删除服务" title="Direct link to 命令行工具查看、新增、删除服务">​</a></h3><ul><li><p>查询名字下的 IP 端口
/usr/local/easyops/ens_client/tools/get_all_service.py 要查的名字</p></li><li><p>往名字里面注册 IP 端口
/usr/local/easyops/ens_client/tools/register_service.py 要注册的名字 要注册的端口 要注册的 IP（可选，默认本机 IP）</p></li><li><p>从名字里面删除 IP 端口
/usr/local/easyops/ens_client/tools/unregister_service.py 要注销的名字 要注销的端口 要注销的 IP（可选，默认本机 IP）</p></li></ul><h2 class="anchor anchorWithStickyNavbar_LWe7" id="未来展望">未来展望<a href="#未来展望" class="hash-link" aria-label="Direct link to 未来展望" title="Direct link to 未来展望">​</a></h2><p>名字服务过去在 EasyOps 平台中扮演了核心的角色，未来 ens2.0 将会在多方面做优化。</p><ul><li><p>结合契约中心做更多优化，比如支持按契约名及契约版本路由，支持路由的优先级。</p></li><li><p>除了 ip, port 以外，支持记录服务/契约相关的更多字段，如 host，契约版本号等。</p></li><li><p>更灵敏的自动容灾。</p></li><li><p>结合 CMDB 做功能更强大的控制台管理功能。</p></li></ul>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[EasyOps 微服务组件清单]]></title>
            <link>http://uwintech.cn/next-docs/blog/2019/07/10/easyops-component-list</link>
            <guid>http://uwintech.cn/next-docs/blog/2019/07/10/easyops-component-list</guid>
            <pubDate>Wed, 10 Jul 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[说明]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorWithStickyNavbar_LWe7" id="说明">说明<a href="#说明" class="hash-link" aria-label="Direct link to 说明" title="Direct link to 说明">​</a></h2><ul><li>-A、-B、-NA、-NB、-NT 是前台的 js 插件，本质是一个 js 文件。优维前端框架是基于插件式的开发，每一个页面都是由各个插件拼装而成，这样提供了非常灵活的定制化能力。详见 <a href="/next-docs/">Brick Next</a></li><li>_<!-- -->db 组件是各个组件的数据组件，提供了数据初始化和数据割接的能力，本身不会启动常驻进程</li></ul><h2 class="anchor anchorWithStickyNavbar_LWe7" id="组件列表">组件列表<a href="#组件列表" class="hash-link" aria-label="Direct link to 组件列表" title="Direct link to 组件列表">​</a></h2><p>如下组件列表是<strong>build007 版本</strong></p><table><thead><tr><th align="center"><strong>编号</strong></th><th align="center"><strong>分类</strong></th><th align="center"><strong>组件</strong></th><th align="center"><strong>所属模块</strong></th><th align="center"><strong>进程名称</strong></th><th align="center"><strong>启动用户</strong></th><th align="center"><strong>占用端口</strong></th><th align="center"><strong>语言</strong></th><th align="center"><strong>说明</strong></th></tr></thead><tbody><tr><td align="center">1</td><td align="center">后台自研组件</td><td align="center">admin</td><td align="center">平台公共服务</td><td align="center">admin_worker.py admin_main.py</td><td align="center">easyops</td><td align="center">8082</td><td align="center">Python</td><td align="center">可以查看所有 agent 的状态和版本</td></tr><tr><td align="center">2</td><td align="center">后台自研组件</td><td align="center">agent</td><td align="center">平台客户端</td><td align="center">easyAgent easy_collector easy_monitor</td><td align="center">root</td><td align="center">无</td><td align="center">Python</td><td align="center">agent</td></tr><tr><td align="center">3</td><td align="center">后台自研组件</td><td align="center">agent_admin</td><td align="center">平台公共服务</td><td align="center"></td><td align="center"></td><td align="center">8102</td><td align="center">Go</td><td align="center">Agent 管理的新版，增加了 Agent 插件的管理</td></tr><tr><td align="center">4</td><td align="center">后台自研组件</td><td align="center">agent_admin_db</td><td align="center">平台公共服务</td><td align="center"></td><td align="center"></td><td align="center">无</td><td align="center">Python</td><td align="center">用于 agent 插件的模型新建、数据初始化</td></tr><tr><td align="center">5</td><td align="center">后台自研组件</td><td align="center">agent_download</td><td align="center">平台公共服务</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">用来生成和更新 agent 下载包</td></tr><tr><td align="center">6</td><td align="center">后台自研组件</td><td align="center">alert_adapter</td><td align="center">智能监控</td><td align="center">alert_adapter_m</td><td align="center">easyops</td><td align="center">8081</td><td align="center">Python</td><td align="center">告警通知组件</td></tr><tr><td align="center">7</td><td align="center">后台自研组件</td><td align="center">alert_channel</td><td align="center">智能监控</td><td align="center">alert_channel_m</td><td align="center">easyops</td><td align="center"></td><td align="center">Python</td><td align="center">告警通道，告警收敛汇聚</td></tr><tr><td align="center">8</td><td align="center">后台自研组件</td><td align="center">anomaly_detect</td><td align="center">智能监控</td><td align="center">anomaly<em>detect</em></td><td align="center">easyops</td><td align="center">8093</td><td align="center">Python</td><td align="center">异常探测模型训练后台</td></tr><tr><td align="center">9</td><td align="center">后台自研组件</td><td align="center">appconfig</td><td align="center">持续交付</td><td align="center"></td><td align="center"></td><td align="center">8100</td><td align="center">go</td><td align="center">应用配置后台</td></tr><tr><td align="center">10</td><td align="center">后台自研组件</td><td align="center">auto_detect</td><td align="center">IT 资源管理</td><td align="center">auto_detect_mai</td><td align="center">easyops</td><td align="center">8084</td><td align="center">Python</td><td align="center">自动发现, 安装 agent 后 cmdb 数据上报入库的处理组件</td></tr><tr><td align="center">11</td><td align="center">后台自研组件</td><td align="center">autodiscovery</td><td align="center">IT 资源管理</td><td align="center"></td><td align="center"></td><td align="center">80</td><td align="center">PHP</td><td align="center">自动发现接口后台</td></tr><tr><td align="center">12</td><td align="center">后台自研组件</td><td align="center">cmdb</td><td align="center">IT 资源管理</td><td align="center"></td><td align="center"></td><td align="center">80</td><td align="center">PHP</td><td align="center">CMDB 的接口后台</td></tr><tr><td align="center">13</td><td align="center">后台自研组件</td><td align="center">cmdb_resource</td><td align="center">IT 资源管理</td><td align="center"></td><td align="center"></td><td align="center">80</td><td align="center">PHP</td><td align="center">基于图数据库重构的 CMDB 后台, 原有的 CMDB 的接口都转发到这个组件</td></tr><tr><td align="center">14</td><td align="center">后台自研组件</td><td align="center">cmdb_service</td><td align="center">IT 资源管理</td><td align="center"></td><td align="center"></td><td align="center">8079</td><td align="center">Go</td><td align="center">cmdb 接口的后台</td></tr><tr><td align="center">15</td><td align="center">后台自研组件</td><td align="center">collector_center</td><td align="center">IT 资源管理</td><td align="center"></td><td align="center"></td><td align="center">8075</td><td align="center">Go</td><td align="center">用于 CMDB 自动采集任务管理</td></tr><tr><td align="center">16</td><td align="center">后台自研组件</td><td align="center">collector_center_db</td><td align="center">IT 资源管理</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">Python</td><td align="center">用于 CMDB 自动采集任务管理数据初始化</td></tr><tr><td align="center">17</td><td align="center">后台自研组件</td><td align="center">custom_report</td><td align="center">智能监控</td><td align="center">custom_report_m</td><td align="center">easyops</td><td align="center">8086</td><td align="center">Python</td><td align="center">自定义上报</td></tr><tr><td align="center">18</td><td align="center">后台自研组件</td><td align="center">data_loader</td><td align="center">IT 资源管理</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">Rust</td><td align="center">用于 CMDB 自动采集及服务发现入库</td></tr><tr><td align="center">19</td><td align="center">后台自研组件</td><td align="center">database_delivery</td><td align="center">数据库变更</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">Go</td><td align="center">数据库变更</td></tr><tr><td align="center">20</td><td align="center">后台自研组件</td><td align="center">database_delivery_db</td><td align="center">数据库变更</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">Go</td><td align="center">数据库变更 db</td></tr><tr><td align="center">21</td><td align="center">后台自研组件</td><td align="center">dc_console</td><td align="center">智能监控</td><td align="center">dc_console_main</td><td align="center">easyops</td><td align="center">8087</td><td align="center">pyhton</td><td align="center">数据处理中心（监控）的接口后台</td></tr><tr><td align="center">22</td><td align="center">后台自研组件</td><td align="center">deploy</td><td align="center">持续交付</td><td align="center"></td><td align="center"></td><td align="center">80</td><td align="center">PHP</td><td align="center">发布平台的接口后台（旧）</td></tr><tr><td align="center">23</td><td align="center">后台自研组件</td><td align="center">deploy_repository</td><td align="center">持续交付</td><td align="center"></td><td align="center"></td><td align="center">80</td><td align="center">PHP</td><td align="center">包仓库，存储程序包和配置包</td></tr><tr><td align="center">24</td><td align="center">后台自研组件</td><td align="center">easy_command</td><td align="center">持续交付</td><td align="center">easy_command</td><td align="center">easyops</td><td align="center">8060</td><td align="center">go</td><td align="center">命令通道, 保证发布/工具/采集命令的下发与结果的返回，提供实时回显能力</td></tr><tr><td align="center">25</td><td align="center">后台自研组件</td><td align="center">easy_core</td><td align="center">IT 资源管理</td><td align="center"></td><td align="center"></td><td align="center">8070</td><td align="center">Rust</td><td align="center">替换 orientdb</td></tr><tr><td align="center">26</td><td align="center">后台自研组件</td><td align="center">easy_flow</td><td align="center">持续交付</td><td align="center">easy_flow</td><td align="center">easyops</td><td align="center">8061</td><td align="center">go</td><td align="center">发布平台接口后台（新）</td></tr><tr><td align="center">27</td><td align="center">后台自研组件</td><td align="center">easy_framework</td><td align="center">平台公共服务</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">Python</td><td align="center">开发框架，本身不提供业务逻辑</td></tr><tr><td align="center">28</td><td align="center">后台自研组件</td><td align="center">easy_kube</td><td align="center">持续交付</td><td align="center">easy_kube</td><td align="center">easyops</td><td align="center">8063</td><td align="center">go</td><td align="center">对接 K8S 组件，提供 docker 发布能力</td></tr><tr><td align="center">29</td><td align="center">后台自研组件</td><td align="center">easy_plugin</td><td align="center">平台公共服务</td><td align="center"></td><td align="center"></td><td align="center">8062</td><td align="center">Python</td><td align="center">后台插件中心接口后台，用户可自行编写插件，提供接口服务</td></tr><tr><td align="center">30</td><td align="center">后台自研组件</td><td align="center">easy_proxy</td><td align="center">持续交付</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">go</td><td align="center">无 agent 方案，对接 unix 服务器的代理层</td></tr><tr><td align="center">31</td><td align="center">后台自研组件</td><td align="center">ens_client</td><td align="center">平台公共服务</td><td align="center">ens_client</td><td align="center">easyops</td><td align="center"></td><td align="center">Python</td><td align="center">名字服务 sdk 支持</td></tr><tr><td align="center">32</td><td align="center">后台自研组件</td><td align="center">ens_root</td><td align="center">平台公共服务</td><td align="center">ens_server</td><td align="center">easyops</td><td align="center">9100</td><td align="center">Python</td><td align="center">名字服务管理主控中心</td></tr><tr><td align="center">33</td><td align="center">后台自研组件</td><td align="center">f5_manage</td><td align="center">Easyops+ 小产品</td><td align="center"></td><td align="center"></td><td align="center">8097</td><td align="center">go</td><td align="center">f5 管理</td></tr><tr><td align="center">34</td><td align="center">后台自研组件</td><td align="center">f5_manage_db</td><td align="center">Easyops+ 小产品</td><td align="center"></td><td align="center"></td><td align="center">无</td><td align="center">Python</td><td align="center">f5 管理 db</td></tr><tr><td align="center">35</td><td align="center">后台自研组件</td><td align="center">flow_task</td><td align="center">持续交付</td><td align="center"></td><td align="center"></td><td align="center">8068</td><td align="center">Go</td><td align="center">部署编排</td></tr><tr><td align="center">36</td><td align="center">后台自研组件</td><td align="center">flow_task_db</td><td align="center">持续交付</td><td align="center"></td><td align="center"></td><td align="center">无</td><td align="center">Python</td><td align="center">部署编排 db</td></tr><tr><td align="center">37</td><td align="center">后台自研组件</td><td align="center">gateway</td><td align="center">平台公共服务</td><td align="center">gateway</td><td align="center">easyops</td><td align="center">5511</td><td align="center">go</td><td align="center">与 agent 保持下行长连接, 提供命令下发通道</td></tr><tr><td align="center">38</td><td align="center">后台自研组件</td><td align="center">graph</td><td align="center">IT 资源管理</td><td align="center"></td><td align="center"></td><td align="center">80</td><td align="center">PHP</td><td align="center">cmdb 拓扑接口后台</td></tr><tr><td align="center">39</td><td align="center">后台自研组件</td><td align="center">holmes</td><td align="center">平台公共服务</td><td align="center"></td><td align="center"></td><td align="center">8078</td><td align="center">Go</td><td align="center">用于平台接口支持分布式追踪监控</td></tr><tr><td align="center">40</td><td align="center">后台自研组件</td><td align="center">jobservice</td><td align="center">运维自动化</td><td align="center"></td><td align="center"></td><td align="center">80</td><td align="center">PHP</td><td align="center">新运维自动化后台</td></tr><tr><td align="center">41</td><td align="center">后台自研组件</td><td align="center">monitor</td><td align="center">智能监控</td><td align="center">monitor_main.py</td><td align="center">easyops</td><td align="center">8089</td><td align="center">Python</td><td align="center">告警数据接入组件</td></tr><tr><td align="center">42</td><td align="center">后台自研组件</td><td align="center">msgsender</td><td align="center">平台公共服务</td><td align="center">msgsender_main.</td><td align="center">easyops</td><td align="center">8095</td><td align="center">Python</td><td align="center">用来做统一的通知通道，比如发邮件、短信、微信</td></tr><tr><td align="center">43</td><td align="center">后台自研组件</td><td align="center">msgsender_db</td><td align="center">智能监控</td><td align="center"></td><td align="center"></td><td align="center">无</td><td align="center">Python</td><td align="center">向 notify 注册订阅者</td></tr><tr><td align="center">44</td><td align="center">后台自研组件</td><td align="center">notify</td><td align="center">平台公共服务</td><td align="center">notify notify_worker</td><td align="center">easyops</td><td align="center">80</td><td align="center">PHP</td><td align="center">通知中心，提供统一的任务管理及对外通知能力</td></tr><tr><td align="center">45</td><td align="center">后台自研组件</td><td align="center">openapi</td><td align="center">平台公共服务</td><td align="center"></td><td align="center"></td><td align="center">80</td><td align="center">PHP</td><td align="center">openapi，有限制的开放接口给予外部使用</td></tr><tr><td align="center">46</td><td align="center">后台自研组件</td><td align="center">periodic_tasks</td><td align="center">持续交付</td><td align="center">periodic<em>tasks</em></td><td align="center">easyops</td><td align="center"></td><td align="center">Python</td><td align="center">离线的周期任务执行, 可用性计算，数据清理等</td></tr><tr><td align="center">47</td><td align="center">后台自研组件</td><td align="center">permission</td><td align="center">平台公共服务</td><td align="center">permission_main</td><td align="center">easyops</td><td align="center">8085</td><td align="center">Python</td><td align="center">权限管理接口后台</td></tr><tr><td align="center">48</td><td align="center">后台自研组件</td><td align="center">php_license</td><td align="center">平台公共服务</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">PHP</td><td align="center"></td></tr><tr><td align="center">49</td><td align="center">后台自研组件</td><td align="center">receiver</td><td align="center">平台公共服务</td><td align="center">receiver</td><td align="center">easyops</td><td align="center">8820</td><td align="center">rust</td><td align="center">与 agent 保持上行长连接，用户 agent 上报数据</td></tr><tr><td align="center">50</td><td align="center">后台自研组件</td><td align="center">resource_manage</td><td align="center">平台公共服务</td><td align="center"></td><td align="center"></td><td align="center">8073</td><td align="center">Go</td><td align="center">CMDB 部分资源管理中台</td></tr><tr><td align="center">51</td><td align="center">后台自研组件</td><td align="center">resource_manage_db</td><td align="center">平台公共服务</td><td align="center"></td><td align="center"></td><td align="center">无</td><td align="center">Python</td><td align="center">resource manage db</td></tr><tr><td align="center">52</td><td align="center">后台自研组件</td><td align="center">scheduler</td><td align="center">持续交付</td><td align="center">scheduler_main.</td><td align="center">easyops</td><td align="center">8083</td><td align="center">Python</td><td align="center">定时任务接口后台</td></tr><tr><td align="center">53</td><td align="center">后台自研组件</td><td align="center">solutions</td><td align="center">平台公共服务</td><td align="center"></td><td align="center"></td><td align="center">无</td><td align="center">无</td><td align="center">平台解决方案。通过 uc 的依赖，带出原 console 内置的插件和对应的 console</td></tr><tr><td align="center">54</td><td align="center">后台自研组件</td><td align="center">storm_topology</td><td align="center">智能监控</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">shell</td><td align="center">非实例, 用来提交 topo 给 nimbus, nimbus 再分配给 supervisor 来执行</td></tr><tr><td align="center">55</td><td align="center">后台自研组件</td><td align="center">sync_center</td><td align="center">IT 资源管理</td><td align="center"></td><td align="center"></td><td align="center">8076</td><td align="center">GO</td><td align="center">CMDB 自动采集任务定时同步</td></tr><tr><td align="center">56</td><td align="center">后台自研组件</td><td align="center">system_settings</td><td align="center">平台公共服务</td><td align="center"></td><td align="center"></td><td align="center">80</td><td align="center">PHP</td><td align="center">系统菜单管理接口后台</td></tr><tr><td align="center">57</td><td align="center">后台自研组件</td><td align="center">tools</td><td align="center">工具与流程</td><td align="center"></td><td align="center"></td><td align="center">80</td><td align="center">PHP</td><td align="center">作业/调度平台后台接口</td></tr><tr><td align="center">58</td><td align="center">前端插件</td><td align="center">console</td><td align="center">统一前台</td><td align="center"></td><td align="center"></td><td align="center">80</td><td align="center">PHP/js</td><td align="center">前台 web 组件</td></tr><tr><td align="center">59</td><td align="center">前端插件</td><td align="center">brick-host-A</td><td align="center">IT 资源管理</td><td align="center"></td><td align="center"></td><td align="center">无</td><td align="center">TypeScript</td><td align="center">brick-host TypeScript 构件库</td></tr><tr><td align="center">60</td><td align="center">前端插件</td><td align="center">common-toolkit-py</td><td align="center">IT 资源管理</td><td align="center"></td><td align="center"></td><td align="center">7744</td><td align="center">Python</td><td align="center">任意门接口后台</td></tr><tr><td align="center">61</td><td align="center">前端插件</td><td align="center">databases-A</td><td align="center">持续交付</td><td align="center"></td><td align="center"></td><td align="center">8067</td><td align="center">TypeScript</td><td align="center">数据库变更，TypeScript 构件</td></tr><tr><td align="center">62</td><td align="center">前端插件</td><td align="center">f-five-A</td><td align="center">Easyops+ 小产品</td><td align="center"></td><td align="center"></td><td align="center">8097</td><td align="center">TypeScript</td><td align="center">f5 管理，前台插件</td></tr><tr><td align="center">63</td><td align="center">前端插件</td><td align="center">flows-A</td><td align="center">工具与流程</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">流程库，TypeScript 插件。（现里面只有流程版本页面）</td></tr><tr><td align="center">64</td><td align="center">前端插件</td><td align="center">group-B</td><td align="center">IT 资源管理</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">模型二级菜单，TypeScript 构件；为 brick-group-A 插件所用</td></tr><tr><td align="center">65</td><td align="center">前端插件</td><td align="center">home-A</td><td align="center">IT 资源管理</td><td align="center"></td><td align="center"></td><td align="center">无</td><td align="center">TypeScript</td><td align="center">cmdb 首页 TypeScript 构件库</td></tr><tr><td align="center">66</td><td align="center">前端插件</td><td align="center">orchestration-A</td><td align="center">持续交付</td><td align="center"></td><td align="center"></td><td align="center">无</td><td align="center"></td><td align="center">部署编排，TypeScript 插件</td></tr><tr><td align="center">67</td><td align="center">前端插件</td><td align="center">service-discovery-B</td><td align="center">IT 资源管理</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">服务发现，TypeScript 构建，为 service-discovery-A 插件所用</td></tr><tr><td align="center">68</td><td align="center">前端插件</td><td align="center">template-data-editor-B</td><td align="center">持续交付</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">动态配置，TypeScript 构建；为 packages-A 插件所用</td></tr><tr><td align="center">69</td><td align="center">基础中间件</td><td align="center">elasticsearch</td><td align="center">IT 资源管理</td><td align="center">Java</td><td align="center">easyops</td><td align="center">9201</td><td align="center">Java</td><td align="center">实现 cmdb 全文搜索, 以及存储搜索日志</td></tr><tr><td align="center">70</td><td align="center">基础中间件</td><td align="center">grafana</td><td align="center">智能监控</td><td align="center">grafana-server</td><td align="center">easyops</td><td align="center">3000</td><td align="center">go</td><td align="center">大屏展示组件</td></tr><tr><td align="center">71</td><td align="center">基础中间件</td><td align="center">influx_proxy</td><td align="center">智能监控</td><td align="center"></td><td align="center"></td><td align="center">6666</td><td align="center">go</td><td align="center">influxdb 高可用方案 proxy</td></tr><tr><td align="center">72</td><td align="center">基础中间件</td><td align="center">influxdb</td><td align="center">智能监控</td><td align="center">influxd</td><td align="center">easyops</td><td align="center">8092</td><td align="center">go</td><td align="center">监控数据存储</td></tr><tr><td align="center">73</td><td align="center">基础中间件</td><td align="center">jdk</td><td align="center">平台公共服务</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">Java</td><td align="center">Java 语言的软件开发工具包</td></tr><tr><td align="center">74</td><td align="center">基础中间件</td><td align="center">kafka</td><td align="center">智能监控</td><td align="center">Java</td><td align="center">easyops</td><td align="center">9092</td><td align="center">Java</td><td align="center">监控数据, 日志等数据接入缓冲队列</td></tr><tr><td align="center">75</td><td align="center">基础中间件</td><td align="center">liquibase</td><td align="center">数据库变更</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">数据库变更管理 db 连接库</td></tr><tr><td align="center">76</td><td align="center">基础中间件</td><td align="center">mongodb</td><td align="center">IT 资源管理</td><td align="center">mongod</td><td align="center">easyops</td><td align="center">27017</td><td align="center">c</td><td align="center">cmdb, 监控等配置数据存储</td></tr><tr><td align="center">77</td><td align="center">基础中间件</td><td align="center">mysql</td><td align="center">平台公共服务</td><td align="center">mysqld</td><td align="center">easyops</td><td align="center">3306</td><td align="center">c</td><td align="center">发布, 作业等历史数据存储</td></tr><tr><td align="center">78</td><td align="center">基础中间件</td><td align="center">nginx</td><td align="center">平台公共服务</td><td align="center">nginx</td><td align="center">root+easyops</td><td align="center">80</td><td align="center">c</td><td align="center">nginx，接入 7 层代理</td></tr><tr><td align="center">79</td><td align="center">基础中间件</td><td align="center">php</td><td align="center">平台公共服务</td><td align="center">PHP-fpm</td><td align="center">easyops</td><td align="center">9000</td><td align="center">PHP</td><td align="center">PHP</td></tr><tr><td align="center">80</td><td align="center">基础中间件</td><td align="center">python</td><td align="center">平台公共服务</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">Python</td><td align="center">Python</td></tr><tr><td align="center">81</td><td align="center">基础中间件</td><td align="center">rabbitmq</td><td align="center">平台公共服务</td><td align="center">rabbitmq-server beam.smp</td><td align="center">root root</td><td align="center">5672</td><td align="center">erlang</td><td align="center">notify 依赖, 消息队列</td></tr><tr><td align="center">82</td><td align="center">基础中间件</td><td align="center">redis</td><td align="center">平台公共服务</td><td align="center">redis-server</td><td align="center">easyops</td><td align="center">6379</td><td align="center">c</td><td align="center">redis</td></tr><tr><td align="center">83</td><td align="center">基础中间件</td><td align="center">storm_nimbus</td><td align="center">智能监控</td><td align="center">Java</td><td align="center">easyops</td><td align="center">6627/8080</td><td align="center">Java</td><td align="center">storm master 管理进程</td></tr><tr><td align="center">84</td><td align="center">基础中间件</td><td align="center">storm_supervisor</td><td align="center">智能监控</td><td align="center">Java</td><td align="center">easypos</td><td align="center"></td><td align="center">Java</td><td align="center">storm worker 进程</td></tr><tr><td align="center">85</td><td align="center">基础中间件</td><td align="center">zookeeper</td><td align="center">智能监控</td><td align="center">Java</td><td align="center">easyops</td><td align="center">2181</td><td align="center">Java</td><td align="center">用来管理 storm, kafka 集群</td></tr></tbody></table>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[如何接入客户的 SSO 系统，实现统一登录]]></title>
            <link>http://uwintech.cn/next-docs/blog/2019/07/10/how-to-login-by-sso</link>
            <guid>http://uwintech.cn/next-docs/blog/2019/07/10/how-to-login-by-sso</guid>
            <pubDate>Wed, 10 Jul 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[在项目中，我们经常会遇到接入客户 SSO 系统的需求，这样客户的各个 IT 系统就能够实现统一登录。]]></description>
            <content:encoded><![CDATA[<p>在项目中，我们经常会遇到接入客户 SSO 系统的需求，这样客户的各个 IT 系统就能够实现统一登录。</p><p>EasyOps （console 3.16.0 +）支持接入客户已有 SSO 系统。配置如下：
<code>console.ini</code></p><table><thead><tr><th><strong>字段</strong></th><th><strong>可选值</strong></th><th><strong>说明</strong></th></tr></thead><tbody><tr><td>sso_enabled</td><td>yes/no</td><td>是否打开 SSO 登录</td></tr><tr><td>sso_endpoint_type</td><td>keystone 等</td><td>类型：keystone 等，具体请接入的时候询问开发人员</td></tr><tr><td>sso_app_id</td><td></td><td>app id</td></tr><tr><td>sso_app_key</td><td></td><td>app key</td></tr><tr><td>sso_login_url</td><td></td><td>登录回调地址，如 https://your-sso-domain/login.aspx</td></tr><tr><td>sso_logout_url</td><td></td><td>登出回调地址：https://your-sso-domain/logout.aspx</td></tr><tr><td>sso_verify_ticket_url</td><td></td><td>验证票据地址（部分客户需要）</td></tr><tr><td>sso_user_id_field</td><td></td><td>SSO 用户 ID 字段名</td></tr></tbody></table><p>事实上，每个客户的 SSO 的实现方式都有所差异，需要做一定的适配，更多接入示例参考可询问优维对接同学</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[编写良好的单元测试]]></title>
            <link>http://uwintech.cn/next-docs/blog/2019/06/15/writing-a-good-unit-test</link>
            <guid>http://uwintech.cn/next-docs/blog/2019/06/15/writing-a-good-unit-test</guid>
            <pubDate>Sat, 15 Jun 2019 00:00:00 GMT</pubDate>
            <description><![CDATA[“万物之始，大道至简”]]></description>
            <content:encoded><![CDATA[<blockquote><p>“万物之始，大道至简”</p></blockquote><p>本文尝试从简单的单元测试思想着手，探讨如何编写良好的单元测试。<!-- -->以下将主要基于 <a href="https://www.typescriptlang.org/" target="_blank" rel="noopener noreferrer">TypeScript</a>, <a href="https://jestjs.io/" target="_blank" rel="noopener noreferrer">Jest</a>, <a href="https://reactjs.org/" target="_blank" rel="noopener noreferrer">React</a>, <a href="https://airbnb.io/enzyme/" target="_blank" rel="noopener noreferrer">Enzyme</a> 给出示例。关于单元测试的<a href="https://zh.wikipedia.org/wiki/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95" target="_blank" rel="noopener noreferrer">基本概念和重要性</a>不在本文讨论范围。</p><p><img loading="lazy" src="/next-docs/assets/images/martin-sanchez-MD6E2Sv__iA-unsplash-789596ac9e4fb582a0e35d8acc8c5d9c.jpg" width="900" height="600" class="img_ev3q"></p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="basics">基本方法<a href="#basics" class="hash-link" aria-label="Direct link to 基本方法" title="Direct link to 基本方法">​</a></h2><p>编写单元测试的基本方法其实很简单：</p><ol><li>给定输入</li><li>运行</li><li>断言输出</li></ol><p>而一个好的单元测试的要求也很简单：</p><ul><li>覆盖足够的输入场景</li><li>进行充分的输出断言</li></ul><p>一个最简单的例子：</p><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// `add.ts`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">add</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">a</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">number</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> b</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">number</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">number</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> a </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> b</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// `add.spec.ts`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">test</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"add(1, 2) should return 3"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">expect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token function" style="color:#d73a49">add</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">toBe</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">3</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>我们编写单元测试的步骤如下：</p><ol><li>给定输入：<code>1</code>, <code>2</code></li><li>运行： <code>add(...)</code></li><li>断言输出：<code>expect(...).toBe(3)</code></li></ol><p>是不是很简单？当然，真实业务场景下我们要测试的单元远比上述例子复杂得多。</p><ul><li>输入更加复杂，除了普通的函数输入参数，还可能有外部的事件，因此难以覆盖所有场景</li><li>输出更加复杂，除了普通的函数输出结果，还可能有对外部的副作用，因此难以断言运行结果</li></ul><blockquote><p>这里提到的<em>输入</em>、<em>输出</em>不再是狭义上的函数输入、输出。我们将所有可能影响测试对象行为的外部因素都称之为输入，将所有测试对象运行后对外部造成的影响都称之为输出。这样理解之后，我们就可以化繁为简，将测试过程回归到前面提到的最基本的方法上。</p></blockquote><p>所以编写良好的单元测试首先要做的就是厘清测试对象的输入、输出，掌握覆盖不同形式的输入、断言不同形式的输出的方法。我们将分开讨论它们。</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="inputs">输入<a href="#inputs" class="hash-link" aria-label="Direct link to 输入" title="Direct link to 输入">​</a></h2><p>足够简单的输入让我们可以花更少的时间、覆盖更多的场景。输入的来源大致有以下几种：</p><ul><li>普通变量参数</li><li>外部依赖发送的事件</li><li>GUI 操作事件</li></ul><p>编写测试覆盖它们的复杂度依次增大。除了第一个，其它都可以看作<em>外部事件</em>，也可以理解为<em>来自外界的副作用</em>。对于普通变量参数，我们只需构造这些参数即可完成<em>给定输入</em>的任务。而对于外部事件，我们要做的就是想办法触发这些事件。</p><p>我们依然看一个简单的例子：</p><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// `MyComponent.ts`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">window</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">addEventListener</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"resize"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">...</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>如何覆盖？主动发送这个事件：</p><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// `MyComponent.spec.ts`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">test</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"MyComponent"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  window</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">dispatchEvent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">new</span><span class="token plain"> </span><span class="token class-name">Event</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"resize"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>再看一个例子：</p><div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// `MyComponent.tsx`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> handleChange </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">value</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token spread operator" style="color:#393A34">...</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Editor</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">onChange</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript" style="color:#00009f">handleChange</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>如何覆盖依赖组件的特定事件？主动触发依赖组件的事件：</p><div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// `MyComponent.spec.tsx`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">test</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"MyComponent"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> wrapper </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">shallow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">MyComponent</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  wrapper</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">find</span><span class="token punctuation" style="color:#393A34">(</span><span class="token maybe-class-name">Editor</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">invoke</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"onChange"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"faked value"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h2 class="anchor anchorWithStickyNavbar_LWe7" id="outputs">输出<a href="#outputs" class="hash-link" aria-label="Direct link to 输出" title="Direct link to 输出">​</a></h2><p>足够简单的输出让我们可以更容易地断言运行结果。输出的形态大致有以下几种：</p><ul><li>普通变量输出</li><li>GUI 的变化</li><li>外部依赖的调用</li></ul><p>在测试中对它们进行断言的复杂度依次增大。除了第一个，其它都可以看作<em>对外界的副作用</em>。对于普通变量输出，我们只需简单地断言它的值即可。而对于对外界的副作用，我们要做的就是想办法断言这些副作用的影响。</p><p>我们继续看一个简单的例子：</p><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// `handleClick.ts`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">function</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">handleClick</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  history</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">push</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"/next/url"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>如何断言？我们可以断言副作用的影响结果：</p><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// `handleClick.spec.ts`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">test</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"handleClick"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">handleClick</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">expect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">history</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">location</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">pathname</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">toBe</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"/next/url"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>有时副作用所影响的结果难以断言，或者该依赖被 <em>Mocked</em>，那么我们可以监视该副作用的触发点是否被正确调用了：</p><div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// `handleClick.spec.ts`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">test</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"handleClick"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> spyOnHistoryPush </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> jest</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">spyOn</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">history</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"push"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">expect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">spyOnHistoryPush</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">toBeCalledWith</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"/next/url"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>再看一个 React 组件的例子：</p><div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// `MyComponent.tsx`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> handleValidation </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">valid</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">boolean</span><span class="token punctuation" style="color:#393A34">)</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">void</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">setState</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> valid </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Form.Item</span><span class="token tag" style="color:#00009f"> </span><span class="token tag attr-name" style="color:#00a4db">className</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:#393A34">=</span><span class="token tag script language-javascript punctuation" style="color:#393A34">{</span><span class="token tag script language-javascript keyword" style="color:#00009f">this</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript property-access" style="color:#00009f">state</span><span class="token tag script language-javascript punctuation" style="color:#393A34">.</span><span class="token tag script language-javascript property-access" style="color:#00009f">valid</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript operator" style="color:#393A34">?</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"valid"</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript operator" style="color:#393A34">:</span><span class="token tag script language-javascript" style="color:#00009f"> </span><span class="token tag script language-javascript string" style="color:#e3116c">"invalid"</span><span class="token tag script language-javascript punctuation" style="color:#393A34">}</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">Input</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:#393A34">&lt;/</span><span class="token tag class-name" style="color:#00009f">Form.Item</span><span class="token tag punctuation" style="color:#393A34">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>如何断言？判断依赖组件的变化：</p><div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// `MyComponent.spec.tsx`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">test</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"MyComponent"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token arrow operator" style="color:#393A34">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> wrapper </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">shallow</span><span class="token punctuation" style="color:#393A34">(</span><span class="token tag punctuation" style="color:#393A34">&lt;</span><span class="token tag class-name" style="color:#00009f">MyComponent</span><span class="token tag" style="color:#00009f"> </span><span class="token tag punctuation" style="color:#393A34">/&gt;</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic">// ... after something trigger `handleValidation()`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token function" style="color:#d73a49">expect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">wrapper</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">find</span><span class="token punctuation" style="color:#393A34">(</span><span class="token maybe-class-name">Form</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access maybe-class-name">Item</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">prop</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"className"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toBe</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"invalid"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>始终记得要断言测试对象运行后对外界的副作用影响。</p><p>另外断言的目标应该是<em>对外的影响</em>，而不是<em>内部状态</em>，因为内部状态并不是测试对象的<em>输出</em>。一个错误的例子：</p><div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic">// `MyComponent.bad.spec.tsx`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">expect</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">wrapper</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">instance</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">state</span><span class="token punctuation" style="color:#393A34">.</span><span class="token property-access">valid</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token method function property-access" style="color:#d73a49">toBe</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"invalid"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h2 class="anchor anchorWithStickyNavbar_LWe7" id="refactoring">重构与拆分<a href="#refactoring" class="hash-link" aria-label="Direct link to 重构与拆分" title="Direct link to 重构与拆分">​</a></h2><p>更简单的输入、输出让我们可以更容易地编写好的单元测试，但往往实际情况是业务需求不断增长，组件内部逻辑不断复杂化，输入输出的形式形态更加多样化，为组件编写单元测试的难度也随之陡增。</p><p><strong>适时地重构与拆分</strong>是解决这个问题的关键。在如今的前端组件化的模式下尤为重要，合理拆分后的组件可以让每个测试单元的输入输出都变得更少、更聚焦。诸如 React, <a href="https://redux.js.org/" target="_blank" rel="noopener noreferrer">Redux</a> 等主流框架和工具推崇的<a href="https://flaviocopes.com/react-unidirectional-data-flow/" target="_blank" rel="noopener noreferrer">单向数据流</a>盛行的其中一个原因就是它们巧妙地让各个单元的输入来源、输出影响单一化，从而降低编写单元测试的难度，同时提升组件集成时的信心。</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="summary">总结<a href="#summary" class="hash-link" aria-label="Direct link to 总结" title="Direct link to 总结">​</a></h2><p>编写良好的单元测试总结下来就是三条：</p><ul><li>识别测试对象的输入、输出</li><li>掌握不同形态下的输入覆盖、输出断言的方法</li><li>适时地重构与拆分</li></ul><p>希望以上内容对大家有所帮助。</p>]]></content:encoded>
        </item>
    </channel>
</rss>