<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>zhennann (zhennann)</title>
    <link>https://ruby-china.org/zhennann</link>
    <description/>
    <language>en-us</language>
    <item>
      <title>在 Vue3 中如何实现四种全局状态数据的统一管理？</title>
      <description>&lt;h2 id="四种全局状态数据"&gt;四种全局状态数据&lt;/h2&gt;
&lt;p&gt;在实际开发当中，会遇到四种全局状态数据：&lt;code&gt;异步数据（一般来自服务端）&lt;/code&gt;、&lt;code&gt;同步数据&lt;/code&gt;。同步数据又分为三种：&lt;code&gt;localstorage&lt;/code&gt;、&lt;code&gt;cookie&lt;/code&gt;、&lt;code&gt;内存&lt;/code&gt;。在传统的 Vue3 当中，分别采用不同的机制来处理这些状态数据，而在 Zova 中只需要采用统一的&lt;code&gt;Model&lt;/code&gt;机制&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;状态数据&lt;/th&gt;
&lt;th&gt;传统的 Vue3&lt;/th&gt;
&lt;th&gt;Zova&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;异步数据&lt;/td&gt;
&lt;td&gt;Pinia&lt;/td&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;localstorage&lt;/td&gt;
&lt;td&gt;Pinia + Localstorage&lt;/td&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cookie&lt;/td&gt;
&lt;td&gt;Pinia + Cookie&lt;/td&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;内存&lt;/td&gt;
&lt;td&gt;Pinia&lt;/td&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;采用 Model 机制统一管理这些全局状态数据，就可以提供一些通用的系统能力，比如，&lt;code&gt;内存优化&lt;/code&gt;、&lt;code&gt;持久化&lt;/code&gt;和&lt;code&gt;SSR支持&lt;/code&gt;等等，从而规范数据使用方式，简化代码结构，提升代码的可维护性&lt;/p&gt;
&lt;h2 id="特性1. 支持异步数据和同步数据"&gt;特性 1. 支持异步数据和同步数据&lt;/h2&gt;
&lt;p&gt;Zova Model 的基座是&lt;a href="https://tanstack.com/query/latest/docs/framework/vue/overview" rel="nofollow" target="_blank" title=""&gt;TanStack Query&lt;/a&gt;。TanStack Query 提供了强大的数据获取、缓存和更新能力。如果你没有使用过类似 TanStack Query 的数据管理机制，那么强烈建议了解一下，相信你一定会受到思想的洗礼
但是，TanStack Query 的核心是对异步数据（一般来自服务端）进行管理。Zova Model 在 TanStack Query 的基础上做了扩展，因此也支持同步数据的管理。换而言之，以下所述所有特性和能力同时适用于&lt;code&gt;异步数据&lt;/code&gt;和&lt;code&gt;同步数据&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="特性2. 自动缓存"&gt;特性 2. 自动缓存&lt;/h2&gt;
&lt;p&gt;对获取的异步数据进行本地缓存，避免重复获取。对于同步数据，会自动针对 localstorage 或者 cookie 进行读写操作&lt;/p&gt;
&lt;h2 id="特性3. 自动更新"&gt;特性 3. 自动更新&lt;/h2&gt;
&lt;p&gt;提供数据过期策略，在合适的时机自动更新&lt;/p&gt;
&lt;h2 id="特性4. 减少重复请求"&gt;特性 4. 减少重复请求&lt;/h2&gt;
&lt;p&gt;在程序的多个地方同时访问数据，将只调用一次服务端 api。如果是同步数据，也只针对 localstorage 或者 cookie 调用一次操作&lt;/p&gt;
&lt;h2 id="特性5. 内存优化"&gt;特性 5. 内存优化&lt;/h2&gt;
&lt;p&gt;通过 Zova Model 管理的数据，虽然是全局范围的状态，但是并不总是占用内存，而是提供了内存释放与回收的机制。具体而言，就是在创建 Vue 组件实例时根据业务的需要创建缓存数据，当 Vue 组件实例卸载时释放对缓存数据的引用，到达约定的过期时间如果仍然没有其他 Vue 组件引用，就会触发回收机制 (GC)，完成对内存的释放，从而节约内存占用。这对于大型项目，用户需要长时间进行界面交互的场景，具有显著的好处&lt;/p&gt;
&lt;h2 id="特性6. 持久化"&gt;特性 6. 持久化&lt;/h2&gt;
&lt;p&gt;本地缓存可以持久化，当页面刷新时可以自动恢复，避免服务端调用。如果是异步数据，就会自动持久化到 IndexDB 中，从而满足大数据量的存储需要。如果是同步数据，就会自动持久化到 localstorage 或者 cookie&lt;/p&gt;

&lt;p&gt;&lt;code&gt;内存优化&lt;/code&gt;与&lt;code&gt;持久化&lt;/code&gt;配合发挥作用，对于大型项目效果更佳明显。比如，第一次从服务端获取的数据，会生成本地缓存，并自动持久化。当页面不再使用并且过期时，会自动销毁本地缓存，从而释放内存。当再次访问该数据时，会自动从持久化中恢复本地缓存数据，而不是再次从服务端获取数据&lt;/p&gt;
&lt;h2 id="特性7. SSR支持"&gt;特性 7. SSR 支持&lt;/h2&gt;
&lt;p&gt;不同类型的状态数据，在 SSR 模式下也会有不同的实现机制。Zova Model 把这些状态数据的差异进行抹平，并且采用统一的机制进行水合，从而让 SSR 的实现更加自然、直观，显著降低了心智负担&lt;/p&gt;
&lt;h2 id="特性8. 自动命名空间隔离"&gt;特性 8. 自动命名空间隔离&lt;/h2&gt;
&lt;p&gt;Zova 通过 Model Bean 来管理数据。而 Bean 本身有唯一的标识，可以作为数据的命名空间，从而自动保证了 Bean 内部状态数据命名的唯一性，避免数据冲突&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;参见：&lt;a href="https://zova.js.org/zh/guide/essentials/ioc/bean-identifier.html" rel="nofollow" target="_blank" title=""&gt;Bean 标识&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="如何创建一个Model Bean"&gt;如何创建一个 Model Bean&lt;/h2&gt;
&lt;p&gt;Zova 提供了 VS Code 插件，通过右键菜单可以非常便利的创建一个 Model Bean&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;右键菜单 - [模块路径]: &lt;code&gt;Zova Create/Bean: Model&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;依据提示输入 model bean 的名称，比如&lt;code&gt;todo&lt;/code&gt;，VSCode 插件会自动添加 model bean 的代码骨架&lt;/p&gt;

&lt;p&gt;比如，在 demo-todo 模块中创建一个 Model Bean &lt;code&gt;todo&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;demo-todo/src/bean/model.todo.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zova&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BeanModelBase&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zova-module-a-model&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModelTodo&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BeanModelBase&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;使用&lt;a href="/Model" class="user-mention" title="@Model"&gt;&lt;i&gt;@&lt;/i&gt;Model&lt;/a&gt; 装饰器&lt;/li&gt;
&lt;li&gt;继承自基类 BeanModelBase&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="异步数据"&gt;异步数据&lt;/h2&gt;
&lt;p&gt;TanStack Query 的核心是对服务端数据进行管理。为简化起见，这里仅展示 select 方法的定义与使用：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;完整代码示例，请参见：&lt;a href="https://github.com/cabloy/zova/blob/main/zova-dev/src/suite/a-demo/modules/demo-todo/src/bean/model.todo.ts" rel="nofollow" target="_blank" title=""&gt;demo-todo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="如何定义"&gt;如何定义&lt;/h3&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModelTodo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$useQueryExisting&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;select&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;queryFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;调用$useQueryExisting 创建 Query 对象

&lt;ul&gt;
&lt;li&gt;为何不使用&lt;code&gt;$useQuery&lt;/code&gt;方法？因为异步数据一般是在需要时才进行异步加载。因此我们需要确保在多次调用&lt;code&gt;select&lt;/code&gt;方法时始终返回同一个 Query 对象，所以必须使用&lt;code&gt;$useQueryExisting&lt;/code&gt;方法&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;传入 queryKey，确保本地缓存的唯一性&lt;/li&gt;
&lt;li&gt;传入 queryFn，在合适的时机调用此函数获取服务端数据

&lt;ul&gt;
&lt;li&gt;service.todo.select：参见&lt;a href="../../essentials/scope/service.md" title=""&gt;Api 服务&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="如何使用"&gt;如何使用&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;demo-todo/src/page/todo/controller.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ModelTodo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../../bean/model.todo.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ControllerPageTodo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;$$modelTodo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ModelTodo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;注入 Model Bean 实例：$$modelTodo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;demo-todo/src/page/todo/render.tsx&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RenderTodo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;todos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$$modelTodo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;          &lt;span class="p"&gt;})}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;调用 select 方法获取 Query 对象

&lt;ul&gt;
&lt;li&gt;render 方法会多次执行，重复调用 select 方法返回的是同一个 Query 对象&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;直接使用 Query 对象中的状态和数据

&lt;ul&gt;
&lt;li&gt;参见：&lt;a href="https://tanstack.com/query/latest/docs/framework/vue/guides/queries" rel="nofollow" target="_blank" title=""&gt;TanStack Query: Queries&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="如何支持SSR"&gt;如何支持 SSR&lt;/h3&gt;
&lt;p&gt;在 SSR 模式下，我们需要这样使用异步数据：在服务端加载状态数据，然后通过 render 方法渲染成 html 字符串。状态数据和 html 字符串会同时发送到客户端，客户端在进行水合时仍然使用此相同的状态数据，从而保持状态的一致性&lt;/p&gt;

&lt;p&gt;要实现以上逻辑，在 Zova Model 中只需要执行一个步骤：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;demo-todo/src/page/todo/controller.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ModelTodo } from '../../bean/model.todo.js';

export class ControllerPageTodo {
  @Use()
  $$modelTodo: ModelTodo;

  protected async __init__() {
    const queryTodos = this.$$modelTodo.select();
    await queryTodos.suspense();
    if (queryTodos.error) throw queryTodos.error;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;只需要在&lt;code&gt;__init__&lt;/code&gt;方法中调用&lt;code&gt;suspense&lt;/code&gt;等待异步数据加载完成&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="同步数据: localstorage"&gt;同步数据：localstorage&lt;/h2&gt;
&lt;p&gt;由于服务端不支持&lt;code&gt;window.localStorage&lt;/code&gt;，因此 localstorage 状态数据不参与 SSR 的水合过程&lt;/p&gt;

&lt;p&gt;下面演示把用户信息存入 localstorage，当页面刷新时也会保持状态&lt;/p&gt;
&lt;h3 id="如何定义"&gt;如何定义&lt;/h3&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModelUser&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BeanModelBase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;ServiceUserEntity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$useQueryLocal&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;与&lt;code&gt;异步数据&lt;/code&gt;定义不同，同步数据直接在初始化方法&lt;code&gt;__init__&lt;/code&gt;中定义&lt;/li&gt;
&lt;li&gt;调用$useQueryLocal 创建 Query 对象&lt;/li&gt;
&lt;li&gt;传入 queryKey，确保本地缓存的唯一性&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="如何使用"&gt;如何使用&lt;/h3&gt;
&lt;p&gt;直接像常规变量一样读取和设置数据&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newUser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="同步数据: cookie"&gt;同步数据：cookie&lt;/h2&gt;
&lt;p&gt;在服务端会自动使用&lt;code&gt;Request Header&lt;/code&gt;中的 Cookies，在客户端会自动使用&lt;code&gt;document.cookie&lt;/code&gt;，因此会自动保证 SSR 水合过程中 cookie 状态数据的一致性&lt;/p&gt;

&lt;p&gt;下面演示把用户 Token 存入 cookie，当页面刷新时也会保持状态。这样，在 SSR 模式下，客户端和服务端都可以使用相同的&lt;code&gt;jwt token&lt;/code&gt;访问后端 API 服务&lt;/p&gt;
&lt;h3 id="如何定义"&gt;如何定义&lt;/h3&gt;&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModelUser&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BeanModelBase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;token&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$useQueryCookie&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;与&lt;code&gt;异步数据&lt;/code&gt;定义不同，同步数据直接在初始化方法&lt;code&gt;__init__&lt;/code&gt;中定义&lt;/li&gt;
&lt;li&gt;调用$useQueryCookie 创建 Query 对象&lt;/li&gt;
&lt;li&gt;传入 queryKey，确保本地缓存的唯一性&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="如何使用"&gt;如何使用&lt;/h3&gt;
&lt;p&gt;直接像常规变量一样读取和设置数据&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="同步数据: 内存"&gt;同步数据：内存&lt;/h2&gt;
&lt;p&gt;在 SSR 模式下，服务端定义的全局状态数据会同步到客户端，并自动完成水合&lt;/p&gt;

&lt;p&gt;下面演示基于内存的全局状态数据&lt;/p&gt;
&lt;h3 id="如何定义"&gt;如何定义&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;zova-ui-quasar/src/suite-vendor/a-quasar/modules/quasar-adapter/src/bean/model.theme.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModelTheme&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BeanModelBase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;cBrand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cBrand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$useQueryMem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cBrand&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;与&lt;code&gt;异步数据&lt;/code&gt;定义不同，同步数据直接在初始化方法&lt;code&gt;__init__&lt;/code&gt;中定义&lt;/li&gt;
&lt;li&gt;调用$useQueryMem 创建 Query 对象&lt;/li&gt;
&lt;li&gt;传入 queryKey，确保本地缓存的唯一性&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="如何使用"&gt;如何使用&lt;/h3&gt;
&lt;p&gt;直接像常规变量一样读取和设置数据&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cBrand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cBrand&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cBrand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="结语"&gt;结语&lt;/h2&gt;
&lt;p&gt;Zova 是一款支持 IOC 容器的 Vue3 框架，在代码风格上结合了 Vue/React/Angular 的优点，同时规避他们的缺点，让我们的开发体验更加优雅，减轻心智负担。Zova 已经内置了大量实用、有趣的功能特性，Model 机制仅仅是其中一个&lt;/p&gt;

&lt;p&gt;Zova 框架已经开源，欢迎关注，参与共建：&lt;a href="https://github.com/cabloy/zova" rel="nofollow" target="_blank" title=""&gt;https://github.com/cabloy/zova&lt;/a&gt;。可添加我的微信，入群交流：yangjian2025&lt;/p&gt;</description>
      <author>zhennann</author>
      <pubDate>Thu, 10 Oct 2024 19:36:22 +0800</pubDate>
      <link>https://ruby-china.org/topics/43905</link>
      <guid>https://ruby-china.org/topics/43905</guid>
    </item>
    <item>
      <title>在一个项目中同时实现“后台管理系统”和“前台应用”</title>
      <description>&lt;h2 id="微信一起点菜"&gt;&lt;strong&gt;微信一起点菜&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;一个月紧张而愉快的开发又过去了。在这个月，开发了一个&lt;code&gt;微信一起点菜&lt;/code&gt;项目，用来再次验证&lt;code&gt;CabloyJS全栈&lt;/code&gt;框架定制开发的灵活性和扩展性。实践再次证明，使用 CabloyJS 全栈框架开发项目确实可以做到&lt;code&gt;多快好省&lt;/code&gt;。因为：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;只需一个项目，既可同时实现“后台管理系统”和“前台应用&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;只需一个项目，既可同时跨端 pc、mobile。mobile 端是接近原生体验&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;下面就以&lt;code&gt;微信一起点菜&lt;/code&gt;为例： &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;前台点菜应用：&lt;a href="https://test.cabloy.com/?appKey=diancai-h5%3AappDianCaiWechatH5&amp;amp;appIsolate=true" rel="nofollow" target="_blank"&gt;https://test.cabloy.com/?appKey=diancai-h5%3AappDianCaiWechatH5&amp;amp;appIsolate=true&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;后台商家管理系统：&lt;a href="https://test.cabloy.com/" rel="nofollow" target="_blank"&gt;https://test.cabloy.com/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;以上两个链接可以分别用 pc 和 mobile 打开。&lt;code&gt;全新交互体验，全新架构设计，试过便知&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="截图"&gt;&lt;strong&gt;截图&lt;/strong&gt;&lt;/h2&gt;&lt;h3 id="后台商家管理系统"&gt;&lt;strong&gt;后台商家管理系统&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;PC 端&lt;/p&gt;

&lt;p&gt;&lt;img src="https://admin.cabloy.com/api/a/file/file/download/75cfb17801d8421f84a5595186cebba3.png" title="" alt="dian-back-pc"&gt;&lt;/p&gt;

&lt;p&gt;Mobile 端&lt;/p&gt;

&lt;p&gt;&lt;img src="https://admin.cabloy.com/api/a/file/file/download/72ccc0e5680f4370b77bc50e46d6e53b.png" title="" alt="dian-back-mobile"&gt;&lt;/p&gt;
&lt;h3 id="前台点菜应用"&gt;&lt;strong&gt;前台点菜应用&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;PC 端&lt;/p&gt;

&lt;p&gt;&lt;img src="https://admin.cabloy.com/api/a/file/file/download/61b84c84071a4242b2af17cd36de1950.png" title="" alt="dian-front-pc"&gt;&lt;/p&gt;

&lt;p&gt;Mobile 端&lt;/p&gt;

&lt;p&gt;&lt;img src="https://admin.cabloy.com/api/a/file/file/download/87302575434146e587d14b4048973e83.png" title="" alt="dian-front-mobile-1"&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://admin.cabloy.com/api/a/file/file/download/8c47557558794085b41a6c5b51f48bc0.png" title="" alt="dian-front-mobile-2"&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://admin.cabloy.com/api/a/file/file/download/7a348332b1e84177a8b1f9041f43a729.png" title="" alt="dian-front-mobile-3"&gt;&lt;/p&gt;</description>
      <author>zhennann</author>
      <pubDate>Thu, 27 Oct 2022 07:14:27 +0800</pubDate>
      <link>https://ruby-china.org/topics/42706</link>
      <guid>https://ruby-china.org/topics/42706</guid>
    </item>
    <item>
      <title>CabloyJS 4.22 重磅推出弹出式页面交互风格</title>
      <description>&lt;h2 id="升级说明"&gt;升级说明&lt;/h2&gt;
&lt;p&gt;我们知道 CabloyJS 提供了&lt;code&gt;pc=mobile+pad自适应布局&lt;/code&gt;机制，可以通过一套代码同时适配 mobile 端和 pc 端。基本思路就是优先适配 mobile 端，然后再把 mobile 端的交互体验带入 pc 端。因此，pc 端可以看作是许多 mobile 尺寸和 pad 尺寸页面组件的组合。在旧版本中，这些页面组件就像大大小小的手机和平板依次向右展开，可以称作&lt;code&gt;展开式&lt;/code&gt;交互风格&lt;/p&gt;

&lt;p&gt;新版本&lt;code&gt;CabloyJS 4.22&lt;/code&gt;提供了&lt;code&gt;弹出式&lt;/code&gt;交互风格。这样，用户可以根据 pc 电脑的尺寸大小和个人喜好，在这两个交互风格中随时切换&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;关于&lt;code&gt;pc=mobile+pad自适应布局&lt;/code&gt;机制的详细信息，请参见文档：&lt;a href="https://cabloy.com/zh-cn/articles/adaptive-layout.html" rel="nofollow" target="_blank" title=""&gt;自适应布局：pc = mobile + pad&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="意义"&gt;意义&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;CabloyJS 4.22&lt;/code&gt;的发布，重磅引入&lt;code&gt;弹出式&lt;/code&gt;页面交互风格，是及其主要的里程碑，将 CabloyJS 推入一个新的发展阶段&lt;/p&gt;

&lt;p&gt;是以为记 2022 年 6 月 30 日&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="预览"&gt;预览&lt;/h2&gt;&lt;h3 id="1. PC端（展开式）"&gt;1. PC 端（展开式）&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://admin.cabloy.com/api/a/file/file/download/487c6c81a8984da8822aaec7a5676c1e.gif" title="" alt="layoutpc-tile-zhcn"&gt;&lt;/p&gt;
&lt;h3 id="2. PC端（弹出式）"&gt;2. PC 端（弹出式）&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://admin.cabloy.com/api/a/file/file/download/95d88b18fd2347cd902507d44da81b1b.gif" title="" alt="layoutpc-popup-zhcn"&gt;&lt;/p&gt;
&lt;h3 id="3. Mobile端"&gt;3. Mobile 端&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://admin.cabloy.com/api/a/file/file/download/a0dd451e2bd74337ae026075aee9ca3d.gif" title="" alt="layoutmobile-zhcn"&gt;&lt;/p&gt;
&lt;h2 id="演示站点"&gt;演示站点&lt;/h2&gt;
&lt;p&gt;直接浏览 CabloyJS 的演示站点，增加更直观的感性认知&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;体验 CabloyJS 应对大型项目的&lt;code&gt;三驾马车&lt;/code&gt;：&lt;code&gt;套件&lt;/code&gt;、&lt;code&gt;模块&lt;/code&gt;、&lt;code&gt;App应用&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;体验与众不同的&lt;code&gt;pc=mobile+pad&lt;/code&gt;自适应布局风格，强烈推荐：分别用&lt;code&gt;PC&lt;/code&gt;和&lt;code&gt;Mobile&lt;/code&gt;单独体验演示站点。&lt;strong&gt;此言不虚，请您品鉴&lt;/strong&gt;！！！&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;演示站点：&lt;a href="https://test.cabloy.com/" rel="nofollow" target="_blank"&gt;https://test.cabloy.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;演示站点的二维码：
&lt;img src="https://admin.cabloy.com/api/a/file/file/download/d8cedc9dd14e4a10a06ba1627b6ed1a1.png" title="" alt="cabloy-test"&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="相关链接"&gt;&lt;strong&gt;相关链接&lt;/strong&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;文档：&lt;a href="https://cabloy.com/" rel="nofollow" target="_blank"&gt;https://cabloy.com/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GitHub: &lt;a href="https://github.com/zhennann/cabloy" rel="nofollow" target="_blank"&gt;https://github.com/zhennann/cabloy&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>zhennann</author>
      <pubDate>Thu, 30 Jun 2022 21:45:27 +0800</pubDate>
      <link>https://ruby-china.org/topics/42492</link>
      <guid>https://ruby-china.org/topics/42492</guid>
    </item>
    <item>
      <title>且看这个 Node 全栈框架，实现了个 Cli 终端引擎，可无限扩充命令集</title>
      <description>&lt;p&gt;&lt;img src="https://admin.cabloy.com/api/a/file/file/download/5af71e2c678040c0844289e78de83ebd.png" title="" alt="cli"&gt;&lt;/p&gt;
&lt;h2 id="背景介绍"&gt;背景介绍&lt;/h2&gt;
&lt;p&gt;一般而言，大多数框架都会提供&lt;code&gt;Cli终端工具&lt;/code&gt;，用于通过&lt;code&gt;命令行&lt;/code&gt;执行一些工具类脚本&lt;/p&gt;

&lt;p&gt;CabloyJS 提供的&lt;code&gt;Cli终端工具&lt;/code&gt;却与众不同。更确切的说，CabloyJS 提供的是&lt;code&gt;Cli终端引擎&lt;/code&gt;，由一套&lt;code&gt;Cli终端运行机制&lt;/code&gt;+&lt;code&gt;众多命令集&lt;/code&gt;组成。CabloyJS 是一个基于模块化体系的全栈框架，因此，命令集也由具体的模块提供。因此，我们可以通过安装各种模块来动态添加可以运行的命令集，从而让 CabloyJS 的&lt;code&gt;Cli终端&lt;/code&gt;变得无比强大、功能也无比丰富&lt;/p&gt;
&lt;h2 id="特点"&gt;特点&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;可无限扩充&lt;/code&gt;：由于是通过安装模块来提供命令集，因此可以无限扩充&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;同时支持&lt;code&gt;开发环境&lt;/code&gt;和&lt;code&gt;生产环境&lt;/code&gt;：当我们在命令行输入指令后，系统会自动调用后端服务的 API 接口，执行具体的脚本逻辑。而后端服务既可以是在本地运行的开发服务，也可以是在远程运行的生成环境&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;简化&lt;code&gt;系统管理&lt;/code&gt;和&lt;code&gt;运维工作&lt;/code&gt;：正是由于&lt;code&gt;Cli终端&lt;/code&gt;可以用于生产环境，所以许多后台管理工作都可以通过 API 接口暴露出来，从而可以直接通过命令行进行驱动&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;完善的&lt;code&gt;权限控制&lt;/code&gt;：后端服务暴露的所有 API 接口都使用资源授权机制进行控制，&lt;code&gt;Cli终端&lt;/code&gt;通过&lt;code&gt;开放认证Token&lt;/code&gt;进行受控访问&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="如何使用"&gt;如何使用&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Cli终端&lt;/code&gt;的使用规范如下：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm run cli &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;args] &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;options]
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="- command"&gt;- command&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;命名规范：&lt;/code&gt;由于 command 由业务模块提供，为了进一步规范管理，在业务模块内部还将提供的 commands 进行分组。因此，command 命名规范如下：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;moduleName:groupName:commandName
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;比如，模块&lt;code&gt;a-clibooster&lt;/code&gt;提供了一个分组&lt;code&gt;create&lt;/code&gt;，分组&lt;code&gt;create&lt;/code&gt;提供了一个&lt;code&gt;module&lt;/code&gt; 命令，那么，完整的 command 命名就是：&lt;code&gt;a-clibooster:create:module&lt;/code&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;特殊约定：&lt;/code&gt;为了简化 command 的输入，特别做了如下约定：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;如果模块名称是&lt;code&gt;a-clibooster&lt;/code&gt;，则可以不输入&lt;/li&gt;
&lt;li&gt;如果分组名称是&lt;code&gt;default&lt;/code&gt;，则可以不输入&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;举例如下：&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;全称&lt;/th&gt;
&lt;th&gt;简称&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;a-clibooster:default:list&lt;/td&gt;
&lt;td&gt;::list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;a-clibooster:create:module&lt;/td&gt;
&lt;td&gt;:create:module&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;test-party:default:demo&lt;/td&gt;
&lt;td&gt;test-party::demo&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h3 id="- args"&gt;- args&lt;/h3&gt;
&lt;p&gt;命令后是否可以跟随参数，跟随多少参数，由具体的命令决定，例如：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 安装模块test-flow和test-note&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npm run cli :store:sync test-flow test-note
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="- options"&gt;- options&lt;/h3&gt;
&lt;p&gt;命令后是否可以跟随选项，跟随多少选项，也由具体的命令决定，例如：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 向套件test-suite1添加模块test-module1&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npm run cli :create:module test-module1 &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--suite&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;test-suite1
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="- help"&gt;- help&lt;/h3&gt;
&lt;p&gt;如果我们不知道如何使用某个命令，可以直接打印出帮助信息，例如&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm run cli :create:module &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="- version"&gt;- version&lt;/h3&gt;
&lt;p&gt;还可查询某个命令的版本号，例如：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm run cli :create:module &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="- 列出命令"&gt;- 列出命令&lt;/h3&gt;
&lt;p&gt;如果要查询当前系统一共支持多少命令，可以按如下操作：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 列出所有命令&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npm run cli ::list
&lt;span class="c"&gt;# 列出模块test-party提供的命令&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npm run cli ::list  &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--module&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;test-party
&lt;span class="c"&gt;# 列出模块a-clibooster内create分组提供的命令&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npm run cli ::list  &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--module&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;a-clibooster &lt;span class="nt"&gt;--group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;create
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="内置命令"&gt;内置命令&lt;/h2&gt;
&lt;p&gt;CabloyJS 通过模块&lt;code&gt;a-clibooster&lt;/code&gt;内置了大量常用的命令，具体如下：&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;::list&lt;/td&gt;
&lt;td&gt;列出所有命令&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;:token:add&lt;/td&gt;
&lt;td&gt;添加开放认证 Token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;:token:delete&lt;/td&gt;
&lt;td&gt;删除开放认证 Token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;:token:list&lt;/td&gt;
&lt;td&gt;列出所有开放认证 Token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;:tools:babel&lt;/td&gt;
&lt;td&gt;编译 JS 文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;:tools:icons&lt;/td&gt;
&lt;td&gt;处理图标模块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;:create:suite&lt;/td&gt;
&lt;td&gt;新建套件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;:create:module&lt;/td&gt;
&lt;td&gt;新建模块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;:create:atom&lt;/td&gt;
&lt;td&gt;新建原子类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;:create:controller&lt;/td&gt;
&lt;td&gt;新建控制器（包括 Route、Controller、Service 一套文件）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;:store:sync&lt;/td&gt;
&lt;td&gt;安装来自 Cabloy 商店的&lt;code&gt;套件/模块&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;:store:publish&lt;/td&gt;
&lt;td&gt;将本地开发的&lt;code&gt;套件/模块&lt;/code&gt;发布到 Cabloy 商店&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h2 id="简写命令"&gt;简写命令&lt;/h2&gt;
&lt;p&gt;CabloyJS 还提供了更多简写命令，可以亲自体验一番，一定会显著提升开发效率&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm run cli
&lt;span class="nv"&gt;$ &lt;/span&gt;npm run cli :
&lt;span class="nv"&gt;$ &lt;/span&gt;npm run cli ::
&lt;span class="nv"&gt;$ &lt;/span&gt;npm run cli :default:
&lt;span class="nv"&gt;$ &lt;/span&gt;npm run cli :create:
&lt;span class="nv"&gt;$ &lt;/span&gt;npm run cli test-party:
&lt;span class="nv"&gt;$ &lt;/span&gt;npm run cli test-party::
&lt;span class="nv"&gt;$ &lt;/span&gt;npm run cli test-party:default:
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="如何创建Cli命令"&gt;&lt;strong&gt;如何创建 Cli 命令&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;如果我们想创建自己的 Cli 命令，也非常简便，参见文档：&lt;a href="https://cabloy.com/zh-cn/articles/cli-create.html" rel="nofollow" target="_blank" title=""&gt;如何创建 Cli 命令&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="相关链接"&gt;&lt;strong&gt;相关链接&lt;/strong&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;文档：&lt;a href="https://cabloy.com/" rel="nofollow" target="_blank"&gt;https://cabloy.com/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GitHub: &lt;a href="https://github.com/zhennann/cabloy" rel="nofollow" target="_blank"&gt;https://github.com/zhennann/cabloy&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>zhennann</author>
      <pubDate>Wed, 08 Jun 2022 19:21:27 +0800</pubDate>
      <link>https://ruby-china.org/topics/42443</link>
      <guid>https://ruby-china.org/topics/42443</guid>
    </item>
    <item>
      <title>看看 CabloyJS 是如何实现编辑页面脏标记的</title>
      <description>&lt;h2 id="应用场景"&gt;应用场景&lt;/h2&gt;
&lt;p&gt;我们在使用 Word、Excel 时，当修改了内容之后在标题栏会显示&lt;code&gt;脏标记&lt;/code&gt;，从而可以明确的告知用户内容有变动。此外，如果在没有保存的情况下关闭窗口，系统会弹出提示框，让用户选择是否放弃修改&lt;/p&gt;

&lt;p&gt;那么，在 Web 系统中，我们是否也可以支持这种&lt;code&gt;脏标记&lt;/code&gt;机制呢？&lt;/p&gt;

&lt;p&gt;答案是肯定的，CabloyJS 就提供了这种&lt;code&gt;脏标记&lt;/code&gt;机制，而且 CabloyJS 内置的所有可以编辑的页面都支持了这种效果&lt;/p&gt;
&lt;h2 id="效果演示"&gt;效果演示&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://admin.cabloy.com/api/a/file/file/download/49ef58bcaf8148f2b0ef9cc723a9e063.gif" title="" alt="pagedirty-zhcn"&gt;&lt;/p&gt;
&lt;h2 id="如何使用"&gt;如何使用&lt;/h2&gt;
&lt;p&gt;下面以模块&lt;code&gt;test-party&lt;/code&gt;为例，演示如何向编辑页面添加&lt;code&gt;脏标记&lt;/code&gt;机制。演示代码都在下面的文件中：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;src/module-vendor/test-party/front/src/kitchen-sink/pages/markdownEditor.vue&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="1. 引入脏标记组件"&gt;1. 引入脏标记组件&lt;/h3&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Vue&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ebPageDirty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a-components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mixins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ebPageDirty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;mixins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ebPageDirty&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ebPageDirty&lt;/code&gt;是由模块&lt;code&gt;a-components&lt;/code&gt;提供的组件，放入当前页面组件的&lt;code&gt;mixins&lt;/code&gt;中&lt;/p&gt;
&lt;h3 id="2. Title显示脏标记"&gt;2. Title 显示脏标记&lt;/h3&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;eb&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;eb&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;navbar&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;page_title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;eb&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;back&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Back&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/eb-navbar&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/eb-page&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/template&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Vue&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ebPageDirty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a-components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mixins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ebPageDirty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;mixins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ebPageDirty&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;page_title&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Markdown Editor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;page_getDirtyTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;行 16：获取国际化处理后的标题&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;行 17：调用&lt;code&gt;page_getDirtyTitle&lt;/code&gt;方法向&lt;code&gt;title&lt;/code&gt;动态添加脏标记&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;行 3：动态绑定&lt;code&gt;page_title&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3. 修改脏标记"&gt;3. 修改脏标记&lt;/h3&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;onPerformSave&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkContent&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Saved&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;page_setDirty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;onInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;page_setDirty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;行 13：当用户输入内容时，调用&lt;code&gt;page_setDirty&lt;/code&gt;方法将脏标记置为&lt;code&gt;true&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;行 8：当保存内容时，调用&lt;code&gt;page_setDirty&lt;/code&gt;方法将脏标记置为&lt;code&gt;false&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="组件ebPageDirty的方法"&gt;组件 ebPageDirty 的方法&lt;/h2&gt;&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;page_getDirtyTitle&lt;/td&gt;
&lt;td&gt;向 Title 动态添加脏标记&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;page_setDirty&lt;/td&gt;
&lt;td&gt;修改脏标记&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h2 id="相关链接"&gt;&lt;strong&gt;相关链接&lt;/strong&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;官网：&lt;a href="https://cabloy.com/" rel="nofollow" target="_blank"&gt;https://cabloy.com/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GitHub: &lt;a href="https://github.com/zhennann/cabloy" rel="nofollow" target="_blank"&gt;https://github.com/zhennann/cabloy&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>zhennann</author>
      <pubDate>Tue, 18 Jan 2022 22:06:04 +0800</pubDate>
      <link>https://ruby-china.org/topics/42093</link>
      <guid>https://ruby-china.org/topics/42093</guid>
    </item>
    <item>
      <title>CabloyJS 4.12 震撼发布，及新版教程尝鲜</title>
      <description>&lt;h2 id="引言"&gt;引言&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;凡是可以用 JavaScript 来写的应用，最终都会用 JavaScript 来写 | Atwood 定律&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;目前市面上出现的大多数与 NodeJS 相关的框架，基本都将 NodeJS 定位在&lt;code&gt;工具层&lt;/code&gt;、&lt;code&gt;中间层&lt;/code&gt;、&lt;code&gt;代理层&lt;/code&gt;，很少在业务层面进行深耕，认为这是 JAVA 的领域，NodeJS 不适合。这种思潮明显是与&lt;code&gt;Atwood 定律&lt;/code&gt;相悖的&lt;/p&gt;

&lt;p&gt;如果您想感受&lt;code&gt;与众不同&lt;/code&gt;的 NodeJS 全栈开发体验，一定要试试&lt;code&gt;自带工作流引擎&lt;/code&gt;的 CabloyJS 全栈开源框架。为了提升业务层面的开发效率和开发体验，CabloyJS 在前端和后端均提供了大量实用的工具和组件&lt;/p&gt;
&lt;h2 id="CabloyJS 4.12"&gt;CabloyJS 4.12&lt;/h2&gt;
&lt;p&gt;CabloyJS 从&lt;code&gt;4.11&lt;/code&gt;升级到&lt;code&gt;4.12&lt;/code&gt;，历时 5 个月，Github 提交数从&lt;code&gt;6000+&lt;/code&gt;一路干到&lt;code&gt;8000+&lt;/code&gt;，这次的功能更新绝对是诚意满满，一次爽到爆&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;新增特性&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;数据：增加 &lt;code&gt;simple&lt;/code&gt; 模式&lt;/li&gt;
&lt;li&gt;默认的业务数据都具有生命周期，即三个阶段：&lt;code&gt;草稿&lt;/code&gt;、&lt;code&gt;正式&lt;/code&gt;、&lt;code&gt;历史&lt;/code&gt;。&lt;code&gt;草稿&lt;/code&gt;到&lt;code&gt;正式&lt;/code&gt;之间还可以加入&lt;code&gt;审批流&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;新增的&lt;code&gt;simple&lt;/code&gt;模式，就是可以简化业务数据的生命周期，只保留&lt;code&gt;正式&lt;/code&gt;版本，从而满足一些简单业务数据的管理&lt;/li&gt;
&lt;li&gt;脏标记机制：所有 Form 表单均支持&lt;code&gt;脏标记&lt;/code&gt;机制。当有字段值变动时，Form 表单标题就会有醒目提示，并且当关闭当前表单页面时，也会提示用户&lt;code&gt;表单数据没有保存&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;测试：增加新的测试模块&lt;code&gt;test-note&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;演示&lt;code&gt;数据列表&lt;/code&gt;的布局切换：列表/卡片/表格&lt;/li&gt;
&lt;li&gt;演示&lt;code&gt;数据条目&lt;/code&gt;的布局切换：信息/正文 (Markdown)&lt;/li&gt;
&lt;li&gt;演示如何增加&lt;code&gt;便签&lt;/code&gt;部件，从而可以在&lt;code&gt;仪表板&lt;/code&gt;中直接编辑和查看便签数据&lt;/li&gt;
&lt;li&gt;测试：&lt;code&gt;test-party&lt;/code&gt; 模块&lt;/li&gt;
&lt;li&gt;演示如何增加&lt;code&gt;简单聊天&lt;/code&gt;部件，从而可以在&lt;code&gt;仪表板&lt;/code&gt;中进行直接的聊天互动&lt;/li&gt;
&lt;li&gt;工作流引擎：&lt;/li&gt;
&lt;li&gt;增加&lt;code&gt;转办&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;增加&lt;code&gt;代办&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;工作流引擎：&lt;/li&gt;
&lt;li&gt;增加&lt;code&gt;行为&lt;/code&gt;机制：从而实现与 activiti 中&lt;code&gt;边界事件&lt;/code&gt;所对应的应用场景

&lt;ul&gt;
&lt;li&gt;一个&lt;code&gt;节点&lt;/code&gt;可以附加多个&lt;code&gt;行为&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;行为&lt;/code&gt;可以指定专属的&lt;code&gt;边&lt;/code&gt;，从而进行&lt;code&gt;节点&lt;/code&gt;的迁移&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;增加一个具体的&lt;code&gt;行为&lt;/code&gt;实现：&lt;code&gt;超时处理&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;可以指定某个&lt;code&gt;审批节点&lt;/code&gt;的超时行为，一旦超时就跳转到指定的流程节点&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;工作流引擎：&lt;/li&gt;
&lt;li&gt;增加&lt;code&gt;网关节点&lt;/code&gt;，包括：

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;排他网关&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;并行网关&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;包含网关&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;数据字典：&lt;/li&gt;
&lt;li&gt;支持版本控制&lt;/li&gt;
&lt;li&gt;支持对字典单独授权&lt;/li&gt;
&lt;li&gt;支持多级树形字典&lt;/li&gt;
&lt;li&gt;内置：美国城市区划、中国城市区划&lt;/li&gt;
&lt;li&gt;通用的后端逻辑处理&lt;/li&gt;
&lt;li&gt;通用的前端渲染组件&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;增强&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Markdown：实现了一个&lt;code&gt;所见即所得&lt;/code&gt;的&lt;code&gt;Markdown富文本编辑器&lt;/code&gt;，&lt;strong&gt;绝对好用，一定要体验一下&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;JSON：重构了带格式化的&lt;code&gt;JSON编辑器&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="文档与教程："&gt;文档与教程：&lt;/h2&gt;
&lt;p&gt;伴随着 CabloyJS 4.12 新版功能的推出，还全新制作了一套教程。为了体现 CabloyJS&lt;code&gt;低代码的开箱即用&lt;/code&gt;和&lt;code&gt;专业代码的灵活定制&lt;/code&gt;，教程也由浅到深相应的分为几个部分：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://cabloy.com/zh-cn/articles/c8f7c13d56a94b75b6bbf84f0471ef24.html" rel="nofollow" target="_blank" title=""&gt;教程：业务表单与审批流&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://cabloy.com/zh-cn/articles/b0d03442aeac43eda2ef16556ad203d1.html" rel="nofollow" target="_blank" title=""&gt;教程：博客&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://cabloy.com/zh-cn/articles/85817f4eda93449fab67bc9bf0633633.html" rel="nofollow" target="_blank" title=""&gt;教程：文档&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://cabloy.com/zh-cn/articles/85817f4eda93449fab67bc9bf0633633.html" rel="nofollow" target="_blank" title=""&gt;教程：社区&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://cabloy.com/zh-cn/articles/98c7ad5fb03748e29a69236a5418400b.html" rel="nofollow" target="_blank" title=""&gt;教程：第三方平台&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://cabloy.com/zh-cn/articles/532e6433c1154a7587ae9d3ccb1242fc.html" rel="nofollow" target="_blank" title=""&gt;进阶&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="演示站点"&gt;演示站点&lt;/h2&gt;
&lt;p&gt;通过这套教程，可以让我们更快的了解 CabloyJS 的开发风格，以及可以用来做哪些事情。欢迎大家尝鲜、拍砖&lt;/p&gt;

&lt;p&gt;也可以在阅读教程的同时，直接浏览 CabloyJS 的演示站点，增加更直观的感性认知&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;演示站点：&lt;a href="https://test.cabloy.com/" rel="nofollow" target="_blank"&gt;https://test.cabloy.com/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;为了体验与众不同的&lt;code&gt;pc=mobile+pad&lt;/code&gt;自适应布局风格，一定要分别用&lt;code&gt;PC&lt;/code&gt;和&lt;code&gt;Mobile&lt;/code&gt;单独体验演示站点。&lt;strong&gt;此言不虚，请您品鉴&lt;/strong&gt;！！！&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;演示站点的二维码：
&lt;img src="https://admin.cabloy.com/api/a/file/file/download/d8cedc9dd14e4a10a06ba1627b6ed1a1.png" title="" alt="cabloy-test"&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>zhennann</author>
      <pubDate>Mon, 29 Nov 2021 22:15:21 +0800</pubDate>
      <link>https://ruby-china.org/topics/41937</link>
      <guid>https://ruby-china.org/topics/41937</guid>
    </item>
    <item>
      <title>CabloyJS 基于 EggJS 实现的模块编译与发布</title>
      <description>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;现在，&lt;code&gt;EggJS&lt;/code&gt;被许多开发团队所采用。有的团队基于&lt;code&gt;商业知识产权&lt;/code&gt;的考量，往往会提一个问题：是否可以把&lt;code&gt;EggJS&lt;/code&gt;当中的代码编译打包，然后再把代码丑化？&lt;/p&gt;
&lt;h2 id="模块编译的机制"&gt;模块编译的机制&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;EggJS&lt;/code&gt;为何不能便利的实现&lt;code&gt;编译&lt;/code&gt;的特性？&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在&lt;code&gt;EggJS&lt;/code&gt;中，代码文件都是通过&lt;code&gt;约定代码位置&lt;/code&gt;的方式组织并加载的。也就是说，代码文件都放置在约定的目录结构当中，EggJS 在启动系统时，在约定的位置扫描加载约定的代码文件。因此，在这种机制下，不能便利的实现编译特性&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CabloyJS&lt;/code&gt;是如何实现编译特性的？&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;EggJS&lt;/code&gt;作为企业级框架提供了足够灵活的机制，比如，允许&lt;code&gt;上层框架&lt;/code&gt;提供自定义的&lt;code&gt;加载器&lt;/code&gt;。&lt;code&gt;CabloyJS&lt;/code&gt;就是基于&lt;code&gt;EggJS&lt;/code&gt;的这种机制实现了一套自定义的加载器&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;CabloyJS&lt;/code&gt;中，模块当中的代码文件都是通过&lt;code&gt;require&lt;/code&gt;的方式&lt;code&gt;显式&lt;/code&gt;组织并加载的。这种加载机制为模块内部的源码文件提供了清晰的调用依赖关系，因此我们就可以采用&lt;code&gt;Webpack&lt;/code&gt;完成编译打包，以及丑化代码的工作&lt;/p&gt;
&lt;h2 id="模块编译的意义"&gt;模块编译的意义&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;模块复用、构建生态&lt;/code&gt;：模块可单独编译，从而可以单独发布、单独部署，单独升级，从而促进 CabloyJS 整个生态圈的繁荣，进一步加速实际业务的开发&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;知识产权&lt;/code&gt;：模块可单独编译，也可以满足&lt;code&gt;保护商业代码&lt;/code&gt;的需求&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="如何编译模块"&gt;如何编译模块&lt;/h2&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 进入模块所在目录&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/module
&lt;span class="c"&gt;# 编译模块前端代码&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npm run build:front
&lt;span class="c"&gt;# 编译模块后端代码&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npm run build:backend
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="编译参数"&gt;编译参数&lt;/h2&gt;
&lt;p&gt;所有模块均采用缺省的编译参数，当然也可以提供自定义的编译参数，以模块&lt;code&gt;test-party&lt;/code&gt;为例：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;src/module/test-party/build/config.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;front&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;productionSourceMap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;uglify&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;productionSourceMap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;uglify&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;productionSourceMap&lt;/td&gt;
&lt;td&gt;是否生成&lt;code&gt;SourceMap&lt;/code&gt;文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;uglify&lt;/td&gt;
&lt;td&gt;是否&lt;code&gt;uglify&lt;/code&gt;代码&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h2 id="模块加载约定"&gt;模块加载约定&lt;/h2&gt;
&lt;p&gt;在模块目录下，既有&lt;code&gt;src&lt;/code&gt;源代码文件，也有&lt;code&gt;dist&lt;/code&gt;打包文件。那么什么时候加载&lt;code&gt;src&lt;/code&gt;，什么时候加载&lt;code&gt;dist&lt;/code&gt;呢？&lt;/p&gt;

&lt;p&gt;模块有两类：&lt;code&gt;全局模块&lt;/code&gt;和&lt;code&gt;局部模块&lt;/code&gt;。这两类模块有不同的加载约定：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;全局模块&lt;/code&gt;：位于项目的&lt;code&gt;node_modules&lt;/code&gt;目录中，系统总是加载&lt;code&gt;全局模块&lt;/code&gt;的&lt;code&gt;dist&lt;/code&gt;打包文件&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;局部模块&lt;/code&gt;：位于项目的&lt;code&gt;src/module&lt;/code&gt;目录中，系统优先查找&lt;code&gt;src&lt;/code&gt;目录并加载模块源码，如果没有找到则加载&lt;code&gt;局部模块&lt;/code&gt;的&lt;code&gt;dist&lt;/code&gt;打包文件&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;理解了&lt;code&gt;全局模块&lt;/code&gt;与&lt;code&gt;局部模块&lt;/code&gt;的代码加载约定，在将项目部署在生产环境时有利于做出正确的配置（主要的诉求就是：如何保护&lt;code&gt;商业代码&lt;/code&gt;）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="最佳实践（模块前端）"&gt;最佳实践（模块前端）&lt;/h2&gt;
&lt;p&gt;在部署时，项目前端总是要进行&lt;code&gt;整体编译&lt;/code&gt;，把所有&lt;code&gt;全局模块&lt;/code&gt;和&lt;code&gt;局部模块&lt;/code&gt;的前端源码和前端资源都打包，然后输出到项目的&lt;code&gt;dist&lt;/code&gt;目录&lt;/p&gt;

&lt;p&gt;如果模块作为&lt;code&gt;局部模块&lt;/code&gt;而存在，则不需要考虑模块前端的编译环节&lt;/p&gt;

&lt;p&gt;如果模块要发布为&lt;code&gt;全局模块&lt;/code&gt;，则必须先进行模块前端的编译&lt;/p&gt;
&lt;h2 id="最佳实践（模块后端）"&gt;最佳实践（模块后端）&lt;/h2&gt;&lt;h3 id="1. 不进行模块编译"&gt;1. 不进行模块编译&lt;/h3&gt;
&lt;p&gt;如果没有&lt;code&gt;保护商业代码&lt;/code&gt;的需求，那么就不用考虑模块编译的环节。在部署时，直接作为&lt;code&gt;局部模块&lt;/code&gt;加载源码运行即可&lt;/p&gt;
&lt;h3 id="2. 进行模块编译"&gt;2. 进行模块编译&lt;/h3&gt;
&lt;p&gt;如果要进行模块编译，那么在部署时有两个选择：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;作为局部模块

&lt;ol&gt;
&lt;li&gt;模块仍然位于项目的&lt;code&gt;src/module&lt;/code&gt;目录&lt;/li&gt;
&lt;li&gt;将模块编译后，在生产环境删除模块的&lt;code&gt;src&lt;/code&gt;源码目录即可&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;作为全局模块

&lt;ol&gt;
&lt;li&gt;将模块编译后，发布至公司的私有仓库&lt;/li&gt;
&lt;li&gt;在项目中将模块作为&lt;code&gt;全局模块&lt;/code&gt;安装至&lt;code&gt;node_modules&lt;/code&gt;目录&lt;/li&gt;
&lt;li&gt;如果没有私有仓库，也可以采用&lt;code&gt;npm link&lt;/code&gt;机制安装为&lt;code&gt;全局模块&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="模块发布"&gt;模块发布&lt;/h2&gt;
&lt;p&gt;当项目中的模块代码稳定后，可以将模块公开发布，贡献到开源社区。也可以在公司内部建立 npm 私有仓库，然后把模块发布到私有仓库，形成公司资产，便于重复使用&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/module
&lt;span class="nv"&gt;$ &lt;/span&gt;npm run build:front
&lt;span class="nv"&gt;$ &lt;/span&gt;npm run build:backend
&lt;span class="nv"&gt;$ &lt;/span&gt;npm publish
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;由于发布到 npm 仓库的模块将作为&lt;code&gt;全局模块&lt;/code&gt;来使用，因此需要先编译模块的前端和后端&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="效果图（模块后端编译）"&gt;效果图（模块后端编译）&lt;/h2&gt;&lt;h3 id="编译之前的源码结构"&gt;编译之前的源码结构&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://admin.cabloy.com/api/a/file/file/download/736d6225257a4a5babc1a2e709bfd937.png" title="" alt="build-backend-before"&gt;&lt;/p&gt;
&lt;h3 id="编译之后的输出文件"&gt;编译之后的输出文件&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://admin.cabloy.com/api/a/file/file/download/0bb44e0e79184c1cb86856d21bfe4b9b.png" title="" alt="build-backend-after"&gt;&lt;/p&gt;
&lt;h2 id="相关链接"&gt;相关链接&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/" rel="nofollow" target="_blank" title=""&gt;官网：https://cabloy.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/zhennann/cabloy" rel="nofollow" target="_blank" title=""&gt;GitHub: https://github.com/zhennann/cabloy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>zhennann</author>
      <pubDate>Sun, 04 Jul 2021 16:43:55 +0800</pubDate>
      <link>https://ruby-china.org/topics/41444</link>
      <guid>https://ruby-china.org/topics/41444</guid>
    </item>
    <item>
      <title>CabloyJS 实现了一款基于 X6 的工作流可视化编辑器</title>
      <description>&lt;h2 id="介绍"&gt;介绍&lt;/h2&gt;
&lt;p&gt;文档&lt;a href="https://cabloy.com/zh-cn/articles/ddda3ea8638d4d2b88fb57df7ed67107.html" rel="nofollow" target="_blank" title=""&gt;演示：CMS 审批工作流&lt;/a&gt;演示了如何通过&lt;code&gt;JSON&lt;/code&gt;来直接创建一个工作流定义，通常用于为具体的业务数据生成&lt;code&gt;预定义&lt;/code&gt;或&lt;code&gt;内置&lt;/code&gt;审批工作流的场景&lt;/p&gt;

&lt;p&gt;CabloyJS 4.8.0 采用&lt;a href="https://antv-x6.gitee.io" rel="nofollow" target="_blank" title=""&gt;X6 图编辑引擎&lt;/a&gt;实现了一款&lt;code&gt;工作流可视化编辑器&lt;/code&gt;，从而可以在系统运行过程中，&lt;code&gt;随时、动态、可视化&lt;/code&gt;的创建工作流定义&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;特别要感谢&lt;code&gt;X6&lt;/code&gt;提供了非常强大的图编辑引擎，而且也提供了非常灵活的开发接口和扩展接口。基于 X6 的有力支撑，工作流可视化编辑器的复杂部分反而不再是图编辑本身了，而是各个节点的属性编辑表单，以及表单背后的 API 接口的配套支持&lt;/p&gt;

&lt;p&gt;如果大家对&lt;a href="https://antv-x6.gitee.io" rel="nofollow" target="_blank" title=""&gt;X6 图编辑引擎&lt;/a&gt;有兴趣，一定要看看 CabloyJS 4.8.0，这会是一个非常清晰的入门案例&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="演示"&gt;演示&lt;/h2&gt;&lt;h3 id="Mobile"&gt;Mobile&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/zhennann/f916f61c-cf29-4c4e-919c-ec8810a2b9d5.gif!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h3 id="PC"&gt;PC&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/zhennann/60638433-eaa9-47ec-8817-460c6c1032c5.gif!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="相关链接"&gt;相关链接&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/" rel="nofollow" target="_blank" title=""&gt;官网：https://cabloy.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/zhennann/cabloy" rel="nofollow" target="_blank" title=""&gt;GitHub: https://github.com/zhennann/cabloy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>zhennann</author>
      <pubDate>Mon, 26 Apr 2021 08:44:33 +0800</pubDate>
      <link>https://ruby-china.org/topics/41192</link>
      <guid>https://ruby-china.org/topics/41192</guid>
    </item>
    <item>
      <title>如何在 Uniapp 中访问 CabloyJS 后端管理系统 API</title>
      <description>&lt;h2 id="介绍"&gt;介绍&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;CabloyJS&lt;/code&gt;是一款免费开源的 NodeJS 全栈开发框架，采用前后端分离设计，具备开箱即用的后台管理系统&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Cabloy-SDK&lt;/code&gt;是专门为 Uniapp 应用量身定制的前端 SDK，用于便捷的访问 CabloyJS 提供的所有 API 接口，让 Uniapp 前端开发再无&lt;code&gt;后顾之忧&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="特性"&gt;特性&lt;/h2&gt;
&lt;p&gt;各个平台的小程序后端 API 系统，最复杂的就是账号体系对接。CabloyJS 提供了一个模块化的生态，需要开发什么平台下的小程序应用，只需安装相应的模块即可&lt;/p&gt;

&lt;p&gt;目前&lt;code&gt;Cabloy-SDK&lt;/code&gt;支持以下平台小程序的开箱即用：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/wechat-introduce.html" rel="nofollow" target="_blank" title=""&gt;微信小程序&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/wxwork-introduce.html" rel="nofollow" target="_blank" title=""&gt;企业微信小程序&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/dingtalk-introduce.html" rel="nofollow" target="_blank" title=""&gt;钉钉小程序&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="如何使用 - 前端"&gt;如何使用 - 前端&lt;/h2&gt;&lt;h3 id="1. 导入插件"&gt;1. 导入插件&lt;/h3&gt;
&lt;p&gt;进入&lt;a href="https://ext.dcloud.net.cn/plugin?id=3997" rel="nofollow" target="_blank" title=""&gt;Uniapp 插件页面&lt;/a&gt;，点击按钮&lt;code&gt;使用HBuilderX导入插件&lt;/code&gt;，将&lt;code&gt;Cabloy-SDK&lt;/code&gt;插件导入 Uniapp 项目中&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;强烈建议&lt;code&gt;下载示例项目&lt;/code&gt;查看插件的基本用法&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="2. 修改main.js"&gt;2. 修改 main.js&lt;/h3&gt;
&lt;p&gt;在&lt;code&gt;main.js&lt;/code&gt;文件中添加如下代码：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Vue&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./App&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Cabloy&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./js_sdk/cabloy-sdk/main.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// 初始化cabloy&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cabloyOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-us&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://yourdomain.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$cabloy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cabloy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cabloyOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// 登录&lt;/span&gt;
&lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$cabloy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 由于 login 是网络请求，可能会在 Page.onLoad 之后才返回&lt;/span&gt;
  &lt;span class="c1"&gt;// 所以此处加入 callback 以防止这种情况&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$cabloy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;__loginReadyCallback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$cabloy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;__loginReadyCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;productionTip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mpType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$mount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;首先要初始化一个&lt;code&gt;cabloy&lt;/code&gt;实例，并保存至 Vue.prototype.$cabloy，便于在所有 Vue 组件中引用&lt;/li&gt;
&lt;li&gt;其次调用&lt;code&gt;cabloy.util.login&lt;/code&gt;进行登录&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;cabloyOptions&lt;/li&gt;
&lt;/ul&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;base.scene&lt;/td&gt;
&lt;td&gt;小程序场景名，默认为&lt;code&gt;default&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;base.locale&lt;/td&gt;
&lt;td&gt;前端默认使用的语言&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;api.baseURL&lt;/td&gt;
&lt;td&gt;后端服务的 API 地址&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;base.scene&lt;/code&gt;：CabloyJS 后端可以支持创建多个小程序，前端通过此参数设置要对接的小程序场景名，默认为&lt;code&gt;default&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="3. API清单"&gt;3. API 清单&lt;/h3&gt;
&lt;p&gt;插件&lt;code&gt;Cabloy-SDK&lt;/code&gt;主要提供了以下 API 组件&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cabloy.com/zh-cn/articles/ca6ac903cd8b4b4fb5e3c2199975c3eb.html" rel="nofollow" target="_blank" title=""&gt;cabloy&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;根对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cabloy.com/zh-cn/articles/df78d29252d749bbb59ad95255130470.html" rel="nofollow" target="_blank" title=""&gt;cabloy.util&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;工具函数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cabloy.com/zh-cn/articles/4ae53800c6604cb192dd9587b56fd85a.html" rel="nofollow" target="_blank" title=""&gt;cabloy.api&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;访问后端 API 接口&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cabloy.com/zh-cn/articles/f3b7901db46e4c16b5840452528926b0.html" rel="nofollow" target="_blank" title=""&gt;cabloy.data&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;状态数据存储&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cabloy.com/zh-cn/articles/6c216ad813214aca84cdcc83b3079c25.html" rel="nofollow" target="_blank" title=""&gt;cabloy.config&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;配置参数&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h2 id="如何使用 - 后端"&gt;如何使用 - 后端&lt;/h2&gt;
&lt;p&gt;CabloyJS 提供了一个模块化的生态，需要开发什么平台下的小程序应用，只需安装相应的模块即可&lt;/p&gt;
&lt;h3 id="1. 微信小程序"&gt;1. &lt;a href="https://cabloy.com/zh-cn/articles/wechat-introduce.html" rel="nofollow" target="_blank" title=""&gt;微信小程序&lt;/a&gt;
&lt;/h3&gt;&lt;h3 id="2. 企业微信小程序"&gt;2. &lt;a href="https://cabloy.com/zh-cn/articles/wxwork-introduce.html" rel="nofollow" target="_blank" title=""&gt;企业微信小程序&lt;/a&gt;
&lt;/h3&gt;&lt;h3 id="3. 钉钉小程序"&gt;3. &lt;a href="https://cabloy.com/zh-cn/articles/dingtalk-introduce.html" rel="nofollow" target="_blank" title=""&gt;钉钉小程序&lt;/a&gt;
&lt;/h3&gt;&lt;h2 id="相关链接"&gt;相关链接&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/" rel="nofollow" target="_blank" title=""&gt;官网：https://cabloy.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/zhennann/cabloy" rel="nofollow" target="_blank" title=""&gt;GitHub: https://github.com/zhennann/cabloy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/uniapp-introduce.html" rel="nofollow" target="_blank" title=""&gt;Cabloy-Uniapp 开发文档&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>zhennann</author>
      <pubDate>Sun, 24 Jan 2021 20:31:30 +0800</pubDate>
      <link>https://ruby-china.org/topics/40849</link>
      <guid>https://ruby-china.org/topics/40849</guid>
    </item>
    <item>
      <title>CabloyJS 自带工作流引擎的文档清单</title>
      <description>&lt;h2 id="文档清单"&gt;文档清单&lt;/h2&gt;
&lt;p&gt;CabloyJS 自带&lt;code&gt;工作流引擎&lt;/code&gt;文档已经整理出来，欢迎大家围观、拍砖&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;介绍

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/flow-introduce.html" rel="nofollow" target="_blank" title=""&gt;介绍&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/ddda3ea8638d4d2b88fb57df7ed67107.html" rel="nofollow" target="_blank" title=""&gt;演示：CMS 审批工作流&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/b8887f42a6bd4da8a6f1f1b299beefe7.html" rel="nofollow" target="_blank" title=""&gt;单元测试用例集&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;流程定义

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/7fe5e138dd514e4987826cd498bc3913.html" rel="nofollow" target="_blank" title=""&gt;基本概念&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/fe296ad2d9d741d0b7001ed74d43633e.html" rel="nofollow" target="_blank" title=""&gt;JSON 规范&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/09f72f0d29e34ff1a3c8cef868c6668d.html" rel="nofollow" target="_blank" title=""&gt;listener 规范&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/1021fdd4ab174226a616f060c480f7d4.html" rel="nofollow" target="_blank" title=""&gt;listener 规范 - 用户任务&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;流程实例

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/d4254a2c7a624015b4418f88baf5e7eb.html" rel="nofollow" target="_blank" title=""&gt;外观 Bean 组件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/a7b64dea056f4e02a745be4834e16584.html" rel="nofollow" target="_blank" title=""&gt;安全沙箱&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/7ee5d2c03f6b44859f6213f1bbdb4f05.html" rel="nofollow" target="_blank" title=""&gt;流程检索&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/24eefd1767ec41719fdac61c84cec567.html" rel="nofollow" target="_blank" title=""&gt;环境对象&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;转移线

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/58a7ea022bcc4a989c998278d8148231.html" rel="nofollow" target="_blank" title=""&gt;环境对象&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/7de9fbf49f794cee90fe62a544ee9791.html" rel="nofollow" target="_blank" title=""&gt;顺序流&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;活动节点

&lt;ul&gt;
&lt;li&gt;基本概念&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/3b08cfc69dfe4776a6d536283e5bb41f.html" rel="nofollow" target="_blank" title=""&gt;环境对象&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;开始事件&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/75c0b584a1d546fba930d9185a95fadf.html" rel="nofollow" target="_blank" title=""&gt;空开始事件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/162edbd052c54fe89c6828d22480b723.html" rel="nofollow" target="_blank" title=""&gt;定时开始事件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/ed28d24df228468a9c4d9b675d207003.html" rel="nofollow" target="_blank" title=""&gt;原子起草开始事件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;结束事件&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/bd7ff950ad1d4a9aa9a93df409faad97.html" rel="nofollow" target="_blank" title=""&gt;空结束事件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;活动&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/42852c94708d473ba99a9f0689d4dbf0.html" rel="nofollow" target="_blank" title=""&gt;空活动&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/962ed51ed0b44245b3e1a40f3e32fa7f.html" rel="nofollow" target="_blank" title=""&gt;服务活动&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/52f8e206b6e342a98c0afd57b34f8453.html" rel="nofollow" target="_blank" title=""&gt;用户任务活动&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;用户任务

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/e7a47fa53d5d4d909de22fa7654b81fc.html" rel="nofollow" target="_blank" title=""&gt;JSON 规范&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/3d2ffe6d14a94cf293ba5c60d46d6b24.html" rel="nofollow" target="_blank" title=""&gt;外观 Bean 组件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/ae92829d783943eba63afb93a46810e6.html" rel="nofollow" target="_blank" title=""&gt;任务检索&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/9678ca4df98341d38d7dd5ee47343348.html" rel="nofollow" target="_blank" title=""&gt;环境对象&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;进阶篇（待续...） &lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="什么是CabloyJS自带工作流引擎"&gt;什么是 CabloyJS 自带工作流引擎&lt;/h2&gt;
&lt;p&gt;众所周知，NodeJS 作为后端开发语言和运行环境，样样都好，就差一个&lt;code&gt;NodeJS工作流引擎&lt;/code&gt;。CabloyJS 4.0 重点开发了&lt;code&gt;NodeJS工作流引擎&lt;/code&gt;，并作为内置的基础核心模块，近一步拓展了 NodeJS 在后端的应用场景，为深入研发各类商业业务逻辑，提供了基础支撑&lt;/p&gt;
&lt;h2 id="NodeJS工作流引擎的特点"&gt;NodeJS 工作流引擎的特点&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;更简便的配置：采用&lt;code&gt;JSON&lt;/code&gt;进行流程定义的配置，告别 XML 配置文件的冗杂&lt;/li&gt;
&lt;li&gt;流程定义：支持历史版本、支持启用/禁用&lt;/li&gt;
&lt;li&gt;更清晰的架构：采用三个核心模块用分层的机制实现工作流引擎的架构，让工作流不再神秘，源码也不再叠床架屋&lt;/li&gt;
&lt;/ol&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;模块名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;a-flow&lt;/td&gt;
&lt;td&gt;流程定义、流程实例&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;a-flownode&lt;/td&gt;
&lt;td&gt;流程节点（活动节点）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;a-flowtask&lt;/td&gt;
&lt;td&gt;流程任务&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;ol&gt;
&lt;li&gt;支持&lt;code&gt;业务流程&lt;/code&gt;和&lt;code&gt;审批流程&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;与&lt;code&gt;原子数据生命周期&lt;/code&gt;结合，内置了一套基于原子数据的&lt;code&gt;审批工作流&lt;/code&gt;。参见：&lt;a href="https://cabloy.com/zh-cn/articles/atom-stage.html" rel="nofollow" target="_blank" title=""&gt;原子阶段（数据生命周期）&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;与&lt;code&gt;表单验证&lt;/code&gt;结合，支持分别配置不同流程节点的&lt;code&gt;读取字段权限&lt;/code&gt;和&lt;code&gt;修改字段权限&lt;/code&gt;。参见：&lt;a href="https://cabloy.com/zh-cn/articles/form-validation.html" rel="nofollow" target="_blank" title=""&gt;表单验证&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;可通过&lt;code&gt;AOP&lt;/code&gt;机制定制工作流逻辑。参见：&lt;a href="https://cabloy.com/zh-cn/articles/aop.html" rel="nofollow" target="_blank" title=""&gt;AOP&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;可通过&lt;code&gt;Listener&lt;/code&gt;机制定制工作流逻辑。参见：&lt;a href="https://cabloy.com/zh-cn/articles/09f72f0d29e34ff1a3c8cef868c6668d.html" rel="nofollow" target="_blank" title=""&gt;listener 规范&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;开放式的架构，支持更多&lt;code&gt;流程节点&lt;/code&gt;的定制开发&lt;/li&gt;
&lt;li&gt;包含大量&lt;code&gt;测试驱动&lt;/code&gt;代码，可快速上手使用工作流&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="与Activiti的对比"&gt;与&lt;code&gt;Activiti&lt;/code&gt;的对比&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;流程定义&lt;/code&gt;是&lt;code&gt;工作流引擎&lt;/code&gt;的灵魂，一个合理的&lt;code&gt;流程定义&lt;/code&gt;规范，既要考虑使用的便利性，又要考虑功能特性的可扩展性&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Activiti&lt;/code&gt;无疑是 Java 语言领域工作流引擎的标杆，依据&lt;code&gt;BPMN2.0&lt;/code&gt;规范采用&lt;code&gt;XML&lt;/code&gt;格式来定义工作流&lt;/p&gt;
&lt;h3 id="1. JAVA + BPMN + XML"&gt;1. JAVA + BPMN + XML&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;BPMN + XML&lt;/code&gt;的优点是标准统一，与&lt;code&gt;JAVA&lt;/code&gt;语言的开发调性相匹配。当然，缺点是&lt;code&gt;繁琐&lt;/code&gt;，如果脱离了&lt;code&gt;图形化编辑工具&lt;/code&gt;，采用手写 XML 定义，非常不便利&lt;/p&gt;
&lt;h3 id="2. JS + 类BPMN + JSON"&gt;2. JS + 类 BPMN + JSON&lt;/h3&gt;
&lt;p&gt;CabloyJS 经过反复的评估与权衡，决定仍然参考&lt;code&gt;BPMN规范&lt;/code&gt;的特性集，但是采用&lt;code&gt;JSON&lt;/code&gt;格式进行定义。虽然属于自定义格式，但符合&lt;code&gt;JS&lt;/code&gt;语言的调性，优点是&lt;code&gt;简约、易写、易读&lt;/code&gt;，同样具备优秀的&lt;code&gt;可迁移性&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="相关链接"&gt;相关链接&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/" rel="nofollow" target="_blank" title=""&gt;官网：https://cabloy.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/zhennann/cabloy" rel="nofollow" target="_blank" title=""&gt;GitHub: https://github.com/zhennann/cabloy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>zhennann</author>
      <pubDate>Fri, 15 Jan 2021 18:47:37 +0800</pubDate>
      <link>https://ruby-china.org/topics/40814</link>
      <guid>https://ruby-china.org/topics/40814</guid>
    </item>
    <item>
      <title>基于原生 JS 实现的 Bean 容器和 AOP 编程</title>
      <description>&lt;h2 id="Bean是什么"&gt;Bean 是什么&lt;/h2&gt;
&lt;p&gt;我们知道&lt;code&gt;Bean&lt;/code&gt;是&lt;code&gt;Spring&lt;/code&gt;最基础的核心构件，大多数逻辑代码都通过&lt;code&gt;Bean&lt;/code&gt;进行管理。&lt;code&gt;NestJS&lt;/code&gt;基于&lt;code&gt;TypeScript&lt;/code&gt;和&lt;code&gt;依赖注入&lt;/code&gt;也实现了类似于&lt;code&gt;Spring Bean&lt;/code&gt;的机制：服务提供者（Provider）&lt;/p&gt;

&lt;p&gt;CabloyJS 则是在&lt;code&gt;原生JS（Vanilla JS）&lt;/code&gt;上实现了更轻量、更灵活的 Bean 容器&lt;/p&gt;
&lt;h2 id="理念"&gt;理念&lt;/h2&gt;
&lt;p&gt;CabloyJS 在设计 Bean 容器机制时，遵循了以下 3 个理念：&lt;/p&gt;
&lt;h3 id="1. 几乎所有事物都是Bean"&gt;1. 几乎所有事物都是 Bean&lt;/h3&gt;
&lt;p&gt;我们绝大多数逻辑代码都通过 Bean 组件进行管理，比如：Controller、Service、Model、Middleware、Event、Queue、Broadcast、Schedule、Startup、Flow、Flow Task，等等&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CabloyJS 4.0 在实现了 Bean 容器之后，基本上所有核心组件都以 Bean 为基础进行了重构。比如基于 EggJS 的 Controller、Service、Middleware，也实现了 Bean 组件化&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="2. Bean支持AOP"&gt;2. Bean 支持 AOP&lt;/h3&gt;
&lt;p&gt;所有 Bean 组件都可以通过 AOP 组件进行逻辑扩展&lt;/p&gt;
&lt;h3 id="3. AOP也是一种Bean"&gt;3. AOP 也是一种 Bean&lt;/h3&gt;
&lt;p&gt;AOP 组件既然也是 Bean，那么也可以通过其他 AOP 组件进行逻辑扩展&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;这种递归设计，为系统的可定制性和延展性，提供了强大的想象空间&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="定义Bean"&gt;定义 Bean&lt;/h2&gt;
&lt;p&gt;CabloyJS 约定了两种定义 Bean 的模式：app 和 ctx。由于 Bean 被容器托管，可以很方便的跨模块调用。因此，为了清晰的辨识 Bean 被应用的场景，一般约定：如果 Bean 只被本模块内部调用，那么就使用 app 模式；如果大概率会被其他模块调用，那么就使用 ctx 模式&lt;/p&gt;
&lt;h3 id="1. app模式"&gt;1. app 模式&lt;/h3&gt;
&lt;p&gt;比如：Controller、Service 都采用 app 模式&lt;/p&gt;

&lt;p&gt;&lt;code&gt;src/module/test-party/backend/src/bean/test.app.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;appBean&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BeanBase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nf"&gt;actionSync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;actionAsync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;appBean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="2. ctx模式"&gt;2. ctx 模式&lt;/h3&gt;
&lt;p&gt;比如：&lt;code&gt;ctx.bean.atom&lt;/code&gt;、&lt;code&gt;ctx.bean.user&lt;/code&gt;、&lt;code&gt;ctx.bean.role&lt;/code&gt;都采用 ctx 模式&lt;/p&gt;

&lt;p&gt;&lt;code&gt;src/module/test-party/backend/src/bean/test.ctx.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ctxBean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moduleName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;moduleName&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;relativeName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;actionSync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;actionAsync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ctxBean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;ctx.module.info.relativeName: 由于 ctx 模式的 Bean 经常被其他模块调用，那么可以通过此属性取得调用方模块的名称&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="注册Bean"&gt;注册 Bean&lt;/h2&gt;
&lt;p&gt;对于大多数组件，EggJS 采用&lt;code&gt;约定优先&lt;/code&gt;的策略，会在指定的位置查找资源，并自动加载。而 CabloyJS 采用&lt;code&gt;显式注册&lt;/code&gt;，从而 Webpack 可以收集所有后端源码，实现&lt;a href="https://cabloy.com/zh-cn/articles/beef7cd0ab0a495284797a5af933a155.html" rel="nofollow" target="_blank" title=""&gt;模块编译&lt;/a&gt;的特性&lt;/p&gt;

&lt;p&gt;&lt;code&gt;src/module/test-party/backend/src/beans.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;testApp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./bean/test.app.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;testCtx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./bean/test.ctx.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;beans&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// test&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test.app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;bean&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;testApp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;testctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ctx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;bean&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;testCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;beans&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mode&lt;/td&gt;
&lt;td&gt;模式：app/ctx&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bean&lt;/td&gt;
&lt;td&gt;bean 组件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;global&lt;/td&gt;
&lt;td&gt;是否是全局组件&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h2 id="使用Bean"&gt;使用 Bean&lt;/h2&gt;&lt;h3 id="1. beanFullName"&gt;1. beanFullName&lt;/h3&gt;
&lt;p&gt;每一个注册的 Bean 组件都被分配了全称，具体规则如下&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;注册名称&lt;/th&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;所属模块&lt;/th&gt;
&lt;th&gt;global&lt;/th&gt;
&lt;th&gt;beanFullName&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;test.app&lt;/td&gt;
&lt;td&gt;test&lt;/td&gt;
&lt;td&gt;test-party&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;test-party.test.app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;testctx&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;test-party&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;testctx&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;全局 Bean（&lt;code&gt;global:true&lt;/code&gt;）: 当一个 Bean 组件可以作为一个核心的基础组件的时候，可以设置为全局 Bean，方便其他模块的调用，比如：&lt;code&gt;atom&lt;/code&gt;、&lt;code&gt;user&lt;/code&gt;、&lt;code&gt;role&lt;/code&gt;、&lt;code&gt;flow&lt;/code&gt;、&lt;code&gt;flowTask&lt;/code&gt;，等等&lt;/p&gt;

&lt;p&gt;本地 Bean（&lt;code&gt;global:false&lt;/code&gt;）: 当一个 Bean 组件一般只用于本模块时，可以设置为本地 Bean，从而避免命名冲突&lt;/p&gt;

&lt;p&gt;场景：对于&lt;code&gt;本地Bean&lt;/code&gt;，我们一般为其分配一个&lt;code&gt;场景名称&lt;/code&gt;作为前缀，一方面便于 Bean 的分类管理，另一方面也便于辨识 Bean 的用途&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="2. 基本调用"&gt;2. 基本调用&lt;/h3&gt;
&lt;p&gt;可以直接通过&lt;code&gt;this.ctx.bean&lt;/code&gt;取得 Bean 容器，然后通过&lt;code&gt;beanFullName&lt;/code&gt;获取 Bean 实例&lt;/p&gt;

&lt;p&gt;&lt;code&gt;src/module/test-party/backend/src/controller/test/feat/bean.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="c1"&gt;// global: false&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bean&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-party.test.app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;actionSync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; 
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bean&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-party.test.app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;actionAsync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// global: true&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;testctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actionSync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;testctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actionAsync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="3. 新建Bean实例"&gt;3. 新建 Bean 实例&lt;/h3&gt;
&lt;p&gt;通过&lt;code&gt;this.ctx.bean&lt;/code&gt;获取 Bean 实例，那么这个实例对当前&lt;code&gt;ctx&lt;/code&gt;而言是单例的。如果需要新建 Bean 实例，可以按如下方式进行：&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_newBean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;beanFullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;比如我们要新建一个 Flow 实例：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;src/module-system/a-flow/backend/src/bean/bean.flow.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;_createFlowInstance&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;flowDef&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flowInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_newBean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;moduleInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;relativeName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.local.flow.flow`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;flowDef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;flowInstance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="4. 跨模块调用本地Bean"&gt;4. 跨模块调用本地 Bean&lt;/h3&gt;
&lt;p&gt;本地 Bean 也可以被跨模块调用&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;跨模块调用的本质：新建一个 ctx 上下文环境，该 ctx 的 module 信息与本地 Bean 一致，然后通过新容器&lt;code&gt;ctx.bean&lt;/code&gt;来调用本地 Bean&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeBean&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subdomain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;beanModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;beanFullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;可选&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;locale&lt;/td&gt;
&lt;td&gt;可选&lt;/td&gt;
&lt;td&gt;默认等于 ctx.locale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;subdomain&lt;/td&gt;
&lt;td&gt;可选&lt;/td&gt;
&lt;td&gt;默认等于 ctx.subdomain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;beanModule&lt;/td&gt;
&lt;td&gt;必需&lt;/td&gt;
&lt;td&gt;本地 Bean 所属模块名称&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;beanFullName&lt;/td&gt;
&lt;td&gt;必需&lt;/td&gt;
&lt;td&gt;本地 Bean 的全称&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;context&lt;/td&gt;
&lt;td&gt;可选&lt;/td&gt;
&lt;td&gt;调用本地 Bean 时传入的参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fn&lt;/td&gt;
&lt;td&gt;必需&lt;/td&gt;
&lt;td&gt;调用本地 Bean 的方法名&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;transaction&lt;/td&gt;
&lt;td&gt;可选&lt;/td&gt;
&lt;td&gt;是否要启用数据库事务&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;比如我们要调用模块&lt;code&gt;a-file&lt;/code&gt;的本地 Bean: &lt;code&gt;service.file&lt;/code&gt;，直接上传用户的 avatar，并返回 downloadUrl&lt;/p&gt;

&lt;p&gt;&lt;code&gt;src/module-system/a-base-sync/backend/src/bean/bean.user.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// upload&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeBean&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;beanModule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a-file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;beanFullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a-file.service.file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fileContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;_upload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// hold&lt;/span&gt;
&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_avatar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;downloadUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="5. app.bean"&gt;5. app.bean&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ctx.bean&lt;/code&gt;是每个请求初始化一个容器，而&lt;code&gt;app.bean&lt;/code&gt;则可以实现整个应用使用一个容器，从而实现 Bean 组件的应用级别的单例模式&lt;/p&gt;

&lt;p&gt;&lt;code&gt;src/module/test-party/backend/src/controller/test/feat/bean.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bean&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-party.test.app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;actionSync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; 
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bean&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test-party.test.app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;actionAsync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="AOP编程"&gt;AOP 编程&lt;/h2&gt;
&lt;p&gt;限于篇幅，关于&lt;code&gt;AOP编程&lt;/code&gt;请参见：&lt;a href="https://cabloy.com/zh-cn/articles/aop.html" rel="nofollow" target="_blank" title=""&gt;cabloy-aop&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="相关链接"&gt;相关链接&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/" rel="nofollow" target="_blank" title=""&gt;官网：https://cabloy.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/zhennann/cabloy" rel="nofollow" target="_blank" title=""&gt;GitHub: https://github.com/zhennann/cabloy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>zhennann</author>
      <pubDate>Wed, 06 Jan 2021 10:42:59 +0800</pubDate>
      <link>https://ruby-china.org/topics/40784</link>
      <guid>https://ruby-china.org/topics/40784</guid>
    </item>
    <item>
      <title>CabloyJS 也有工作流引擎了，是你想要的吗？</title>
      <description>&lt;p&gt;&lt;img src="https://admin.cabloy.com/api/a/file/file/download/e1a375c5c14542e9be9774647e1ea528.png" title="" alt="IMG_8706"&gt;&lt;/p&gt;
&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;众所周知，NodeJS 作为后端开发语言和运行环境，样样都好，就差一个&lt;code&gt;NodeJS工作流引擎&lt;/code&gt;。CabloyJS 4.0 重点开发了&lt;code&gt;NodeJS工作流引擎&lt;/code&gt;，并作为内置的基础核心模块，近一步拓展了 NodeJS 在后端的应用场景，为深入研发各类商业业务逻辑，提供了基础支撑&lt;/p&gt;
&lt;h2 id="NodeJS工作流引擎的特点"&gt;NodeJS 工作流引擎的特点&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;更简便的配置：采用&lt;code&gt;JSON&lt;/code&gt;进行流程定义的配置，告别 XML 配置文件的冗杂&lt;/li&gt;
&lt;li&gt;流程定义：支持历史版本、支持启用/禁用&lt;/li&gt;
&lt;li&gt;更清晰的架构：采用三个核心模块用分层的机制实现工作流引擎的架构，让工作流不再神秘，源码也不再叠床架屋&lt;/li&gt;
&lt;/ol&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;模块名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;a-flow&lt;/td&gt;
&lt;td&gt;流程定义、流程实例&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;a-flownode&lt;/td&gt;
&lt;td&gt;流程节点（活动节点）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;a-flowtask&lt;/td&gt;
&lt;td&gt;流程任务&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;ol&gt;
&lt;li&gt;支持&lt;code&gt;业务流程&lt;/code&gt;和&lt;code&gt;审批流程&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;与&lt;code&gt;Atom三生三世&lt;/code&gt;结合，内置了一套基于 Atom 的&lt;code&gt;审批工作流&lt;/code&gt;。参见：&lt;a href="https://cabloy.com/zh-cn/articles/atom-stage.html" rel="nofollow" target="_blank" title=""&gt;原子阶段（三生三世）&lt;/a&gt;&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;与&lt;code&gt;表单验证&lt;/code&gt;结合，支持分别配置不同流程节点的&lt;code&gt;读取字段权限&lt;/code&gt;和&lt;code&gt;修改字段权限&lt;/code&gt;。参见：&lt;a href="https://cabloy.com/zh-cn/articles/form-validation.html" rel="nofollow" target="_blank" title=""&gt;表单验证&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;可通过&lt;code&gt;AOP&lt;/code&gt;机制定制工作流逻辑&lt;/li&gt;
&lt;li&gt;可通过&lt;code&gt;Listener&lt;/code&gt;机制定制工作流逻辑&lt;/li&gt;
&lt;li&gt;开放式的架构，支持更多&lt;code&gt;流程节点&lt;/code&gt;的定制开发&lt;/li&gt;
&lt;li&gt;包含大量&lt;code&gt;测试驱动&lt;/code&gt;代码，可快速上手使用工作流&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="工作流演示"&gt;工作流演示&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;新建一个草稿：&lt;code&gt;采购订单&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;选择要使用的&lt;code&gt;流程定义&lt;/code&gt;，然后提交，草稿进入相应的&lt;code&gt;审批流程&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;签收任务、并处理任务&lt;/li&gt;
&lt;li&gt;流程结束，草稿转为&lt;code&gt;归档&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src="https://admin.cabloy.com/api/a/file/file/download/a2e337ef9450431cbd130f8da48eb392.gif" title="" alt="flow-zhcn"&gt;&lt;/p&gt;
&lt;h2 id="一个最简工作流定义"&gt;一个最简工作流定义&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;src/module/test-flow/backend/src/config/static/flowDef/set00_simple.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;startEvent_1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;startEventNone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;endEvent_1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;End&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;endEventNone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nx"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;edge_1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;startEvent_1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;endEvent_1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;listener&lt;/td&gt;
&lt;td&gt;监听器，可监听 flow/node/task 各类事件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;process.nodes&lt;/td&gt;
&lt;td&gt;流程节点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;process.nodes.type&lt;/td&gt;
&lt;td&gt;流程节点类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;process.edges&lt;/td&gt;
&lt;td&gt;流程转移线&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;process.edges.source&lt;/td&gt;
&lt;td&gt;来源&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;process.edges.target&lt;/td&gt;
&lt;td&gt;去向&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h2 id="一个审批流程定义"&gt;一个审批流程定义&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;src/module/test-flow/backend/src/config/static/flowDef/set01_atomUserTask.js&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;startEvent_1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Drafting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;startEventAtom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;atom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;moduleInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;relativeName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;atomClassName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;purchaseOrder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;conditionExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;atom._flowDefKey===&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;set01_atomUserTask&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;activity_1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Review&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;activityUserTask&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;assignees&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// users: '1,2',&lt;/span&gt;
            &lt;span class="c1"&gt;// roles: '1,2',&lt;/span&gt;
            &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flowUser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;confirmation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;bidding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;completionCondition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// passed: 1,&lt;/span&gt;
            &lt;span class="c1"&gt;// rejected: '100%',&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="c1"&gt;// rejectedNode:null,&lt;/span&gt;
          &lt;span class="c1"&gt;// allowRejectTask: true,&lt;/span&gt;
          &lt;span class="c1"&gt;// allowCancelFlow: false,&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
              &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;atomName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;property&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="na"&gt;ebType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="na"&gt;ebTitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
              &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;endEvent_1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;End&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;endEventNone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nx"&gt;edges&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;edge_1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;startEvent_1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;activity_1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;edge_2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;activity_1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;endEvent_1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;process.nodes.type&lt;/li&gt;
&lt;/ul&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;startEventAtom&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;开始事件节点（起草）&lt;/code&gt;：通过 options.atom 和 options.conditionExpression 与指定的 Atom 类型绑定。当指定的 Atom 提交时自动启动相匹配的工作流定义&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;activityUserTask&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;用户任务节点&lt;/code&gt;：可指定参与人、是否竞签、完成条件、读字段权限、写字段权限，等等&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;endEventNone&lt;/td&gt;
&lt;td&gt;&lt;code&gt;结束事件节点&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h2 id="相关链接"&gt;相关链接&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/" rel="nofollow" target="_blank" title=""&gt;官网：https://cabloy.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/zhennann/cabloy" rel="nofollow" target="_blank" title=""&gt;GitHub: https://github.com/zhennann/cabloy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>zhennann</author>
      <pubDate>Mon, 21 Dec 2020 15:53:51 +0800</pubDate>
      <link>https://ruby-china.org/topics/40723</link>
      <guid>https://ruby-china.org/topics/40723</guid>
    </item>
    <item>
      <title>CabloyJS v4.0.0 支持工作流引擎及更多 🎉</title>
      <description>&lt;p&gt;截至 2020 年 12 月 21 日冬至，花了近 5 年时间作出最小可用 NodeJS 开源全栈框架，这就是 CabloyJS V4.0.0&lt;/p&gt;

&lt;p&gt;5 年，90 个模块，30 万行代码，5400 次提交 (Commits)，开启 NodeJS 全栈开发的全新体验&lt;/p&gt;
&lt;h2 id="CabloyJS V4.0.0主要完成了以下特性"&gt;CabloyJS V4.0.0 主要完成了以下特性&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;采用 lerna 将 cabloy 所有核心模块集中在一个源码仓库管理&lt;/li&gt;
&lt;li&gt;基于原生 JS 的 Bean 容器和 AOP 架构

&lt;ul&gt;
&lt;li&gt;几乎所有事物都是 Bean &lt;/li&gt;
&lt;li&gt;Bean 支持 AOP&lt;/li&gt;
&lt;li&gt;AOP 也是一种 Bean&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/flow-introduce.html" rel="nofollow" target="_blank" title=""&gt;通用的 NodeJS 工作流引擎&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cabloy.com/zh-cn/articles/atom-stage.html" rel="nofollow" target="_blank" title=""&gt;原子三生三世（数据的生命周期管理）&lt;/a&gt;：草稿-&amp;gt;归档-&amp;gt;历史&lt;/li&gt;
&lt;li&gt;基于 NodeJS 工作流引擎的草稿审核业务流&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cabloy.com/zh-cn/articles/adaptive-layout.html" rel="nofollow" target="_blank" title=""&gt;PC 端与移动端自适应&lt;/a&gt;：页面组件只需要开发一次。不是采用&lt;code&gt;media query&lt;/code&gt;，也不是采用&lt;code&gt;iframe&lt;/code&gt;，而是真正的把手机操控体验带入 PC 端。只要用一下，就会有一种&lt;code&gt;相逢恨晚&lt;/code&gt;的感觉&lt;/li&gt;
&lt;li&gt;统计值自动更新与推送架构。比如，我有多少待办一目了然。这是一个通用的架构，可以简便的匹配到任何场景&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cabloy.com/zh-cn/articles/wechat-introduce.html" rel="nofollow" target="_blank" title=""&gt;接口对接&lt;/a&gt;：微信公众号、企业微信、钉钉企业应用&lt;/li&gt;
&lt;li&gt;基于 Redis 的原生分布式架构

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/queue.html" rel="nofollow" target="_blank" title=""&gt;Queue&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/schedule.html" rel="nofollow" target="_blank" title=""&gt;Schedule&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/broadcast.html" rel="nofollow" target="_blank" title=""&gt;Broadcast&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/cache.html" rel="nofollow" target="_blank" title=""&gt;Cache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/startup.html" rel="nofollow" target="_blank" title=""&gt;Startup&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;基于 socketio 的消息推送架构&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/5c90f4fd15174772adb34dfbf6d1adfb.html" rel="nofollow" target="_blank" title=""&gt;具有部件间数据绑定机制的“仪表板”架构&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cabloy.com/zh-cn/articles/28f14f839af5457b9243c9e9210d5324.html" rel="nofollow" target="_blank" title=""&gt;高度定制化的 PC 端布局&lt;/a&gt;：header、sidebar、statusbar&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="相关链接"&gt;相关链接&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/" rel="nofollow" target="_blank" title=""&gt;官网：https://cabloy.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/zhennann/cabloy" rel="nofollow" target="_blank" title=""&gt;GitHub: https://github.com/zhennann/cabloy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>zhennann</author>
      <pubDate>Mon, 21 Dec 2020 15:33:38 +0800</pubDate>
      <link>https://ruby-china.org/topics/40722</link>
      <guid>https://ruby-china.org/topics/40722</guid>
    </item>
    <item>
      <title>CabloyJS 微信模块、企业微信模块已出齐</title>
      <description>&lt;h2 id="前言"&gt;前言&lt;/h2&gt;
&lt;p&gt;当&lt;code&gt;Cabloy-企业微信&lt;/code&gt;模块完成时，加上之前已完成的&lt;code&gt;Cabloy-微信&lt;/code&gt;模块，关于在 CabloyJS 中与&lt;code&gt;微信/企业微信&lt;/code&gt;对接的任务已经完成了。这些模块的目标就是，只需填入各类服务的参数，就可以直接进入具体的业务开发，从而达到&lt;code&gt;开箱即用&lt;/code&gt;的效果&lt;/p&gt;
&lt;h2 id="背景分析"&gt;背景分析&lt;/h2&gt;&lt;h3 id="1. 账户体系"&gt;1. 账户体系&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;微信/企业微信&lt;/code&gt;的开发，有诸多的坑，而最大的坑就是&lt;code&gt;账户体系&lt;/code&gt;了&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;首先，&lt;code&gt;微信&lt;/code&gt;与&lt;code&gt;企业微信&lt;/code&gt;是不同的账户体系&lt;/li&gt;
&lt;li&gt;在&lt;code&gt;微信&lt;/code&gt;中，openid 是识别用户的唯一标识，如果开通了&lt;code&gt;微信开放平台&lt;/code&gt;，那么就可以通过 unionid 把散落在&lt;code&gt;微信公共号&lt;/code&gt;、&lt;code&gt;微信小程序&lt;/code&gt;等不同服务的 openid 关联起来，标识同一个用户。那么，如何把 openid 与系统中的用户系统对接起来？如果开通了 unionid，又如何对接？unionid 开通之前的旧账户是否也可以平滑迁移？&lt;/li&gt;
&lt;li&gt;在&lt;code&gt;企业微信&lt;/code&gt;中，通过 userid 来识别每个企业成员。与&lt;code&gt;微信&lt;/code&gt;不同的是，&lt;code&gt;企业微信&lt;/code&gt;通过&lt;code&gt;部门树&lt;/code&gt;来管理成员，从而为&lt;code&gt;资源授权和分配&lt;/code&gt;提供组织依据。那么，我们在进行系统初始化的第一步就是要把企业的&lt;code&gt;部门和成员&lt;/code&gt;同步到系统中，并实现&lt;code&gt;双向查找&lt;/code&gt;功能&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2. 杂项"&gt;2. 杂项&lt;/h3&gt;
&lt;p&gt;除了纷繁复杂的账户体系对接任务，接下来我们还要面对一些随时开发随时遇到的小问题。因为微信/企业微信开发涉及到的场景、概念和术语有很多，往往需要通读官方文档。而官方文档对有些概念的表述语焉不详，惜墨如金，于是不得不写代码来验证一些猜测，然后再回头重温文档。当这一遍走完把项目完成后，如果有一个新项目，很可能还要再走一遍。这里仅仅列举一些经常出现的问题：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSON 与 XML：微信公共号的消息推送系统采用 XML 作为数据格式，而小程序既要支持 XML 也要支持 JSON。同时又有明文模式、兼容模式、安全模式之别。&lt;/li&gt;
&lt;li&gt; openid/unionid与openId/unionId：在进行账号登录时，微信公共号返回的是&lt;code&gt;openid/unionid&lt;/code&gt;，而小程序返回的&lt;code&gt;openId/unionId&lt;/code&gt;。如果稍不留意，就掉在坑里了 &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;企业微信&lt;/code&gt;支持不同的应用：内置的应用、第三方应用、自建应用、关联的小程序等等。这些应用如何进行架构，提供统一便捷的 API 调用模型&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;企业微信小程序&lt;/code&gt;本体是&lt;code&gt;微信小程序&lt;/code&gt;，然后关联到&lt;code&gt;企业微信&lt;/code&gt;成为一个企业微信应用。只有明白了这些概念和关系，才能准确的进行对接&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="核心目标"&gt;核心目标&lt;/h2&gt;&lt;h3 id="1、常见微信API SDK的问题"&gt;1、常见微信 API SDK 的问题&lt;/h3&gt;
&lt;p&gt;不同的开发语言，都能找到对应的微信 API SDK。但这些微信 API SDK 往往是把官方提供的 http api 进行了一个语言层面的封装，很少走得更远的。比如，通过微信 API SDK，我们可以很方便的获取 AccessToken，可以获取 User 信息，但是如何把获取到的 User 与系统对接，如何处理 openid 和 unionid 之间的关联，仍然需要我们自己设计和开发&lt;/p&gt;

&lt;p&gt;再比如，通过微信 API SDK 可以很方便的获取企业微信的部门和成员。但是如何与系统中的角色和用户对接，并支持不同的场景和登录方式，仍然有大量的工作需要做&lt;/p&gt;
&lt;h3 id="2. 开箱即用"&gt;2. 开箱即用&lt;/h3&gt;
&lt;p&gt;而 CabloyJS 的&lt;code&gt;微信/企业微信&lt;/code&gt;模块的&lt;code&gt;核心目标&lt;/code&gt;就是提供&lt;code&gt;开箱即用&lt;/code&gt;的效果。只需配置好参数，所有的对接工作全部自动完成，使我们一步跨过纷繁的细节，直接进入具体的业务开发当中。当然，CabloyJS 的&lt;code&gt;微信/企业微信&lt;/code&gt;模块是完全&lt;code&gt;开源&lt;/code&gt;的，我们仍然可以从中清晰地看到这些细节，以及处理的方式&lt;/p&gt;

&lt;p&gt;为什么 CabloyJS 可以做到&lt;code&gt;开箱即用&lt;/code&gt;的效果？就是因为 CabloyJS 是全栈 NodeJS 框架，可以把&lt;code&gt;前端组件&lt;/code&gt;、&lt;code&gt;后端服务&lt;/code&gt;、&lt;code&gt;数据存储与访问&lt;/code&gt;等等，有机的结合在一起&lt;/p&gt;

&lt;p&gt;比如，CabloyJS 模块通过这些前后端的配合，直接实现了企业微信中部门与用户的同步工作，甚至还通过 CabloyJS 底层提供的&lt;code&gt;SocketIO&lt;/code&gt;机制实现了前端同步进度的实时显示&lt;/p&gt;

&lt;p&gt;&lt;img src="https://admin.cabloy.com/api/a/file/file/download/290fa8460c3a45ccb9b86546a6627ffe.gif" title="" alt="contacts-sync-zhcn"&gt;&lt;/p&gt;
&lt;h2 id="两大亮点"&gt;两大亮点&lt;/h2&gt;
&lt;p&gt;基于 CabloyJS 全栈业务开发框架本身提供的特性，使得&lt;code&gt;Cabloy-企业微信&lt;/code&gt;模块具有以下两个显著的亮点：&lt;/p&gt;
&lt;h3 id="1. PC、Mobile自适应"&gt;1. PC、Mobile 自适应&lt;/h3&gt;
&lt;p&gt;许多企业微信应用存在这样一个问题：在 Mobile 端只能使用一部分功能，其他更多功能则需要登录 PC 系统进行操作&lt;/p&gt;

&lt;p&gt;而 CabloyJS 框架下的&lt;code&gt;Cabloy-企业微信&lt;/code&gt;允许所有的业务功能同时支持 PC 和 Mobile 使用。同时又能保证以下两点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;通过角色权限系统，控制不同用户使用不同功能&lt;/li&gt;
&lt;li&gt;前端页面采用异步加载策略，从而适应大型项目的开发&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Mobile 端效果&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://admin.cabloy.com/api/a/file/file/download/a4b86debef2044ad86b84d378f2db594.gif" title="" alt="mobile-zhcn"&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PC 端效果&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://admin.cabloy.com/api/a/file/file/download/32ef8d7bb0d24bc79d314051758cc91a.gif" title="" alt="pc-zhcn"&gt;&lt;/p&gt;
&lt;h3 id="2. 数据孤岛"&gt;2. 数据孤岛&lt;/h3&gt;
&lt;p&gt;企业从不同服务提供商采购不同的企业微信应用，必然导致&lt;code&gt;数据孤岛&lt;/code&gt;的出现，而且这些数据散存在不同服务商的后台，缺乏数据联动与共享机制&lt;/p&gt;

&lt;p&gt;而 CabloyJS 框架本身就是基于&lt;code&gt;业务模块&lt;/code&gt;构建的。企业自建的模块或者使用第三方的模块，都汇集在一个 CabloyJS 项目之中，并进行私有部署，从而从根本上解决了&lt;code&gt;数据孤岛&lt;/code&gt;的问题，不仅能实现数据联动与共享，也可以更灵活的进行数据采集、处理和分析&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;归根结底一句话，数据和程序都掌握在自己的手中&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="特性"&gt;特性&lt;/h2&gt;
&lt;p&gt;基于 CabloyJS 全栈框架提供的便利性和灵活性，&lt;code&gt;Cabloy-企业微信&lt;/code&gt;主要有如下特性：&lt;/p&gt;
&lt;h3 id="1. 一站式整合"&gt;1. 一站式整合&lt;/h3&gt;
&lt;p&gt;当前整合了&lt;code&gt;企业微信自建应用&lt;/code&gt;和&lt;code&gt;企业微信小程序&lt;/code&gt;的接口，具体如下：&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;场景&lt;/th&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;自建应用&lt;/td&gt;
&lt;td&gt;消息推送系统&lt;/td&gt;
&lt;td&gt;自动完成接口对接，并对用户进行认证&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;自建应用&lt;/td&gt;
&lt;td&gt;网页登录&lt;/td&gt;
&lt;td&gt;自动跳转微信登录，并对用户进行认证&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;自建应用&lt;/td&gt;
&lt;td&gt;网页 JSSDK&lt;/td&gt;
&lt;td&gt;自动注入 JSSDK，并自动完成配置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;小程序&lt;/td&gt;
&lt;td&gt;后台登录接口&lt;/td&gt;
&lt;td&gt;自动完成接口对接，并对用户进行认证&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;小程序&lt;/td&gt;
&lt;td&gt;前端 SDK&lt;/td&gt;
&lt;td&gt;提供 SDK，便于企业微信小程序前端直接访问 CabloyJS 后端 API&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h3 id="2. 开箱即用"&gt;2. 开箱即用&lt;/h3&gt;
&lt;p&gt;只需配置好企业微信账号参数，所有接口自动完成对接&lt;/p&gt;
&lt;h3 id="3. 多小程序支持"&gt;3. 多小程序支持&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;企业微信&lt;/code&gt;可以关联多个&lt;code&gt;企业微信小程序&lt;/code&gt;，因此，模块也提供了多小程序支持&lt;/p&gt;
&lt;h3 id="4. 多站点支持"&gt;4. 多站点支持&lt;/h3&gt;
&lt;p&gt;通过 CabloyJS 提供的&lt;code&gt;多实例&lt;/code&gt;特性，可以实现&lt;code&gt;多站点支持&lt;/code&gt;，比如为不同的企业提供企业微信服务。请参见：&lt;a href="https://cabloy.com/zh-cn/articles/44e45b3928ca4c6cb63809558145e000.html" rel="nofollow" target="_blank" title=""&gt;EggBornJS：多实例&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="参考链接"&gt;参考链接&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/zh-cn/articles/wechat-introduce.html" rel="nofollow" target="_blank" title=""&gt;Cabloy-微信：介绍&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://cabloy.com/zh-cn/articles/wechat-quick-start.html" rel="nofollow" target="_blank" title=""&gt;Cabloy-微信：快速开始&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://cabloy.com/zh-cn/articles/wxwork-introduce.html" rel="nofollow" target="_blank" title=""&gt;Cabloy-企业微信：介绍&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://cabloy.com/zh-cn/articles/wxwork-quick-start.html" rel="nofollow" target="_blank" title=""&gt;Cabloy-企业微信：快速开始&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>zhennann</author>
      <pubDate>Fri, 26 Jun 2020 21:46:21 +0800</pubDate>
      <link>https://ruby-china.org/topics/40025</link>
      <guid>https://ruby-china.org/topics/40025</guid>
    </item>
  </channel>
</rss>
