{
  "paths": [
    {
      "type": "file",
      "value": "index.md"
    },
    {
      "type": "dir",
      "name": "1.Hello,Bucky!",
      "children": [
        {
          "type": "file",
          "value": "1.Hello,Bucky!/1.1.让Demo跑起来.md"
        },
        {
          "type": "file",
          "value": "1.Hello,Bucky!/1.2.代码分析.md"
        },
        {
          "type": "file",
          "value": "1.Hello,Bucky!/1.3.聊天室demo.md"
        },
        {
          "type": "file",
          "value": "1.Hello,Bucky!/1.4.自己动手添加些新功能.md"
        },
        {
          "type": "file",
          "value": "1.Hello,Bucky!/1.5.问题诊断与调试.md"
        }
      ]
    },
    {
      "type": "dir",
      "name": "2.构架",
      "children": [
        {
          "type": "file",
          "value": "2.构架/2.1.整体架构介绍.md"
        },
        {
          "type": "file",
          "value": "2.构架/2.2.工程目录结构.md"
        }
      ]
    },
    {
      "type": "dir",
      "name": "3.开发手册",
      "children": [
        {
          "type": "file",
          "value": "3.开发手册/3.1.Step-by-step创建新项目.md"
        },
        {
          "type": "file",
          "value": "3.开发手册/3.2.bucky命令行手册.md"
        },
        {
          "type": "file",
          "value": "3.开发手册/3.3.使用h5debug.md"
        },
        {
          "type": "file",
          "value": "3.开发手册/3.4.API参考手册.md"
        },
        {
          "type": "file",
          "value": "3.开发手册/3.5.错误码.md"
        },
        {
          "type": "file",
          "value": "3.开发手册/3.6.常见问题.md"
        }
      ]
    },
    {
      "type": "dir",
      "name": "4.例子",
      "children": [
        {
          "type": "file",
          "value": "4.例子/4.1.使用MySQL.md"
        },
        {
          "type": "file",
          "value": "4.例子/4.2.使用OSS读写文件.md"
        },
        {
          "type": "file",
          "value": "4.例子/4.3.创建ReactNative项目.md"
        },
        {
          "type": "file",
          "value": "4.例子/4.4.创建微信小程序项目.md"
        }
      ]
    },
    {
      "type": "dir",
      "name": "5.知识库",
      "children": [
        {
          "type": "file",
          "value": "5.知识库/5.1.理解async.md"
        },
        {
          "type": "file",
          "value": "5.知识库/5.2.模块接口.md"
        },
        {
          "type": "file",
          "value": "5.知识库/5.3.模块加载.md"
        },
        {
          "type": "file",
          "value": "5.知识库/5.4.公共类库.md"
        },
        {
          "type": "file",
          "value": "5.知识库/5.5.系统事件.md"
        },
        {
          "type": "file",
          "value": "5.知识库/5.6.全局事件.md"
        }
      ]
    }
  ],
  "contents": [
    {
      "path": "index.md",
      "url": "index.html",
      "content": "# Bucky介绍\nBucky，下一代分布式系统开发\n* Serverless，有效减低开发成本\n* Auto Scaling，灵活适应业务需求\n* One Language，一致的构架\n* Just Build，单机一般的开发体验\n\n# 快速上手\n* 安装node环境：https://nodejs.org/\n* 安装bucky sdk: `npm install -g buckyos`\n* 创建[示例](1.Hello,Bucky!/1.1.让Demo跑起来.md)\n\n",
      "html": "<h1>Bucky介绍</h1>\n<p>Bucky，下一代分布式系统开发</p>\n<ul>\n<li>Serverless，有效减低开发成本</li>\n<li>Auto Scaling，灵活适应业务需求</li>\n<li>One Language，一致的构架</li>\n<li>Just Build，单机一般的开发体验</li>\n</ul>\n<h1>快速上手</h1>\n<ul>\n<li>安装node环境：<a href=\"https://nodejs.org/\">https://nodejs.org/</a></li>\n<li>安装bucky sdk: <code>npm install -g buckyos</code></li>\n<li>创建<a href=\"1.Hello,Bucky!/1.1.%E8%AE%A9Demo%E8%B7%91%E8%B5%B7%E6%9D%A5.html\">示例</a></li>\n</ul>\n",
      "id": 0
    },
    {
      "path": "1.Hello,Bucky!/1.1.让Demo跑起来.md",
      "url": "1.Hello,Bucky!/1.1.让Demo跑起来.html",
      "content": "# Hello Bucky!\n这个系列教程的目的是用最短的时间让您能快速体验Bucky框架，并对一些基础概念有一些感性的理解。让我们开始吧！第一篇文章我们会完成环境的搭建，并把一个最简单的用户登陆/注册的服务运行起来。\n\n### 准备开发环境\n安装最新版本的nodejs 8.9.1 LTS，以及npm工具\n在各个操作系统上安装nodejs请参考 https://nodejs.org/en/\n* windows: 下载https://nodejs.org/en/ LTS版\n* mac: 下载https://nodejs.org/en/ LTS版 或者 brew install nodejs@8.9.1\n\n### 安装buckyos sdk\n```\nnpm install -g buckyos\n```\n\n### 得到一个有效的bucky appid\n在[www.buckycloud.com](www.buckycloud.com)登陆bucky账号，然后进入控制面板。\n在应用控制面板左侧点击`添加应用`，填写应用的友好名称为Demo.\n应用创建成功后在左侧点击应用名，会进入应用的控制面板。可以看到应用的各种信息和统计数据，这里我们只要看一下appid就好了。\n\n### 建立代码目录\n```\ncd {{你喜欢的目录}}\nmkdir demo\n```\n\n### 安装Demo代码\n```\ncd demo\nbucky init -i\n```\nbucky init -i是使用交互式命令行来创建一个bucky解决方案。我们的选项如下\n\n```\n解决方案类型：\n────────────────────\n1. 示例\n2. 新建解决方案\n\n$请选择解决方案类型[1/2]：\n```\n\n选择1. 创建示例解决方案\n然后在示例列表里\n\n```\n示例项目列表：\n────────────────────\n1. HelloBucky\n2. minichat\n3. mysql\n\n$选择示例项目序号：\n\n```\n继续选择1，创建HelloBucky.\n\n自此，初始化成功。\n\n### 发布代码准备运行\n输入命令\n```\nbucky build\nbucky deploy\n```\n\n运行该命令会要求使用开发者账号登录并给项目设置一个有效的appid。\n根据提示输入用户名密码:\n```\n$请输入用户名：example-user\n\n$请输入密码：*****\n```\n\n验证成功后会自动拉取可用的appid列表（appid则需要在[www.buckycloud.com](www.buckycloud.com)网站后台预先创建），选择一个输入即可：\n```\n--------------------------------\nall appids for user:example-user\n--------------------------------\n1: appname: Demo , appid: 4w9aAOd3MX\n2: appname: example2 , appid: mxkazUeJ0D\n--------------------------------\n\n\n$请选择app序号：\n```\n\n紧接着就会开始执行完整的构建过程，`build`实际上是一个复合命令，不带参数-e可以看到该命令实际执行的是下面命令的序列\n```\nbucky compile\nbucky proxy\nbukcy stop\nbucky pub\nbucky start\n```\n\n完成了`代码编译`，`生成代理包`，`停止应用服务`，`上传代码`，`运行服务`的一整套操作。熟练后可以根据实际需要单独调用子命令。\n\n### 重置Knowledge\n\n每个bucky应用程序有一个全局配置，称之为`Knowledge`。Knowledge的本地配置默认放在根目录下的knowledge.json。到了这里我们还没有完成运行前的准备工作，我们还需要更新一次应用程序的Knowldege。由于这个操作涉及到应用程序的全局配置的更新，我们提供了单独的命令来操作：\n```\nbucky k -reset\n```\n\n事实上，用户并不需要在每次`build`之后都重置`Knowldege`，只有在应用程序的`Knowldege`配置（默认在根目录下的knolwdeg.json）更新之后，需要重置。\n\n设置knwoledge成功后就已经做好了运行服务的全部准备工作\n\n### 测试服务\n\n#### 代码简介\n这个小demo实现了一个非常简单的账号管理服务，在./src/account/account.js里提供两个接口:\n1. signup会在系统里创建一个账号\n    - 如果用户名没有被使用，则返回成功\n    - 如果用户名已经被使用过就会返回用户已存在\n2. signin会进行一次账号的登陆验证，如果用户名和密码输入正确就会成功。\n\n根据上述接口定义，在./test/account/test_account\b.js中包含了一个简单的单元测试：\n1. 先注册，然后再用同样信息注册会返回用户已存在;\n2. 用正确的用户密码登陆成功。\n\n\n#### 执行测试\n对服务进行正常的测试命令也很简单：\n```\nbucky run -main ./test/account/test_account.js\n```\n\n测试通过就表示服务已经正常的运行起来了，是不是非常的简单～。\nbucky工具默认情况下把测试代码的日志级别设置为了error, 所以输出比较简洁，如果想查看更多日志可以通过\n`-blog_level level`来设置，例如：\n\n```\nbucky run -main ./test/account/test_account.js -blog_level info\n```\n\n将会打开测试代码的info级别日志，则可以看到许多bucky运行时在本地执行的日志信息。\n\n\n### 进行本地调试\n\n在把代码发布到正式环境之前，我们当然需要进行开发测试。\nbucky框架支持全本地调试模式，可以非常方便的使用喜欢的js debug来调试业务逻辑。\n\n#### 预先准备：\n1. 如果使用了mysql，则需要本地安装mysql。\n2. 如果使用了mongodb，则需要本地安装mongodb。\n3. 如果使用了redis，则需要本地安装redis，并启动。\n4. 使用`bucky config -localdebug`配置对应的用户名、密码。\n\n#### 本地调试：\n```\nbucky debug -main ./test/account/test_account.js\n```\n\n调试命令和运行命令的差别很小，但跑起来的时候并不需要和[buckycloud](buckycloud.com)的云服务进行任何通信，一切都是在本地完成的。\n\n如果想使用Visual Studio Code调试器来调试，可以通过`-vscode`选项获得准备好的配置信息：\n```\nbucky debug -main ./test/account/test_account.js -vscode\n```\n\n该命令会显示Visual Studio Code的调试配置，类似下面\n```\n{\n    \"type\": \"node\",\n    \"request\": \"launch\",\n    \"name\": \"local-debug\",\n    \"program\": \"/usr/local/lib/node_modules/buckyos/tools/node_loader.js\",\n    \"args\": [\n        \"/usr/local/lib/node_modules/buckyos/tools/node_loader.js\",\n        \"-main\",\n        \"/Users/admin/tmp/test/account/test_account.js\",\n        \"-app\",\n        \"/Users/admin/tmp/dist/bucky/bucky_meta.json\",\n        \"-packages_dir\",\n        \"/Users/admin/tmp/dist/bucky\",\n        \"-local_debug\",\n        \"/Users/admin/tmp/dist/bucky/knowledges.json\"\n    ],\n    \"cwd\": \"/Users/admin/tmp/test\"\n}\n```\n\n即使您不习惯使用vscode,也可以通过阅读这个配置，去配置你喜欢的调试器。\n\n### 总结\n通过学习第一个教程，我们对使用bucky框架进行开发的全流程都有了一个基本的体验，下一章我们会对demo里的代码进行分析，进一步的了解如何使用bucky框架。",
      "html": "<h1>Hello Bucky!</h1>\n<p>这个系列教程的目的是用最短的时间让您能快速体验Bucky框架，并对一些基础概念有一些感性的理解。让我们开始吧！第一篇文章我们会完成环境的搭建，并把一个最简单的用户登陆/注册的服务运行起来。</p>\n<h3>准备开发环境</h3>\n<p>安装最新版本的nodejs 8.9.1 LTS，以及npm工具\n在各个操作系统上安装nodejs请参考 <a href=\"https://nodejs.org/en/\">https://nodejs.org/en/</a></p>\n<ul>\n<li>windows: 下载https://nodejs.org/en/ LTS版</li>\n<li>mac: 下载https://nodejs.org/en/ LTS版 或者 brew install nodejs@8.9.1</li>\n</ul>\n<h3>安装buckyos sdk</h3>\n<pre><code>npm install -g buckyos\n</code></pre>\n<h3>得到一个有效的bucky appid</h3>\n<p>在<a href=\"www.buckycloud.com\">www.buckycloud.com</a>登陆bucky账号，然后进入控制面板。\n在应用控制面板左侧点击<code>添加应用</code>，填写应用的友好名称为Demo.\n应用创建成功后在左侧点击应用名，会进入应用的控制面板。可以看到应用的各种信息和统计数据，这里我们只要看一下appid就好了。</p>\n<h3>建立代码目录</h3>\n<pre><code>cd {{你喜欢的目录}}\nmkdir demo\n</code></pre>\n<h3>安装Demo代码</h3>\n<pre><code>cd demo\nbucky init -i\n</code></pre>\n<p>bucky init -i是使用交互式命令行来创建一个bucky解决方案。我们的选项如下</p>\n<pre><code>解决方案类型：\n────────────────────\n1. 示例\n2. 新建解决方案\n\n$请选择解决方案类型[1/2]：\n</code></pre>\n<p>选择1. 创建示例解决方案\n然后在示例列表里</p>\n<pre><code>示例项目列表：\n────────────────────\n1. HelloBucky\n2. minichat\n3. mysql\n\n$选择示例项目序号：\n\n</code></pre>\n<p>继续选择1，创建HelloBucky.</p>\n<p>自此，初始化成功。</p>\n<h3>发布代码准备运行</h3>\n<p>输入命令</p>\n<pre><code>bucky build\nbucky deploy\n</code></pre>\n<p>运行该命令会要求使用开发者账号登录并给项目设置一个有效的appid。\n根据提示输入用户名密码:</p>\n<pre><code>$请输入用户名：example-user\n\n$请输入密码：*****\n</code></pre>\n<p>验证成功后会自动拉取可用的appid列表（appid则需要在<a href=\"www.buckycloud.com\">www.buckycloud.com</a>网站后台预先创建），选择一个输入即可：</p>\n<pre><code>--------------------------------\nall appids for user:example-user\n--------------------------------\n1: appname: Demo , appid: 4w9aAOd3MX\n2: appname: example2 , appid: mxkazUeJ0D\n--------------------------------\n\n\n$请选择app序号：\n</code></pre>\n<p>紧接着就会开始执行完整的构建过程，<code>build</code>实际上是一个复合命令，不带参数-e可以看到该命令实际执行的是下面命令的序列</p>\n<pre><code>bucky compile\nbucky proxy\nbukcy stop\nbucky pub\nbucky start\n</code></pre>\n<p>完成了<code>代码编译</code>，<code>生成代理包</code>，<code>停止应用服务</code>，<code>上传代码</code>，<code>运行服务</code>的一整套操作。熟练后可以根据实际需要单独调用子命令。</p>\n<h3>重置Knowledge</h3>\n<p>每个bucky应用程序有一个全局配置，称之为<code>Knowledge</code>。Knowledge的本地配置默认放在根目录下的knowledge.json。到了这里我们还没有完成运行前的准备工作，我们还需要更新一次应用程序的Knowldege。由于这个操作涉及到应用程序的全局配置的更新，我们提供了单独的命令来操作：</p>\n<pre><code>bucky k -reset\n</code></pre>\n<p>事实上，用户并不需要在每次<code>build</code>之后都重置<code>Knowldege</code>，只有在应用程序的<code>Knowldege</code>配置（默认在根目录下的knolwdeg.json）更新之后，需要重置。</p>\n<p>设置knwoledge成功后就已经做好了运行服务的全部准备工作</p>\n<h3>测试服务</h3>\n<h4>代码简介</h4>\n<p>这个小demo实现了一个非常简单的账号管理服务，在./src/account/account.js里提供两个接口:</p>\n<ol>\n<li>signup会在系统里创建一个账号\n<ul>\n<li>如果用户名没有被使用，则返回成功</li>\n<li>如果用户名已经被使用过就会返回用户已存在</li>\n</ul>\n</li>\n<li>signin会进行一次账号的登陆验证，如果用户名和密码输入正确就会成功。</li>\n</ol>\n<p>根据上述接口定义，在./test/account/test_account\b.js中包含了一个简单的单元测试：</p>\n<ol>\n<li>先注册，然后再用同样信息注册会返回用户已存在;</li>\n<li>用正确的用户密码登陆成功。</li>\n</ol>\n<h4>执行测试</h4>\n<p>对服务进行正常的测试命令也很简单：</p>\n<pre><code>bucky run -main ./test/account/test_account.js\n</code></pre>\n<p>测试通过就表示服务已经正常的运行起来了，是不是非常的简单～。\nbucky工具默认情况下把测试代码的日志级别设置为了error, 所以输出比较简洁，如果想查看更多日志可以通过\n<code>-blog_level level</code>来设置，例如：</p>\n<pre><code>bucky run -main ./test/account/test_account.js -blog_level info\n</code></pre>\n<p>将会打开测试代码的info级别日志，则可以看到许多bucky运行时在本地执行的日志信息。</p>\n<h3>进行本地调试</h3>\n<p>在把代码发布到正式环境之前，我们当然需要进行开发测试。\nbucky框架支持全本地调试模式，可以非常方便的使用喜欢的js debug来调试业务逻辑。</p>\n<h4>预先准备：</h4>\n<ol>\n<li>如果使用了mysql，则需要本地安装mysql。</li>\n<li>如果使用了mongodb，则需要本地安装mongodb。</li>\n<li>如果使用了redis，则需要本地安装redis，并启动。</li>\n<li>使用<code>bucky config -localdebug</code>配置对应的用户名、密码。</li>\n</ol>\n<h4>本地调试：</h4>\n<pre><code>bucky debug -main ./test/account/test_account.js\n</code></pre>\n<p>调试命令和运行命令的差别很小，但跑起来的时候并不需要和<a href=\"buckycloud.com\">buckycloud</a>的云服务进行任何通信，一切都是在本地完成的。</p>\n<p>如果想使用Visual Studio Code调试器来调试，可以通过<code>-vscode</code>选项获得准备好的配置信息：</p>\n<pre><code>bucky debug -main ./test/account/test_account.js -vscode\n</code></pre>\n<p>该命令会显示Visual Studio Code的调试配置，类似下面</p>\n<pre><code>{\n    &quot;type&quot;: &quot;node&quot;,\n    &quot;request&quot;: &quot;launch&quot;,\n    &quot;name&quot;: &quot;local-debug&quot;,\n    &quot;program&quot;: &quot;/usr/local/lib/node_modules/buckyos/tools/node_loader.js&quot;,\n    &quot;args&quot;: [\n        &quot;/usr/local/lib/node_modules/buckyos/tools/node_loader.js&quot;,\n        &quot;-main&quot;,\n        &quot;/Users/admin/tmp/test/account/test_account.js&quot;,\n        &quot;-app&quot;,\n        &quot;/Users/admin/tmp/dist/bucky/bucky_meta.json&quot;,\n        &quot;-packages_dir&quot;,\n        &quot;/Users/admin/tmp/dist/bucky&quot;,\n        &quot;-local_debug&quot;,\n        &quot;/Users/admin/tmp/dist/bucky/knowledges.json&quot;\n    ],\n    &quot;cwd&quot;: &quot;/Users/admin/tmp/test&quot;\n}\n</code></pre>\n<p>即使您不习惯使用vscode,也可以通过阅读这个配置，去配置你喜欢的调试器。</p>\n<h3>总结</h3>\n<p>通过学习第一个教程，我们对使用bucky框架进行开发的全流程都有了一个基本的体验，下一章我们会对demo里的代码进行分析，进一步的了解如何使用bucky框架。</p>\n",
      "id": 1
    },
    {
      "path": "1.Hello,Bucky!/1.2.代码分析.md",
      "url": "1.Hello,Bucky!/1.2.代码分析.html",
      "content": "# Hello,Bucky 代码分析\n\n上一章我们已经成功的把一个完整的bucky应用在云端运行起来了，这一章我们会对这个简单的例子的关键部分进行一些讲解，介绍bucky框架的一些最重要的基础概念。\n\n## 目录结构\n使用bucky命令行工具创建的解决方案(solution)目录的典型结构如下：\n```\n├── .bucky\n│   └── ...\n├── dist\n│   ├── bucky\n│   │   ├── ...\n│   │   ...   \n│   ├── h5\n│   └── wx\n├── knowledges.json\n├── solution.json\n├── src\n│   └── account\n│       ├── account.js\n│       ├── config.json\n│       └── onload.js\n└── test\n    └── test_account.js\n```\n\n下面对上面的文件和目录逐一说明：\n1. `./solution.json`解决方案的配置文件，bucky工具的正确运行需要从这个文件里读取必要的信息。我们鼓励您用bucky工具的配置命令来修改这个文件(其格式也很易读，你可以用任何文本编辑器来编辑)。\n2. `./knowledges.json` knowledge是bucky的一个重要的基础概念，你可以把他想象成“系统的全局配置”。bucky工具创建的该文件里已经包含了系统正常运行所依赖的关键配置，你可以在此基础上添加自己的配置。  \n3. `./src/` HelloBucky的默认项目(project)目录。一个解决方案(solution)下可以有多个项目(project)方便多人协作开发。\n默认情况下bucky的各种build命令都是操作solution里的所有project,如果你有一个特别大的项目，通过划分project可以提高你的开发构建目录。\n4. `./src/account/` 一个典型的bucky package目录，包含应用代码。构建bucky应用的主要工作就是编写各种package.后面的会详细的介绍package。    \n5. `./test/` 用来放各种单元测试代码的目录。我们提倡为每一个package都编写独立的单元测试。\n6. `./.bucky/` 隐藏目录：包含用户配置。这个目录不需要同步到任何代码管理系统。 \n7. `./dist/` 编译输出目录：考虑到javascrpit有多个运行环境要兼容，开发者编写的逻辑代码可能要经过一些编译处理。这个目录包含的是build以后的文件，在运行/调试的时候系统都是加载这里面的文件。由于这个目录的文件都是生成的，所以不应该提交到任何代码管理工具 。修改任何文件后，一定要经过编译的过程(bucky compile)才会生效。我们还在持续提高调试器友好度，有些时候你在调试器里下断点，需要把断点打到这个目录里的文件上。  \n\n如果您已经使用过javascript社区的一些脚手架，会对这个结构有“是曾相识”的感觉。未来随着bucky工具的改进，我们会进一步提升整个工具链与社区常见脚手架工具的互操作能力，让大家能自由选择自己书舒服的工具链。在早期测试版由于时间的关系，我们计划先做好non-depends的开发体验。\n\n## 模块化与应用逻辑\nbucky框架设计的一个重要使用体验就是“简洁直观”，在开发应用的过程中，我们希望工程师的主要关注点在业务逻辑上，而不是如何构建环境并处理各种细节。我们回顾一下我们要实现的需求，就是实现两个接口：\n1. signup会在系统里创建一个账号，如果用户名已经被使用过就会返回失败。\n2. signin会进行一次账号的登陆验证，如果用户名和密码输入正确就会成功。\n\n在动手实现需求之前，我们要考虑的问题只有两个\n1. 模块化怎么设计    \n2. 数据保存在什么地方\n\n模块化是软件开发里最朴素的思想，包含了“分层”、“隔离”、“复用”的多种目的，我们这里就不详细介绍如何进行模块化了（大型系统进行正确的模块划分一直是系统架构领域的关键问题）。毫无疑问，这么简单的需求，只需要一个模块就能实现了。我们给这个模块起名叫\"account\"。\n\n物理上，我们将会创建一个packageID为account的XAR package(简称package). 这个package的包含如下文件：\n```\n├── src\n│   └── account\n│       ├── account.js\n│       ├── config.json\n│       └── onload.js\n```\n\n其中：\n1. `config.json` package的配置文件\n2. `onload.js` package的入口脚本，会在package被加载后自动运行一次\n3. `account.js` 代码文件，又叫bucky module(简称module),一个package中可以包含多个module.\n\n### config.json\nXARPackage的config里包含了两部分信息，“package是什么”，以及“package里的代码运行起来依赖什么”。bucky框架会在将要加载一个包时根据这些信息，分配一个合适的Runtime。\n\n```json\n{\n    //package id,一个app里package id不能重复\n    \"packageID\": \"account\",\n\n    //package 友好版本\n    \"version\": \"1.0.0.0\",\n\n    //package build号，通过package id来加载包一般会加载当前版本，也可以通过指定build号来加载特定版本\n    \"build\": 1,\n\n    //package 希望在什么样的js环境里运行。\n    \"runtimeType\": \"pc_server.bucky\",\n\n    // 描述信息\n    \"meta\": {\n        \"desc\": \"\"\n    },\n\n    //依赖的package,在加载当前package之前，depends里的package要处于已加载状态。\n    \"depends\": [],\n\n    //该XARPackage下导出的模块文件名和对应的相对路径\n    \"modules\": {\n        \"account\": \"account.js\"\n    },\n\n    //package需要使用的“driver”，这里我们要使用mysql\n    \"drivers\": [\"bx.mysql.client\"],\n\n    //package 依赖的knowledges,在加载成功之前系统会自动完成这些knowledge的同步\n    //依赖的knowledges必须在解决方案的knowledges.json里有配置过。\n    \"knowledges\": [\n        {\n            \"key\": \"global.events\",\n            \"type\": 0\n        },\n        {\n            \"key\": \"global.runtimes\",\n            \"type\": 0\n        },\n        {\n            \"key\": \"global.storages\",\n            \"type\": 0\n        },\n        {\n            \"key\": \"global.mysql.instances\",\n            \"type\": 0\n        },\n        {\n            \"key\": \"global.mysql.schemas\",\n            \"type\": 0\n        },\n        {\n            \"key\": \"global.mysql.configs\",\n            \"type\": 0\n        }\n    ]\n}\n```\n需要重点关注的字段有:     \n1. `runtimeType`:格式是 $deviceType.$jsvmType，pc_server.bucky 的意思是在“x86架构的服务器上的bucky容器jsvm里运行”,如果package可以任何runtime里运行（比如一些工具函数组成的package)，可以配置成*。参考API References`->`core.RuntimeType。  \n2. `drivers`: package中会使用的“驱动”资源，参考API References->core.DeviceDriver。 \n3. `depends`: 依赖的packages。\n4. `modules`: package中定义的模块，包含模块名和模块相对路径。\n5. `knowledges`: package中会使用的knowledge，这些knowledges必须是在解决方案的knowledges.json里配置过。\n\n使用bucky add package命令创建包的时候，会通过交互命令行询问这个package需要依赖的资源。\n\n## 使用资源\n任何代码运行都需要计算资源。传统的编程模型通过启动命令先创建了用于代码执行的进程，然后再通过进程获得当前设备上的各种资源（CPU,内存，磁盘，各种IO设备)。进程也作为单机操作系统里最重要的基础概念成为了一个常识。任何系统架构落到实现层面，都可以用“在哪些机器的哪些进程里运行哪些代码”，来详细说明。\n\n我们认为这个常识并不适合分布式系统（通过这个常识进行分布式系统的架构，需要关注的细节太多了），bucky创建了一种新的`分布式系统资源申请与使用`的机制：开发直接编写逻辑代码，在代码里直接申请并使用资源，在代码运行的时候，由云内核把代码dispatch到合适的设备上去运行。这就做到了`计算拓扑无关性`，分布式系统里的各种资源也能更灵活的被使用，调优。\n\n分布式系统中最基础的计算资源是CPU和内存，通过`加载package`的操作触发，由某个合适的`Device`提供，最终代码运行在一个合适的`Runtime`中来使用CPU和内存资源。\n\n分布式系统中的状态持久化需求由`Block`标准资源实现，但直接使用Block资源的目的是用于开发分布式文件系统等中间件，目前还处于测试验证阶段。当前版本我们把一些常见的中件间以非标准资源的方式提供给应用开发者。系统允许通过驱动来访问这些非标准资源。在这个Demo里，我们使用`mysql DB`资源来保存账号信息。当应用申请一个mysql资源的时候，系统会根据应用对mysql资源的配置，自动的创建DB，返回给业务代码访问。\n\n### 配置mysql资源\nbucky要求任何mysql DB资源都必须先配置才能使用。对mysql资源的配置是全局配置，因为分布式系统的所有资源，都可能被所有的XARPackage访问。全局配置按照我们的设计，写在knowledges.json中。内容如下\n```json\n\"global.mysql.instances\": {\n        \"type\": 0,\n        \"object\": {\n            \"db_account\": {\n                \"schema\": \"account_db_schema\"\n            }\n        }\n    },\n    \"global.mysql.schemas\": {\n        \"type\": 0,\n        \"object\": {\n            \"account_db_schema\": {\n                \"onCreate\": [\n                    \"CREATE TABLE IF NOT EXISTS `users` (`username` VARCHAR( 100 ) NOT NULL ,`password` VARCHAR( 100 ) NOT NULL, `userinfo` VARCHAR( 256 ), UNIQUE KEY (username)) ENGINE=InnoDB DEFAULT CHARSET=utf8;\"\n                ]\n            }\n        }\n    },\n    \"global.mysql.configs\": {\n        \"type\": 0,\n        \"object\": {}\n    }\n```\n上面这段配置的含义是：系统里定义了一个id为`db_account`的mysql db instance，这个DB在首次初始化的时候，会根据`account_db_schema`的定义，执行一条创建表的sql语句。\n\n\n## 应用逻辑\n有了上面我们看看signup接口的代码\n```javascript\nconst DB_INSTANCE_ID = \"db_account\";\nvar ErrorCode = bucky.ErrorCode;\n\nfunction signup(username,password,userinfo,onComplete) {\n\n    let thisRuntime = getCurrentRuntime();\n    let mysqlDriver = thisRuntime.getDriver('bx.mysql.client');\n    if(userinfo == null) {\n        userinfo = {};\n    }\n\n    mysqlDriver.getMySQLInstance(DB_INSTANCE_ID, thisRuntime, true, function(ret, mysqlInstance) {\n        let conn = mysqlInstance.createConnection();\n        conn.connect((err) => {\n            if (err) {\n                BX_WARN(\"connect to db failed\",DB_INSTANCE_ID);\n                onComplete(ErrorCode.RESULT_DB_OPEN_FAILED);\n            } else {\n                let sql = \"INSERT INTO users SET username=?, password=?,userinfo=?\";\n                conn.query(sql, [username, password,JSON.stringify(userinfo)], (err, results) => {\n                    conn.end();\n                    if (err == null) {\n                        if (results.affectedRows === 0) {\n                            if (results.code === \"ER_DUP_ENTRY\") {\n                                onComplete(ErrorCode.RESULT_ALREADY_EXIST);\n                            } else {\n                                onComplete(ErrorCode.RESULT_EXCEPTION);\n                            }\n\n                        } else if (results.affectedRows === 1) {\n                            onComplete(ErrorCode.RESULT_OK);\n                        } \n                    } else {\n                        BX_WARN(\"signup failed,username:\" + username,err);\n                        onComplete(ErrorCode.RESULT_EXCEPTION);\n                    }\n                });\n            }\n        });\n    });\n}\n```\n这段代码重点演示了如何从分布式系统中获取一个mysql DB资源，并使用它。使用mysql DB资源来实现注册逻辑的过程非常单纯，如果您有一定mysql经验相信非常容易看懂，就不详细介绍了。这段代码由于使用了mysql资源，所以在运行的时候，系统会把这段代码放到一个能访问mysql资源的服务器上去运行，自动的实现了微服务化。\n\n\n## Proxy与RPC\n当Runtime试图加载一个XARPackage时，系统的加载策略如下：\n\n1. 判断该原始XARPackage是否适合加载在当前Runtime。\n2. 如果不适合，bucky的调度器（Scheduler）会尝试寻找一个已经加载了该XARPackage的Runtime(或则创建一个)来加载原始XARPackage。\n3. 而在当前Runtime里加载的则是一个Proxy XARPackage。\n\n其中，Proxy XARPackage实现了原始 XARPackage中的所有模块和接口，并通过RPC完成对原始包的代理调用：\n\n```javascript\nfunction signup(...rpc_args) {\n    const onComplete = rpc_args[rpc_args.length - 1];\n    bucky.BX_CHECK(typeof onComplete === 'function');\n    rpc_args.pop();\n\n    const thisRuntime = bucky.getCurrentRuntime();\n    thisRuntime.rpcCall(targetPackageInfo, 'account:account::signup', rpc_args, onComplete);\n}\n```\n\n简单说，bucky的调度器（Scheduler）会为不能直接本地运行的目标XARPackage选择一个合适的Runtime加载，然后再发起一个从当前Runtime到目标Runtime的RPC Call。\n\n每一个XARPackage都应该有一个Proxy Package。我们已经在SDK中提供了工具来生成这些Proxy Package。发布代码的时候会自动把包和包的代理都发布到包仓库(Repository)上。这个过程都在构建的过程中完成，详细参考工具链->bucky命令行文档。\n\n>[TIP: 系统不存在魔法]\n>\n>系统不存在魔法，是我们多年坚持的理念，通过阅读Proxy的代码，工程师可以分析调试应用系统。而且系统也允许高级开发者根据需要，手工编写自己的Proxy逻辑。\n\n### 完整的执行流程\n1. 加载XARPackage：\n    1. 通过`Repository`服务，下载`account`这个XARPackage的最新版本。\n    2. 读取`account`的配置文件config.json，判断当前Runtime是否能直接加载account这个包，如果不能直接加载则：\n        - 加载其代理包`account proxy` (proxy包通过框架提供的工具生成）。\n        - 通过`Repository`服务，下载`account proxy`这个XARPackage的最新版本。\n2. 加载module：\n    1. 读取配置文件config.json，加载依赖项(依赖的包，knowledge)。\n    2. 运行package的onload.js，XARPackage加载成功。\n    3. 开始加载模块，通过config.json里的module表查到模块对应的实现文件。\n3. 调用module的导出接口：\n    - 如果加载的是`account`包，那么直接加载模块的实现文件，调用并返回结果。\n    - 如果加载的是`account proxy`包，那么login调用实际上会发与小应用云的调度器通信，调度器会在云端创建一个合适的Runtime加载真正的`account`包，然后再进行PRC。\n\n## 总结\n下一章我们会介绍一个复杂一点的聊天室的Demo, 进一步了解bucky的一些概念和提供的基础分布式系统资源。\n",
      "html": "<h1>Hello,Bucky 代码分析</h1>\n<p>上一章我们已经成功的把一个完整的bucky应用在云端运行起来了，这一章我们会对这个简单的例子的关键部分进行一些讲解，介绍bucky框架的一些最重要的基础概念。</p>\n<h2>目录结构</h2>\n<p>使用bucky命令行工具创建的解决方案(solution)目录的典型结构如下：</p>\n<pre><code>├── .bucky\n│   └── ...\n├── dist\n│   ├── bucky\n│   │   ├── ...\n│   │   ...   \n│   ├── h5\n│   └── wx\n├── knowledges.json\n├── solution.json\n├── src\n│   └── account\n│       ├── account.js\n│       ├── config.json\n│       └── onload.js\n└── test\n    └── test_account.js\n</code></pre>\n<p>下面对上面的文件和目录逐一说明：</p>\n<ol>\n<li><code>./solution.json</code>解决方案的配置文件，bucky工具的正确运行需要从这个文件里读取必要的信息。我们鼓励您用bucky工具的配置命令来修改这个文件(其格式也很易读，你可以用任何文本编辑器来编辑)。</li>\n<li><code>./knowledges.json</code> knowledge是bucky的一个重要的基础概念，你可以把他想象成“系统的全局配置”。bucky工具创建的该文件里已经包含了系统正常运行所依赖的关键配置，你可以在此基础上添加自己的配置。</li>\n<li><code>./src/</code> HelloBucky的默认项目(project)目录。一个解决方案(solution)下可以有多个项目(project)方便多人协作开发。\n默认情况下bucky的各种build命令都是操作solution里的所有project,如果你有一个特别大的项目，通过划分project可以提高你的开发构建目录。</li>\n<li><code>./src/account/</code> 一个典型的bucky package目录，包含应用代码。构建bucky应用的主要工作就是编写各种package.后面的会详细的介绍package。</li>\n<li><code>./test/</code> 用来放各种单元测试代码的目录。我们提倡为每一个package都编写独立的单元测试。</li>\n<li><code>./.bucky/</code> 隐藏目录：包含用户配置。这个目录不需要同步到任何代码管理系统。</li>\n<li><code>./dist/</code> 编译输出目录：考虑到javascrpit有多个运行环境要兼容，开发者编写的逻辑代码可能要经过一些编译处理。这个目录包含的是build以后的文件，在运行/调试的时候系统都是加载这里面的文件。由于这个目录的文件都是生成的，所以不应该提交到任何代码管理工具 。修改任何文件后，一定要经过编译的过程(bucky compile)才会生效。我们还在持续提高调试器友好度，有些时候你在调试器里下断点，需要把断点打到这个目录里的文件上。</li>\n</ol>\n<p>如果您已经使用过javascript社区的一些脚手架，会对这个结构有“是曾相识”的感觉。未来随着bucky工具的改进，我们会进一步提升整个工具链与社区常见脚手架工具的互操作能力，让大家能自由选择自己书舒服的工具链。在早期测试版由于时间的关系，我们计划先做好non-depends的开发体验。</p>\n<h2>模块化与应用逻辑</h2>\n<p>bucky框架设计的一个重要使用体验就是“简洁直观”，在开发应用的过程中，我们希望工程师的主要关注点在业务逻辑上，而不是如何构建环境并处理各种细节。我们回顾一下我们要实现的需求，就是实现两个接口：</p>\n<ol>\n<li>signup会在系统里创建一个账号，如果用户名已经被使用过就会返回失败。</li>\n<li>signin会进行一次账号的登陆验证，如果用户名和密码输入正确就会成功。</li>\n</ol>\n<p>在动手实现需求之前，我们要考虑的问题只有两个</p>\n<ol>\n<li>模块化怎么设计</li>\n<li>数据保存在什么地方</li>\n</ol>\n<p>模块化是软件开发里最朴素的思想，包含了“分层”、“隔离”、“复用”的多种目的，我们这里就不详细介绍如何进行模块化了（大型系统进行正确的模块划分一直是系统架构领域的关键问题）。毫无疑问，这么简单的需求，只需要一个模块就能实现了。我们给这个模块起名叫&quot;account&quot;。</p>\n<p>物理上，我们将会创建一个packageID为account的XAR package(简称package). 这个package的包含如下文件：</p>\n<pre><code>├── src\n│   └── account\n│       ├── account.js\n│       ├── config.json\n│       └── onload.js\n</code></pre>\n<p>其中：</p>\n<ol>\n<li><code>config.json</code> package的配置文件</li>\n<li><code>onload.js</code> package的入口脚本，会在package被加载后自动运行一次</li>\n<li><code>account.js</code> 代码文件，又叫bucky module(简称module),一个package中可以包含多个module.</li>\n</ol>\n<h3>config.json</h3>\n<p>XARPackage的config里包含了两部分信息，“package是什么”，以及“package里的代码运行起来依赖什么”。bucky框架会在将要加载一个包时根据这些信息，分配一个合适的Runtime。</p>\n<pre><code class=\"language-json\">{\n    //package id,一个app里package id不能重复\n    &quot;packageID&quot;: &quot;account&quot;,\n\n    //package 友好版本\n    &quot;version&quot;: &quot;1.0.0.0&quot;,\n\n    //package build号，通过package id来加载包一般会加载当前版本，也可以通过指定build号来加载特定版本\n    &quot;build&quot;: 1,\n\n    //package 希望在什么样的js环境里运行。\n    &quot;runtimeType&quot;: &quot;pc_server.bucky&quot;,\n\n    // 描述信息\n    &quot;meta&quot;: {\n        &quot;desc&quot;: &quot;&quot;\n    },\n\n    //依赖的package,在加载当前package之前，depends里的package要处于已加载状态。\n    &quot;depends&quot;: [],\n\n    //该XARPackage下导出的模块文件名和对应的相对路径\n    &quot;modules&quot;: {\n        &quot;account&quot;: &quot;account.js&quot;\n    },\n\n    //package需要使用的“driver”，这里我们要使用mysql\n    &quot;drivers&quot;: [&quot;bx.mysql.client&quot;],\n\n    //package 依赖的knowledges,在加载成功之前系统会自动完成这些knowledge的同步\n    //依赖的knowledges必须在解决方案的knowledges.json里有配置过。\n    &quot;knowledges&quot;: [\n        {\n            &quot;key&quot;: &quot;global.events&quot;,\n            &quot;type&quot;: 0\n        },\n        {\n            &quot;key&quot;: &quot;global.runtimes&quot;,\n            &quot;type&quot;: 0\n        },\n        {\n            &quot;key&quot;: &quot;global.storages&quot;,\n            &quot;type&quot;: 0\n        },\n        {\n            &quot;key&quot;: &quot;global.mysql.instances&quot;,\n            &quot;type&quot;: 0\n        },\n        {\n            &quot;key&quot;: &quot;global.mysql.schemas&quot;,\n            &quot;type&quot;: 0\n        },\n        {\n            &quot;key&quot;: &quot;global.mysql.configs&quot;,\n            &quot;type&quot;: 0\n        }\n    ]\n}\n</code></pre>\n<p>需要重点关注的字段有:</p>\n<ol>\n<li><code>runtimeType</code>:格式是 $deviceType.$jsvmType，pc_server.bucky 的意思是在“x86架构的服务器上的bucky容器jsvm里运行”,如果package可以任何runtime里运行（比如一些工具函数组成的package)，可以配置成*。参考API References<code>-&gt;</code>core.RuntimeType。</li>\n<li><code>drivers</code>: package中会使用的“驱动”资源，参考API References-&gt;core.DeviceDriver。</li>\n<li><code>depends</code>: 依赖的packages。</li>\n<li><code>modules</code>: package中定义的模块，包含模块名和模块相对路径。</li>\n<li><code>knowledges</code>: package中会使用的knowledge，这些knowledges必须是在解决方案的knowledges.json里配置过。</li>\n</ol>\n<p>使用bucky add package命令创建包的时候，会通过交互命令行询问这个package需要依赖的资源。</p>\n<h2>使用资源</h2>\n<p>任何代码运行都需要计算资源。传统的编程模型通过启动命令先创建了用于代码执行的进程，然后再通过进程获得当前设备上的各种资源（CPU,内存，磁盘，各种IO设备)。进程也作为单机操作系统里最重要的基础概念成为了一个常识。任何系统架构落到实现层面，都可以用“在哪些机器的哪些进程里运行哪些代码”，来详细说明。</p>\n<p>我们认为这个常识并不适合分布式系统（通过这个常识进行分布式系统的架构，需要关注的细节太多了），bucky创建了一种新的<code>分布式系统资源申请与使用</code>的机制：开发直接编写逻辑代码，在代码里直接申请并使用资源，在代码运行的时候，由云内核把代码dispatch到合适的设备上去运行。这就做到了<code>计算拓扑无关性</code>，分布式系统里的各种资源也能更灵活的被使用，调优。</p>\n<p>分布式系统中最基础的计算资源是CPU和内存，通过<code>加载package</code>的操作触发，由某个合适的<code>Device</code>提供，最终代码运行在一个合适的<code>Runtime</code>中来使用CPU和内存资源。</p>\n<p>分布式系统中的状态持久化需求由<code>Block</code>标准资源实现，但直接使用Block资源的目的是用于开发分布式文件系统等中间件，目前还处于测试验证阶段。当前版本我们把一些常见的中件间以非标准资源的方式提供给应用开发者。系统允许通过驱动来访问这些非标准资源。在这个Demo里，我们使用<code>mysql DB</code>资源来保存账号信息。当应用申请一个mysql资源的时候，系统会根据应用对mysql资源的配置，自动的创建DB，返回给业务代码访问。</p>\n<h3>配置mysql资源</h3>\n<p>bucky要求任何mysql DB资源都必须先配置才能使用。对mysql资源的配置是全局配置，因为分布式系统的所有资源，都可能被所有的XARPackage访问。全局配置按照我们的设计，写在knowledges.json中。内容如下</p>\n<pre><code class=\"language-json\">&quot;global.mysql.instances&quot;: {\n        &quot;type&quot;: 0,\n        &quot;object&quot;: {\n            &quot;db_account&quot;: {\n                &quot;schema&quot;: &quot;account_db_schema&quot;\n            }\n        }\n    },\n    &quot;global.mysql.schemas&quot;: {\n        &quot;type&quot;: 0,\n        &quot;object&quot;: {\n            &quot;account_db_schema&quot;: {\n                &quot;onCreate&quot;: [\n                    &quot;CREATE TABLE IF NOT EXISTS `users` (`username` VARCHAR( 100 ) NOT NULL ,`password` VARCHAR( 100 ) NOT NULL, `userinfo` VARCHAR( 256 ), UNIQUE KEY (username)) ENGINE=InnoDB DEFAULT CHARSET=utf8;&quot;\n                ]\n            }\n        }\n    },\n    &quot;global.mysql.configs&quot;: {\n        &quot;type&quot;: 0,\n        &quot;object&quot;: {}\n    }\n</code></pre>\n<p>上面这段配置的含义是：系统里定义了一个id为<code>db_account</code>的mysql db instance，这个DB在首次初始化的时候，会根据<code>account_db_schema</code>的定义，执行一条创建表的sql语句。</p>\n<h2>应用逻辑</h2>\n<p>有了上面我们看看signup接口的代码</p>\n<pre><code class=\"language-javascript\">const DB_INSTANCE_ID = &quot;db_account&quot;;\nvar ErrorCode = bucky.ErrorCode;\n\nfunction signup(username,password,userinfo,onComplete) {\n\n    let thisRuntime = getCurrentRuntime();\n    let mysqlDriver = thisRuntime.getDriver('bx.mysql.client');\n    if(userinfo == null) {\n        userinfo = {};\n    }\n\n    mysqlDriver.getMySQLInstance(DB_INSTANCE_ID, thisRuntime, true, function(ret, mysqlInstance) {\n        let conn = mysqlInstance.createConnection();\n        conn.connect((err) =&gt; {\n            if (err) {\n                BX_WARN(&quot;connect to db failed&quot;,DB_INSTANCE_ID);\n                onComplete(ErrorCode.RESULT_DB_OPEN_FAILED);\n            } else {\n                let sql = &quot;INSERT INTO users SET username=?, password=?,userinfo=?&quot;;\n                conn.query(sql, [username, password,JSON.stringify(userinfo)], (err, results) =&gt; {\n                    conn.end();\n                    if (err == null) {\n                        if (results.affectedRows === 0) {\n                            if (results.code === &quot;ER_DUP_ENTRY&quot;) {\n                                onComplete(ErrorCode.RESULT_ALREADY_EXIST);\n                            } else {\n                                onComplete(ErrorCode.RESULT_EXCEPTION);\n                            }\n\n                        } else if (results.affectedRows === 1) {\n                            onComplete(ErrorCode.RESULT_OK);\n                        } \n                    } else {\n                        BX_WARN(&quot;signup failed,username:&quot; + username,err);\n                        onComplete(ErrorCode.RESULT_EXCEPTION);\n                    }\n                });\n            }\n        });\n    });\n}\n</code></pre>\n<p>这段代码重点演示了如何从分布式系统中获取一个mysql DB资源，并使用它。使用mysql DB资源来实现注册逻辑的过程非常单纯，如果您有一定mysql经验相信非常容易看懂，就不详细介绍了。这段代码由于使用了mysql资源，所以在运行的时候，系统会把这段代码放到一个能访问mysql资源的服务器上去运行，自动的实现了微服务化。</p>\n<h2>Proxy与RPC</h2>\n<p>当Runtime试图加载一个XARPackage时，系统的加载策略如下：</p>\n<ol>\n<li>判断该原始XARPackage是否适合加载在当前Runtime。</li>\n<li>如果不适合，bucky的调度器（Scheduler）会尝试寻找一个已经加载了该XARPackage的Runtime(或则创建一个)来加载原始XARPackage。</li>\n<li>而在当前Runtime里加载的则是一个Proxy XARPackage。</li>\n</ol>\n<p>其中，Proxy XARPackage实现了原始 XARPackage中的所有模块和接口，并通过RPC完成对原始包的代理调用：</p>\n<pre><code class=\"language-javascript\">function signup(...rpc_args) {\n    const onComplete = rpc_args[rpc_args.length - 1];\n    bucky.BX_CHECK(typeof onComplete === 'function');\n    rpc_args.pop();\n\n    const thisRuntime = bucky.getCurrentRuntime();\n    thisRuntime.rpcCall(targetPackageInfo, 'account:account::signup', rpc_args, onComplete);\n}\n</code></pre>\n<p>简单说，bucky的调度器（Scheduler）会为不能直接本地运行的目标XARPackage选择一个合适的Runtime加载，然后再发起一个从当前Runtime到目标Runtime的RPC Call。</p>\n<p>每一个XARPackage都应该有一个Proxy Package。我们已经在SDK中提供了工具来生成这些Proxy Package。发布代码的时候会自动把包和包的代理都发布到包仓库(Repository)上。这个过程都在构建的过程中完成，详细参考工具链-&gt;bucky命令行文档。</p>\n<blockquote>\n<p>[TIP: 系统不存在魔法]</p>\n<p>系统不存在魔法，是我们多年坚持的理念，通过阅读Proxy的代码，工程师可以分析调试应用系统。而且系统也允许高级开发者根据需要，手工编写自己的Proxy逻辑。</p>\n</blockquote>\n<h3>完整的执行流程</h3>\n<ol>\n<li>加载XARPackage：\n<ol>\n<li>通过<code>Repository</code>服务，下载<code>account</code>这个XARPackage的最新版本。</li>\n<li>读取<code>account</code>的配置文件config.json，判断当前Runtime是否能直接加载account这个包，如果不能直接加载则：\n<ul>\n<li>加载其代理包<code>account proxy</code> (proxy包通过框架提供的工具生成）。</li>\n<li>通过<code>Repository</code>服务，下载<code>account proxy</code>这个XARPackage的最新版本。</li>\n</ul>\n</li>\n</ol>\n</li>\n<li>加载module：\n<ol>\n<li>读取配置文件config.json，加载依赖项(依赖的包，knowledge)。</li>\n<li>运行package的onload.js，XARPackage加载成功。</li>\n<li>开始加载模块，通过config.json里的module表查到模块对应的实现文件。</li>\n</ol>\n</li>\n<li>调用module的导出接口：\n<ul>\n<li>如果加载的是<code>account</code>包，那么直接加载模块的实现文件，调用并返回结果。</li>\n<li>如果加载的是<code>account proxy</code>包，那么login调用实际上会发与小应用云的调度器通信，调度器会在云端创建一个合适的Runtime加载真正的<code>account</code>包，然后再进行PRC。</li>\n</ul>\n</li>\n</ol>\n<h2>总结</h2>\n<p>下一章我们会介绍一个复杂一点的聊天室的Demo, 进一步了解bucky的一些概念和提供的基础分布式系统资源。</p>\n",
      "id": 2
    },
    {
      "path": "1.Hello,Bucky!/1.3.聊天室demo.md",
      "url": "1.Hello,Bucky!/1.3.聊天室demo.html",
      "content": "# 聊天室Demo\n\n### 实现一个最简单的聊天室，需求如下：\n\n* 全局只有一个房间\n* 支持查看历史\n* 支持实时推送\n\n用传统方式实现，一般是nginx/apache+php/nodejs+mysql/mongo+redis+websocket,对于一个经验丰富的后端开发来说，这很容易。但对于一个新手来说，这些技术点每个都要熟悉几天，满满的挫败感。就算可以跑起来，还有后面复杂的运维。\n\n用bucky来实现这些就容易很多。bucky提供的基础设施，让开发者不用考虑nginx/apache+php/nodejs+redis+websocket，只要熟悉一点简单的mongo即可。整个后端，不用搭任何服务，不到40行代码搞定。前端实现也更简单同样不需要40行，还包括操作UI。\n\n### 这里用到了bucky的两个功能\n\n* GlobalEvent 实现跨设备推送，简单易用，不用关心网络拓扑结构\n* Mongo资源 数据持久化存储\n\n### 准备工作\n* 登陆buckycloud.com后台创建一个新应用，起名叫minichat\n\n### 创建项目\n\n创建一个空目录:\n```bash\nmkdir minichat\n```\n\n在此目录执行`bucky init -i`，选择新建解决方案(solution):\n\n```bash\n◎ init bucky...\n◎ install npm packages...\n\n解决方案类型：\n────────────────────\n1. 示例\n2. 新建解决方案\n\n$请选择解决方案类型[1/2]：2\n```\n\n接着输入要创建的项目(project)相对子路径，此处我们创建一个服务端project:\n\n```bash\n◎ 添加新项目...\n\n请输入项目相对子路径(i.e: src/test)：src/server\n```\n\n接着选择项目类型，此处有三种选择，我们选择默认的第一种`bucky项目（后台）`，后面两种后面再介绍，此处先忽略。\n\n```bash\n◎ 选择项目类型...\n\n请选择项目类型：\n────────────────────\n1. bucky项目（后台）\n2. HTML5（前端）\n3. 微信小程序（前端）\n\n$请输入序号[1/2/3]：1\n```\n\n接着在项目目录下创建新的`XARPackage`，输入包名，选择新建package:\n\n```bash\n◎ 添加新package到项目src....\n\n$请输入package名字：minichat\n\n选择package类型：\n────────────────────\n1. 新建package\n2. 示例package\n\n$请输入序号：1\n```\n\n因为要用到mongo资源，下面在配置中要显式指定\n\n```bash\n请问这个包需要限制在什么运行时(runtime)加载么？\n────────────────────\n1. 只允许前端（不能使用mysql/redis/mongo驱动）\n2. 只允许后端（可以使用mysql/redis/mongo驱动）\n3. 没有限制（不能使用mysql/redis/mongo驱动，一般是工具包）\n\n$请输入序号：2\n\n$请问这个包需要使用mysql资源么? [y/n]: n\n\n$请问这个包需要使用redis资源么? [y/n]: n\n\n$请问这个包需要使用mongo资源么? [y/n]: y\n\n注意：如果有更多修改，请阅读文档并修改包目录下的config.json\n位置：src/minichat/config.json\n```\n\n提示是否继续创建同项目下的package。目前，我们的服务端只需要创建一个package就够：\n\n```bash\n$继续添加package？ [y/n]: n\n```\n\n提示是否继续创建其他项目。我们需要继续创建前端H5项目：\n```bash\n$继续添加项目？ [y/n]: y\n\n◎ 添加新项目...\n\n请输入项目相对子路径(i.e: src/test)：src/client\n\n◎ 选择项目类型...\n\n请选择项目类型：\n────────────────────\n1. bucky项目（后台）\n2. HTML5（前端）\n3. 微信小程序（前端）\n\n$请输入序号[1/2/3]：2\n\n[+]add: /test/minichat/src/client\n[+]add: /test/minichat/src/client/.gitkeep\n[+]add: /test/publish/minichat/src/client/css\n[+]add: /test/minichat/src/client/img\n[+]add: /test/minichat/src/client/index.html\n[+]add: /test/minichat/src/client/js\n[+]add: /test/minichat/src/client/css/main.css\n[+]add: /test/minichat/src/client/img/bkg.jpg\n[+]add: /test/minichat/src/client/js/app.js\n[+]add: /test/minichat/src/client/js/main.js\n\n[+]add: /test/minichat/src/client/js/h5_core.js\n[+]add: /test/minichat/src/client/js/h5_ld_core.js\n```\n\n然后结束项目创建：\n```bash\n$继续添加项目？ [y/n]: n\n\n解决方案初始化成功，请尝试后续操作：\n1. 使用下面的命令编译：\n    - bucky build \n2. 使用下面的命令部署到巴克云\n    - bucky deploy\n3. 配置konwledges.json并重置knowldege：\n    - bucky k -reset\n4. 请按照文档添加包调用的测试代码（或参考test/目录下为示例package生成的测试代码）：\n5. 使用下面的命令运行测试代码或者本地调试：\n    - bucky run -main ${path_to_test_file}\n    - bucky debug -main ${path_to_test_file}\n\n\ndone.\n─────────────\n```\n\n打开编辑器，可以看到到目前为止的目录结构：\n```\n├── dist\n│   ├── bucky\n│   ├── h5\n│   └── wx\n├── knowledges.json\n├── solution.json\n├── src\n│   ├── client\n│   │   ├── css\n│   │   │   └── main.css\n│   │   ├── img\n│   │   │   └── bkg.jpg\n│   │   ├── index.html\n│   │   └── js\n│   │       ├── app.js\n│   │       ├── h5_core.js\n│   │       ├── h5_ld_core.js\n│   │       └── main.js\n│   └── server\n│       └── minichat\n│           ├── config.json\n│           ├── minichat.js\n│           └── onload.js\n└── test\n    └── minichat\n```\n\n\n### 服务端逻辑\n\n\n#### 功能实现\n\n首先，修改`knowledges.json`，增加mongo instance配置 ，只有配置了instance在代码中才可以使用。\n\n```json\n\"global.mongo.instances\": {\n  \"type\": 0,\n  \"object\": {\n  \t\"chatroom\": {}\n  }\n}\n```\n\n其次，编辑`src/server/minichat/minichat.js`，添加如下的代码：\n\n```javascript\nasync function _fireMessage(username, message, time) {\n  const category = 'chatroom';\n  const em = getCurrentRuntime().getGlobalEventManager();\n\n  if (!em.queryExist(category)) {\n  \tawait em.create(category);\n  }\n\n  await em.activeEvent(category, 'message', { username, message, time });\n}\n\nasync function _getMongoDB() {\n  const runtime = getCurrentRuntime();\n  const mongoDriver = runtime.getDriver(\"bx.mongo.client\");\n  let [err, mongo] = await mongoDriver.getMongoInstance(\"chatroom\", runtime, true);\n  if (err !== 0) {\n  \t[err, mongo] = await mongoDriver.getMongoInstance(\"chatroom\", runtime, false);\n  }\n\n  const [_, db] = await mongo.connect();\n  return db;\n}\n\nasync function getMessageList() {\n  const db = await _getMongoDB();\n  const messages = await db.collection('message').find({}).toArray();\n  db.close();\n  return [messages];\n}\n\nasync function putMessage(username, message) {\n  const time = Date.now();\n  const db = await _getMongoDB();\n  const ret = await db.collection('message').insertOne({username, message, time});\n  db.close();\n  await _fireMessage(username, message, time);\n  return [ret.result];\n}\n\nmodule.exports = {\n  putMessage,\n  getMessageList\n};\n```\n\n简单介绍下实现，putMessage和getMessageList是客户端调用的接口。功能是发一条消息和获取聊天历史记录。\n- `putMessage` 第一步获取mongo instance，将新消息插入到数据库中，然后fire一次有新消息的事件。\n- `getMessageList` 比较简单，就是把所有的历史记录查询返回。\n- `_getMongoDB` 获取mongo db对象，用于操作mongo。注意getMongoInstance的第一个参数要和knowledges.json中的配置一致\n- `_fireMessage` 将新消息推送到客户端\n\n最后，执行构建（按需提示输入用户名和密码，注意选择app的时候选择前面在官网后台创建的`minichat`）和重置knowledge动作：\n\n```bash\nbucky build\nbucky deploy\nbucky k -reset\n```\n\n则服务端代码已经自动部署完毕，下面我们可以为该模块编写简单的单元测试。\n\n#### 单元测试 \n\n在`test/minichat`目录下新建一个`test.js`，添加测试逻辑：\n\n```javascript\n// test.js\nconst readline = require('readline');\n\nconst rl = readline.createInterface({\n  input: process.stdin,\n  output: process.stdout\n});\n\n\n//\n// global variables, received messges\n//\nlet g_receivedMessages=[];\nlet g_showMessages = false;\n\n/**\n * do question\n * @param  {[type]} title [description]\n * @return {[type]}       [description]\n */\nasync function _question(title) {\n  return new Promise((resolve) => {\n    rl.question(title, (answer) => {\n      resolve(answer)\n    });\n  });\n}\n\n/**\n * attach global event\n * @return {[type]} [description]\n */\nasync function _attachEvent() {\n  const eventCategory = 'chatroom';\n  const eventManager = getCurrentRuntime().getGlobalEventManager();\n\n  // if global 'chatroom' event not exist, create a new one\n  if (!eventManager.queryExist(eventCategory)) {\n      await eventManager.create(eventCategory);\n  }\n\n  // attach to listen the 'chatroom' global event\n  eventManager.attach(eventCategory, 'message', ({ username, message, time }) =>{\n    let msg = `${new Date(time)} ${username}说:${message}`;   \n    g_receivedMessages.push(msg); \n  }); \n}\n\n/**\n * put message and read message loop\n * @param  {[type]} minichat [description]\n * @param  {[type]} username [description]\n * @return {[type]}          [description]\n */\nasync function _processMessageLoop(minichat, username){\n  if(g_showMessages){\n    // read message\n    return new Promise((resolve, reject) => {\n        setTimeout(()=>{\n          for(let msg of g_receivedMessages){\n            console.log(msg);\n          }\n\n          // if receive messages not empty, \n          // switch to put message again\n          g_showMessages = g_receivedMessages.length===0;\n          g_receivedMessages = [];\n          resolve();\n        },1);  \n    });\n  }else{\n    // put message\n    const message = await _question(`${username}:`);\n    await minichat.putMessage(username, message);\n\n    // switch to read message\n    g_showMessages = true;\n\n    if (message === 'bye'){\n      process.exit(0); \n    }\n  }\n}\n\n/**\n * 1. input username\n * 2. show message history\n * 3. run process message loop\n * @return {[type]} [description]\n */\nasync function _questionMessagesLoop(){\n  const [_, minichat] = await getCurrentRuntime().loadModule('minichat:minichat');\n  console.log({ minichat });\n\n  // input username\n  const [messages] = await minichat.getMessageList();\n  const username = await _question('请输入昵称：');\n\n  // show history \n  console.log('----------------');\n  console.log('message history:');\n  console.log('----------------');\n  messages.forEach(({ username, message, time }) =>{\n    console.log(`${new Date(time)} ${username}说:${message}`)}\n  );\n\n  // quesion loop\n  while(true){\n    await _processMessageLoop(minichat,username);\n  }\n}\n\n/**\n * main funciton\n * @return {[type]} [description]\n */\nasync function main() {\n  console.log('enter main...');\n  await _attachEvent();\n  await _questionMessagesLoop();\n}\n\n// start\nmain();\n```\n\n执行下面命令，看看是否正常工作，可以反复输入聊天内容。\n\n```bash\nbucky run -main test/minichat/test.js\n```\n\n### 前端代码\n\n#### 布局\n\n打开`src/client/index.html`，在`<body>`标签内添加布局代码：\n```html\n<div id=\"container\">\n  <textarea id=\"chat-area\" class=\"form-control\"></textarea>\n  <div class=\"input-send\">\n    <div class=\"input-group\">\n      <span class=\"input-group-addon\" id=\"basic-addon1\">@</span>\n      <input type=\"text\" class=\"form-control\" id=\"nickname\" placeholder=\"昵称\" aria-describedby=\"basic-addon1\">\n    </div>\n    <div class=\"input-group\">\n      <input id=\"sendtxt\" type=\"text\" class=\"form-control\" placeholder=\"聊天内容\">\n      <span class=\"input-group-btn\">\n      <button id=\"submit\" class=\"btn btn-default\" type=\"button\">Send</button>\n      </span>\n    </div>\n  </div>\n  \n  <!-- /input-group -->\n\n</div>\n```\n\n打开`src/client/css/main.css`，添加如下的CSS代码：\n```css\n.input-send {\n  display: flex;\n  flex-direction: column;\n}\n\n.input-send .input-group {\n  margin-top: 8px;\n}\n\n#chat-area {\n  flex: 1;\n}\n```\n\n#### 交互\n\n打开`src/client/js/main.js`，添加页面交互代码：\n```javascript\nfunction append(line) {\n    const textarea = $('#chat-area');\n    textarea.append(line + '\\n');\n    if(textarea.length)\n        textarea.scrollTop(textarea[0].scrollHeight - textarea.height());\n}\n\nasync function init() {\n    let app = new bucky.Application();\n    bucky.setCurrentApp(app);\n\n    const [err, metaInfo] = await app.init(appMetaInfo);\n    bucky.initCurrentRuntime(app);\n}\n\nasync function _attachEvent() {\n    const eventCategory = 'chatroom';\n    const eventManager = bucky.getCurrentRuntime().getGlobalEventManager();\n\n    if (!eventManager.queryExist(eventCategory)) {\n        await eventManager.create(eventCategory);\n    }\n\n    eventManager.attach(eventCategory, 'message', ({username, message, time}) => \n        append(`${moment(time).format('YYYY/MM/DD HH:mm:SS')} ${username} 说: ${message}`));\n}\n\nasync function main() {\n    await init();\n    await _attachEvent();\n    const [_, minichat] = await bucky.getCurrentRuntime().loadModule('minichat:minichat');\n\n    const [messages] = await minichat.getMessageList();\n    messages.forEach(({ username, message, time }) =>\n        append(`${moment(time).format('YYYY/MM/DD HH:mm:SS')} ${username} 说: ${message}`));\n\n    $('#submit').click(async () => {\n        minichat.putMessage($('#nickname').val(), $('#sendtxt').val());\n        $('#sendtxt').val('');\n    });\n}\n\nmain();\n```\n\n下面简单介绍下实现\n\nmain是入口函数，主要做了\n\n1. 初始化bucky\n2. 关注新消息事件\n3. 拉取历史消息并展示\n4. 发送按钮点击实现\n\n这里可以看到调用了两个服务器端的导出函数putMessage和getMessageList。就像调用本地函数一样。框架自动完成RPC。（RPC的目标服务器是buckycloud.com,所以要求浏览器支持跨域CORS访问）接收服务器的推送也很简单，attach事件即可。\n\n#### 设置AppID\n\n编辑`src/client/js/app.js`，修改`${appid}`为当前选择的应用的appid，或者可以通过再次执行构建命令自动设置：\n```\nbucky build\nbucky deploy\n```\n\n#### 测试\n\n使用chrome打开`src/client/index.html`，即可看到聊天界面，输入昵称和信息，点击发送开始测试。\n\n_**上面介绍的所有代码，都在sdk的demos/minichat里**_\n\n### 关于async/await\n\n代码中用了很多的async/await，这个是ES7标准引入的。作用是 使得异步操作变得更加方便\n\nasync/await 在node8、主流浏览器中都已经支持(不包括IE)。具体版本支持情况，可参考 [async-functions](https://caniuse.com/#feat=async-functions)\n\nasync/await 使用介绍可参考 [MDN文档](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)\n\n##### bucky中使用async/await的注意事项\n\n```javascript\nasync function exportFunction() {\n\treturn [result];\n}\n\nmodule.exports = { exportFunction };\n```\n\n定义声明async类型的导出函数\n\n```javascript\nconst [result, err, errStack] = await targetModule.exportFunction();\n```\n\n使用async类型的导出函数。\n\n- result：返回值\n- err: RPC错误码\n- errStack: 如果目标函数执行发生脚本错误，errStack会保存错误堆栈\n\n---\n\n### 如何部署\n\n1. 申请域名，并备案。\n2. 购买云主机，并将域名dns指向云主机外网ip\n3. 将html目录上传至服务器，/opt/www\n4. 切换到/opt/www，执行\n\n```bash\n[root@VM_116_61_centos www]# python -m SimpleHTTPServer 80\n```\n\n如果需要https，还需要购买ssl证书\n\n---\n\n### 为何bucky不提供静态页面托管服务\n\n因为在国内，对于提供互联网服务的人或组织，需要实名和监管。\n\n",
      "html": "<h1>聊天室Demo</h1>\n<h3>实现一个最简单的聊天室，需求如下：</h3>\n<ul>\n<li>全局只有一个房间</li>\n<li>支持查看历史</li>\n<li>支持实时推送</li>\n</ul>\n<p>用传统方式实现，一般是nginx/apache+php/nodejs+mysql/mongo+redis+websocket,对于一个经验丰富的后端开发来说，这很容易。但对于一个新手来说，这些技术点每个都要熟悉几天，满满的挫败感。就算可以跑起来，还有后面复杂的运维。</p>\n<p>用bucky来实现这些就容易很多。bucky提供的基础设施，让开发者不用考虑nginx/apache+php/nodejs+redis+websocket，只要熟悉一点简单的mongo即可。整个后端，不用搭任何服务，不到40行代码搞定。前端实现也更简单同样不需要40行，还包括操作UI。</p>\n<h3>这里用到了bucky的两个功能</h3>\n<ul>\n<li>GlobalEvent 实现跨设备推送，简单易用，不用关心网络拓扑结构</li>\n<li>Mongo资源 数据持久化存储</li>\n</ul>\n<h3>准备工作</h3>\n<ul>\n<li>登陆buckycloud.com后台创建一个新应用，起名叫minichat</li>\n</ul>\n<h3>创建项目</h3>\n<p>创建一个空目录:</p>\n<pre><code class=\"language-bash\">mkdir minichat\n</code></pre>\n<p>在此目录执行<code>bucky init -i</code>，选择新建解决方案(solution):</p>\n<pre><code class=\"language-bash\">◎ init bucky...\n◎ install npm packages...\n\n解决方案类型：\n────────────────────\n1. 示例\n2. 新建解决方案\n\n$请选择解决方案类型[1/2]：2\n</code></pre>\n<p>接着输入要创建的项目(project)相对子路径，此处我们创建一个服务端project:</p>\n<pre><code class=\"language-bash\">◎ 添加新项目...\n\n请输入项目相对子路径(i.e: src/test)：src/server\n</code></pre>\n<p>接着选择项目类型，此处有三种选择，我们选择默认的第一种<code>bucky项目（后台）</code>，后面两种后面再介绍，此处先忽略。</p>\n<pre><code class=\"language-bash\">◎ 选择项目类型...\n\n请选择项目类型：\n────────────────────\n1. bucky项目（后台）\n2. HTML5（前端）\n3. 微信小程序（前端）\n\n$请输入序号[1/2/3]：1\n</code></pre>\n<p>接着在项目目录下创建新的<code>XARPackage</code>，输入包名，选择新建package:</p>\n<pre><code class=\"language-bash\">◎ 添加新package到项目src....\n\n$请输入package名字：minichat\n\n选择package类型：\n────────────────────\n1. 新建package\n2. 示例package\n\n$请输入序号：1\n</code></pre>\n<p>因为要用到mongo资源，下面在配置中要显式指定</p>\n<pre><code class=\"language-bash\">请问这个包需要限制在什么运行时(runtime)加载么？\n────────────────────\n1. 只允许前端（不能使用mysql/redis/mongo驱动）\n2. 只允许后端（可以使用mysql/redis/mongo驱动）\n3. 没有限制（不能使用mysql/redis/mongo驱动，一般是工具包）\n\n$请输入序号：2\n\n$请问这个包需要使用mysql资源么? [y/n]: n\n\n$请问这个包需要使用redis资源么? [y/n]: n\n\n$请问这个包需要使用mongo资源么? [y/n]: y\n\n注意：如果有更多修改，请阅读文档并修改包目录下的config.json\n位置：src/minichat/config.json\n</code></pre>\n<p>提示是否继续创建同项目下的package。目前，我们的服务端只需要创建一个package就够：</p>\n<pre><code class=\"language-bash\">$继续添加package？ [y/n]: n\n</code></pre>\n<p>提示是否继续创建其他项目。我们需要继续创建前端H5项目：</p>\n<pre><code class=\"language-bash\">$继续添加项目？ [y/n]: y\n\n◎ 添加新项目...\n\n请输入项目相对子路径(i.e: src/test)：src/client\n\n◎ 选择项目类型...\n\n请选择项目类型：\n────────────────────\n1. bucky项目（后台）\n2. HTML5（前端）\n3. 微信小程序（前端）\n\n$请输入序号[1/2/3]：2\n\n[+]add: /test/minichat/src/client\n[+]add: /test/minichat/src/client/.gitkeep\n[+]add: /test/publish/minichat/src/client/css\n[+]add: /test/minichat/src/client/img\n[+]add: /test/minichat/src/client/index.html\n[+]add: /test/minichat/src/client/js\n[+]add: /test/minichat/src/client/css/main.css\n[+]add: /test/minichat/src/client/img/bkg.jpg\n[+]add: /test/minichat/src/client/js/app.js\n[+]add: /test/minichat/src/client/js/main.js\n\n[+]add: /test/minichat/src/client/js/h5_core.js\n[+]add: /test/minichat/src/client/js/h5_ld_core.js\n</code></pre>\n<p>然后结束项目创建：</p>\n<pre><code class=\"language-bash\">$继续添加项目？ [y/n]: n\n\n解决方案初始化成功，请尝试后续操作：\n1. 使用下面的命令编译：\n    - bucky build \n2. 使用下面的命令部署到巴克云\n    - bucky deploy\n3. 配置konwledges.json并重置knowldege：\n    - bucky k -reset\n4. 请按照文档添加包调用的测试代码（或参考test/目录下为示例package生成的测试代码）：\n5. 使用下面的命令运行测试代码或者本地调试：\n    - bucky run -main ${path_to_test_file}\n    - bucky debug -main ${path_to_test_file}\n\n\ndone.\n─────────────\n</code></pre>\n<p>打开编辑器，可以看到到目前为止的目录结构：</p>\n<pre><code>├── dist\n│   ├── bucky\n│   ├── h5\n│   └── wx\n├── knowledges.json\n├── solution.json\n├── src\n│   ├── client\n│   │   ├── css\n│   │   │   └── main.css\n│   │   ├── img\n│   │   │   └── bkg.jpg\n│   │   ├── index.html\n│   │   └── js\n│   │       ├── app.js\n│   │       ├── h5_core.js\n│   │       ├── h5_ld_core.js\n│   │       └── main.js\n│   └── server\n│       └── minichat\n│           ├── config.json\n│           ├── minichat.js\n│           └── onload.js\n└── test\n    └── minichat\n</code></pre>\n<h3>服务端逻辑</h3>\n<h4>功能实现</h4>\n<p>首先，修改<code>knowledges.json</code>，增加mongo instance配置 ，只有配置了instance在代码中才可以使用。</p>\n<pre><code class=\"language-json\">&quot;global.mongo.instances&quot;: {\n  &quot;type&quot;: 0,\n  &quot;object&quot;: {\n  \t&quot;chatroom&quot;: {}\n  }\n}\n</code></pre>\n<p>其次，编辑<code>src/server/minichat/minichat.js</code>，添加如下的代码：</p>\n<pre><code class=\"language-javascript\">async function _fireMessage(username, message, time) {\n  const category = 'chatroom';\n  const em = getCurrentRuntime().getGlobalEventManager();\n\n  if (!em.queryExist(category)) {\n  \tawait em.create(category);\n  }\n\n  await em.activeEvent(category, 'message', { username, message, time });\n}\n\nasync function _getMongoDB() {\n  const runtime = getCurrentRuntime();\n  const mongoDriver = runtime.getDriver(&quot;bx.mongo.client&quot;);\n  let [err, mongo] = await mongoDriver.getMongoInstance(&quot;chatroom&quot;, runtime, true);\n  if (err !== 0) {\n  \t[err, mongo] = await mongoDriver.getMongoInstance(&quot;chatroom&quot;, runtime, false);\n  }\n\n  const [_, db] = await mongo.connect();\n  return db;\n}\n\nasync function getMessageList() {\n  const db = await _getMongoDB();\n  const messages = await db.collection('message').find({}).toArray();\n  db.close();\n  return [messages];\n}\n\nasync function putMessage(username, message) {\n  const time = Date.now();\n  const db = await _getMongoDB();\n  const ret = await db.collection('message').insertOne({username, message, time});\n  db.close();\n  await _fireMessage(username, message, time);\n  return [ret.result];\n}\n\nmodule.exports = {\n  putMessage,\n  getMessageList\n};\n</code></pre>\n<p>简单介绍下实现，putMessage和getMessageList是客户端调用的接口。功能是发一条消息和获取聊天历史记录。</p>\n<ul>\n<li><code>putMessage</code> 第一步获取mongo instance，将新消息插入到数据库中，然后fire一次有新消息的事件。</li>\n<li><code>getMessageList</code> 比较简单，就是把所有的历史记录查询返回。</li>\n<li><code>_getMongoDB</code> 获取mongo db对象，用于操作mongo。注意getMongoInstance的第一个参数要和knowledges.json中的配置一致</li>\n<li><code>_fireMessage</code> 将新消息推送到客户端</li>\n</ul>\n<p>最后，执行构建（按需提示输入用户名和密码，注意选择app的时候选择前面在官网后台创建的<code>minichat</code>）和重置knowledge动作：</p>\n<pre><code class=\"language-bash\">bucky build\nbucky deploy\nbucky k -reset\n</code></pre>\n<p>则服务端代码已经自动部署完毕，下面我们可以为该模块编写简单的单元测试。</p>\n<h4>单元测试</h4>\n<p>在<code>test/minichat</code>目录下新建一个<code>test.js</code>，添加测试逻辑：</p>\n<pre><code class=\"language-javascript\">// test.js\nconst readline = require('readline');\n\nconst rl = readline.createInterface({\n  input: process.stdin,\n  output: process.stdout\n});\n\n\n//\n// global variables, received messges\n//\nlet g_receivedMessages=[];\nlet g_showMessages = false;\n\n/**\n * do question\n * @param  {[type]} title [description]\n * @return {[type]}       [description]\n */\nasync function _question(title) {\n  return new Promise((resolve) =&gt; {\n    rl.question(title, (answer) =&gt; {\n      resolve(answer)\n    });\n  });\n}\n\n/**\n * attach global event\n * @return {[type]} [description]\n */\nasync function _attachEvent() {\n  const eventCategory = 'chatroom';\n  const eventManager = getCurrentRuntime().getGlobalEventManager();\n\n  // if global 'chatroom' event not exist, create a new one\n  if (!eventManager.queryExist(eventCategory)) {\n      await eventManager.create(eventCategory);\n  }\n\n  // attach to listen the 'chatroom' global event\n  eventManager.attach(eventCategory, 'message', ({ username, message, time }) =&gt;{\n    let msg = `${new Date(time)} ${username}说:${message}`;   \n    g_receivedMessages.push(msg); \n  }); \n}\n\n/**\n * put message and read message loop\n * @param  {[type]} minichat [description]\n * @param  {[type]} username [description]\n * @return {[type]}          [description]\n */\nasync function _processMessageLoop(minichat, username){\n  if(g_showMessages){\n    // read message\n    return new Promise((resolve, reject) =&gt; {\n        setTimeout(()=&gt;{\n          for(let msg of g_receivedMessages){\n            console.log(msg);\n          }\n\n          // if receive messages not empty, \n          // switch to put message again\n          g_showMessages = g_receivedMessages.length===0;\n          g_receivedMessages = [];\n          resolve();\n        },1);  \n    });\n  }else{\n    // put message\n    const message = await _question(`${username}:`);\n    await minichat.putMessage(username, message);\n\n    // switch to read message\n    g_showMessages = true;\n\n    if (message === 'bye'){\n      process.exit(0); \n    }\n  }\n}\n\n/**\n * 1. input username\n * 2. show message history\n * 3. run process message loop\n * @return {[type]} [description]\n */\nasync function _questionMessagesLoop(){\n  const [_, minichat] = await getCurrentRuntime().loadModule('minichat:minichat');\n  console.log({ minichat });\n\n  // input username\n  const [messages] = await minichat.getMessageList();\n  const username = await _question('请输入昵称：');\n\n  // show history \n  console.log('----------------');\n  console.log('message history:');\n  console.log('----------------');\n  messages.forEach(({ username, message, time }) =&gt;{\n    console.log(`${new Date(time)} ${username}说:${message}`)}\n  );\n\n  // quesion loop\n  while(true){\n    await _processMessageLoop(minichat,username);\n  }\n}\n\n/**\n * main funciton\n * @return {[type]} [description]\n */\nasync function main() {\n  console.log('enter main...');\n  await _attachEvent();\n  await _questionMessagesLoop();\n}\n\n// start\nmain();\n</code></pre>\n<p>执行下面命令，看看是否正常工作，可以反复输入聊天内容。</p>\n<pre><code class=\"language-bash\">bucky run -main test/minichat/test.js\n</code></pre>\n<h3>前端代码</h3>\n<h4>布局</h4>\n<p>打开<code>src/client/index.html</code>，在<code>&lt;body&gt;</code>标签内添加布局代码：</p>\n<pre><code class=\"language-html\">&lt;div id=&quot;container&quot;&gt;\n  &lt;textarea id=&quot;chat-area&quot; class=&quot;form-control&quot;&gt;&lt;/textarea&gt;\n  &lt;div class=&quot;input-send&quot;&gt;\n    &lt;div class=&quot;input-group&quot;&gt;\n      &lt;span class=&quot;input-group-addon&quot; id=&quot;basic-addon1&quot;&gt;@&lt;/span&gt;\n      &lt;input type=&quot;text&quot; class=&quot;form-control&quot; id=&quot;nickname&quot; placeholder=&quot;昵称&quot; aria-describedby=&quot;basic-addon1&quot;&gt;\n    &lt;/div&gt;\n    &lt;div class=&quot;input-group&quot;&gt;\n      &lt;input id=&quot;sendtxt&quot; type=&quot;text&quot; class=&quot;form-control&quot; placeholder=&quot;聊天内容&quot;&gt;\n      &lt;span class=&quot;input-group-btn&quot;&gt;\n      &lt;button id=&quot;submit&quot; class=&quot;btn btn-default&quot; type=&quot;button&quot;&gt;Send&lt;/button&gt;\n      &lt;/span&gt;\n    &lt;/div&gt;\n  &lt;/div&gt;\n  \n  &lt;!-- /input-group --&gt;\n\n&lt;/div&gt;\n</code></pre>\n<p>打开<code>src/client/css/main.css</code>，添加如下的CSS代码：</p>\n<pre><code class=\"language-css\">.input-send {\n  display: flex;\n  flex-direction: column;\n}\n\n.input-send .input-group {\n  margin-top: 8px;\n}\n\n#chat-area {\n  flex: 1;\n}\n</code></pre>\n<h4>交互</h4>\n<p>打开<code>src/client/js/main.js</code>，添加页面交互代码：</p>\n<pre><code class=\"language-javascript\">function append(line) {\n    const textarea = $('#chat-area');\n    textarea.append(line + '\\n');\n    if(textarea.length)\n        textarea.scrollTop(textarea[0].scrollHeight - textarea.height());\n}\n\nasync function init() {\n    let app = new bucky.Application();\n    bucky.setCurrentApp(app);\n\n    const [err, metaInfo] = await app.init(appMetaInfo);\n    bucky.initCurrentRuntime(app);\n}\n\nasync function _attachEvent() {\n    const eventCategory = 'chatroom';\n    const eventManager = bucky.getCurrentRuntime().getGlobalEventManager();\n\n    if (!eventManager.queryExist(eventCategory)) {\n        await eventManager.create(eventCategory);\n    }\n\n    eventManager.attach(eventCategory, 'message', ({username, message, time}) =&gt; \n        append(`${moment(time).format('YYYY/MM/DD HH:mm:SS')} ${username} 说: ${message}`));\n}\n\nasync function main() {\n    await init();\n    await _attachEvent();\n    const [_, minichat] = await bucky.getCurrentRuntime().loadModule('minichat:minichat');\n\n    const [messages] = await minichat.getMessageList();\n    messages.forEach(({ username, message, time }) =&gt;\n        append(`${moment(time).format('YYYY/MM/DD HH:mm:SS')} ${username} 说: ${message}`));\n\n    $('#submit').click(async () =&gt; {\n        minichat.putMessage($('#nickname').val(), $('#sendtxt').val());\n        $('#sendtxt').val('');\n    });\n}\n\nmain();\n</code></pre>\n<p>下面简单介绍下实现</p>\n<p>main是入口函数，主要做了</p>\n<ol>\n<li>初始化bucky</li>\n<li>关注新消息事件</li>\n<li>拉取历史消息并展示</li>\n<li>发送按钮点击实现</li>\n</ol>\n<p>这里可以看到调用了两个服务器端的导出函数putMessage和getMessageList。就像调用本地函数一样。框架自动完成RPC。（<a href=\"http://xn--RPCbuckycloud-dk4vs35boq5d0cdtxfp74guzb.com\">RPC的目标服务器是buckycloud.com</a>,所以要求浏览器支持跨域CORS访问）接收服务器的推送也很简单，attach事件即可。</p>\n<h4>设置AppID</h4>\n<p>编辑<code>src/client/js/app.js</code>，修改<code>${appid}</code>为当前选择的应用的appid，或者可以通过再次执行构建命令自动设置：</p>\n<pre><code>bucky build\nbucky deploy\n</code></pre>\n<h4>测试</h4>\n<p>使用chrome打开<code>src/client/index.html</code>，即可看到聊天界面，输入昵称和信息，点击发送开始测试。</p>\n<p><em><strong>上面介绍的所有代码，都在sdk的demos/minichat里</strong></em></p>\n<h3>关于async/await</h3>\n<p>代码中用了很多的async/await，这个是ES7标准引入的。作用是 使得异步操作变得更加方便</p>\n<p>async/await 在node8、主流浏览器中都已经支持(不包括IE)。具体版本支持情况，可参考 <a href=\"https://caniuse.com/#feat=async-functions\">async-functions</a></p>\n<p>async/await 使用介绍可参考 <a href=\"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function\">MDN文档</a></p>\n<h5>bucky中使用async/await的注意事项</h5>\n<pre><code class=\"language-javascript\">async function exportFunction() {\n\treturn [result];\n}\n\nmodule.exports = { exportFunction };\n</code></pre>\n<p>定义声明async类型的导出函数</p>\n<pre><code class=\"language-javascript\">const [result, err, errStack] = await targetModule.exportFunction();\n</code></pre>\n<p>使用async类型的导出函数。</p>\n<ul>\n<li>result：返回值</li>\n<li>err: RPC错误码</li>\n<li>errStack: 如果目标函数执行发生脚本错误，errStack会保存错误堆栈</li>\n</ul>\n<hr>\n<h3>如何部署</h3>\n<ol>\n<li>申请域名，并备案。</li>\n<li>购买云主机，并将域名dns指向云主机外网ip</li>\n<li>将html目录上传至服务器，/opt/www</li>\n<li>切换到/opt/www，执行</li>\n</ol>\n<pre><code class=\"language-bash\">[root@VM_116_61_centos www]# python -m SimpleHTTPServer 80\n</code></pre>\n<p>如果需要https，还需要购买ssl证书</p>\n<hr>\n<h3>为何bucky不提供静态页面托管服务</h3>\n<p>因为在国内，对于提供互联网服务的人或组织，需要实名和监管。</p>\n",
      "id": 3
    },
    {
      "path": "1.Hello,Bucky!/1.4.自己动手添加些新功能.md",
      "url": "1.Hello,Bucky!/1.4.自己动手添加些新功能.html",
      "content": "# 自己动手添加些新功能\n\n通过运行前面的HelloBucky项目，以及聊天室Demo的讲解，想必你已经迫不及待想要在项目里增加点新的代码\n来熟悉bucky框架。本节设计了一些新的需求，请聪明的你自己动手添加些新功能来满足这些需求，Learning by doing!\n\n### 功能1：用户登陆\n\n在第一节`让Demo跑起来`的时候，实现了一个简单的用户模块，轻松跑起来了HelloBucky项目，包含两个功能：\n- 用户注册\n- 用户登陆\n\n在第二节我们又通过简洁的代码实现了一个简易聊天程序，包含如下三个基本功能：\n- 输入用户名\n- 发送消息\n- 接收消息并显示在对话框里\n\n那么，作为一个正式的产品，我们希望把用户模块添加到聊天程序里。思考10秒钟之后，直接动手拷贝文件夹？\n等等，如果你什么都不做就动手拷贝，那么该如何编辑solution.json。别急，使用bucky命令行工具，\n我们可以轻松导入一个XARPackage到已有项目。\n\n1. 拷贝HelloBucky的`src/account`文件夹到minichat的`src/server/`目录下。\n2. 拷贝HelloBucky的`test/account`文件夹到minichat的`test/`目录下。\n3. 在minichat根目录下执行如下的命令来自动导入src/server项目下的新包：\n```\nbucky add -package src/server/account/\n```\n4. 重新构建：\n```\nbucky build\nbucky deploy\n```\n5. 对account模块做下单元测试：\n```\nbucky run -main test/account/test_account.js\n```\n\n不出意外的话，可以看到测试...失败了！可以看到错误日志：\n```\n[error],[2017-12-14 16:20:12.264],<@f4346679-a53b-49d1-89f1-a0539ab1dc39@1@2>,rpc call complete with error, pkgid=account, func=account:account::signup, statusCode=502, errcode=13, result= null, node_core.js:10952\n```\n\n发起了RPC失败，怎么回事呢？\n\n### Knowledges: 配置并重置\n\n回顾一下HelloBucky的配置过程，`account`包是依赖了mysql资源的，我们在HelloBucky的讲解中配置了mysql的knowledge，但是前面导入\n到minichat的时候，忘记了这个配置哦。\n\n编辑`kowledges.json`，配置下account依赖的mysql：\n```javascript\n\"global.mysql.instances\": {\n        \"type\": 0,\n        \"object\": {\n            \"db_account\": {\n                \"schema\": \"account_db_schema\"\n            }\n        }\n    },\n    \"global.mysql.schemas\": {\n        \"type\": 0,\n        \"object\": {\n            \"account_db_schema\": {\n                \"onCreate\": [\n                    \"CREATE TABLE IF NOT EXISTS `users` (`username` VARCHAR( 100 ) NOT NULL ,`password` VARCHAR( 100 ) NOT NULL, `userinfo` VARCHAR( 256 ), UNIQUE KEY (username)) ENGINE=InnoDB DEFAULT CHARSET=utf8;\"\n                ]\n            }\n        }\n    },\n    \"global.mysql.configs\": {\n        \"type\": 0,\n        \"object\": {}\n    }\n```\n\n修改了knowleges.json后要重置它：\n```\nbucky k -reset\n```\n\n再次测试：\n```\nbucky run -main test/account/test_account.js\n```\n\n可以看到测试成功了。\n\n### 作业1：使用account模块\n\n我们已经把account模块整合到minichat的后端模块里，发布并测试通过，那么，可以进一步完成需求：\n1. 使用account模块在HTML5前端代码里实现注册/登陆功能。\n2. 为account模块增加判断用户是否存在的函数并导出。\n3. 在minichat模块里对用户进行校验，不存在的用户，忽略其发送的信息。\n\n在这个作业里，你可以充分练习：\n1. 修改源代码\n2. 一键构建\n3. 执行测试\n\n### 功能2：实现一个四则运算机器人\n\n经过上面的练习，想必你已经对`修改->构建->测试`的流程很熟悉了，本节我们将学习添加一个全新模块的过程。\n\n输入下面的命令，进入交互式环境。\n```\nbucky add -i\n```\n\n选择对`src/server`项目操作：\n```\n◎ init bucky...\n◎ install npm packages...\n\n请选择要操作的项目：\n────────────────────\n1. 新建项目\n2. src/server\n\n$请输入序号：2\n```\n\n然后，按提示创建calc包：\n```\n◎ 添加新package到项目src/server....\n\n$请输入package名字：calc\n```\n\n接着，选择package类型，由于`示例package`就是一个除法计算的例子，我们直接选择2:\n```\n选择package类型：\n────────────────────\n1. 新建package\n2. 示例package\n\n$请输入序号：2\n```\n\n接着，选择package运行的环境，我们的计算器不需要用到数据库，选择3:\n```\n请问这个包需要限制在什么运行时(runtime)加载么？\n────────────────────\n1. 只允许前端（不能使用mysql/redis/mongo驱动）\n2. 只允许后端（可以使用mysql/redis/mongo驱动）\n3. 没有限制（不能使用mysql/redis/mongo驱动，一般是工具包）\n\n$请输入序号：3\n```\n\n然后退出继续创建：\n```\n$继续添加package？ [y/n]: n\n```\n\n完成，可以看到分别添加了源代码和测试目录：\n- src/server/calc\n- test/calc/\n\n现在，我们构建并测试下：\n```\nbucky build\nbucky deploy\nbucky run -main test/calc/test_calc.js\n```\n\n可以看到除法计算的结果：\n```\n---------\ntest result:\n---------\n10/2=5\n```\n\ncalc这个包默认只实现了一个div函数，请实现以下作业：\n\n### 作业2：实现四则运算calc功能，并完成四则运算聊天机器人\n\n1. 在calc模块中完整实现四则运算函数：calc，能计算四则运算，并做错误处理。\n2. 编写单元测试代码，并测试。\n3. 在minichat模块里自动检测表达式，并计算结果后推送。\n4. 在HTML聊天窗口里测试四则运算聊天机器人能力。\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",
      "html": "<h1>自己动手添加些新功能</h1>\n<p>通过运行前面的HelloBucky项目，以及聊天室Demo的讲解，想必你已经迫不及待想要在项目里增加点新的代码\n来熟悉bucky框架。本节设计了一些新的需求，请聪明的你自己动手添加些新功能来满足这些需求，Learning by doing!</p>\n<h3>功能1：用户登陆</h3>\n<p>在第一节<code>让Demo跑起来</code>的时候，实现了一个简单的用户模块，轻松跑起来了HelloBucky项目，包含两个功能：</p>\n<ul>\n<li>用户注册</li>\n<li>用户登陆</li>\n</ul>\n<p>在第二节我们又通过简洁的代码实现了一个简易聊天程序，包含如下三个基本功能：</p>\n<ul>\n<li>输入用户名</li>\n<li>发送消息</li>\n<li>接收消息并显示在对话框里</li>\n</ul>\n<p>那么，作为一个正式的产品，我们希望把用户模块添加到聊天程序里。思考10秒钟之后，直接动手拷贝文件夹？\n等等，如果你什么都不做就动手拷贝，那么该如何编辑solution.json。别急，使用bucky命令行工具，\n我们可以轻松导入一个XARPackage到已有项目。</p>\n<ol>\n<li>拷贝HelloBucky的<code>src/account</code>文件夹到minichat的<code>src/server/</code>目录下。</li>\n<li>拷贝HelloBucky的<code>test/account</code>文件夹到minichat的<code>test/</code>目录下。</li>\n<li>在minichat根目录下执行如下的命令来自动导入src/server项目下的新包：</li>\n</ol>\n<pre><code>bucky add -package src/server/account/\n</code></pre>\n<ol start=\"4\">\n<li>重新构建：</li>\n</ol>\n<pre><code>bucky build\nbucky deploy\n</code></pre>\n<ol start=\"5\">\n<li>对account模块做下单元测试：</li>\n</ol>\n<pre><code>bucky run -main test/account/test_account.js\n</code></pre>\n<p>不出意外的话，可以看到测试…失败了！可以看到错误日志：</p>\n<pre><code>[error],[2017-12-14 16:20:12.264],&lt;@f4346679-a53b-49d1-89f1-a0539ab1dc39@1@2&gt;,rpc call complete with error, pkgid=account, func=account:account::signup, statusCode=502, errcode=13, result= null, node_core.js:10952\n</code></pre>\n<p>发起了RPC失败，怎么回事呢？</p>\n<h3>Knowledges: 配置并重置</h3>\n<p>回顾一下HelloBucky的配置过程，<code>account</code>包是依赖了mysql资源的，我们在HelloBucky的讲解中配置了mysql的knowledge，但是前面导入\n到minichat的时候，忘记了这个配置哦。</p>\n<p>编辑<code>kowledges.json</code>，配置下account依赖的mysql：</p>\n<pre><code class=\"language-javascript\">&quot;global.mysql.instances&quot;: {\n        &quot;type&quot;: 0,\n        &quot;object&quot;: {\n            &quot;db_account&quot;: {\n                &quot;schema&quot;: &quot;account_db_schema&quot;\n            }\n        }\n    },\n    &quot;global.mysql.schemas&quot;: {\n        &quot;type&quot;: 0,\n        &quot;object&quot;: {\n            &quot;account_db_schema&quot;: {\n                &quot;onCreate&quot;: [\n                    &quot;CREATE TABLE IF NOT EXISTS `users` (`username` VARCHAR( 100 ) NOT NULL ,`password` VARCHAR( 100 ) NOT NULL, `userinfo` VARCHAR( 256 ), UNIQUE KEY (username)) ENGINE=InnoDB DEFAULT CHARSET=utf8;&quot;\n                ]\n            }\n        }\n    },\n    &quot;global.mysql.configs&quot;: {\n        &quot;type&quot;: 0,\n        &quot;object&quot;: {}\n    }\n</code></pre>\n<p>修改了knowleges.json后要重置它：</p>\n<pre><code>bucky k -reset\n</code></pre>\n<p>再次测试：</p>\n<pre><code>bucky run -main test/account/test_account.js\n</code></pre>\n<p>可以看到测试成功了。</p>\n<h3>作业1：使用account模块</h3>\n<p>我们已经把account模块整合到minichat的后端模块里，发布并测试通过，那么，可以进一步完成需求：</p>\n<ol>\n<li>使用account模块在HTML5前端代码里实现注册/登陆功能。</li>\n<li>为account模块增加判断用户是否存在的函数并导出。</li>\n<li>在minichat模块里对用户进行校验，不存在的用户，忽略其发送的信息。</li>\n</ol>\n<p>在这个作业里，你可以充分练习：</p>\n<ol>\n<li>修改源代码</li>\n<li>一键构建</li>\n<li>执行测试</li>\n</ol>\n<h3>功能2：实现一个四则运算机器人</h3>\n<p>经过上面的练习，想必你已经对<code>修改-&gt;构建-&gt;测试</code>的流程很熟悉了，本节我们将学习添加一个全新模块的过程。</p>\n<p>输入下面的命令，进入交互式环境。</p>\n<pre><code>bucky add -i\n</code></pre>\n<p>选择对<code>src/server</code>项目操作：</p>\n<pre><code>◎ init bucky...\n◎ install npm packages...\n\n请选择要操作的项目：\n────────────────────\n1. 新建项目\n2. src/server\n\n$请输入序号：2\n</code></pre>\n<p>然后，按提示创建calc包：</p>\n<pre><code>◎ 添加新package到项目src/server....\n\n$请输入package名字：calc\n</code></pre>\n<p>接着，选择package类型，由于<code>示例package</code>就是一个除法计算的例子，我们直接选择2:</p>\n<pre><code>选择package类型：\n────────────────────\n1. 新建package\n2. 示例package\n\n$请输入序号：2\n</code></pre>\n<p>接着，选择package运行的环境，我们的计算器不需要用到数据库，选择3:</p>\n<pre><code>请问这个包需要限制在什么运行时(runtime)加载么？\n────────────────────\n1. 只允许前端（不能使用mysql/redis/mongo驱动）\n2. 只允许后端（可以使用mysql/redis/mongo驱动）\n3. 没有限制（不能使用mysql/redis/mongo驱动，一般是工具包）\n\n$请输入序号：3\n</code></pre>\n<p>然后退出继续创建：</p>\n<pre><code>$继续添加package？ [y/n]: n\n</code></pre>\n<p>完成，可以看到分别添加了源代码和测试目录：</p>\n<ul>\n<li>src/server/calc</li>\n<li>test/calc/</li>\n</ul>\n<p>现在，我们构建并测试下：</p>\n<pre><code>bucky build\nbucky deploy\nbucky run -main test/calc/test_calc.js\n</code></pre>\n<p>可以看到除法计算的结果：</p>\n<pre><code>---------\ntest result:\n---------\n10/2=5\n</code></pre>\n<p>calc这个包默认只实现了一个div函数，请实现以下作业：</p>\n<h3>作业2：实现四则运算calc功能，并完成四则运算聊天机器人</h3>\n<ol>\n<li>在calc模块中完整实现四则运算函数：calc，能计算四则运算，并做错误处理。</li>\n<li>编写单元测试代码，并测试。</li>\n<li>在minichat模块里自动检测表达式，并计算结果后推送。</li>\n<li>在HTML聊天窗口里测试四则运算聊天机器人能力。</li>\n</ol>\n",
      "id": 4
    },
    {
      "path": "1.Hello,Bucky!/1.5.问题诊断与调试.md",
      "url": "1.Hello,Bucky!/1.5.问题诊断与调试.html",
      "content": "\n在单机程序的开发中，单步调试的能力对于程序的开发中的除错是十分重要的。然而，在分布式系统下，单步调试变的难以使用，许多情况下程序员都只能依赖日志进行除错。\n\n本节介绍bucky的问题诊断与调试技巧。bucky创新性的以call chain作为分布式系统里类似进程一样的抽象，并基于call chain提供了易于使用的日志系统。同时，bucky也提供了全本地模式的开发支持，让程序员们可以实现`All in one`的调试\n体验。更近一步，bucky还提供了远程调试的能力。多种方式的调试/除错支持，让使用bucky开发高可用分布式系统如虎添翼。\n\n### 日志查看\n\n登陆 www.buckycloud.com 后进入app控制台，可以查看app对应的如下信息：\n- 资源监控\n- 全局knowledges信息，点击`k管理`查看。\n- 已经发布到bucky cloud的XARPackage信息，点击`r管理`查看。\n- 日志工具，包括\n  - 崩溃日志\n  - 实时日志\n  - 历史日志\n\n### 本地调试\n\nbucky支持全本地模式调试`XARPackage`代码。\n\n首先，需要对使用的数据库提供本地支持。\n1. 如果使用了mysql，则需要本地安装mysql。\n2. 如果使用了mongodb，则需要本地安装mongodb。\n3. 如果使用了redis，则需要本地安装redis，并启动。\n4. 使用`bucky config -localdebug`配置对应的用户名、密码。\n\n其次，使用如下命令本地执行代码：\n```\nbucky debug -main test/account/test_account.js\n```\n\n如果想使用Visual Studio Code调试器来调试，可以通过`-vscode`选项获得准备好的配置信息：\n```\nbucky debug -main test/account/test_account.js -vscode\n```\n\n可以得到如下的Visual Studio Code配置参数：\n```\n{\n    \"type\": \"node\",\n    \"request\": \"launch\",\n    \"name\": \"local-debug\",\n    \"program\": \"/usr/local/lib/node_modules/buckyos/tools/node_loader.js\",\n    \"args\": [\n        \"/usr/local/lib/node_modules/buckyos/tools/node_loader.js\",\n        \"-main\",\n        \"/Users/admin/tmp/test/account/test_account.js\",\n        \"-app\",\n        \"/Users/admin/tmp/dist/bucky/bucky_meta.json\",\n        \"-packages_dir\",\n        \"/Users/admin/tmp/dist/bucky\",\n        \"-local_debug\",\n        \"/Users/admin/tmp/dist/bucky/knowledges.json\"\n    ],\n    \"cwd\": \"/Users/admin/tmp/test\"\n}\n```\n\n使用Visual Studio Code打开HelloBucky目录，配置launch.json的configurations节点，添加上述配置后，即可F5本地调试代码，\n可以在src/account/account.js里下断点。\n\n### 远程调试\n\n// bucky即将提供直接线上远程调试的能力！敬请期待！\n\n\n",
      "html": "<p>在单机程序的开发中，单步调试的能力对于程序的开发中的除错是十分重要的。然而，在分布式系统下，单步调试变的难以使用，许多情况下程序员都只能依赖日志进行除错。</p>\n<p>本节介绍bucky的问题诊断与调试技巧。bucky创新性的以call chain作为分布式系统里类似进程一样的抽象，并基于call chain提供了易于使用的日志系统。同时，bucky也提供了全本地模式的开发支持，让程序员们可以实现<code>All in one</code>的调试\n体验。更近一步，bucky还提供了远程调试的能力。多种方式的调试/除错支持，让使用bucky开发高可用分布式系统如虎添翼。</p>\n<h3>日志查看</h3>\n<p>登陆 <a href=\"http://www.buckycloud.com\">www.buckycloud.com</a> 后进入app控制台，可以查看app对应的如下信息：</p>\n<ul>\n<li>资源监控</li>\n<li>全局knowledges信息，点击<code>k管理</code>查看。</li>\n<li>已经发布到bucky cloud的XARPackage信息，点击<code>r管理</code>查看。</li>\n<li>日志工具，包括\n<ul>\n<li>崩溃日志</li>\n<li>实时日志</li>\n<li>历史日志</li>\n</ul>\n</li>\n</ul>\n<h3>本地调试</h3>\n<p>bucky支持全本地模式调试<code>XARPackage</code>代码。</p>\n<p>首先，需要对使用的数据库提供本地支持。</p>\n<ol>\n<li>如果使用了mysql，则需要本地安装mysql。</li>\n<li>如果使用了mongodb，则需要本地安装mongodb。</li>\n<li>如果使用了redis，则需要本地安装redis，并启动。</li>\n<li>使用<code>bucky config -localdebug</code>配置对应的用户名、密码。</li>\n</ol>\n<p>其次，使用如下命令本地执行代码：</p>\n<pre><code>bucky debug -main test/account/test_account.js\n</code></pre>\n<p>如果想使用Visual Studio Code调试器来调试，可以通过<code>-vscode</code>选项获得准备好的配置信息：</p>\n<pre><code>bucky debug -main test/account/test_account.js -vscode\n</code></pre>\n<p>可以得到如下的Visual Studio Code配置参数：</p>\n<pre><code>{\n    &quot;type&quot;: &quot;node&quot;,\n    &quot;request&quot;: &quot;launch&quot;,\n    &quot;name&quot;: &quot;local-debug&quot;,\n    &quot;program&quot;: &quot;/usr/local/lib/node_modules/buckyos/tools/node_loader.js&quot;,\n    &quot;args&quot;: [\n        &quot;/usr/local/lib/node_modules/buckyos/tools/node_loader.js&quot;,\n        &quot;-main&quot;,\n        &quot;/Users/admin/tmp/test/account/test_account.js&quot;,\n        &quot;-app&quot;,\n        &quot;/Users/admin/tmp/dist/bucky/bucky_meta.json&quot;,\n        &quot;-packages_dir&quot;,\n        &quot;/Users/admin/tmp/dist/bucky&quot;,\n        &quot;-local_debug&quot;,\n        &quot;/Users/admin/tmp/dist/bucky/knowledges.json&quot;\n    ],\n    &quot;cwd&quot;: &quot;/Users/admin/tmp/test&quot;\n}\n</code></pre>\n<p>使用Visual Studio Code打开HelloBucky目录，配置launch.json的configurations节点，添加上述配置后，即可F5本地调试代码，\n可以在src/account/account.js里下断点。</p>\n<h3>远程调试</h3>\n<p>// bucky即将提供直接线上远程调试的能力！敬请期待！</p>\n",
      "id": 5
    },
    {
      "path": "2.构架/2.1.整体架构介绍.md",
      "url": "2.构架/2.1.整体架构介绍.html",
      "content": "# 整体架构介绍\n\nBucky的定位是下一代分布式操作系统，目标是：\n\n1. 让分布式应用系统的开发更简单,开发调试和接近单机\n2. 不需要处理系统运维（Serverless)\n3. 根据系统负载自动分配资源，降低系统运行的成本\n4. 磨平开发应用系统与核心系统的\b技能障碍\n5. 使用一种方法开发全平台的优秀UI\n6. 扩展应用能覆盖的拓扑边界\n\n# 基本概念介绍\n\n## Application\n在Bucky的概念里，一个`分布式应用系统`被称作一个Application.用旧的概念来看，Application包括了 前端的App(iOS,Android,Web)以及后台的各种服务。目前Bucky只涵盖了后端逻辑的开发,未来会在现有的基础上延生对前端的支持。\n\n一个Application的生命周期的长度超越了传统的`进程`，实际上其生命周期的表达只是一个状态，用来表示`系统是否在线`。即使系统里没有任何活动的代码，只要系统处于在线状态，那么Application就处于“活”的状态。\n\n大部分的代码在运行的时候都需要知道当前所在的Application,单从开发的角度来说，需要的只是一个用来初始化的appid,以及做某些高权限操作需要的token。\n\n## XARPackage\nXARPackage是在Bucky中代码的物理组织形式。从属于Application的逻辑代码，都应该放在一个合适的XARPackage中。这反映了程序开发活动中最朴素的模块化的概念。\n\nXARPackage里包含的js文件以`module`的概念被XARPackage持有。但加载XARPackage除了会默认运行一次onload.js外，并不会加载任何内部的模块。这样可以通过按需加载的方法，提升性能。\n\nXARPackage的config里包含了两部分信息，“**package是什么**”，以及“**package里的代码运行起来依赖什么**”。在将要加载一个包时，系统会根据这些信息，分配一个合适的Runtime。\n\n一个XARPackage的典型目录结构如下：\n```\n└── account\n    ├── account.js\n    ├── config.json\n    └── onload.js\n```\n\n其中`account`是该XARPackage的名字，而下面包含三个典型文件：\n- `config.json`是XARPackage的配置文件，定义了该XARPackage的元数据信息、包含的module、依赖的驱动、依赖的其他XARPackage、依赖的knowledges等。\n- `onload.js`是XARPackage被一个Runtime加载时会运行的初始化代码，默认什么都不做。\n- `account.js`是该XARPackage的一个module，一个module必须在config.json/modules项里配置。\n\n我们可以看一个config.json的典型配置来初步感受下：\n```javascript\n{\n\t// 元数据信息\n    \"packageID\": \"account\",\n    \"build\": 1,\n    \"runtimeType\": \"pc_server.bucky\",\n    \"meta\": {\n        \"desc\": \"\"\n    },\n\n    // 依赖的其他XARPackage配置\n    \"depends\": [],\n\n    // 该XARPackage包含module配置\n    \"modules\": {\n        \"account\": \"account.js\"\n    },\n\n    // 依赖的驱动(Driver)配置\n    \"drivers\": [\"bx.mysql.client\"],\n\n    // 依赖的knowledges配置，参考下文`Knowledge`介绍。\n    \"knowledges\": [\n        ...\n    ]\n}\n```\n\n## module\n加载XARPackage成功后，就可以从包中加载代码文件了。包里的代码文件我们称作module。应用开发的日常工作，就是把代码填进各个不同的module里。\n\n一个典型的module的代码如下，基本上和nodejs的module写法一致：\n```javascript\n\"use strict\";\n\nfunction md5(str,onComplete) {\n\tconsole.log(\"md5:\" + str);\n\tonComplete(str);\n}\n\nfunction foo(str) {\n\tconsole.log(\"foo:\" + str);\t\n}\n\nmodule.exports = {};\nmodule.exports.md5 = md5;\n```\n\n在这个module中只导出了一个接口函数md5,而foo是内部函数，只能在module内部使用。Bucky框架会经常做本地包/远程包的透明调度，所以要求被导出的函数的最后一个参数`必须`是`完成函数`，也就是说，导出的函数默认是`异步`的。\n\n\n> **[Remark]**: 发布/加载 \n>一个处于开发状态的XARPackage只有被发布到Repository服务器上后才能按正常流程加载。所以在开始正式测试前，请不要忘记运行发布代码。bucky支持`全本地加载`模式，能使用常见的nodejs调试器在本地快速的调试后端代码逻辑，极大的提升开发效率。\n\n## Runtime\n\nRuntime在Bucky的设计中是一个非常重要的概念，是代码运行的容器，一个容器我们称做一个Runtime Instance。其中RuntimeInstance、Applicaton、XARPackage之间的关系如下：\n- 一个Runtime Instance 只属于一个Application。\n- 一个处于运行状态的Application,可能会有多个属于该Application的RuntimeInstance。\n- 一个RuntimeInstance可以加载同Application的不同XARPackage。\n\n传统的开发模型要求**先准备好容器，再加载正确的代码**，而Bucky框架的一个核心理念是**为代码找到合适的运行容器**。我们希望让开发者专注于开发业务逻辑，上传代码到小应用云后，由小应用云的调度器在合适的时机创建合适的Runtime来运行开发者编写的代码 。这样就能让逻辑代码尽可能的与分布式系统的计算拓扑的细节解耦，同时提供近似单机开发一般的分布式系统开发体验。\n\n应用在前端创建的Runtime通常都是`client runtime`,或则被称作`匿名Runtime`。在不同的js运行时下初始化client runtime的方法就是各种initCurrentRuntime()。应用开发可以使用全局函数getCurrentRuntime()方法来得到一个当前Runtime Instance。\n\nRuntime从实现上看很像一个虚拟机，由自己独立的沙盒环境（包括Cache,Storage），并且系统提供了接口 pause/resume 一个指定的 Runtime，方便bucky内核在不同的物理设备之间迁移。\n\n### Driver\nBucky通过Driver来使用非bucky框架内的功能。一个XARPackage内的代码如需使用某些driver，必须在其配置文件（config.json）中声明，这样bucky的调度器才有机会把该XARPackage调度到正确的Runtime上运行。\n\n加载Driver的代码如下：\n```\nlet mysqlDriver = getCurrentRuntime().getDriver(\"bx.mysql.client\")\t\n```\n\n如果当前设备有安装\"bx.mysql.client\"驱动，那么就能加载成功。目前框架只支持少量的驱动，列表如下：   \n- bx.mysql.client     \n- bx.mongodb.client\n- bx.redis.client (未开放)\n\n## 加载XARPackage的流程简介\nRuntime最核心的功能就是加载并运行XARPackage，分析这个流程能更有效的理解Bucky的核心概念。\n\n在一个Runtime环境下加载一个XARPackage并调用其模块接口的代码如下：\n```javascript\ngetCurrentRuntime().loadXARPackage(\"account\",function(thePackage) {\n    let [errorCode, usermodule] = thePackage.getModule(\"userinfo\");\n\tusermodule.login(\"uname\",\"pwd\", function (result, errorCode) {\n\t\tconsole.log(\"login result is \" + result);\n\t});\n});\n```\n\n这段代码的实现流程简介如下：\n\n1. 加载XARPackage：\n\t1. 通过`Repository`服务，下载`account`这个XARPackage的最新版本。\n\t2. 读取`account`的配置文件config.json，判断当前Runtime是否能直接加载account这个包，如果不能直接加载则：\n\t\t- 加载其代理包`account proxy` (proxy包通过框架提供的工具生成）。\n\t\t- 通过`Repository`服务，下载`account proxy`这个XARPackage的最新版本。\n2. 加载module：\n\t1. 读取配置文件config.json，加载依赖项(依赖的包，knowledge)。\n\t2. 运行package的onload.js，XARPackage加载成功。\n\t3. 开始加载模块，通过config.json里的module表查到模块对应的实现文件。\n3. 调用module的导出接口：\n\t- 如果加载的是`account`包，那么直接加载模块的实现文件，调用并返回结果。\n\t- 如果加载的是`account proxy`包，那么login调用实际上会发与小应用云的调度器通信，调度器会在云端创建一个合适的Runtime加载真正的`account`包，然后再进行PRC。\n\n加载XARPackage再加载其中的一个module也可以直接通过`loadModule`接口直接调用：\n\n```javascript\ngetCurrentRuntime().loadModule(\"account:userinfo\",(errorCode,usermodule)=>{\n\tusermodule.login(\"uname\",\"pwd\", function (result, errorCode) {\n\t\tconsole.log(\"login result is \" + result);\n\t});\n});\n```\n\n> **[Remark]**: 同步加载 vs 异步加载\n> 我们在设计上原本希望loadModule是同步的，但会导致在浏览器里无法正确实现。如果使用`all in one file`的方法进行打包，又会在某些场景下产生额外的开销，所以目前实现的`loadModule`是异步的。\n\n\n## Proxy与RPC\n当**Runtime**试图加载一个**XARPackage**时，系统的加载策略如下：\n\n1. 判断该原始**XARPackage**是否适合加载在当前**Runtime。**\n2. 如果不适合，bucky的**调度器（Scheduler）**会尝试寻找一个已经加载了该**XARPackage**的**Runtime**\\(或则创建一个\\)来加载原始**XARPackage**。\n3. 而在当前**Runtime**里加载的则是一个**Proxy XARPackage。**\n\n其中，**Proxy XARPackage**实现了原始 **XARPackage**中的所有模块和接口，并通过RPC完成对原始包的代理调用：\n\n```javascript\nfunction signup(...rpc_args) {\n    const onComplete = rpc_args[rpc_args.length - 1];\n    bucky.BX_CHECK(typeof onComplete === 'function');\n    rpc_args.pop();\n\n    const thisRuntime = bucky.getCurrentRuntime();\n    thisRuntime.rpcCall(targetPackageInfo, 'account:account::signup', rpc_args, onComplete);\n}\n```\n\n简单说，bucky的**调度器（Scheduler）**会为不能直接本地运行的目标**XARPackage**选择一个合适的**Runtime**加载，然后再发起一个从当前Runtime到目标Runtime的RPC Call。\n\n每一个XARPackage都应该有一个Proxy Package。我们已经在SDK中提供了工具来生成这些Proxy Package。发布代码的时候会自动把包和包的代理都发布到**包仓库\\(Repository\\)**上。这个过程都在构建的过程中完成，详细参考工具链-&gt;bucky命令行文档。\n\n> **[Remark]:**`系统不存在魔法`\n>`系统不存在魔法`是我们多年坚持的理念，通过阅读Proxy的代码，工程师可以分析调试应用系统。而且系统也允许高级开发者根据需要，手工编写自己的Proxy逻辑。\n\n\n## Knowledge\n\nKnowledge是应用开发过程中需要经常打交道的一个核心概念，也是Bucky的关键设计之一。可以把Knowledge简单的理解成`全局配置`。当应用开发只读取Knowledge的时候，Knowledge系统也就退化成了一个全局配置系统。\n\n### 配置knowledge\n\nKnowledge采用key-value设计，配置Knowledge分为两个部分：\n- 在应用程序的konwledges.json里配置Knowledge的初始化，定义该应用程序要使用的Knowledge名字、类型、初始值。\n- 在应用程序XARPackage的config.json里，配置该XARPackage依赖了哪些Knowledge。\n\n在bucky的应用程序里，一个典型的`konwledges.json`配置如下，使用bucky命令行工具初始化应用程序时会自动生成默认的konwledges.json文件，参考HelloBucky！\n```javascript\n{\n    \"global.events\": {\n        \"type\": 0,\n        \"object\": {}\n    },\n    \"global.appinfo\": {\n        \"type\": 0,\n        \"object\": {}\n    },\n    \"global.runtimes\": {\n        \"type\": 0,\n        \"object\": {}\n    },\n    \"global.loadrules\": {\n        \"type\": 0,\n        \"object\": {}\n    },\n    \"global.storages\": {\n        \"type\": 0,\n        \"object\": {}\n    },\n    \"global.mysql.instances\": {\n        \"type\": 0,\n        \"object\": {}\n    },\n    \"global.mysql.schemas\": {\n        \"type\": 0,\n        \"object\": {}\n    },\n    \"global.mysql.configs\": {\n        \"type\": 0,\n        \"object\": {}\n    }\n}\n```\n\n该配置文件中配置的是系统预定义的以\"global.\"开头的基础knowledge，因为Bucky的一些基础机制依赖这些knowledge(这也说明Knowledge是框架最底层的概念之一)。\n\n每个XARPackage都应该在自己config.json配置依赖的konwleges，例如典型config.json里配置konwledges如下：\n```javascript\n{\n\t...\n\n    // 依赖的knowledges配置，参考下文`Knowledge`介绍。\n    \"knowledges\": [\n        {\n            \"key\": \"global.events\",\n            \"type\": 0\n        },\n        {\n            \"key\": \"global.runtimes\",\n            \"type\": 0\n        },\n        {\n            \"key\": \"global.storages\",\n            \"type\": 0\n        },\n        {\n            \"key\": \"global.mysql.instances\",\n            \"type\": 0\n        },\n        {\n            \"key\": \"global.mysql.schemas\",\n            \"type\": 0\n        },\n        {\n            \"key\": \"global.mysql.configs\",\n            \"type\": 0\n        }\n    ]\n}\n```\n\n>**[tip]**: 配置依赖项\n>需要注意的是，config.json里只需要配置kowledges的`key`、`type`即可，不必配置对应的值，因为此处是配置依赖的konwledges，而不是配置其初始化。\n\n### 使用Knowledge\nKnowledge采用key-value设计，一个典型的使用Knowledge的代码如下：\n\n```javascript\nlet km = getCurrentRuntime().getKnowledgeManager();\nkm.dependKnowledge(\"myconfig\",NodeInfo.TYPE_OBJECT);\nkm.ready(function(){\n\tlet myconfig = km.getKnowledge(\"myconfig\").objectRead();\n\tlet back_color = myconfig[\"back_color\"];\n});\n```\n\n可以看到，使用Knowledge需要等待一个异步更新，以便得到系统的最新配置。框架为了简化这个过程，可以在XARPackage的config.json中配置依赖的Knowledge，这样在完成XARPackage加载后，运行应用代码前，框架就已经在当前Runtime里同步好了需要的Knowledge。则在XARPackage的module代码中可以直接获取：\n```javascript\nlet km = getCurrentRuntime().getKnowledgeManager();\nlet myconfig = km.getKnowledge(\"myconfig\").objectRead();\nlet back_color = myconfig[\"back_color\"];\n```\n\n## Storage\n\n分布式系统最重要的一个工作就是**保存**／**查询**数据。传统上，应用系统架构选型最总要的一件事情就是选择存储引擎。Bucky认为**全局状态是被调度器管理的一种核心资源**，我们通过调度器**分配**／**迁移**这些资源，来实现系统的自动扩容和容错。Bucky通过驱动(Driver)的方式支持传统存储引擎，并正在开发全新的基于Block的分布式存储引擎。传统的存储引擎为旧世界的代码提供便利，而新的分布式存储引擎将是我们解锁`核心系统开发`难度的利器。 \n\n目前Bucky正在支持和即将支持的存储引擎有：\n- mysql存储引擎，通过Driver形式提供，包含一些必要的knowledges配置。\n- mongo存储引擎，通过Driver形式提供，包含一些必要的knowledges配置。\n- redis存储引擎（未开放）。\n- 全新的分布式存储引擎，敬请期待！\n\n使用Mysql/mongo等存储的细节不在此处展开，可以通过阅读demo和后续文档了解。\n\n## Event\n\n### Global Event\n\nGlobal Event是bukcy框架提供的一个非常有用的组件。实现了一个逻辑上非常常用的功能：\n>“在系统中定义一个全局事件，然后在任何一段代码中都可以Attach这个事件，在任何一段代码中都可以Fire这个事件。”   \n\n在传统的后台开发中，我们常常使用消息中间件来达到类似功能。从过去的经验来看，我们鼓励应用尽量使用端到端的无状态事件系统（即系统允许丢失事件），当然bucky也允许用户开发自己的有状态事件系统。    \n\nGlobal Event的使用也很简单：\n```javascript\nlet em = getCurrentRuntime().getGlobalEventManager();\nlet eventCategory = 'myEventCatagory';\nlet eventID = 'myMessage';\n\n// create an event catagory\nem.create(eventCategory, (result) => {\n\n\t// attach an eventID on the event catagory\n\tconsole.log(`${eventCategory} is created:${result}`);\n\tem.attach(eventCategory, eventID, (msg) => {\n\t\tconsole.log(`${eventCategory}/${eventID} is fired:`, msg);\n\t};\n\t\n\t// Fire the eventID ont the event catagory\n\tBaseLib.setOnceTimer(function() {\n\t\tlet msg = JSON.stringify({\n\t\t\tcmd:'echo',\n\t\t\tbody:{\n\t\t\t\ttext:'Real-world programming, however, requires care, expertise, and wisdom.',\n\t\t\t\tfrom:'SICP'\n\t\t\t}\n\t\t});\n\t\tem.activeEvent(eventCategory, eventID, msg);\n\t},1500);\n});\n```\n\n从上述代码中我们可以看出来：\n1. Global Event的操作接口都是异步的。\n2. Global Event的由event catagory/eventID两层结构构成，方便用户组织事件的名字空间。\n3. Global Event的参数是一个字符串，我们一般鼓励在里面填写一个stringfiy后的JSON.\n4. Global Event是应用程序全局的，所以具有分布式系统在一致性方面的特性：attach成功之后存在与事件控制器失去链接的可能，而系统并不会保证在失去连接的这段时间内产生的事件不会丢失。\n\n### System Event\n\n传统的分布式系统通常会在一些主机上通过系统的Cron服务来跑一些定时任务。这类任务从逻辑上和事件很接近，\n不同之处在于事件并不由某端用户代码出发，而是通过给系统一个控制命令后，由系统触发。我们把这一类事件\n称作System Event。Bucky目前支持的系统事件有两种: 系统定时器(System Timer)和系统任务(System Task)。\n\n参考:[系统事件](../5.知识库/5.5.系统事件.md)\n\n## CallChain\n\n在传统单机程序里，操作系统使用进程/线程模型来区分程序的调用链，在这样的模型里，函数的调用栈（CallStack）无疑是一个十分重要的概念，单线程的程序通过CallStack可以快速跟踪/分析程序的问题。在多线程模式下，CallStack依然发挥着重要的作用。然而，无论是单线程还是多线程模型下，CallStack在应对异步开发模式的时候，其对业务代码逻辑上的调用链条的跟踪调试能力已经捉襟见拙。\n\n例如，在node环境下，虽然单个js文件是在单线程模型下执行，但是大部分代码都是异步链式代码（callback/async-await)，此时CallStack能提供的只是一个函数在同步模式下的调用链条，而对于该函数是从哪个地方异步发起的毫无感知。在分布式系统下，业务逻辑的代码的调用范围是整个分布式系统，仅仅依靠CallStack使得问题的诊断难道大幅上升。\n\n>**[Principle]**: Abstraction\n>软件工程里有许多重要的哲学，其中一个重要的原则是：“任何一个问题，都可以通过增加一个抽象层解决”。事实上，在整个分布式系统的构建里，对资源建立怎样的抽象模型处于核心的地位，有时候，这需要洞见的能力。\n\nBucky对分布式系统里的调用链问题经过深入分析并设计了CallChain这个核心概念，并将CallChain作为系统底层实现的重要基石：\n\n1. 提高开发者/内核开发者诊断问题的效率，内置一种有更基础格式的日志。\n2. 为分布式系统的调度器提供更丰富的信息。\n3. 作为分布式系统调试器的基础。\n\nCallChain是对“逻辑上一件事情处理流程的记录”，其中有两个核心问题：\n\n1. “逻辑上一件事情”的定义，什么时候开始，什么时候结束。\n2. 流程中的哪些步骤需要记录？所有的函数调用都要记录？RPC Call 需要记录？还是只有System Call需要记录。\n\nBucky系统里，任何一个函数都处于某个CallChain下，可以简单理解为每个函数都有一个CallChainID。然后，用一句话描述CallChain的核心能力：\n>“通过一个CallChain对应的ID，把从客户端发起的调用，在整个分布式系统里执行的逻辑调用链条都串起来。”\n\n从而，我们至少获得了两个十分可观的效益：\n\n1. 在日志系统里，通过CallChain的ID，可以把分布式系统里的一次完整逻辑调用链条上的所有日志完整的串起来。\n2. 在调试系统里，通过CallChain的信息，可以对分布式系统里的一次完整逻辑调用链条做断点调试。\n\n那么，我们只要从3个角度去理解CallChain的生命周期就可以使用CallChain：\n\n1. CallChain的产生，例如一个 UI/键盘交互事件，SystemTimer，用户自己创建的新CallChain。\n2. CallChain的继承，一个函数默认会继承父函数的CallChain。\n3. CallChain的分裂，Bucky系统或者用户从父CallChain创建的子CallChain。\n\n例如，上一节在knowledges.json里配置SystemTimer时，可以为其配置一个独立的CallChain：\n```javascript\n\"cc\":{\n\t\"enable\":true,\n\t\"appid\":\"example-app-id\",\n\t\"ccid\":\"180cafb0-8043-4662-a27d-cab110ecec0f\", // CallChain的ID\n\t\"frameid\":2\n}\n```\n\n事实上，SystemTimer的配置里一般不需要手工设置cc，bucky系统会自动完成这个工作。实际上，CallChain很少会单独使用，通常情况下CallChain都是和日志系统绑定在一起使用的，参考下一节介绍的Bucky LOG系统。\n\n## LOG\n\n在分布式系统里，如何打日志是开发严肃项目时必须要考虑的事情。bucky框架提供了内置的日志系统。\n```\nBX_TRACE(...);\nBX_DEBUG(...);\nBX_INFO(...);\nBX_WARN(...);\nBX_ERROR(...);\nBX_CHECK(...);\nBX_FATAL(...);\n```\n\n一条典型的blog日志输出如下：\n```\n[error],[2017-12-14 16:20:12.264],<@f4346679-a53b-49d1-89f1-a0539ab1dc39@1@2>,rpc call complete with error, pkgid=account, func=account:account::signup, statusCode=502, errcode=13, result= null, node_core.js:10952\n```\n\n其中，下面这个部分是blog内部自动添加的日志头，分别是日志级别、时间、ccid：\n```\n[error],[2017-12-14 16:20:12.264],<@f4346679-a53b-49d1-89f1-a0539ab1dc39@1@2>,\n```\n\n可见blog的输出格式如下：\n```\n[level],[date],<ccid>,log\n```\n\n默认情况下blog会使用当前函数所在的CallChain的ccid，如果要切换当前blog的ccid，可以使用blog的withcc函数替换：\n```\nblog.withcc(cc);\n```\n\n如何创建一个新的CallChain，请参考CallChain的API文档。\n\n## 高级功能\nBucky除了支持应用系统开发，还计划支持核心系统开发（我们希望能使用bukcy构建出RDBMS),为了达到这个目的，我们还提供了一些分布式系统开发所必须的高级基础设施，包括：\n1. 统一ID生成\n2. 全局锁\n3. Logic Time Vector\n4. Binlog 写入\n\n\n\n\n",
      "html": "<h1>整体架构介绍</h1>\n<p>Bucky的定位是下一代分布式操作系统，目标是：</p>\n<ol>\n<li>让分布式应用系统的开发更简单,开发调试和接近单机</li>\n<li>不需要处理系统运维（Serverless)</li>\n<li>根据系统负载自动分配资源，降低系统运行的成本</li>\n<li>磨平开发应用系统与核心系统的\b技能障碍</li>\n<li>使用一种方法开发全平台的优秀UI</li>\n<li>扩展应用能覆盖的拓扑边界</li>\n</ol>\n<h1>基本概念介绍</h1>\n<h2>Application</h2>\n<p>在Bucky的概念里，一个<code>分布式应用系统</code>被称作一个Application.用旧的概念来看，Application包括了 前端的App(iOS,Android,Web)以及后台的各种服务。目前Bucky只涵盖了后端逻辑的开发,未来会在现有的基础上延生对前端的支持。</p>\n<p>一个Application的生命周期的长度超越了传统的<code>进程</code>，实际上其生命周期的表达只是一个状态，用来表示<code>系统是否在线</code>。即使系统里没有任何活动的代码，只要系统处于在线状态，那么Application就处于“活”的状态。</p>\n<p>大部分的代码在运行的时候都需要知道当前所在的Application,单从开发的角度来说，需要的只是一个用来初始化的appid,以及做某些高权限操作需要的token。</p>\n<h2>XARPackage</h2>\n<p>XARPackage是在Bucky中代码的物理组织形式。从属于Application的逻辑代码，都应该放在一个合适的XARPackage中。这反映了程序开发活动中最朴素的模块化的概念。</p>\n<p>XARPackage里包含的js文件以<code>module</code>的概念被XARPackage持有。但加载XARPackage除了会默认运行一次onload.js外，并不会加载任何内部的模块。这样可以通过按需加载的方法，提升性能。</p>\n<p>XARPackage的config里包含了两部分信息，“<strong>package是什么</strong>”，以及“<strong>package里的代码运行起来依赖什么</strong>”。在将要加载一个包时，系统会根据这些信息，分配一个合适的Runtime。</p>\n<p>一个XARPackage的典型目录结构如下：</p>\n<pre><code>└── account\n    ├── account.js\n    ├── config.json\n    └── onload.js\n</code></pre>\n<p>其中<code>account</code>是该XARPackage的名字，而下面包含三个典型文件：</p>\n<ul>\n<li><code>config.json</code>是XARPackage的配置文件，定义了该XARPackage的元数据信息、包含的module、依赖的驱动、依赖的其他XARPackage、依赖的knowledges等。</li>\n<li><code>onload.js</code>是XARPackage被一个Runtime加载时会运行的初始化代码，默认什么都不做。</li>\n<li><code>account.js</code>是该XARPackage的一个module，一个module必须在config.json/modules项里配置。</li>\n</ul>\n<p>我们可以看一个config.json的典型配置来初步感受下：</p>\n<pre><code class=\"language-javascript\">{\n\t// 元数据信息\n    &quot;packageID&quot;: &quot;account&quot;,\n    &quot;build&quot;: 1,\n    &quot;runtimeType&quot;: &quot;pc_server.bucky&quot;,\n    &quot;meta&quot;: {\n        &quot;desc&quot;: &quot;&quot;\n    },\n\n    // 依赖的其他XARPackage配置\n    &quot;depends&quot;: [],\n\n    // 该XARPackage包含module配置\n    &quot;modules&quot;: {\n        &quot;account&quot;: &quot;account.js&quot;\n    },\n\n    // 依赖的驱动(Driver)配置\n    &quot;drivers&quot;: [&quot;bx.mysql.client&quot;],\n\n    // 依赖的knowledges配置，参考下文`Knowledge`介绍。\n    &quot;knowledges&quot;: [\n        ...\n    ]\n}\n</code></pre>\n<h2>module</h2>\n<p>加载XARPackage成功后，就可以从包中加载代码文件了。包里的代码文件我们称作module。应用开发的日常工作，就是把代码填进各个不同的module里。</p>\n<p>一个典型的module的代码如下，基本上和nodejs的module写法一致：</p>\n<pre><code class=\"language-javascript\">&quot;use strict&quot;;\n\nfunction md5(str,onComplete) {\n\tconsole.log(&quot;md5:&quot; + str);\n\tonComplete(str);\n}\n\nfunction foo(str) {\n\tconsole.log(&quot;foo:&quot; + str);\t\n}\n\nmodule.exports = {};\nmodule.exports.md5 = md5;\n</code></pre>\n<p>在这个module中只导出了一个接口函数md5,而foo是内部函数，只能在module内部使用。Bucky框架会经常做本地包/远程包的透明调度，所以要求被导出的函数的最后一个参数<code>必须</code>是<code>完成函数</code>，也就是说，导出的函数默认是<code>异步</code>的。</p>\n<blockquote>\n<p><strong>[Remark]</strong>: 发布/加载\n一个处于开发状态的XARPackage只有被发布到Repository服务器上后才能按正常流程加载。所以在开始正式测试前，请不要忘记运行发布代码。bucky支持<code>全本地加载</code>模式，能使用常见的nodejs调试器在本地快速的调试后端代码逻辑，极大的提升开发效率。</p>\n</blockquote>\n<h2>Runtime</h2>\n<p>Runtime在Bucky的设计中是一个非常重要的概念，是代码运行的容器，一个容器我们称做一个Runtime Instance。其中RuntimeInstance、Applicaton、XARPackage之间的关系如下：</p>\n<ul>\n<li>一个Runtime Instance 只属于一个Application。</li>\n<li>一个处于运行状态的Application,可能会有多个属于该Application的RuntimeInstance。</li>\n<li>一个RuntimeInstance可以加载同Application的不同XARPackage。</li>\n</ul>\n<p>传统的开发模型要求<strong>先准备好容器，再加载正确的代码</strong>，而Bucky框架的一个核心理念是<strong>为代码找到合适的运行容器</strong>。我们希望让开发者专注于开发业务逻辑，上传代码到小应用云后，由小应用云的调度器在合适的时机创建合适的Runtime来运行开发者编写的代码 。这样就能让逻辑代码尽可能的与分布式系统的计算拓扑的细节解耦，同时提供近似单机开发一般的分布式系统开发体验。</p>\n<p>应用在前端创建的Runtime通常都是<code>client runtime</code>,或则被称作<code>匿名Runtime</code>。在不同的js运行时下初始化client runtime的方法就是各种initCurrentRuntime()。应用开发可以使用全局函数getCurrentRuntime()方法来得到一个当前Runtime Instance。</p>\n<p>Runtime从实现上看很像一个虚拟机，由自己独立的沙盒环境（包括Cache,Storage），并且系统提供了接口 pause/resume 一个指定的 Runtime，方便bucky内核在不同的物理设备之间迁移。</p>\n<h3>Driver</h3>\n<p>Bucky通过Driver来使用非bucky框架内的功能。一个XARPackage内的代码如需使用某些driver，必须在其配置文件（config.json）中声明，这样bucky的调度器才有机会把该XARPackage调度到正确的Runtime上运行。</p>\n<p>加载Driver的代码如下：</p>\n<pre><code>let mysqlDriver = getCurrentRuntime().getDriver(&quot;bx.mysql.client&quot;)\t\n</code></pre>\n<p>如果当前设备有安装&quot;bx.mysql.client&quot;驱动，那么就能加载成功。目前框架只支持少量的驱动，列表如下：</p>\n<ul>\n<li>bx.mysql.client</li>\n<li>bx.mongodb.client</li>\n<li>bx.redis.client (未开放)</li>\n</ul>\n<h2>加载XARPackage的流程简介</h2>\n<p>Runtime最核心的功能就是加载并运行XARPackage，分析这个流程能更有效的理解Bucky的核心概念。</p>\n<p>在一个Runtime环境下加载一个XARPackage并调用其模块接口的代码如下：</p>\n<pre><code class=\"language-javascript\">getCurrentRuntime().loadXARPackage(&quot;account&quot;,function(thePackage) {\n    let [errorCode, usermodule] = thePackage.getModule(&quot;userinfo&quot;);\n\tusermodule.login(&quot;uname&quot;,&quot;pwd&quot;, function (result, errorCode) {\n\t\tconsole.log(&quot;login result is &quot; + result);\n\t});\n});\n</code></pre>\n<p>这段代码的实现流程简介如下：</p>\n<ol>\n<li>加载XARPackage：\n<ol>\n<li>通过<code>Repository</code>服务，下载<code>account</code>这个XARPackage的最新版本。</li>\n<li>读取<code>account</code>的配置文件config.json，判断当前Runtime是否能直接加载account这个包，如果不能直接加载则：\n<ul>\n<li>加载其代理包<code>account proxy</code> (proxy包通过框架提供的工具生成）。</li>\n<li>通过<code>Repository</code>服务，下载<code>account proxy</code>这个XARPackage的最新版本。</li>\n</ul>\n</li>\n</ol>\n</li>\n<li>加载module：\n<ol>\n<li>读取配置文件config.json，加载依赖项(依赖的包，knowledge)。</li>\n<li>运行package的onload.js，XARPackage加载成功。</li>\n<li>开始加载模块，通过config.json里的module表查到模块对应的实现文件。</li>\n</ol>\n</li>\n<li>调用module的导出接口：\n<ul>\n<li>如果加载的是<code>account</code>包，那么直接加载模块的实现文件，调用并返回结果。</li>\n<li>如果加载的是<code>account proxy</code>包，那么login调用实际上会发与小应用云的调度器通信，调度器会在云端创建一个合适的Runtime加载真正的<code>account</code>包，然后再进行PRC。</li>\n</ul>\n</li>\n</ol>\n<p>加载XARPackage再加载其中的一个module也可以直接通过<code>loadModule</code>接口直接调用：</p>\n<pre><code class=\"language-javascript\">getCurrentRuntime().loadModule(&quot;account:userinfo&quot;,(errorCode,usermodule)=&gt;{\n\tusermodule.login(&quot;uname&quot;,&quot;pwd&quot;, function (result, errorCode) {\n\t\tconsole.log(&quot;login result is &quot; + result);\n\t});\n});\n</code></pre>\n<blockquote>\n<p><strong>[Remark]</strong>: 同步加载 vs 异步加载\n我们在设计上原本希望loadModule是同步的，但会导致在浏览器里无法正确实现。如果使用<code>all in one file</code>的方法进行打包，又会在某些场景下产生额外的开销，所以目前实现的<code>loadModule</code>是异步的。</p>\n</blockquote>\n<h2>Proxy与RPC</h2>\n<p>当<strong>Runtime</strong>试图加载一个<strong>XARPackage</strong>时，系统的加载策略如下：</p>\n<ol>\n<li>判断该原始<strong>XARPackage</strong>是否适合加载在当前<strong>Runtime。</strong></li>\n<li>如果不适合，bucky的<strong>调度器（Scheduler）<strong>会尝试寻找一个已经加载了该</strong>XARPackage</strong>的<strong>Runtime</strong>(或则创建一个)来加载原始<strong>XARPackage</strong>。</li>\n<li>而在当前<strong>Runtime</strong>里加载的则是一个<strong>Proxy XARPackage。</strong></li>\n</ol>\n<p>其中，<strong>Proxy XARPackage</strong>实现了原始 <strong>XARPackage</strong>中的所有模块和接口，并通过RPC完成对原始包的代理调用：</p>\n<pre><code class=\"language-javascript\">function signup(...rpc_args) {\n    const onComplete = rpc_args[rpc_args.length - 1];\n    bucky.BX_CHECK(typeof onComplete === 'function');\n    rpc_args.pop();\n\n    const thisRuntime = bucky.getCurrentRuntime();\n    thisRuntime.rpcCall(targetPackageInfo, 'account:account::signup', rpc_args, onComplete);\n}\n</code></pre>\n<p>简单说，bucky的<strong>调度器（Scheduler）<strong>会为不能直接本地运行的目标</strong>XARPackage</strong>选择一个合适的<strong>Runtime</strong>加载，然后再发起一个从当前Runtime到目标Runtime的RPC Call。</p>\n<p>每一个XARPackage都应该有一个Proxy Package。我们已经在SDK中提供了工具来生成这些Proxy Package。发布代码的时候会自动把包和包的代理都发布到**包仓库(Repository)**上。这个过程都在构建的过程中完成，详细参考工具链-&gt;bucky命令行文档。</p>\n<blockquote>\n<p><strong>[Remark]:</strong><code>系统不存在魔法</code>\n<code>系统不存在魔法</code>是我们多年坚持的理念，通过阅读Proxy的代码，工程师可以分析调试应用系统。而且系统也允许高级开发者根据需要，手工编写自己的Proxy逻辑。</p>\n</blockquote>\n<h2>Knowledge</h2>\n<p>Knowledge是应用开发过程中需要经常打交道的一个核心概念，也是Bucky的关键设计之一。可以把Knowledge简单的理解成<code>全局配置</code>。当应用开发只读取Knowledge的时候，Knowledge系统也就退化成了一个全局配置系统。</p>\n<h3>配置knowledge</h3>\n<p>Knowledge采用key-value设计，配置Knowledge分为两个部分：</p>\n<ul>\n<li>在应用程序的konwledges.json里配置Knowledge的初始化，定义该应用程序要使用的Knowledge名字、类型、初始值。</li>\n<li>在应用程序XARPackage的config.json里，配置该XARPackage依赖了哪些Knowledge。</li>\n</ul>\n<p>在bucky的应用程序里，一个典型的<code>konwledges.json</code>配置如下，使用bucky命令行工具初始化应用程序时会自动生成默认的konwledges.json文件，参考HelloBucky！</p>\n<pre><code class=\"language-javascript\">{\n    &quot;global.events&quot;: {\n        &quot;type&quot;: 0,\n        &quot;object&quot;: {}\n    },\n    &quot;global.appinfo&quot;: {\n        &quot;type&quot;: 0,\n        &quot;object&quot;: {}\n    },\n    &quot;global.runtimes&quot;: {\n        &quot;type&quot;: 0,\n        &quot;object&quot;: {}\n    },\n    &quot;global.loadrules&quot;: {\n        &quot;type&quot;: 0,\n        &quot;object&quot;: {}\n    },\n    &quot;global.storages&quot;: {\n        &quot;type&quot;: 0,\n        &quot;object&quot;: {}\n    },\n    &quot;global.mysql.instances&quot;: {\n        &quot;type&quot;: 0,\n        &quot;object&quot;: {}\n    },\n    &quot;global.mysql.schemas&quot;: {\n        &quot;type&quot;: 0,\n        &quot;object&quot;: {}\n    },\n    &quot;global.mysql.configs&quot;: {\n        &quot;type&quot;: 0,\n        &quot;object&quot;: {}\n    }\n}\n</code></pre>\n<p>该配置文件中配置的是系统预定义的以&quot;global.&quot;开头的基础knowledge，因为Bucky的一些基础机制依赖这些knowledge(这也说明Knowledge是框架最底层的概念之一)。</p>\n<p>每个XARPackage都应该在自己config.json配置依赖的konwleges，例如典型config.json里配置konwledges如下：</p>\n<pre><code class=\"language-javascript\">{\n\t...\n\n    // 依赖的knowledges配置，参考下文`Knowledge`介绍。\n    &quot;knowledges&quot;: [\n        {\n            &quot;key&quot;: &quot;global.events&quot;,\n            &quot;type&quot;: 0\n        },\n        {\n            &quot;key&quot;: &quot;global.runtimes&quot;,\n            &quot;type&quot;: 0\n        },\n        {\n            &quot;key&quot;: &quot;global.storages&quot;,\n            &quot;type&quot;: 0\n        },\n        {\n            &quot;key&quot;: &quot;global.mysql.instances&quot;,\n            &quot;type&quot;: 0\n        },\n        {\n            &quot;key&quot;: &quot;global.mysql.schemas&quot;,\n            &quot;type&quot;: 0\n        },\n        {\n            &quot;key&quot;: &quot;global.mysql.configs&quot;,\n            &quot;type&quot;: 0\n        }\n    ]\n}\n</code></pre>\n<blockquote>\n<p><strong>[tip]</strong>: 配置依赖项\n需要注意的是，config.json里只需要配置kowledges的<code>key</code>、<code>type</code>即可，不必配置对应的值，因为此处是配置依赖的konwledges，而不是配置其初始化。</p>\n</blockquote>\n<h3>使用Knowledge</h3>\n<p>Knowledge采用key-value设计，一个典型的使用Knowledge的代码如下：</p>\n<pre><code class=\"language-javascript\">let km = getCurrentRuntime().getKnowledgeManager();\nkm.dependKnowledge(&quot;myconfig&quot;,NodeInfo.TYPE_OBJECT);\nkm.ready(function(){\n\tlet myconfig = km.getKnowledge(&quot;myconfig&quot;).objectRead();\n\tlet back_color = myconfig[&quot;back_color&quot;];\n});\n</code></pre>\n<p>可以看到，使用Knowledge需要等待一个异步更新，以便得到系统的最新配置。框架为了简化这个过程，可以在XARPackage的config.json中配置依赖的Knowledge，这样在完成XARPackage加载后，运行应用代码前，框架就已经在当前Runtime里同步好了需要的Knowledge。则在XARPackage的module代码中可以直接获取：</p>\n<pre><code class=\"language-javascript\">let km = getCurrentRuntime().getKnowledgeManager();\nlet myconfig = km.getKnowledge(&quot;myconfig&quot;).objectRead();\nlet back_color = myconfig[&quot;back_color&quot;];\n</code></pre>\n<h2>Storage</h2>\n<p>分布式系统最重要的一个工作就是<strong>保存</strong>／<strong>查询</strong>数据。传统上，应用系统架构选型最总要的一件事情就是选择存储引擎。Bucky认为<strong>全局状态是被调度器管理的一种核心资源</strong>，我们通过调度器<strong>分配</strong>／<strong>迁移</strong>这些资源，来实现系统的自动扩容和容错。Bucky通过驱动(Driver)的方式支持传统存储引擎，并正在开发全新的基于Block的分布式存储引擎。传统的存储引擎为旧世界的代码提供便利，而新的分布式存储引擎将是我们解锁<code>核心系统开发</code>难度的利器。</p>\n<p>目前Bucky正在支持和即将支持的存储引擎有：</p>\n<ul>\n<li>mysql存储引擎，通过Driver形式提供，包含一些必要的knowledges配置。</li>\n<li>mongo存储引擎，通过Driver形式提供，包含一些必要的knowledges配置。</li>\n<li>redis存储引擎（未开放）。</li>\n<li>全新的分布式存储引擎，敬请期待！</li>\n</ul>\n<p>使用Mysql/mongo等存储的细节不在此处展开，可以通过阅读demo和后续文档了解。</p>\n<h2>Event</h2>\n<h3>Global Event</h3>\n<p>Global Event是bukcy框架提供的一个非常有用的组件。实现了一个逻辑上非常常用的功能：</p>\n<blockquote>\n<p>“在系统中定义一个全局事件，然后在任何一段代码中都可以Attach这个事件，在任何一段代码中都可以Fire这个事件。”</p>\n</blockquote>\n<p>在传统的后台开发中，我们常常使用消息中间件来达到类似功能。从过去的经验来看，我们鼓励应用尽量使用端到端的无状态事件系统（即系统允许丢失事件），当然bucky也允许用户开发自己的有状态事件系统。</p>\n<p>Global Event的使用也很简单：</p>\n<pre><code class=\"language-javascript\">let em = getCurrentRuntime().getGlobalEventManager();\nlet eventCategory = 'myEventCatagory';\nlet eventID = 'myMessage';\n\n// create an event catagory\nem.create(eventCategory, (result) =&gt; {\n\n\t// attach an eventID on the event catagory\n\tconsole.log(`${eventCategory} is created:${result}`);\n\tem.attach(eventCategory, eventID, (msg) =&gt; {\n\t\tconsole.log(`${eventCategory}/${eventID} is fired:`, msg);\n\t};\n\t\n\t// Fire the eventID ont the event catagory\n\tBaseLib.setOnceTimer(function() {\n\t\tlet msg = JSON.stringify({\n\t\t\tcmd:'echo',\n\t\t\tbody:{\n\t\t\t\ttext:'Real-world programming, however, requires care, expertise, and wisdom.',\n\t\t\t\tfrom:'SICP'\n\t\t\t}\n\t\t});\n\t\tem.activeEvent(eventCategory, eventID, msg);\n\t},1500);\n});\n</code></pre>\n<p>从上述代码中我们可以看出来：</p>\n<ol>\n<li>Global Event的操作接口都是异步的。</li>\n<li>Global Event的由event catagory/eventID两层结构构成，方便用户组织事件的名字空间。</li>\n<li>Global Event的参数是一个字符串，我们一般鼓励在里面填写一个stringfiy后的JSON.</li>\n<li>Global Event是应用程序全局的，所以具有分布式系统在一致性方面的特性：attach成功之后存在与事件控制器失去链接的可能，而系统并不会保证在失去连接的这段时间内产生的事件不会丢失。</li>\n</ol>\n<h3>System Event</h3>\n<p>传统的分布式系统通常会在一些主机上通过系统的Cron服务来跑一些定时任务。这类任务从逻辑上和事件很接近，\n不同之处在于事件并不由某端用户代码出发，而是通过给系统一个控制命令后，由系统触发。我们把这一类事件\n称作System Event。Bucky目前支持的系统事件有两种: 系统定时器(System Timer)和系统任务(System Task)。</p>\n<p>参考:<a href=\"../5.%E7%9F%A5%E8%AF%86%E5%BA%93/5.5.%E7%B3%BB%E7%BB%9F%E4%BA%8B%E4%BB%B6.html\">系统事件</a></p>\n<h2>CallChain</h2>\n<p>在传统单机程序里，操作系统使用进程/线程模型来区分程序的调用链，在这样的模型里，函数的调用栈（CallStack）无疑是一个十分重要的概念，单线程的程序通过CallStack可以快速跟踪/分析程序的问题。在多线程模式下，CallStack依然发挥着重要的作用。然而，无论是单线程还是多线程模型下，CallStack在应对异步开发模式的时候，其对业务代码逻辑上的调用链条的跟踪调试能力已经捉襟见拙。</p>\n<p>例如，在node环境下，虽然单个js文件是在单线程模型下执行，但是大部分代码都是异步链式代码（callback/async-await)，此时CallStack能提供的只是一个函数在同步模式下的调用链条，而对于该函数是从哪个地方异步发起的毫无感知。在分布式系统下，业务逻辑的代码的调用范围是整个分布式系统，仅仅依靠CallStack使得问题的诊断难道大幅上升。</p>\n<blockquote>\n<p><strong>[Principle]</strong>: Abstraction\n软件工程里有许多重要的哲学，其中一个重要的原则是：“任何一个问题，都可以通过增加一个抽象层解决”。事实上，在整个分布式系统的构建里，对资源建立怎样的抽象模型处于核心的地位，有时候，这需要洞见的能力。</p>\n</blockquote>\n<p>Bucky对分布式系统里的调用链问题经过深入分析并设计了CallChain这个核心概念，并将CallChain作为系统底层实现的重要基石：</p>\n<ol>\n<li>提高开发者/内核开发者诊断问题的效率，内置一种有更基础格式的日志。</li>\n<li>为分布式系统的调度器提供更丰富的信息。</li>\n<li>作为分布式系统调试器的基础。</li>\n</ol>\n<p>CallChain是对“逻辑上一件事情处理流程的记录”，其中有两个核心问题：</p>\n<ol>\n<li>“逻辑上一件事情”的定义，什么时候开始，什么时候结束。</li>\n<li>流程中的哪些步骤需要记录？所有的函数调用都要记录？RPC Call 需要记录？还是只有System Call需要记录。</li>\n</ol>\n<p>Bucky系统里，任何一个函数都处于某个CallChain下，可以简单理解为每个函数都有一个CallChainID。然后，用一句话描述CallChain的核心能力：</p>\n<blockquote>\n<p>“通过一个CallChain对应的ID，把从客户端发起的调用，在整个分布式系统里执行的逻辑调用链条都串起来。”</p>\n</blockquote>\n<p>从而，我们至少获得了两个十分可观的效益：</p>\n<ol>\n<li>在日志系统里，通过CallChain的ID，可以把分布式系统里的一次完整逻辑调用链条上的所有日志完整的串起来。</li>\n<li>在调试系统里，通过CallChain的信息，可以对分布式系统里的一次完整逻辑调用链条做断点调试。</li>\n</ol>\n<p>那么，我们只要从3个角度去理解CallChain的生命周期就可以使用CallChain：</p>\n<ol>\n<li>CallChain的产生，例如一个 UI/键盘交互事件，SystemTimer，用户自己创建的新CallChain。</li>\n<li>CallChain的继承，一个函数默认会继承父函数的CallChain。</li>\n<li>CallChain的分裂，Bucky系统或者用户从父CallChain创建的子CallChain。</li>\n</ol>\n<p>例如，上一节在knowledges.json里配置SystemTimer时，可以为其配置一个独立的CallChain：</p>\n<pre><code class=\"language-javascript\">&quot;cc&quot;:{\n\t&quot;enable&quot;:true,\n\t&quot;appid&quot;:&quot;example-app-id&quot;,\n\t&quot;ccid&quot;:&quot;180cafb0-8043-4662-a27d-cab110ecec0f&quot;, // CallChain的ID\n\t&quot;frameid&quot;:2\n}\n</code></pre>\n<p>事实上，SystemTimer的配置里一般不需要手工设置cc，bucky系统会自动完成这个工作。实际上，CallChain很少会单独使用，通常情况下CallChain都是和日志系统绑定在一起使用的，参考下一节介绍的Bucky LOG系统。</p>\n<h2>LOG</h2>\n<p>在分布式系统里，如何打日志是开发严肃项目时必须要考虑的事情。bucky框架提供了内置的日志系统。</p>\n<pre><code>BX_TRACE(...);\nBX_DEBUG(...);\nBX_INFO(...);\nBX_WARN(...);\nBX_ERROR(...);\nBX_CHECK(...);\nBX_FATAL(...);\n</code></pre>\n<p>一条典型的blog日志输出如下：</p>\n<pre><code>[error],[2017-12-14 16:20:12.264],&lt;@f4346679-a53b-49d1-89f1-a0539ab1dc39@1@2&gt;,rpc call complete with error, pkgid=account, func=account:account::signup, statusCode=502, errcode=13, result= null, node_core.js:10952\n</code></pre>\n<p>其中，下面这个部分是blog内部自动添加的日志头，分别是日志级别、时间、ccid：</p>\n<pre><code>[error],[2017-12-14 16:20:12.264],&lt;@f4346679-a53b-49d1-89f1-a0539ab1dc39@1@2&gt;,\n</code></pre>\n<p>可见blog的输出格式如下：</p>\n<pre><code>[level],[date],&lt;ccid&gt;,log\n</code></pre>\n<p>默认情况下blog会使用当前函数所在的CallChain的ccid，如果要切换当前blog的ccid，可以使用blog的withcc函数替换：</p>\n<pre><code>blog.withcc(cc);\n</code></pre>\n<p>如何创建一个新的CallChain，请参考CallChain的API文档。</p>\n<h2>高级功能</h2>\n<p>Bucky除了支持应用系统开发，还计划支持核心系统开发（我们希望能使用bukcy构建出RDBMS),为了达到这个目的，我们还提供了一些分布式系统开发所必须的高级基础设施，包括：</p>\n<ol>\n<li>统一ID生成</li>\n<li>全局锁</li>\n<li>Logic Time Vector</li>\n<li>Binlog 写入</li>\n</ol>\n",
      "id": 6
    },
    {
      "path": "2.构架/2.2.工程目录结构.md",
      "url": "2.构架/2.2.工程目录结构.html",
      "content": "## 工程目录结构\n\n通过bucky命令行工具创建工程目录，可以比较清晰的组织工程的目录结构。例如通过下面的步骤，可以创建一个清晰的服务端、前端工程目录结构。\n\n0. `mkdir test`\n1. `cd test`\n2. `bucky init -i` 进入交互式初始化模式\n3. 创建项目，输入项目相对路径：`src/server`，项目类型选择`Bucky项目`的序号\n    - 并分别创建名为`account`和`setting`两个新的`新建package`\n    - 继续创建一个名为`common`的`静态库`\n    - 选择不继续创建包\n4. 继续创建项目，输入项目相对路径:`src/client/react_native_ui`，项目类型选择`ReactNative（前端）`的序号\n5. 继续创建项目，输入项目相对路径:`src/client/html`，项目类型选择`HTML5（前端）`的序号\n6. 继续创建项目，输入项目相对路径:`src/client/node_cli`，项目类型选择`Node（命令行程序）`的序号\n7. 选择不继续创建项目\n8. 完成\n\n此时，目录结构如下（显示了4层）：\n```\n.\n├── dist\n│   ├── bucky\n│   ├── h5\n│   └── wx\n├── knowledges.json\n├── solution.json\n├── src\n│   ├── client\n│   │   ├── html5\n│   │   │   ├── core\n│   │   │   ├── css\n│   │   │   ├── img\n│   │   │   ├── index.html\n│   │   │   └── main.js\n│   │   ├── node_cli\n│   │   │   ├── core\n│   │   │   └── main.js\n│   │   └── react_native_ui\n│   │       ├── App.js\n│   │       ├── android\n│   │       ├── app.json\n│   │       ├── core\n│   │       ├── index.js\n│   │       ├── ios\n│   │       ├── node_modules\n│   │       ├── package-lock.json\n│   │       ├── package.json\n│   │       ├── shim.js\n│   │       └── yarn.lock\n│   └── server\n│       ├── account\n│       │   ├── account.js\n│       │   ├── config.json\n│       │   └── onload.js\n│       ├── common\n│       │   ├── common.js\n│       │   ├── config.lib.json\n│       │   └── onload.js\n│       └── setting\n│           ├── config.json\n│           ├── onload.js\n│           └── setting.js\n└── test\n    ├── account\n    ├── common\n    └── setting\n```\n\n可以看到，工程目录结构下主要包含如下几个关键要素：\n\n#### 解决方案配置文件\n- `solution.json` 表示整个解决方案，该文件里包含了对目录下所有子`project`的配置信息，以及`bucky`相关的重要配置。用户日常会修改的有：\n    - `solution.json`里面的`meta/locale`字段，用来配置代码会在bucky的哪个集群上运行，目前有`china`,`us`两个可选集群。\n\n#### 全局配置文件\n- `knowledges.json` 里面主要是App的全局Konwledges配置，主要在使用`MySQL`,`MongoDB`,`OSS存储`,`系统事件`的时候需要进行对应的配置\n\n#### bucky项目目录结构\n- `src/server`，是刚刚创建的`Bucky项目`，里面包含了一个一个`XAR Package`，每个`XAR Package`下含有：\n    - `config.json`，代表这是一个`XAR Package`\n    - `config.lib.json`，代表这是一个公用的`静态库`，其他`XAR Package`里可以直接使用相对路径require`静态库`里面的js模块，这对编写多个`XAR Package`的公共类库是十分有用的。\n    - 注意Bucky项目下，一个包要么是`XAR Package`，要么是`静态库`，不可以混淆。\n    - `onload.js`，是一个`XAR Package`加载时会被执行的受限代码\n    - 其他js文件，代表`XAR Package`内的模块，模块的导出配置在`config.json`/`modules`字段下。\n- 注意到与`solution.json`同级还有一个`test/xxx`目录，这几个目录是在每次创建一个新的bucky `XAR Package`的时候，自动创建的对应包的测试目录\n\n在对应包的对应测试目录下编写测试代码是bucky推荐的做法。例如通过本地调试可以快速排错：\n1. 在`test/account`目录下添加`test/account/test_account.js`\n2. 本地调试：`bucky debug -main test/account/test_account.js`\n\n#### 前端项目目录结构\n- `src/client/react_native_ui`，是新创建的react-native前端项目代码，其中`src/client/react_native_ui/core`目录下会在bucky编译的时候，自动同步下面三个文件：\n    - `rn_core.js`\n    - `rn_ld_core.js`\n    - `bucky_meta.js`\n    - 上述三个文件都是在`src/client/react_native_ui/App.js`里面使用bucky必须的。\n- `src/client/html5`，是新创建的html5前端项目代码，其中`src/client/html5/core`目录下会在bucky编译的时候，自动同步下面三个文件：\n    - `h5_core.js`\n    - `h5_ld_core.js`\n    - `bucky_meta.js`\n    - 上述三个文件都是在`src/client/html5/main.js`里面使用bucky必须的。\n- `src/client/node_cli`，是新创建的node命令行程序，其中`src/client/node_cli/core`目录下会在bucky编译的时候，自动同步下面三个文件：\n    - `node_core.js`\n    - `node_ld_core.js`\n    - `bucky_meta.js`\n    - 上述三个文件都是在`src/client/node_cli/main.js`里面使用bucky必须的。\n\n可以看到，前端项目的目录下通常都有一个`core`目录，bucky会在执行`bucky build`的过程中，自动同步这些文件。\n\n其中`bucky build`实际包含了下面2个个动作：\n1. `bucky compile`: 编译解决方案，执行的动作如下：\n    - 编译Bucky类型项目下的`XAR Package`，输出目录是`dist/bucky/`\n    - 编译`konwledges.json`, 生成`dist/bucky/knowledges.json`\n    - 生成`bucky_meta.json`，生成`dist/bucky/bucky_meta.json`\n    - 同步不同前端项目的core目录，开发者可以在`solution.json`里对应`project`配置里修改`core_dir`的相对路径。\n2. `bucky proxy`: 代理生成步骤，执行的动作如下：\n    - 为`dist/bucky/`下的`XAR Package`生成代理包\n    - 计算`dist/bucky/`下所有`XAR Package`（包含代理包）的带哈希版本集信息，生成`dist/bucky/version_set.json`\n    - 版本集的设置，为开发者做灰度测试提供了便利，可参考版本集相关的文档\n  \n其中`bucky deploy`实际包含了如下3个部署动作\n1. `bucky pub`：把`XAR Package`发布到bucky的包仓库上(bucky repository)上\n2. `bucky stop`: 在bucky cloud上停止该app，该步骤可能会耗时，取决于app正在运行中的云端runtime的数量\n3. `bucky start`：在bucky cloud上启动该app\n\n\n## 小结\n\n到此，可以清晰的看到使用bucky 命令行工具创建工程目录，具有`solution`、`project`两层清晰的结构，而`knowledges.json`则作为全局配置存在。\n\n并且，`project`里又分为`Bucky项目`、`react-native前端项目`、`html5前端项目`、`node命令行程序`等。客户端项目和bucky之间通过这些项目的core目录下的bucky类库连接在一起。\n\n单独说明的是`Bucky项目`，包含了`XAR Pacakage`子结构，用来模块化在bucky cloud上被自动调度的代码包，同时它们的输出目录默认位于`dist/bucky`下。\n\n通过理解bucky的工程目录结构，开发者可以更清晰的组织、管理项目。\n",
      "html": "<h2>工程目录结构</h2>\n<p>通过bucky命令行工具创建工程目录，可以比较清晰的组织工程的目录结构。例如通过下面的步骤，可以创建一个清晰的服务端、前端工程目录结构。</p>\n<ol start=\"0\">\n<li><code>mkdir test</code></li>\n<li><code>cd test</code></li>\n<li><code>bucky init -i</code> 进入交互式初始化模式</li>\n<li>创建项目，输入项目相对路径：<code>src/server</code>，项目类型选择<code>Bucky项目</code>的序号\n<ul>\n<li>并分别创建名为<code>account</code>和<code>setting</code>两个新的<code>新建package</code></li>\n<li>继续创建一个名为<code>common</code>的<code>静态库</code></li>\n<li>选择不继续创建包</li>\n</ul>\n</li>\n<li>继续创建项目，输入项目相对路径:<code>src/client/react_native_ui</code>，项目类型选择<code>ReactNative（前端）</code>的序号</li>\n<li>继续创建项目，输入项目相对路径:<code>src/client/html</code>，项目类型选择<code>HTML5（前端）</code>的序号</li>\n<li>继续创建项目，输入项目相对路径:<code>src/client/node_cli</code>，项目类型选择<code>Node（命令行程序）</code>的序号</li>\n<li>选择不继续创建项目</li>\n<li>完成</li>\n</ol>\n<p>此时，目录结构如下（显示了4层）：</p>\n<pre><code>.\n├── dist\n│   ├── bucky\n│   ├── h5\n│   └── wx\n├── knowledges.json\n├── solution.json\n├── src\n│   ├── client\n│   │   ├── html5\n│   │   │   ├── core\n│   │   │   ├── css\n│   │   │   ├── img\n│   │   │   ├── index.html\n│   │   │   └── main.js\n│   │   ├── node_cli\n│   │   │   ├── core\n│   │   │   └── main.js\n│   │   └── react_native_ui\n│   │       ├── App.js\n│   │       ├── android\n│   │       ├── app.json\n│   │       ├── core\n│   │       ├── index.js\n│   │       ├── ios\n│   │       ├── node_modules\n│   │       ├── package-lock.json\n│   │       ├── package.json\n│   │       ├── shim.js\n│   │       └── yarn.lock\n│   └── server\n│       ├── account\n│       │   ├── account.js\n│       │   ├── config.json\n│       │   └── onload.js\n│       ├── common\n│       │   ├── common.js\n│       │   ├── config.lib.json\n│       │   └── onload.js\n│       └── setting\n│           ├── config.json\n│           ├── onload.js\n│           └── setting.js\n└── test\n    ├── account\n    ├── common\n    └── setting\n</code></pre>\n<p>可以看到，工程目录结构下主要包含如下几个关键要素：</p>\n<h4>解决方案配置文件</h4>\n<ul>\n<li><code>solution.json</code> 表示整个解决方案，该文件里包含了对目录下所有子<code>project</code>的配置信息，以及<code>bucky</code>相关的重要配置。用户日常会修改的有：\n<ul>\n<li><code>solution.json</code>里面的<code>meta/locale</code>字段，用来配置代码会在bucky的哪个集群上运行，目前有<code>china</code>,<code>us</code>两个可选集群。</li>\n</ul>\n</li>\n</ul>\n<h4>全局配置文件</h4>\n<ul>\n<li><code>knowledges.json</code> 里面主要是App的全局Konwledges配置，主要在使用<code>MySQL</code>,<code>MongoDB</code>,<code>OSS存储</code>,<code>系统事件</code>的时候需要进行对应的配置</li>\n</ul>\n<h4>bucky项目目录结构</h4>\n<ul>\n<li><code>src/server</code>，是刚刚创建的<code>Bucky项目</code>，里面包含了一个一个<code>XAR Package</code>，每个<code>XAR Package</code>下含有：\n<ul>\n<li><code>config.json</code>，代表这是一个<code>XAR Package</code></li>\n<li><code>config.lib.json</code>，代表这是一个公用的<code>静态库</code>，其他<code>XAR Package</code>里可以直接使用相对路径require<code>静态库</code>里面的js模块，这对编写多个<code>XAR Package</code>的公共类库是十分有用的。</li>\n<li>注意Bucky项目下，一个包要么是<code>XAR Package</code>，要么是<code>静态库</code>，不可以混淆。</li>\n<li><code>onload.js</code>，是一个<code>XAR Package</code>加载时会被执行的受限代码</li>\n<li>其他js文件，代表<code>XAR Package</code>内的模块，模块的导出配置在<code>config.json</code>/<code>modules</code>字段下。</li>\n</ul>\n</li>\n<li>注意到与<code>solution.json</code>同级还有一个<code>test/xxx</code>目录，这几个目录是在每次创建一个新的bucky <code>XAR Package</code>的时候，自动创建的对应包的测试目录</li>\n</ul>\n<p>在对应包的对应测试目录下编写测试代码是bucky推荐的做法。例如通过本地调试可以快速排错：</p>\n<ol>\n<li>在<code>test/account</code>目录下添加<code>test/account/test_account.js</code></li>\n<li>本地调试：<code>bucky debug -main test/account/test_account.js</code></li>\n</ol>\n<h4>前端项目目录结构</h4>\n<ul>\n<li><code>src/client/react_native_ui</code>，是新创建的react-native前端项目代码，其中<code>src/client/react_native_ui/core</code>目录下会在bucky编译的时候，自动同步下面三个文件：\n<ul>\n<li><code>rn_core.js</code></li>\n<li><code>rn_ld_core.js</code></li>\n<li><code>bucky_meta.js</code></li>\n<li>上述三个文件都是在<code>src/client/react_native_ui/App.js</code>里面使用bucky必须的。</li>\n</ul>\n</li>\n<li><code>src/client/html5</code>，是新创建的html5前端项目代码，其中<code>src/client/html5/core</code>目录下会在bucky编译的时候，自动同步下面三个文件：\n<ul>\n<li><code>h5_core.js</code></li>\n<li><code>h5_ld_core.js</code></li>\n<li><code>bucky_meta.js</code></li>\n<li>上述三个文件都是在<code>src/client/html5/main.js</code>里面使用bucky必须的。</li>\n</ul>\n</li>\n<li><code>src/client/node_cli</code>，是新创建的node命令行程序，其中<code>src/client/node_cli/core</code>目录下会在bucky编译的时候，自动同步下面三个文件：\n<ul>\n<li><code>node_core.js</code></li>\n<li><code>node_ld_core.js</code></li>\n<li><code>bucky_meta.js</code></li>\n<li>上述三个文件都是在<code>src/client/node_cli/main.js</code>里面使用bucky必须的。</li>\n</ul>\n</li>\n</ul>\n<p>可以看到，前端项目的目录下通常都有一个<code>core</code>目录，bucky会在执行<code>bucky build</code>的过程中，自动同步这些文件。</p>\n<p>其中<code>bucky build</code>实际包含了下面2个个动作：</p>\n<ol>\n<li><code>bucky compile</code>: 编译解决方案，执行的动作如下：\n<ul>\n<li>编译Bucky类型项目下的<code>XAR Package</code>，输出目录是<code>dist/bucky/</code></li>\n<li>编译<code>konwledges.json</code>, 生成<code>dist/bucky/knowledges.json</code></li>\n<li>生成<code>bucky_meta.json</code>，生成<code>dist/bucky/bucky_meta.json</code></li>\n<li>同步不同前端项目的core目录，开发者可以在<code>solution.json</code>里对应<code>project</code>配置里修改<code>core_dir</code>的相对路径。</li>\n</ul>\n</li>\n<li><code>bucky proxy</code>: 代理生成步骤，执行的动作如下：\n<ul>\n<li>为<code>dist/bucky/</code>下的<code>XAR Package</code>生成代理包</li>\n<li>计算<code>dist/bucky/</code>下所有<code>XAR Package</code>（包含代理包）的带哈希版本集信息，生成<code>dist/bucky/version_set.json</code></li>\n<li>版本集的设置，为开发者做灰度测试提供了便利，可参考版本集相关的文档</li>\n</ul>\n</li>\n</ol>\n<p>其中<code>bucky deploy</code>实际包含了如下3个部署动作</p>\n<ol>\n<li><code>bucky pub</code>：把<code>XAR Package</code>发布到bucky的包仓库上(bucky repository)上</li>\n<li><code>bucky stop</code>: 在bucky cloud上停止该app，该步骤可能会耗时，取决于app正在运行中的云端runtime的数量</li>\n<li><code>bucky start</code>：在bucky cloud上启动该app</li>\n</ol>\n<h2>小结</h2>\n<p>到此，可以清晰的看到使用bucky 命令行工具创建工程目录，具有<code>solution</code>、<code>project</code>两层清晰的结构，而<code>knowledges.json</code>则作为全局配置存在。</p>\n<p>并且，<code>project</code>里又分为<code>Bucky项目</code>、<code>react-native前端项目</code>、<code>html5前端项目</code>、<code>node命令行程序</code>等。客户端项目和bucky之间通过这些项目的core目录下的bucky类库连接在一起。</p>\n<p>单独说明的是<code>Bucky项目</code>，包含了<code>XAR Pacakage</code>子结构，用来模块化在bucky cloud上被自动调度的代码包，同时它们的输出目录默认位于<code>dist/bucky</code>下。</p>\n<p>通过理解bucky的工程目录结构，开发者可以更清晰的组织、管理项目。</p>\n",
      "id": 7
    },
    {
      "path": "3.开发手册/3.1.Step-by-step创建新项目.md",
      "url": "3.开发手册/3.1.Step-by-step创建新项目.html",
      "content": "## 示例1，交互式创建：\n\n1. mkdir test\n2. cd test\n3. bucky init -i\n\t- 选择从demo创建solution还是创建新solution\n\t\t- 如果从demo创建\n\t\t\t- 则可以从demo列表里选择并快速完成创建\n\t\t- 如果创建新解决方案\n\t\t\t- 创建新项目(project)，可多次创建\n\t\t\t\t- 创建包(package)，可多次创建，\n\t\t\t\t- 如果选择创建示例包，则会在根目录下生成test/${package_name}/test1.js\n4. bucky build\n5. bucky deploy\n6. 重置knowledges: `bucky k -reset`，首次设置，后面只需knowledges.json有变动才需要重置\n7. 根目录下创建main.js，添加测试代码，（如果选择创建示例包，则有自动生成的测试代码test/${package_name}/test1.js\n8. 本地调试：\n\t- 调试测试代码：`bucky debug -main main.js`\n\t- 如果选择创建了示例包，则可直接执行：`bucky debug -main test/${package_name}/test1.js`\n\t- 可以添加`-vscode`选项生成vscode的启动配置\n8. 运行：\n\t- 运行测试代码：`bucky run -main main.js`\n\t- 如果选择创建了示例包，则可直接执行：`bucky run -main test/${package_name}/test1.js`\n\t- 可以添加`-vscode`选项生成vscode的启动配置\n\n## 示例2，一步一步非交互式创建：\n\n1. mkdir demo\n2. cd demo\n3. bucky init\n4. bucky add sln，如果添加`-demo`则可以直接导入示例工程，可忽略5、6两个步骤。\n5. bucky add -project source/test -target bucky \n6. bucky add -package source/test/calc  -example\n7. bucky build\n8. bucky deploy\n9. 重置knowledges: `bucky k -reset`，首次设置，后面只需knowledges.json有变动才需要重置\n10. 根目录下创建main.js，添加测试代码，（如果选择创建示例包，则有自动生成的测试代码test/${package_name}/test1.js\n11. 本地调试：\n\t- 调试测试代码：`bucky debug -main main.js`\n\t- 如果选择创建了示例包，则可直接执行：`bucky debug -main test/${package_name}/test1.js`\n\t- 可以添加`-vscode`选项生成vscode的启动配置\n11. 运行：\n\t- 运行测试代码：`bucky run -main main.js`\n\t- 如果选择创建了示例包，则可直接执行：`bucky run -main test/${package_name}/test1.js`\n\t- 可以添加`-vscode`选项生成vscode的启动配置",
      "html": "<h2>示例1，交互式创建：</h2>\n<ol>\n<li>mkdir test</li>\n<li>cd test</li>\n<li>bucky init -i\n<ul>\n<li>选择从demo创建solution还是创建新solution\n<ul>\n<li>如果从demo创建\n<ul>\n<li>则可以从demo列表里选择并快速完成创建</li>\n</ul>\n</li>\n<li>如果创建新解决方案\n<ul>\n<li>创建新项目(project)，可多次创建\n<ul>\n<li>创建包(package)，可多次创建，</li>\n<li>如果选择创建示例包，则会在根目录下生成test/${package_name}/test1.js</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>bucky build</li>\n<li>bucky deploy</li>\n<li>重置knowledges: <code>bucky k -reset</code>，首次设置，后面只需knowledges.json有变动才需要重置</li>\n<li>根目录下创建main.js，添加测试代码，（如果选择创建示例包，则有自动生成的测试代码test/${package_name}/test1.js</li>\n<li>本地调试：\n<ul>\n<li>调试测试代码：<code>bucky debug -main main.js</code></li>\n<li>如果选择创建了示例包，则可直接执行：<code>bucky debug -main test/${package_name}/test1.js</code></li>\n<li>可以添加<code>-vscode</code>选项生成vscode的启动配置</li>\n</ul>\n</li>\n<li>运行：\n<ul>\n<li>运行测试代码：<code>bucky run -main main.js</code></li>\n<li>如果选择创建了示例包，则可直接执行：<code>bucky run -main test/${package_name}/test1.js</code></li>\n<li>可以添加<code>-vscode</code>选项生成vscode的启动配置</li>\n</ul>\n</li>\n</ol>\n<h2>示例2，一步一步非交互式创建：</h2>\n<ol>\n<li>mkdir demo</li>\n<li>cd demo</li>\n<li>bucky init</li>\n<li>bucky add sln，如果添加<code>-demo</code>则可以直接导入示例工程，可忽略5、6两个步骤。</li>\n<li>bucky add -project source/test -target bucky</li>\n<li>bucky add -package source/test/calc  -example</li>\n<li>bucky build</li>\n<li>bucky deploy</li>\n<li>重置knowledges: <code>bucky k -reset</code>，首次设置，后面只需knowledges.json有变动才需要重置</li>\n<li>根目录下创建main.js，添加测试代码，（如果选择创建示例包，则有自动生成的测试代码test/${package_name}/test1.js</li>\n<li>本地调试：\n<ul>\n<li>调试测试代码：<code>bucky debug -main main.js</code></li>\n<li>如果选择创建了示例包，则可直接执行：<code>bucky debug -main test/${package_name}/test1.js</code></li>\n<li>可以添加<code>-vscode</code>选项生成vscode的启动配置</li>\n</ul>\n</li>\n<li>运行：\n<ul>\n<li>运行测试代码：<code>bucky run -main main.js</code></li>\n<li>如果选择创建了示例包，则可直接执行：<code>bucky run -main test/${package_name}/test1.js</code></li>\n<li>可以添加<code>-vscode</code>选项生成vscode的启动配置</li>\n</ul>\n</li>\n</ol>\n",
      "id": 8
    },
    {
      "path": "3.开发手册/3.2.bucky命令行手册.md",
      "url": "3.开发手册/3.2.bucky命令行手册.html",
      "content": "命令手册\n----------------\n\n## 帮助系统：\n```\nbucky help \nbucky help -all\nbucky help -action_name\nbucky help -example\nbucky doc \n```\n\n## 初始化：init\n\n```\nbucky init [-i]\n```\n\n如果指定了`-i`选项，则会开始交互式创建solution、添加project、添加package的流程。\n\n## 添加：add\n\n#### 选项(option)\n\n```\nbucky add -i\nbucky add -sln [-knowledge knowledgePath] [-demo]\nbucky add -project project_path -target target\nbucky add -package project_path/package_name [-example]\n```\n\n其中 `target` 可选的有：\n- bucky\n- h5\n- wx\n- rn\n\n其中`bucky add -sln -demo`，在指定了demo后，会进入交互式命令行，从已有的demo创建solution，\n可以通过该选项快速创建示例项目。\n\n基本的结构包含是：\n- 一个解决方案(solution)可以包含多个项目(project)，每个项目(project)下可以有多个包(package)。\n- 同一个solution下，所有的package名字不能重复，即使处在不同的project下\n\n\n#### 用法(usage)\n\n- 进入交互式添加模式：\n```\nbucky add -i\n```\n\n- 添加解决方案(add solution)\n```\nbucky add -sln [-knowledge knowledgePath]\n```\n\n- 添加bucky项目(add bucky project)\n```\nbucky add -project source/test -target target\n```\n\n- 添加微信小程序项目(add wx project)\n```\nbucky add -project source/wx -target wx\n```\n\n- 添加bucky包(add bucky package)\n```\nbucky add -package source/test/calc -example\n```\n\n- 导入已有的包(import exist package)\n```\nbucky add -import source/test -target target\n```\n\n## 移除：remove\n```\nbucky remove -i\nbucky remove -project projectpath\nbucky remove -package packagepath\n```\n\n其中`bucky remove -i` 是交互式模式\n\n## 列表：list\n```\nbucky list -i\nbucky list -project\nbucky list -package\n```\n\n列出工程和包，其中`bucky list -i`是交互式模式\n\n## 编译(compile) \n\n- 编译solution(compile solution)\n```\nbucky compile\n```\n\n- 编译特定项目(compile project)\n```\nbucky compile -project project_path\n```\n\n## 生成代理包(proxy)\n```\nndoe bucky.js proxy\n```\n\n## 发布(pub)\n\n```\nbucky pub\n```\n\n## 启动App(start)\n```\nbucky start\n```\n\n## 停止App(stop)\n```\nbucky stop\n```\n\n## 构建/部署\n\n- 执行构建，包含编译、生成代理\n```\nbucky build\n```\n\n- 执行部署，包含发布，停止App，重启App\n```\nbucky deploy\n```\n\n## 管理Konwledge(manager knowledge)\n\n- 重置App的Konwledge(reset app knowledge)\n```\nbucky k -reset\nbucky k -reset -local\n```\n\n其中`-local`表示重置的是本机调试模式的Knowledge\n\n- 查询(query knowledge)\n```\nbucky k -s 'ro global.runtimes'\n```\n\n其中`ro`表示read object, \n\n- 交互式查询模式(interactive mode)\n```\nbucky k -i\n```\n\n进入交互式查询模式后，可以输入查询命令\n\n## 运行(run)\n```\nbucky run -main main.js\n```\n\n通过`run -main`执行测试代码文件并运行。\n\n或者可以添加`-vscode`来生成在vscode里调试运行的配置(generate VSCose lauch script):\n```\nbucky run -main main.js -vscode\n```\n\n## 本地调试(debug)\n```\nbucky debug -main main.js\n```\n\n如果使用`debug`，则可以使用全本地模式调试代码\n\n同样的可以生成vscode的启动配置(generate VSCose lauch script):\n```\nbucky debug -main main.js -vscode\n```\n\n## 配置(config)\n\n- 设置或者更新用户信息并选择appid(set or update current user and select appid):\n```\nbucky config -user \n```\n\n实际上，在`bucky deploy`或者`bucky k`相关的操作中，遇到需要开发者账号的地方会有交互式命令\n行这只开发者账号，选择appid的过程，但也可以通过config命令单独操作。\n\n- 首次/重新选择appid(select appid):\n```\nbucky config -appid\n```\n\n如果要查看当前的appid信息，添加`-current`选项：\n```\nbucky config -appid -current\n```\n\n- 改变konwledges.json的路径，默认在根目录下(change kownledge path):\n```\nbucky config -knowledge knowledgepath\n```\n\n## 日志级别(log level)\n\n命令行工具下可以调整显示的日志级别，添加`-blog_level level`选项控制，默认是`error`级别的日志，可选的level有：\n- all\n- trace\n- debug\n- info\n- warn\n- error\n- check\n- fatal\n- ctrl\n- off\n\n## XARPackage单元测试自动生成\n```\nbucky gentest\n```\n\n",
      "html": "<h2>命令手册</h2>\n<h2>帮助系统：</h2>\n<pre><code>bucky help \nbucky help -all\nbucky help -action_name\nbucky help -example\nbucky doc \n</code></pre>\n<h2>初始化：init</h2>\n<pre><code>bucky init [-i]\n</code></pre>\n<p>如果指定了<code>-i</code>选项，则会开始交互式创建solution、添加project、添加package的流程。</p>\n<h2>添加：add</h2>\n<h4>选项(option)</h4>\n<pre><code>bucky add -i\nbucky add -sln [-knowledge knowledgePath] [-demo]\nbucky add -project project_path -target target\nbucky add -package project_path/package_name [-example]\n</code></pre>\n<p>其中 <code>target</code> 可选的有：</p>\n<ul>\n<li>bucky</li>\n<li>h5</li>\n<li>wx</li>\n<li>rn</li>\n</ul>\n<p>其中<code>bucky add -sln -demo</code>，在指定了demo后，会进入交互式命令行，从已有的demo创建solution，\n可以通过该选项快速创建示例项目。</p>\n<p>基本的结构包含是：</p>\n<ul>\n<li>一个解决方案(solution)可以包含多个项目(project)，每个项目(project)下可以有多个包(package)。</li>\n<li>同一个solution下，所有的package名字不能重复，即使处在不同的project下</li>\n</ul>\n<h4>用法(usage)</h4>\n<ul>\n<li>进入交互式添加模式：</li>\n</ul>\n<pre><code>bucky add -i\n</code></pre>\n<ul>\n<li>添加解决方案(add solution)</li>\n</ul>\n<pre><code>bucky add -sln [-knowledge knowledgePath]\n</code></pre>\n<ul>\n<li>添加bucky项目(add bucky project)</li>\n</ul>\n<pre><code>bucky add -project source/test -target target\n</code></pre>\n<ul>\n<li>添加微信小程序项目(add wx project)</li>\n</ul>\n<pre><code>bucky add -project source/wx -target wx\n</code></pre>\n<ul>\n<li>添加bucky包(add bucky package)</li>\n</ul>\n<pre><code>bucky add -package source/test/calc -example\n</code></pre>\n<ul>\n<li>导入已有的包(import exist package)</li>\n</ul>\n<pre><code>bucky add -import source/test -target target\n</code></pre>\n<h2>移除：remove</h2>\n<pre><code>bucky remove -i\nbucky remove -project projectpath\nbucky remove -package packagepath\n</code></pre>\n<p>其中<code>bucky remove -i</code> 是交互式模式</p>\n<h2>列表：list</h2>\n<pre><code>bucky list -i\nbucky list -project\nbucky list -package\n</code></pre>\n<p>列出工程和包，其中<code>bucky list -i</code>是交互式模式</p>\n<h2>编译(compile)</h2>\n<ul>\n<li>编译solution(compile solution)</li>\n</ul>\n<pre><code>bucky compile\n</code></pre>\n<ul>\n<li>编译特定项目(compile project)</li>\n</ul>\n<pre><code>bucky compile -project project_path\n</code></pre>\n<h2>生成代理包(proxy)</h2>\n<pre><code>ndoe bucky.js proxy\n</code></pre>\n<h2>发布(pub)</h2>\n<pre><code>bucky pub\n</code></pre>\n<h2>启动App(start)</h2>\n<pre><code>bucky start\n</code></pre>\n<h2>停止App(stop)</h2>\n<pre><code>bucky stop\n</code></pre>\n<h2>构建/部署</h2>\n<ul>\n<li>执行构建，包含编译、生成代理</li>\n</ul>\n<pre><code>bucky build\n</code></pre>\n<ul>\n<li>执行部署，包含发布，停止App，重启App</li>\n</ul>\n<pre><code>bucky deploy\n</code></pre>\n<h2>管理Konwledge(manager knowledge)</h2>\n<ul>\n<li>重置App的Konwledge(reset app knowledge)</li>\n</ul>\n<pre><code>bucky k -reset\nbucky k -reset -local\n</code></pre>\n<p>其中<code>-local</code>表示重置的是本机调试模式的Knowledge</p>\n<ul>\n<li>查询(query knowledge)</li>\n</ul>\n<pre><code>bucky k -s 'ro global.runtimes'\n</code></pre>\n<p>其中<code>ro</code>表示read object,</p>\n<ul>\n<li>交互式查询模式(interactive mode)</li>\n</ul>\n<pre><code>bucky k -i\n</code></pre>\n<p>进入交互式查询模式后，可以输入查询命令</p>\n<h2>运行(run)</h2>\n<pre><code>bucky run -main main.js\n</code></pre>\n<p>通过<code>run -main</code>执行测试代码文件并运行。</p>\n<p>或者可以添加<code>-vscode</code>来生成在vscode里调试运行的配置(generate VSCose lauch script):</p>\n<pre><code>bucky run -main main.js -vscode\n</code></pre>\n<h2>本地调试(debug)</h2>\n<pre><code>bucky debug -main main.js\n</code></pre>\n<p>如果使用<code>debug</code>，则可以使用全本地模式调试代码</p>\n<p>同样的可以生成vscode的启动配置(generate VSCose lauch script):</p>\n<pre><code>bucky debug -main main.js -vscode\n</code></pre>\n<h2>配置(config)</h2>\n<ul>\n<li>设置或者更新用户信息并选择appid(set or update current user and select appid):</li>\n</ul>\n<pre><code>bucky config -user \n</code></pre>\n<p>实际上，在<code>bucky deploy</code>或者<code>bucky k</code>相关的操作中，遇到需要开发者账号的地方会有交互式命令\n行这只开发者账号，选择appid的过程，但也可以通过config命令单独操作。</p>\n<ul>\n<li>首次/重新选择appid(select appid):</li>\n</ul>\n<pre><code>bucky config -appid\n</code></pre>\n<p>如果要查看当前的appid信息，添加<code>-current</code>选项：</p>\n<pre><code>bucky config -appid -current\n</code></pre>\n<ul>\n<li>改变konwledges.json的路径，默认在根目录下(change kownledge path):</li>\n</ul>\n<pre><code>bucky config -knowledge knowledgepath\n</code></pre>\n<h2>日志级别(log level)</h2>\n<p>命令行工具下可以调整显示的日志级别，添加<code>-blog_level level</code>选项控制，默认是<code>error</code>级别的日志，可选的level有：</p>\n<ul>\n<li>all</li>\n<li>trace</li>\n<li>debug</li>\n<li>info</li>\n<li>warn</li>\n<li>error</li>\n<li>check</li>\n<li>fatal</li>\n<li>ctrl</li>\n<li>off</li>\n</ul>\n<h2>XARPackage单元测试自动生成</h2>\n<pre><code>bucky gentest\n</code></pre>\n",
      "id": 9
    },
    {
      "path": "3.开发手册/3.3.使用h5debug.md",
      "url": "3.开发手册/3.3.使用h5debug.html",
      "content": "# 使用h5debug\n\nbucky命令行除了可以使用debug命令在控制台下对XARPackage进行全本地调试，也提供了h5debug命令支持在浏览器里进行全本地调试。\n\n## 准备必要的环境\n\n全本地调试要求本地安装使用到的数据库，目前有下面三种。\n- MySQL，并设置用户名root，密码：123456\n- MongoDB\n- Redis（暂不开放）\n\n安装好使用到的上述数据库，并启动。\n\n## 启动h5debug\n\n在命令行下启动h5debug服务，在调试期间需要一直开着：\n```\nbucky h5debug\n```\n\n## 切换h5_core.js到h5_ld_core.js\n\n例如，打开minichart项目，编辑src/client/index.html，切换h5_core.js到h5_ld_core.js，编辑并保存：\n\n![](images/h5-ld-core.jpg)\n\n## 使用Chrome浏览器开发者工具查看\n\n首先，打开src/client/index.html：\n\n![](images/h5-ld-empty.jpg)\n\n其次，打开chrome的开发者工具，可以看到源代码里包含两部分：\n- 对应到src/client目录的html前端代码\n- 本地加载的XARPackage的模块代码，在目录`127.0.0.1:8089`下的`services/repository/...`，该目录下会显示加载了的XARPackage里的模块（module），此处已经加载了`src/server/minichat/minichat.js`这个模块代码。\n\n![](images/h5-ld-dev.jpg)\n\n## 设置断点\n\n然后，我们在minichat.js里的`_getMongoDB`函数下设置一个断点：\n\n![](images/h5-ld-debug.jpg)\n\n最后，输入聊天内容，点击发送，可以看到代码在断点的位置停下来了：\n\n![](images/h5-ld-break.jpg)\n\n通过全本地调试，可以快速的发现和解决问题，Enjoy Code!\n\n**注意**： 如果修改了konwledges.json，需要重新发布knowledges：\n```\nbucky k -reset\n```\n\n以及重启h5debug:\n```\nbucky h5debug\n```\n\n\n\n\n",
      "html": "<h1>使用h5debug</h1>\n<p>bucky命令行除了可以使用debug命令在控制台下对XARPackage进行全本地调试，也提供了h5debug命令支持在浏览器里进行全本地调试。</p>\n<h2>准备必要的环境</h2>\n<p>全本地调试要求本地安装使用到的数据库，目前有下面三种。</p>\n<ul>\n<li>MySQL，并设置用户名root，密码：123456</li>\n<li>MongoDB</li>\n<li>Redis（暂不开放）</li>\n</ul>\n<p>安装好使用到的上述数据库，并启动。</p>\n<h2>启动h5debug</h2>\n<p>在命令行下启动h5debug服务，在调试期间需要一直开着：</p>\n<pre><code>bucky h5debug\n</code></pre>\n<h2>切换h5_core.js到h5_ld_core.js</h2>\n<p>例如，打开minichart项目，编辑src/client/index.html，切换h5_core.js到h5_ld_core.js，编辑并保存：</p>\n<p><img src=\"images/h5-ld-core.jpg\" alt=\"\"></p>\n<h2>使用Chrome浏览器开发者工具查看</h2>\n<p>首先，打开src/client/index.html：</p>\n<p><img src=\"images/h5-ld-empty.jpg\" alt=\"\"></p>\n<p>其次，打开chrome的开发者工具，可以看到源代码里包含两部分：</p>\n<ul>\n<li>对应到src/client目录的html前端代码</li>\n<li>本地加载的XARPackage的模块代码，在目录<code>127.0.0.1:8089</code>下的<code>services/repository/...</code>，该目录下会显示加载了的XARPackage里的模块（module），此处已经加载了<code>src/server/minichat/minichat.js</code>这个模块代码。</li>\n</ul>\n<p><img src=\"images/h5-ld-dev.jpg\" alt=\"\"></p>\n<h2>设置断点</h2>\n<p>然后，我们在minichat.js里的<code>_getMongoDB</code>函数下设置一个断点：</p>\n<p><img src=\"images/h5-ld-debug.jpg\" alt=\"\"></p>\n<p>最后，输入聊天内容，点击发送，可以看到代码在断点的位置停下来了：</p>\n<p><img src=\"images/h5-ld-break.jpg\" alt=\"\"></p>\n<p>通过全本地调试，可以快速的发现和解决问题，Enjoy Code!</p>\n<p><strong>注意</strong>： 如果修改了konwledges.json，需要重新发布knowledges：</p>\n<pre><code>bucky k -reset\n</code></pre>\n<p>以及重启h5debug:</p>\n<pre><code>bucky h5debug\n</code></pre>\n",
      "id": 10
    },
    {
      "path": "3.开发手册/3.4.API参考手册.md",
      "url": "3.开发手册/3.4.API参考手册.html",
      "content": "\n# **参考手册**  \n\n## 全局函数 \n### + runtime getCurrentRuntime() \n常用接口，获得当前代码所在的RuntimeInstance对象。  \n如果该函数返回null,说明框架没有正确初始化。  \n\n### + app getCurrentApp()  \n常用接口，获得当前的应用系统。  \n如果该函数返回null,说明框架没有正确初始化。  \n\n### + xar getCurrentXAR()\n获取当前代码所属的xar，一般在xar的内部代码或者模块代码里面，用以获取当前所属的xar\n\n### + initCurrentRuntime   \n在各种javascript环境中用来初始化bucky的client runtime。根据javascript运行环境的不同，该函数可能有不同的参数。  \n在小程序环境中初始化Bucky Runtime  \n\n## 日志\n#### + BX_XXX(content)\n输出一行日志，内容为info,级别为一下可选值\n* BX_DEBUG\n* BX_TRACE\n* BX_LOG\n* BX_INFO  \n* BX_WARN\n* BX_ERROR \n* BX_FATAL\n\n条件检测，功能类似于assert\n* BX_CHECK\n* BX_ASSERT\n\n设置日志级别\n>BX_SetLogLevel(levelstring)\n其中levelstring可取值以下，设置后低于该级别的日志不再被输出\n\n* BLOG_LEVEL_ALL\n* BLOG_LEVEL_TRACE\n* BLOG_LEVEL_DEBUG\n* BLOG_LEVEL_INFO\n* BLOG_LEVEL_WARN\n* BLOG_LEVEL_ERROR\n* BLOG_LEVEL_CHECK\n* BLOG_LEVEL_FATAL\n* BLOG_LEVEL_OFF\n\n\n\n## Application  \nApplication对象，用来得到当前运行的分布式应用的信息  \n\n### + void Application.init(metaInfo,function onInitComplete)  \n异步初始化Application对象。Application对象会从core service中读取必要的信息，所以这个操作时异步的。  \n#### 参数列表  \n#### `metaInfo`：一般从app.json读取，内容如下：  \n```\n{\n  \"appid\": \"bx.demos.account\",\n  \"repositoryHost\": \"https://dev.buckycloud.com/services/repository\",\n  \"knowledgeHost\": \"https://dev.buckycloud.com/services/knowledge\",\n  \"schedulerHost\" : \"https://dev.buckycloud.com/services/scheduler\",\n  \"appver\" : \"1.0.0.1\",\n  \"token\" : \"abcdef0123\"\n} \n```\n\nappID是应用的唯一ID。  \nrepositoryHost与knowledgeHost是app运行依赖的核心服务地址。配置不同的服务器可以用来区分线上版本和开发版本。  \n其中repositoryHost可以配置一个或者多个仓库地址，具体如下\n```\n\"repositoryHost\" : \"https://xxxx\",  // 如果直接配置一个字符串，则是默认是normal模式\n\"repositoryHost\": {\n    \"value\" : \"xxxx\",\n    \"type\": \"[normal/local/config]\"\n},\n// 多个仓库配置\n\"repositoryHost\" : [\n    {\n        \"value\" : \"https://xxx\",\n        \"type\" : \"normal\"\n    },{\n        \"value\" : \"[local dir]\",\n        \"type\" : \"local\"\n    }, {\n        \"value\" : \"[packagelist]\",\n        \"type\" : \"config\"\n    }\n]\n```\n#### `onInitComplete`: function(erroCode,appInfo){...}  \n当初始化完成后，会调用该函数。  \n\n### + string Application.getID()  \n返回Application的唯一ID.   \n\n### + string Application.getRuntime()  \n返回当前app绑定的runtime实例\n\n### + string getKnowledgeHost()\n获取配置的knowledge地址\n\n### + string getSchedulerHost()\n获取配置的scheduler地址\n\n### + addRepository(type, value)\n添加一个仓库地址，需要注意的是动态调整仓库地址要在初始化runtime之前\n\n### + cleanRepository()\n清除所有的仓库地址列表\n\n### + array getRepositoryList()\n获取所有的仓库地址列表\n\n\n##RuntimeInfo  \n用来表示一个具体的RuntimeInstance.这个对象应用开发很少使用，我们并不鼓励在应用逻辑中过于依赖某个具体的RuntimeInstance。  \n#### `RuntimeInfo.runtime_id` : RuntimeInstance的唯一ID\n#### `RuntimeInfo.runtime_type` : RuntimeInstance的类型 \n#### `RuntimeInfo.device_id` : RuntimeInstance所在的设备ID  \n#### `RuntimeInfo.device_type` : RuntimeInstance所在的设备类型  \n#### `RuntimeInfo.app_id` : RuntimeInstance所属的Application ID。  \n#### `RuntimeInfo.ability` : 当前runtime支持的能力列表  \n#### `RuntimeInfo.drivers` : 当前runtime支持的驱动列表  \n#### `RuntimeInfo.tag` : 当前runtime的标签列表\n#### `RuntimeInfo.storages` : 当前runtime的绑定的存储路径列表\n\n## RuntimeInstance  \n核心对象。    \nRuntimeInstance代表的是当前代码的运行环境。一般通过全局函数getCurrentRuntime()来获取。  \n\n### + string RuntimeInstance.getID()  \n返回当前Runtime的唯一ID  \n\n### + Device RuntimeInstance.getOwnerDevice()  \n返回当前Runtime所在的设备对象。  \n\n### + Application RuntimeInstance.getOwnerApp()  \n返回当前Runtime属于哪个Application. 通常该函数的返回等价于getCurrentApp()  \n\n\n### + RuntimeInfo RuntimeInstance.createRuntimeInfo()\n返回与指代RuntimeInstance的一个RuntimeInfo \n\n### + KnowledgeManager RuntimeInstance.getKnowledgeManager()\n返回当前的Runtime所使用的KnowledegeManager对象。这是应用开发得到KnowledgeManager对象的主要方法。  \n\n### + GlobalEventManager RuntimeInstance.getGlobalEventManager()\n返回当前的Runtime所使用的GlobalEventManager对象。这是应用开发得到GlobalEventManager对象的主要方法。\n\n### + XARManager RuntimeInstance.getXARManager()\n返回当前的Runtime的XARManager，用以管理xar相关逻辑\n\n### + UIEnv RuntimeInstance.getUIEnv()\n返回当前的Runtime对应的uienv，只有在有前端情况下才会使用此接口，第一次调用该接口会自动创建对应的UIEnv\n\n### + Driver RuntimeInstance.getDriver(string driverID)\n得到一个驱动。驱动能否获取成功取决于当前Runtime所在的设备。  \n驱动通常能提供一些功能，用于与旧世界打交道。应用开发应该尽量避免使用驱动。  \n目前实现的驱动：(`小应用暂未开放加载驱动`)  \n+ bx.mysql.client  \n+ bx.mongo.client  \n\n### + void RuntimeInstance.loadXARPackage(xarInfo xarID,function onComplete)\n核心函数,用于加载一个指定的package\n\n### + void RuntimeInstance.loadXARPackageEx(xarInfo xarID,function onComplete)\n核心函数,用于加载一个指定的package，其中options可以指定该包的特性\noptions包含如下字段\n* isProxy 需要加载的包是不是代理包\n* originOnly 是不是只能加载本体包，而不能加载代理包\n\n### + void RuntimeInstance.refreshXARPackage(xarInfo xarID)\n刷新一个已经加载的xar包，下次使用该包会触发重新加载的流程\n\n### + XARPackage RuntimeInstance.getXARPackage(xarInfo xarID)\n返回当前RuntimeInstance已经加载成功的一个Package。  \n这是一个同步函数。 \n\n### + RuntimeInstance.loadModule(moduleFullID, function onComplete)\n加载指定的模块，其中moduleFullID形如xarID:moduleID\nonComplete(result, module)\n\n### + RuntimeInstance.loadModules(moduleFullIDList, function onComplete)\n加载指定的模块列表，其中moduleFullIDList形如[xarID:moduleID]数组，里面包含了多个moduleFullID\nonComplete(moduleList)\n其中moduleList[packageID][moduleID] = module\n\n\n### + module RuntimeInstance.getModule(moduleID)\n同步获取指定的模块，其中moduleID形如xarID:moduleID,\n如果该模块已经加载，那么会同步返回该module，否则返回null\n\n### + [result, exports] RuntimeInstance.loadFile(fileInfo)\n同步加载指定的文件为模块，其中moduleID形如xarID:moduleID,\n其中fileInfo包含以下字段\n* path 文件路径\n* content 文件内容\n\n\n### + RuntimeInstance.selectTargetRuntime(packageID,packageInfo,useCache,onComplete)\n为一个包选择一个合适的runtime\n* packageID 包名称\n* packageInfo package.json对应的内容\n* useCache 是否使用缓存，不使用缓存会从scheduler查询，否则从本地和knowledge查询\n* onComplete(ret, runtimeInfo)\n\n### + RuntimeInstance.rpcCall(packageInfo, functionName, args, onComplete)\n直接调用目标包里面的函数\n* packageInfo 目标包的package.json\n* functionName的定义参考BaseLib.parseFunctionName  \n* args 调用的参数列表\n* onComplete(result, errCode)\n\n\n### - bool RuntimeInstance.isXARPackageCanLoad(packageInfo,string instanceID) \n判断当前Runtime是否允许`直接加载`目标XAR包。如果不允许，那么当前Runtime在加载目标XAR包时，会加载该XAR的proxy包\n\n\n\n## XARPackage\n代表Bucky框架里最重要一个基础概念，是对各种工程文件进行模块化以后，物理上的一个包。  \n通常通过RuntimeInstance对象的loadXARPackage函数获得。  \n\n### + packageInfo XARPackage.getPackageInfo()\n返回该XARPackage定义在config.json中的配置信息。\n\n### + [errorCode, Module] XARPackage.getModule(string moduleID)\n通过模块ID获取XARPackage中的一个模块,加载结果同步返回。\nXARPackage的config.json里定义了可以被加载的模块。\n通过数组[errorCode, Module]返回结果。加载成功Module为非null值。\n\n### XARPackage.isModuleExist(string moduleID)\n判断模块是否存在。存在返回true.\n\n### - XARPackage.loadLocalModule()\n私有函数，微信小程序的环境加载js文件的方法较为特殊。\n\n##Module\n通过loadModule返回的对象，是一个包含所有导出函数的字典。\n如果一个模块的结尾是按以下方法导出的：\n>module.exports = {};\n>module.exports.Login = Login;\n>module.exports.Register = Register;\n\n那么加载该模块返回的字典为\n>{\n>    \"Login\" : [Function],\n>    \"Register\" : [Function]\n>}\n\n##RuntimeStorage \nApplication所要永久保存的数据，最后一定会在某些Runtime上落地。当应用代码在这些有数据的Runtime上运行时，就能通过RuntimeStorage得到保存的数据。  \nRuntimeStorage使用K-V设计来保存结构化数据。\n\n### + RuntimeStorage.setObject（string objID,object objItem,function onComplete）\n要求在当前Storage目录保存一个ObjectItem.保存完成后会触发onComplete通知\n该调用成功后一定会触发一次磁盘写入。消耗App的磁盘空间资源和IO吞吐资源。\n\n### + RuntimeStorage.getObject(string objID,function onComplete)\n要求从当前Storage目录读取一个ObjectItem.读取成功通过onComplete返回。\n该调用成功后一定会触发一次磁盘读取。消耗App的IO吞吐资源。\n\n### + RuntimeStorage.removeObject(string objID,function onComplete)\n要求从当前Storage目录删除一个ObjectItem。删除结果通过onComplete返回。\n该调用成功后会释放App的磁盘空间资源，消耗一定的IO吞吐资源。\n\n### + RuntimeStorage.isObjectExists(string objID,function onComplete)\n判断当前Storage目录是否存在一个ObjectItem.判断结果通过onComplete返回。\n该调用会消耗App的IO吞吐资源。\n\n##RuntimeCache\n`内测版暂未开放`\n\n## ErrorCode\nBukcy使用一些通用的错误代码（还会持续增加）来表示API的结果。\n\n+ `ErrorCode.RESULT_OK` : 0  表示成功\n+ `ErrorCode.RESULT_TIMEOUT` : 操作超时失败\n+ `ErrorCode.RESULT_WAIT_INIT` : 2 操作失败，需要等待初始化成功\n+ `ErrorCode.RESULT_ERROR_STATE` : 3 操作失败，处于错误的状态\n+ `ErrorCode.RESULT_NOT_FOUND` : 4 操作失败，对象未找到   \n+ `ErrorCode.RESULT_SCRIPT_ERROR` : 5 操作失败，脚本错误  \n+ `ErrorCode.RESULT_NO_IMP` : 6 操作失败，该功能未实现  \n+ `ErrorCode.RESULT_ALREADY_EXIST` : 7 操作失败，对象已存在  \n+ `ErrorCode.RESULT_UNKNOWN` : 8 操作失败，未知错误\n\n## KnowledgeManager\n知识管理器是Bucky的一个关键设计，用于管理／同步 分布式系统里的一些关键的全局状态。   \n在开发者看来，可以把KnowledgeManager想象成一个全局变量管理器（读多写少，但写有强一致性保证）  \n提供必要的并行同步设施。  \n内测阶段请简单实用KnowledgeManager,我们以后会有更详细的文档来更全面的介绍该设施\n\n### InfoNode\nKnowledgeManager通过Key返回的一个指定的全局状态对象。为了方便应用开发，读操作被设计为同步的。\n目前只支持两种数据类型，不同的数据类型有不同粒度的操作函数。 \n+ Object\n+ Map \n\n#### + int InfoNode.getType()\n获得该全局对象的类型。\n\n#### + int InfoNode.getState()\n获得该全局对象当前的状态，正常使用下能拿到的InfoNode的状态都是STATE_READY\n\n#### + Object InfoNode.objectRead()\n如果该状态的类型是Object,那么返回该Object\n\n#### + void InfoNode.objectUpdate(Object newObj,function onComplete)\n如果该状态的类型是Object，那么异步更新该全局状态。\n`内测版未支持`  \n\n#### + Object InfoNode.mapGet(string key)\n如果该状态的类型是Map,那么返回key对应的value对象。  \n\n#### + void InfoNode.mapSet(string key,Object objItem,function onComplete)\n如果该状态的类型是Map，那么异步更新设置key对应的value对象为objItem。 更新结果通过onComplete返回。\n`内测版未支持`  \n\n#### + void InfoNode.mapGetClone()\n如果该状态的类型是Map，那么返回整个map（通常用于便利)  \n\n### + InfoNode KnowledgeManager.getKnowledge(string key)\n核心函数，通过key返回一个全局状态对象。该函数是同步的。\n该key必需通过KnowledgeManager.dependKnowledge事先要求依赖了。\n\n### + void KnowledgeManager.dependKnowledge(string key,Object options)\n核心函数，Runtime要求依赖key指定的全局状态。  \n\n### + void KnowledgeManager.ready(function onReady)\n核心函数，调用后当前所有依赖的全局状态如果都已同步完成，会触发onReady通知。\n\n### + int KnowledgeManager.getState()\n返回当前KnowledgeManager的状态。一般不需要调用函数。  \n\n### - string KnowledgeManager.getInfoURL(string key)\n私有函数  \n\n### - void KnowledgeManager.addknowledgeKey(string key,InfoNode aNode)\n私有函数  \n\n### - void KnowledgeManager.removeknowledgeKey(string key)\n私有函数   \n\n##BaseLib\nBaseLib定义了大量的功能性静态函数。\n这是Bucky框架为了统一各个js运行时库的差异提供的基础功能库。框架会保证这些功能函数在不同的JS环境中都能工作，应用开发应该尽量使用BaseLib中提供的功能来完成需求。\n如果您在开发过程中有基础功能的需求而该功能又不在BaseLib中，请给我们提Issue :)\n`内测阶段BaseLib的功能函数以满足我们自己的需要为主，以后还会进一步调整和整理`\n\n### + int BaseLib.setTimer(function func,int timeout)\n创建一个timer,每隔timeout指定的毫秒的时间过去后，会执行func。\n调用成功返回timerID\n\n### + void BaseLib.killTimer(timerID)\n停止一个timer。timerID由setTimer函数返回。\n\n### + void BaseLib.setOnceTimer(function func,int timeout)\n创建一个一次性的timer.经过timeout指定的毫秒时间后func被调用。\n\n### + BaseLib.asynCall(function func)\n发起一次`异步调用`，func函数会在被投递到下一个消息循环中调用。\n非常有用的小函数，特别是用在处理某些事件的时候。\n\n### + string BaseLib.hash(string method,string content,string format)  \n通用hash函数，对content做method指定的hash算法，并把结果按format指定的格式返回  \n目前method只支持md5  \nformat只支持  \n\n### + string BaseLib.md5(string content,string format)\n相当于调用 \n>BaseLib.hash(\"md5\",content,format)\n\n### + int BaseLib.getRandomNum(min,max)\n返回[min,max)之间的一个随机正数。  \n\n### + string BaseLib.createGUID()\n创建一个GUID String。 \n\n### + bool BaseLib.isArrayContained(StringArray a,StringArray b)\n判断一个字符串数组a是否被另一个字符串数组b包含。  \n\n### + int BaseLib.inet_aton(string IP)\n将一个\"10.10.10.1\"这样的字符串转换为32位整数  \n\n### + string BaseLib.inet_ntoa(int num)\n将一个32位整数转化为\"10.10.10.1\"这样的IP String  \n\n### + FunctionInfo BaseLib.parseFunctionName(string functionName)\n解析一个符合Bukcy定义的FunctionName.FunctionName看起来如下 $xarPackageID:$moudeID::$funcName@$runtimeInstanceID  \n返回一个对象\n```\n{\n  \"packageInfo\":\"\",\n  \"moduleID\":\"\",\n  \"functionID\":\"\",\n  \"instanceID\":\"\"\n}\n```\n\n### + void BaseLib.loadFileFromURL(string fileURL,function onComplete)\n从一个URL使用HTTP GET方法异步加载文件，结果通过onComplete回调返回。  \n\n### + void BaseLib.loadJSONFromURL(string fileURL,function onComplete)\n从一个URL使用HTTP GET方法异步加载文件，并假设该文件的内容是一个JOSN.把解析的结果通过onComplete回调返回。  \n\n### + void BaseLib.postJSON(string postURL,Object postBody,function onComplete)\n往postURL使用HTTP POST方法发送一个用json编码的postBody,通过onComplete返回服务器的结果。  \n\n### + void BaseLib.postData(string postURL,string postBody,function onComplete)\n往postURL使用HTTP POST方法发送一个postBody,通过onComplete返回服务器的结果。    \n\n### - void BaseLib.runScriptFromURL(string fileURL,function onComplete)\n私有函数\n\n### - void BaseLib.wxHttpRequest()\n私有函数，用于兼容微信小程序的HttpRequest\n\n\n\n\n\n## Device\n一般通过RuntimeInstance的getOwnerDevice得到。代表当前Runtime所在的计算设备。\n\n### DeviceInfo 对象\n代表系统中的一个可计算设备（不一定是当前的可计算设备）\n当应用系统\n#### `DeviceID` : 设备的唯一ID\n#### `Type` : 设备的类型。目前有以下可用值  \n+ \"*\"  可模拟任意设备（只有本地调试模式会使用）  \n+ pc_server 后台服务器  \n+ wx_client 微信小程序客户端  \n+ browser_client 浏览器客户端  \n#### `IsOnline` : 设备是否在线  \n#### `Ability` : 设备的能力列表 `目前小应用云所有的设备都支持 wlan-interface,storage`\n#### `Drivers` : 设备支持的驱动列表 `目前小应用云未提供任何驱动`\n\n### + string Device.getDeviceID()\n得到设备的ID   \n\n### + Array Device.getAbility()\n得到设备的能力列表 `目前小应用云所有的设备都支持 wlan-interface,storage`   \n\n### + string Device.getDeviceType()\n得到设备的类型。目前有以下可用值    \n+ \"*\"  可模拟任意设备（只有本地调试模式会使用）  \n+ pc_server 后台服务器  \n+ wx_client 微信小程序客户端  \n+ browser_client 浏览器客户端  \n\n### + bool Device.isDriverInstalled(string driverID)\n查询是否支持某个指定的驱动  \n\n### + DeviceInfo Device.createDeviceInfo()\n创建代表当前设备的DeviceInfo对象  \n\n\n\n## GlobalEventManager\n依赖Knowledge作为底层系统实现的全局事件系统。\n\n### - GlobalEventManager.createEvent(string eventID, function onComplete)\n创建一个事件，只有创建成功的事件才能被其他用户attach\n**每一个被创建的事件都会占用一定的系统资源！请务必确定事件不再使用的时候销毁掉!**\n\n### - GlobalEventManager.removeEvent(string eventID)\n移除一个事件，被移除的事件不能被fire或attach，同时会释放event占用的资源\n\n### - GlobalEventManager.attach(string eventID,function onEvent, function onComplete)\n关注一个事件，当有事件通知到达时，调用onEvent(param), attach动作结束后调用onComplete(ret, cookie)函数，ret==0表示连接成功, cookie用来detach事件；eventID必须事先create成功\n\n### - GlobalEventManager.detach(string eventID, integer cookie)\n不再关注这个事件，cookie会在attach的onComplete里返回。只有attach成功时才需要detach事件\n\n### - GlobalEventManager.fireEvent(string eventID, string param)\n发起一次事件通知，会调用所有已经attach这个事件用户的onEvent(param)方法。我们建议param为一个json字符串，方便事件扩展\n\n### - GlobalEventManager.attachListenerChanged(string eventID,function onEvent, function onComplete)\n关注监听者改变事件，每当用户attach到这个事件，或从事件detach时，会调用onEvent(param)，其余参数与attach相同\n\n### - GlobalEventManager.detachListenerChanged(string eventID, integer cookie)\n不再关注监听者改变事件，用法与detach函数相同\n\n### - GlobalEventManager.getListenerList(string eventID, function onComplete)\n得到所有的事件监听者列表，在onComplete(ret, list)中返回\n\n### - GlobalEventManager.isEventCreated(string eventID)\n私有函数\n\n\n\n\n",
      "html": "<h1><strong>参考手册</strong></h1>\n<h2>全局函数</h2>\n<h3>+ runtime getCurrentRuntime()</h3>\n<p>常用接口，获得当前代码所在的RuntimeInstance对象。<br>\n如果该函数返回null,说明框架没有正确初始化。</p>\n<h3>+ app getCurrentApp()</h3>\n<p>常用接口，获得当前的应用系统。<br>\n如果该函数返回null,说明框架没有正确初始化。</p>\n<h3>+ xar getCurrentXAR()</h3>\n<p>获取当前代码所属的xar，一般在xar的内部代码或者模块代码里面，用以获取当前所属的xar</p>\n<h3>+ initCurrentRuntime</h3>\n<p>在各种javascript环境中用来初始化bucky的client runtime。根据javascript运行环境的不同，该函数可能有不同的参数。<br>\n在小程序环境中初始化Bucky Runtime</p>\n<h2>日志</h2>\n<h4>+ BX_XXX(content)</h4>\n<p>输出一行日志，内容为info,级别为一下可选值</p>\n<ul>\n<li>BX_DEBUG</li>\n<li>BX_TRACE</li>\n<li>BX_LOG</li>\n<li>BX_INFO</li>\n<li>BX_WARN</li>\n<li>BX_ERROR</li>\n<li>BX_FATAL</li>\n</ul>\n<p>条件检测，功能类似于assert</p>\n<ul>\n<li>BX_CHECK</li>\n<li>BX_ASSERT</li>\n</ul>\n<p>设置日志级别</p>\n<blockquote>\n<p>BX_SetLogLevel(levelstring)\n其中levelstring可取值以下，设置后低于该级别的日志不再被输出</p>\n</blockquote>\n<ul>\n<li>BLOG_LEVEL_ALL</li>\n<li>BLOG_LEVEL_TRACE</li>\n<li>BLOG_LEVEL_DEBUG</li>\n<li>BLOG_LEVEL_INFO</li>\n<li>BLOG_LEVEL_WARN</li>\n<li>BLOG_LEVEL_ERROR</li>\n<li>BLOG_LEVEL_CHECK</li>\n<li>BLOG_LEVEL_FATAL</li>\n<li>BLOG_LEVEL_OFF</li>\n</ul>\n<h2>Application</h2>\n<p>Application对象，用来得到当前运行的分布式应用的信息</p>\n<h3>+ void Application.init(metaInfo,function onInitComplete)</h3>\n<p>异步初始化Application对象。Application对象会从core service中读取必要的信息，所以这个操作时异步的。</p>\n<h4>参数列表</h4>\n<h4><code>metaInfo</code>：一般从app.json读取，内容如下：</h4>\n<pre><code>{\n  &quot;appid&quot;: &quot;bx.demos.account&quot;,\n  &quot;repositoryHost&quot;: &quot;https://dev.buckycloud.com/services/repository&quot;,\n  &quot;knowledgeHost&quot;: &quot;https://dev.buckycloud.com/services/knowledge&quot;,\n  &quot;schedulerHost&quot; : &quot;https://dev.buckycloud.com/services/scheduler&quot;,\n  &quot;appver&quot; : &quot;1.0.0.1&quot;,\n  &quot;token&quot; : &quot;abcdef0123&quot;\n} \n</code></pre>\n<p>appID是应用的唯一ID。<br>\nrepositoryHost与knowledgeHost是app运行依赖的核心服务地址。配置不同的服务器可以用来区分线上版本和开发版本。<br>\n其中repositoryHost可以配置一个或者多个仓库地址，具体如下</p>\n<pre><code>&quot;repositoryHost&quot; : &quot;https://xxxx&quot;,  // 如果直接配置一个字符串，则是默认是normal模式\n&quot;repositoryHost&quot;: {\n    &quot;value&quot; : &quot;xxxx&quot;,\n    &quot;type&quot;: &quot;[normal/local/config]&quot;\n},\n// 多个仓库配置\n&quot;repositoryHost&quot; : [\n    {\n        &quot;value&quot; : &quot;https://xxx&quot;,\n        &quot;type&quot; : &quot;normal&quot;\n    },{\n        &quot;value&quot; : &quot;[local dir]&quot;,\n        &quot;type&quot; : &quot;local&quot;\n    }, {\n        &quot;value&quot; : &quot;[packagelist]&quot;,\n        &quot;type&quot; : &quot;config&quot;\n    }\n]\n</code></pre>\n<h4><code>onInitComplete</code>: function(erroCode,appInfo){…}</h4>\n<p>当初始化完成后，会调用该函数。</p>\n<h3>+ string Application.getID()</h3>\n<p>返回Application的唯一ID.</p>\n<h3>+ string Application.getRuntime()</h3>\n<p>返回当前app绑定的runtime实例</p>\n<h3>+ string getKnowledgeHost()</h3>\n<p>获取配置的knowledge地址</p>\n<h3>+ string getSchedulerHost()</h3>\n<p>获取配置的scheduler地址</p>\n<h3>+ addRepository(type, value)</h3>\n<p>添加一个仓库地址，需要注意的是动态调整仓库地址要在初始化runtime之前</p>\n<h3>+ cleanRepository()</h3>\n<p>清除所有的仓库地址列表</p>\n<h3>+ array getRepositoryList()</h3>\n<p>获取所有的仓库地址列表</p>\n<p>##RuntimeInfo<br>\n用来表示一个具体的RuntimeInstance.这个对象应用开发很少使用，我们并不鼓励在应用逻辑中过于依赖某个具体的RuntimeInstance。</p>\n<h4><code>RuntimeInfo.runtime_id</code> : RuntimeInstance的唯一ID</h4>\n<h4><code>RuntimeInfo.runtime_type</code> : RuntimeInstance的类型</h4>\n<h4><code>RuntimeInfo.device_id</code> : RuntimeInstance所在的设备ID</h4>\n<h4><code>RuntimeInfo.device_type</code> : RuntimeInstance所在的设备类型</h4>\n<h4><code>RuntimeInfo.app_id</code> : RuntimeInstance所属的Application ID。</h4>\n<h4><code>RuntimeInfo.ability</code> : 当前runtime支持的能力列表</h4>\n<h4><code>RuntimeInfo.drivers</code> : 当前runtime支持的驱动列表</h4>\n<h4><code>RuntimeInfo.tag</code> : 当前runtime的标签列表</h4>\n<h4><code>RuntimeInfo.storages</code> : 当前runtime的绑定的存储路径列表</h4>\n<h2>RuntimeInstance</h2>\n<p>核心对象。<br>\nRuntimeInstance代表的是当前代码的运行环境。一般通过全局函数getCurrentRuntime()来获取。</p>\n<h3>+ string RuntimeInstance.getID()</h3>\n<p>返回当前Runtime的唯一ID</p>\n<h3>+ Device RuntimeInstance.getOwnerDevice()</h3>\n<p>返回当前Runtime所在的设备对象。</p>\n<h3>+ Application RuntimeInstance.getOwnerApp()</h3>\n<p>返回当前Runtime属于哪个Application. 通常该函数的返回等价于getCurrentApp()</p>\n<h3>+ RuntimeInfo RuntimeInstance.createRuntimeInfo()</h3>\n<p>返回与指代RuntimeInstance的一个RuntimeInfo</p>\n<h3>+ KnowledgeManager RuntimeInstance.getKnowledgeManager()</h3>\n<p>返回当前的Runtime所使用的KnowledegeManager对象。这是应用开发得到KnowledgeManager对象的主要方法。</p>\n<h3>+ GlobalEventManager RuntimeInstance.getGlobalEventManager()</h3>\n<p>返回当前的Runtime所使用的GlobalEventManager对象。这是应用开发得到GlobalEventManager对象的主要方法。</p>\n<h3>+ XARManager RuntimeInstance.getXARManager()</h3>\n<p>返回当前的Runtime的XARManager，用以管理xar相关逻辑</p>\n<h3>+ UIEnv RuntimeInstance.getUIEnv()</h3>\n<p>返回当前的Runtime对应的uienv，只有在有前端情况下才会使用此接口，第一次调用该接口会自动创建对应的UIEnv</p>\n<h3>+ Driver RuntimeInstance.getDriver(string driverID)</h3>\n<p>得到一个驱动。驱动能否获取成功取决于当前Runtime所在的设备。<br>\n驱动通常能提供一些功能，用于与旧世界打交道。应用开发应该尽量避免使用驱动。<br>\n目前实现的驱动：(<code>小应用暂未开放加载驱动</code>)</p>\n<ul>\n<li>bx.mysql.client</li>\n<li>bx.mongo.client</li>\n</ul>\n<h3>+ void RuntimeInstance.loadXARPackage(xarInfo xarID,function onComplete)</h3>\n<p>核心函数,用于加载一个指定的package</p>\n<h3>+ void RuntimeInstance.loadXARPackageEx(xarInfo xarID,function onComplete)</h3>\n<p>核心函数,用于加载一个指定的package，其中options可以指定该包的特性\noptions包含如下字段</p>\n<ul>\n<li>isProxy 需要加载的包是不是代理包</li>\n<li>originOnly 是不是只能加载本体包，而不能加载代理包</li>\n</ul>\n<h3>+ void RuntimeInstance.refreshXARPackage(xarInfo xarID)</h3>\n<p>刷新一个已经加载的xar包，下次使用该包会触发重新加载的流程</p>\n<h3>+ XARPackage RuntimeInstance.getXARPackage(xarInfo xarID)</h3>\n<p>返回当前RuntimeInstance已经加载成功的一个Package。<br>\n这是一个同步函数。</p>\n<h3>+ RuntimeInstance.loadModule(moduleFullID, function onComplete)</h3>\n<p>加载指定的模块，其中moduleFullID形如xarID:moduleID\nonComplete(result, module)</p>\n<h3>+ RuntimeInstance.loadModules(moduleFullIDList, function onComplete)</h3>\n<p>加载指定的模块列表，其中moduleFullIDList形如[xarID:moduleID]数组，里面包含了多个moduleFullID\nonComplete(moduleList)\n其中moduleList[packageID][moduleID] = module</p>\n<h3>+ module RuntimeInstance.getModule(moduleID)</h3>\n<p>同步获取指定的模块，其中moduleID形如xarID:moduleID,\n如果该模块已经加载，那么会同步返回该module，否则返回null</p>\n<h3>+ [result, exports] RuntimeInstance.loadFile(fileInfo)</h3>\n<p>同步加载指定的文件为模块，其中moduleID形如xarID:moduleID,\n其中fileInfo包含以下字段</p>\n<ul>\n<li>path 文件路径</li>\n<li>content 文件内容</li>\n</ul>\n<h3>+ RuntimeInstance.selectTargetRuntime(packageID,packageInfo,useCache,onComplete)</h3>\n<p>为一个包选择一个合适的runtime</p>\n<ul>\n<li>packageID 包名称</li>\n<li>packageInfo package.json对应的内容</li>\n<li>useCache 是否使用缓存，不使用缓存会从scheduler查询，否则从本地和knowledge查询</li>\n<li>onComplete(ret, runtimeInfo)</li>\n</ul>\n<h3>+ RuntimeInstance.rpcCall(packageInfo, functionName, args, onComplete)</h3>\n<p>直接调用目标包里面的函数</p>\n<ul>\n<li>packageInfo 目标包的package.json</li>\n<li>functionName的定义参考BaseLib.parseFunctionName</li>\n<li>args 调用的参数列表</li>\n<li>onComplete(result, errCode)</li>\n</ul>\n<h3>- bool RuntimeInstance.isXARPackageCanLoad(packageInfo,string instanceID)</h3>\n<p>判断当前Runtime是否允许<code>直接加载</code>目标XAR包。如果不允许，那么当前Runtime在加载目标XAR包时，会加载该XAR的proxy包</p>\n<h2>XARPackage</h2>\n<p>代表Bucky框架里最重要一个基础概念，是对各种工程文件进行模块化以后，物理上的一个包。<br>\n通常通过RuntimeInstance对象的loadXARPackage函数获得。</p>\n<h3>+ packageInfo XARPackage.getPackageInfo()</h3>\n<p>返回该XARPackage定义在config.json中的配置信息。</p>\n<h3>+ [errorCode, Module] XARPackage.getModule(string moduleID)</h3>\n<p>通过模块ID获取XARPackage中的一个模块,加载结果同步返回。\nXARPackage的config.json里定义了可以被加载的模块。\n通过数组[errorCode, Module]返回结果。加载成功Module为非null值。</p>\n<h3>XARPackage.isModuleExist(string moduleID)</h3>\n<p>判断模块是否存在。存在返回true.</p>\n<h3>- XARPackage.loadLocalModule()</h3>\n<p>私有函数，微信小程序的环境加载js文件的方法较为特殊。</p>\n<p>##Module\n通过loadModule返回的对象，是一个包含所有导出函数的字典。\n如果一个模块的结尾是按以下方法导出的：</p>\n<blockquote>\n<p>module.exports = {};\nmodule.exports.Login = Login;\nmodule.exports.Register = Register;</p>\n</blockquote>\n<p>那么加载该模块返回的字典为</p>\n<blockquote>\n<p>{\n“Login” : [Function],\n“Register” : [Function]\n}</p>\n</blockquote>\n<p>##RuntimeStorage\nApplication所要永久保存的数据，最后一定会在某些Runtime上落地。当应用代码在这些有数据的Runtime上运行时，就能通过RuntimeStorage得到保存的数据。<br>\nRuntimeStorage使用K-V设计来保存结构化数据。</p>\n<h3>+ RuntimeStorage.setObject（string objID,object objItem,function onComplete）</h3>\n<p>要求在当前Storage目录保存一个ObjectItem.保存完成后会触发onComplete通知\n该调用成功后一定会触发一次磁盘写入。消耗App的磁盘空间资源和IO吞吐资源。</p>\n<h3>+ RuntimeStorage.getObject(string objID,function onComplete)</h3>\n<p>要求从当前Storage目录读取一个ObjectItem.读取成功通过onComplete返回。\n该调用成功后一定会触发一次磁盘读取。消耗App的IO吞吐资源。</p>\n<h3>+ RuntimeStorage.removeObject(string objID,function onComplete)</h3>\n<p>要求从当前Storage目录删除一个ObjectItem。删除结果通过onComplete返回。\n该调用成功后会释放App的磁盘空间资源，消耗一定的IO吞吐资源。</p>\n<h3>+ RuntimeStorage.isObjectExists(string objID,function onComplete)</h3>\n<p>判断当前Storage目录是否存在一个ObjectItem.判断结果通过onComplete返回。\n该调用会消耗App的IO吞吐资源。</p>\n<p>##RuntimeCache\n<code>内测版暂未开放</code></p>\n<h2>ErrorCode</h2>\n<p>Bukcy使用一些通用的错误代码（还会持续增加）来表示API的结果。</p>\n<ul>\n<li><code>ErrorCode.RESULT_OK</code> : 0  表示成功</li>\n<li><code>ErrorCode.RESULT_TIMEOUT</code> : 操作超时失败</li>\n<li><code>ErrorCode.RESULT_WAIT_INIT</code> : 2 操作失败，需要等待初始化成功</li>\n<li><code>ErrorCode.RESULT_ERROR_STATE</code> : 3 操作失败，处于错误的状态</li>\n<li><code>ErrorCode.RESULT_NOT_FOUND</code> : 4 操作失败，对象未找到</li>\n<li><code>ErrorCode.RESULT_SCRIPT_ERROR</code> : 5 操作失败，脚本错误</li>\n<li><code>ErrorCode.RESULT_NO_IMP</code> : 6 操作失败，该功能未实现</li>\n<li><code>ErrorCode.RESULT_ALREADY_EXIST</code> : 7 操作失败，对象已存在</li>\n<li><code>ErrorCode.RESULT_UNKNOWN</code> : 8 操作失败，未知错误</li>\n</ul>\n<h2>KnowledgeManager</h2>\n<p>知识管理器是Bucky的一个关键设计，用于管理／同步 分布式系统里的一些关键的全局状态。<br>\n在开发者看来，可以把KnowledgeManager想象成一个全局变量管理器（读多写少，但写有强一致性保证）<br>\n提供必要的并行同步设施。<br>\n内测阶段请简单实用KnowledgeManager,我们以后会有更详细的文档来更全面的介绍该设施</p>\n<h3>InfoNode</h3>\n<p>KnowledgeManager通过Key返回的一个指定的全局状态对象。为了方便应用开发，读操作被设计为同步的。\n目前只支持两种数据类型，不同的数据类型有不同粒度的操作函数。</p>\n<ul>\n<li>Object</li>\n<li>Map</li>\n</ul>\n<h4>+ int InfoNode.getType()</h4>\n<p>获得该全局对象的类型。</p>\n<h4>+ int InfoNode.getState()</h4>\n<p>获得该全局对象当前的状态，正常使用下能拿到的InfoNode的状态都是STATE_READY</p>\n<h4>+ Object InfoNode.objectRead()</h4>\n<p>如果该状态的类型是Object,那么返回该Object</p>\n<h4>+ void InfoNode.objectUpdate(Object newObj,function onComplete)</h4>\n<p>如果该状态的类型是Object，那么异步更新该全局状态。\n<code>内测版未支持</code></p>\n<h4>+ Object InfoNode.mapGet(string key)</h4>\n<p>如果该状态的类型是Map,那么返回key对应的value对象。</p>\n<h4>+ void InfoNode.mapSet(string key,Object objItem,function onComplete)</h4>\n<p>如果该状态的类型是Map，那么异步更新设置key对应的value对象为objItem。 更新结果通过onComplete返回。\n<code>内测版未支持</code></p>\n<h4>+ void InfoNode.mapGetClone()</h4>\n<p>如果该状态的类型是Map，那么返回整个map（通常用于便利)</p>\n<h3>+ InfoNode KnowledgeManager.getKnowledge(string key)</h3>\n<p>核心函数，通过key返回一个全局状态对象。该函数是同步的。\n该key必需通过KnowledgeManager.dependKnowledge事先要求依赖了。</p>\n<h3>+ void KnowledgeManager.dependKnowledge(string key,Object options)</h3>\n<p>核心函数，Runtime要求依赖key指定的全局状态。</p>\n<h3>+ void KnowledgeManager.ready(function onReady)</h3>\n<p>核心函数，调用后当前所有依赖的全局状态如果都已同步完成，会触发onReady通知。</p>\n<h3>+ int KnowledgeManager.getState()</h3>\n<p>返回当前KnowledgeManager的状态。一般不需要调用函数。</p>\n<h3>- string KnowledgeManager.getInfoURL(string key)</h3>\n<p>私有函数</p>\n<h3>- void KnowledgeManager.addknowledgeKey(string key,InfoNode aNode)</h3>\n<p>私有函数</p>\n<h3>- void KnowledgeManager.removeknowledgeKey(string key)</h3>\n<p>私有函数</p>\n<p>##BaseLib\nBaseLib定义了大量的功能性静态函数。\n这是Bucky框架为了统一各个js运行时库的差异提供的基础功能库。框架会保证这些功能函数在不同的JS环境中都能工作，应用开发应该尽量使用BaseLib中提供的功能来完成需求。\n如果您在开发过程中有基础功能的需求而该功能又不在BaseLib中，请给我们提Issue :)\n<code>内测阶段BaseLib的功能函数以满足我们自己的需要为主，以后还会进一步调整和整理</code></p>\n<h3>+ int BaseLib.setTimer(function func,int timeout)</h3>\n<p>创建一个timer,每隔timeout指定的毫秒的时间过去后，会执行func。\n调用成功返回timerID</p>\n<h3>+ void BaseLib.killTimer(timerID)</h3>\n<p>停止一个timer。timerID由setTimer函数返回。</p>\n<h3>+ void BaseLib.setOnceTimer(function func,int timeout)</h3>\n<p>创建一个一次性的timer.经过timeout指定的毫秒时间后func被调用。</p>\n<h3>+ BaseLib.asynCall(function func)</h3>\n<p>发起一次<code>异步调用</code>，func函数会在被投递到下一个消息循环中调用。\n非常有用的小函数，特别是用在处理某些事件的时候。</p>\n<h3>+ string BaseLib.hash(string method,string content,string format)</h3>\n<p>通用hash函数，对content做method指定的hash算法，并把结果按format指定的格式返回<br>\n目前method只支持md5<br>\nformat只支持</p>\n<h3>+ string BaseLib.md5(string content,string format)</h3>\n<p>相当于调用</p>\n<blockquote>\n<p>BaseLib.hash(“md5”,content,format)</p>\n</blockquote>\n<h3>+ int BaseLib.getRandomNum(min,max)</h3>\n<p>返回[min,max)之间的一个随机正数。</p>\n<h3>+ string BaseLib.createGUID()</h3>\n<p>创建一个GUID String。</p>\n<h3>+ bool BaseLib.isArrayContained(StringArray a,StringArray b)</h3>\n<p>判断一个字符串数组a是否被另一个字符串数组b包含。</p>\n<h3>+ int BaseLib.inet_aton(string IP)</h3>\n<p>将一个&quot;10.10.10.1&quot;这样的字符串转换为32位整数</p>\n<h3>+ string BaseLib.inet_ntoa(int num)</h3>\n<p>将一个32位整数转化为&quot;10.10.10.1&quot;这样的IP String</p>\n<h3>+ FunctionInfo BaseLib.parseFunctionName(string functionName)</h3>\n<p>解析一个符合Bukcy定义的FunctionName.FunctionName看起来如下 $xarPackageID:$moudeID::$funcName@$runtimeInstanceID<br>\n返回一个对象</p>\n<pre><code>{\n  &quot;packageInfo&quot;:&quot;&quot;,\n  &quot;moduleID&quot;:&quot;&quot;,\n  &quot;functionID&quot;:&quot;&quot;,\n  &quot;instanceID&quot;:&quot;&quot;\n}\n</code></pre>\n<h3>+ void BaseLib.loadFileFromURL(string fileURL,function onComplete)</h3>\n<p>从一个URL使用HTTP GET方法异步加载文件，结果通过onComplete回调返回。</p>\n<h3>+ void BaseLib.loadJSONFromURL(string fileURL,function onComplete)</h3>\n<p>从一个URL使用HTTP GET方法异步加载文件，并假设该文件的内容是一个JOSN.把解析的结果通过onComplete回调返回。</p>\n<h3>+ void BaseLib.postJSON(string postURL,Object postBody,function onComplete)</h3>\n<p>往postURL使用HTTP POST方法发送一个用json编码的postBody,通过onComplete返回服务器的结果。</p>\n<h3>+ void BaseLib.postData(string postURL,string postBody,function onComplete)</h3>\n<p>往postURL使用HTTP POST方法发送一个postBody,通过onComplete返回服务器的结果。</p>\n<h3>- void BaseLib.runScriptFromURL(string fileURL,function onComplete)</h3>\n<p>私有函数</p>\n<h3>- void BaseLib.wxHttpRequest()</h3>\n<p>私有函数，用于兼容微信小程序的HttpRequest</p>\n<h2>Device</h2>\n<p>一般通过RuntimeInstance的getOwnerDevice得到。代表当前Runtime所在的计算设备。</p>\n<h3>DeviceInfo 对象</h3>\n<p>代表系统中的一个可计算设备（不一定是当前的可计算设备）\n当应用系统</p>\n<h4><code>DeviceID</code> : 设备的唯一ID</h4>\n<h4><code>Type</code> : 设备的类型。目前有以下可用值</h4>\n<ul>\n<li>“*”  可模拟任意设备（只有本地调试模式会使用）</li>\n<li>pc_server 后台服务器</li>\n<li>wx_client 微信小程序客户端</li>\n<li>browser_client 浏览器客户端</li>\n</ul>\n<h4><code>IsOnline</code> : 设备是否在线</h4>\n<h4><code>Ability</code> : 设备的能力列表 <code>目前小应用云所有的设备都支持 wlan-interface,storage</code></h4>\n<h4><code>Drivers</code> : 设备支持的驱动列表 <code>目前小应用云未提供任何驱动</code></h4>\n<h3>+ string Device.getDeviceID()</h3>\n<p>得到设备的ID</p>\n<h3>+ Array Device.getAbility()</h3>\n<p>得到设备的能力列表 <code>目前小应用云所有的设备都支持 wlan-interface,storage</code></p>\n<h3>+ string Device.getDeviceType()</h3>\n<p>得到设备的类型。目前有以下可用值</p>\n<ul>\n<li>“*”  可模拟任意设备（只有本地调试模式会使用）</li>\n<li>pc_server 后台服务器</li>\n<li>wx_client 微信小程序客户端</li>\n<li>browser_client 浏览器客户端</li>\n</ul>\n<h3>+ bool Device.isDriverInstalled(string driverID)</h3>\n<p>查询是否支持某个指定的驱动</p>\n<h3>+ DeviceInfo Device.createDeviceInfo()</h3>\n<p>创建代表当前设备的DeviceInfo对象</p>\n<h2>GlobalEventManager</h2>\n<p>依赖Knowledge作为底层系统实现的全局事件系统。</p>\n<h3>- GlobalEventManager.createEvent(string eventID, function onComplete)</h3>\n<p>创建一个事件，只有创建成功的事件才能被其他用户attach\n<strong>每一个被创建的事件都会占用一定的系统资源！请务必确定事件不再使用的时候销毁掉!</strong></p>\n<h3>- GlobalEventManager.removeEvent(string eventID)</h3>\n<p>移除一个事件，被移除的事件不能被fire或attach，同时会释放event占用的资源</p>\n<h3>- GlobalEventManager.attach(string eventID,function onEvent, function onComplete)</h3>\n<p>关注一个事件，当有事件通知到达时，调用onEvent(param), attach动作结束后调用onComplete(ret, cookie)函数，ret==0表示连接成功, cookie用来detach事件；eventID必须事先create成功</p>\n<h3>- GlobalEventManager.detach(string eventID, integer cookie)</h3>\n<p>不再关注这个事件，cookie会在attach的onComplete里返回。只有attach成功时才需要detach事件</p>\n<h3>- GlobalEventManager.fireEvent(string eventID, string param)</h3>\n<p>发起一次事件通知，会调用所有已经attach这个事件用户的onEvent(param)方法。我们建议param为一个json字符串，方便事件扩展</p>\n<h3>- GlobalEventManager.attachListenerChanged(string eventID,function onEvent, function onComplete)</h3>\n<p>关注监听者改变事件，每当用户attach到这个事件，或从事件detach时，会调用onEvent(param)，其余参数与attach相同</p>\n<h3>- GlobalEventManager.detachListenerChanged(string eventID, integer cookie)</h3>\n<p>不再关注监听者改变事件，用法与detach函数相同</p>\n<h3>- GlobalEventManager.getListenerList(string eventID, function onComplete)</h3>\n<p>得到所有的事件监听者列表，在onComplete(ret, list)中返回</p>\n<h3>- GlobalEventManager.isEventCreated(string eventID)</h3>\n<p>私有函数</p>\n",
      "id": 11
    },
    {
      "path": "3.开发手册/3.5.错误码.md",
      "url": "3.开发手册/3.5.错误码.html",
      "content": "## 系统通用错误代码\n\n```JavaScript\nconst ErrorCode = {\n    RESULT_OK: 0,\n    RESULT_FAILED: 1,\n    RESULT_WAIT_INIT: 2,\n    RESULT_ERROR_STATE: 3,\n    RESULT_INVALID_TYPE: 4,\n    RESULT_SCRIPT_ERROR: 5,\n    RESULT_NO_IMP: 6,\n    RESULT_ALREADY_EXIST: 7,\n    RESULT_NEED_SYNC: 8,\n    RESULT_NOT_FOUND: 9,\n    RESULT_EXPIRED: 10,\n    RESULT_INVALID_PARAM: 11,\n    RESULT_PARSE_ERROR: 12,\n    RESULT_REQUEST_ERROR: 13,\n    RESULT_NOT_SUPPORT: 14,\n    RESULT_TIMEOUT: 15,\n    RESULT_EXCEPTION: 16,\n    RESULT_INVALID_FORMAT: 17,\n    RESULT_UNKNOWN_VALUE: 18,\n    RESULT_INVALID_TOKEN: 19,\n    RESULT_INVALID_SESSION: 21,\n    RESULT_OUT_OF_LIMIT: 22,\n    RESULT_PERMISSION_DENIED: 23,\n    RESULT_OUT_OF_MEMORY: 24,\n    RESULT_INVALID_STATE : 25,\n    RESULT_UNMATCH_HASH : 26,\n    RESULT_UNMATCH: 27,\n    RESULT_REQUEST_CONNREFUSED: 28,\n    RESULT_REQUEST_CONNRESET: 29,\n    RESULT_NO_TARGET_RUNTIME: 30,\n    RESULT_INVALID_RULES: 31,\n    RESULT_REQUEST_EPIPE: 32,\n    RESULT_REQUEST_DNS_ERROR: 33,\n    RESULT_POST_FAILED: 40,\n    RESULT_MONGO_ERROR: 50,\n    RESULT_CONNECT_ERROR: 51,\n    RESULT_CREATE_USER_ERROR: 52,\n    RESULT_REMOVE_USER_ERROR: 53,\n    RESULT_SIGNUP_FAILED: 60,\n    RESULT_SIGNIN_FAILED: 61,\n    RESULT_INVALID_CONFIG: 80,\n    RESULT_INVALID_VERSION: 81,\n    RESULT_UNMATCH_RUNTIME: 82,\n    RESULT_UNMATCH_SEQ: 83,\n    RESULT_PENDING: 90,\n    RESULT_REACH_LIMIT: 100,\n    RESULT_LEVEL_UNMATCH: 101,\n    RESULT_DB_QUERY_ERROR: 110,\n    RESULT_DB_OPEN_FAILED: 111,\n    RESULT_DB_TRANSACTION_FAILED: 112,\n    RESULT_DB_CONDITION_NOT_MATCH: 113,\n    RESULT_METHOD_NOT_FOUND: 114,\n    RESULT_DB_DATA_NON_CONSISTENCY: 115,\n    RESULT_DB_CREATE_CONN_FAILED: 116,\n    RESULT_NOT_EMPTY: 180,\n    RESULT_LOCK_WRITE: 181,\n    RESULT_LOCK_READ: 182,\n    RESULT_LOCK_NONE: 183,\n    RESULT_LOCK_UNMATCH: 184,\n    RESULT_AUTH_FAILED: 185,\n    RESULT_RES_DROP: 200,\n    RESULT_RES_OFFLINE: 201,\n    RESULT_RES_INVALID_STATE: 202,\n    RESULT_MYSQL_INSTANCE_NOT_FOUND: 203,\n    RESULT_MONGO_INSTANCE_NOT_FOUND: 204,\n    RESULT_OSS_ASSUME_FAILED: 207,\n    RESULT_APP_STOPED: 210,\n    RESULT_ZIP_WRITE_FAILED: 220,\n    RESULT_ZIP_FILE_NOT_EXSIT: 221,\n    RRESULT_ZIP_LOAD_FAILED: 222,\n    RRESULT_ZIP_MD5_MISMATCH: 223,\n    RESULT_FILESIZE_EXCEED: 230,\n    RESULT_UNKNOWN: 255,\n};\n```",
      "html": "<h2>系统通用错误代码</h2>\n<pre><code class=\"language-JavaScript\">const ErrorCode = {\n    RESULT_OK: 0,\n    RESULT_FAILED: 1,\n    RESULT_WAIT_INIT: 2,\n    RESULT_ERROR_STATE: 3,\n    RESULT_INVALID_TYPE: 4,\n    RESULT_SCRIPT_ERROR: 5,\n    RESULT_NO_IMP: 6,\n    RESULT_ALREADY_EXIST: 7,\n    RESULT_NEED_SYNC: 8,\n    RESULT_NOT_FOUND: 9,\n    RESULT_EXPIRED: 10,\n    RESULT_INVALID_PARAM: 11,\n    RESULT_PARSE_ERROR: 12,\n    RESULT_REQUEST_ERROR: 13,\n    RESULT_NOT_SUPPORT: 14,\n    RESULT_TIMEOUT: 15,\n    RESULT_EXCEPTION: 16,\n    RESULT_INVALID_FORMAT: 17,\n    RESULT_UNKNOWN_VALUE: 18,\n    RESULT_INVALID_TOKEN: 19,\n    RESULT_INVALID_SESSION: 21,\n    RESULT_OUT_OF_LIMIT: 22,\n    RESULT_PERMISSION_DENIED: 23,\n    RESULT_OUT_OF_MEMORY: 24,\n    RESULT_INVALID_STATE : 25,\n    RESULT_UNMATCH_HASH : 26,\n    RESULT_UNMATCH: 27,\n    RESULT_REQUEST_CONNREFUSED: 28,\n    RESULT_REQUEST_CONNRESET: 29,\n    RESULT_NO_TARGET_RUNTIME: 30,\n    RESULT_INVALID_RULES: 31,\n    RESULT_REQUEST_EPIPE: 32,\n    RESULT_REQUEST_DNS_ERROR: 33,\n    RESULT_POST_FAILED: 40,\n    RESULT_MONGO_ERROR: 50,\n    RESULT_CONNECT_ERROR: 51,\n    RESULT_CREATE_USER_ERROR: 52,\n    RESULT_REMOVE_USER_ERROR: 53,\n    RESULT_SIGNUP_FAILED: 60,\n    RESULT_SIGNIN_FAILED: 61,\n    RESULT_INVALID_CONFIG: 80,\n    RESULT_INVALID_VERSION: 81,\n    RESULT_UNMATCH_RUNTIME: 82,\n    RESULT_UNMATCH_SEQ: 83,\n    RESULT_PENDING: 90,\n    RESULT_REACH_LIMIT: 100,\n    RESULT_LEVEL_UNMATCH: 101,\n    RESULT_DB_QUERY_ERROR: 110,\n    RESULT_DB_OPEN_FAILED: 111,\n    RESULT_DB_TRANSACTION_FAILED: 112,\n    RESULT_DB_CONDITION_NOT_MATCH: 113,\n    RESULT_METHOD_NOT_FOUND: 114,\n    RESULT_DB_DATA_NON_CONSISTENCY: 115,\n    RESULT_DB_CREATE_CONN_FAILED: 116,\n    RESULT_NOT_EMPTY: 180,\n    RESULT_LOCK_WRITE: 181,\n    RESULT_LOCK_READ: 182,\n    RESULT_LOCK_NONE: 183,\n    RESULT_LOCK_UNMATCH: 184,\n    RESULT_AUTH_FAILED: 185,\n    RESULT_RES_DROP: 200,\n    RESULT_RES_OFFLINE: 201,\n    RESULT_RES_INVALID_STATE: 202,\n    RESULT_MYSQL_INSTANCE_NOT_FOUND: 203,\n    RESULT_MONGO_INSTANCE_NOT_FOUND: 204,\n    RESULT_OSS_ASSUME_FAILED: 207,\n    RESULT_APP_STOPED: 210,\n    RESULT_ZIP_WRITE_FAILED: 220,\n    RESULT_ZIP_FILE_NOT_EXSIT: 221,\n    RRESULT_ZIP_LOAD_FAILED: 222,\n    RRESULT_ZIP_MD5_MISMATCH: 223,\n    RESULT_FILESIZE_EXCEED: 230,\n    RESULT_UNKNOWN: 255,\n};\n</code></pre>\n",
      "id": 12
    },
    {
      "path": "3.开发手册/3.6.常见问题.md",
      "url": "3.开发手册/3.6.常见问题.html",
      "content": "## 常见问题(FAQ)\n\n#### 调用模块函数时出现 `unhandledRejection { reason: TypeError: {xxxModule}.{yyyFunction} is not a function`\n\n检查如下清单：\n1. 检查`xxxModule`是否有`yyyFunction`函数\n2. 检查`bucky deploy`是否正确执行\n\n#### RPC返回错误码`9`\n一般表面这个包不存在，请在网站后台查看`r管理`确认包是否已发布\n\n#### 如果RPC出现返回null或者statucode=0\n请在 www.buckycloud.com 登录后台，进入AppID对应的管理也没，查看是否有崩溃日志\n\n#### 出现`sync object global.version/default not found`\n检查appid所在集群是否正确执行`bucky deploy`部署了代码\n\n#### 平台日志保存限额\n现阶段中国区和美国区的日志最大存储限额均为 300000 条。如果超过 150000 条，会回收 warn 级别以下的日志，只保留关键日志。用户需要注意日志产生频率，防止过多的无意义的 warn 级别以上日志冲掉关键的出错信息。\n\n#### 安装buckyos出现权限失败问题\nsudo npm install -g --unsafe-perm=true --allow-root buckyos\n\n#### 在linux上出现/var/blog目录权限问题\n此时需要需要使用sudo执行，或者配置/var/blog目录权限\n",
      "html": "<h2>常见问题(FAQ)</h2>\n<h4>调用模块函数时出现 <code>unhandledRejection { reason: TypeError: {xxxModule}.{yyyFunction} is not a function</code></h4>\n<p>检查如下清单：</p>\n<ol>\n<li>检查<code>xxxModule</code>是否有<code>yyyFunction</code>函数</li>\n<li>检查<code>bucky deploy</code>是否正确执行</li>\n</ol>\n<h4>RPC返回错误码<code>9</code></h4>\n<p>一般表面这个包不存在，请在网站后台查看<code>r管理</code>确认包是否已发布</p>\n<h4>如果RPC出现返回null或者statucode=0</h4>\n<p>请在 <a href=\"http://www.buckycloud.com\">www.buckycloud.com</a> 登录后台，进入AppID对应的管理也没，查看是否有崩溃日志</p>\n<h4>出现<code>sync object global.version/default not found</code></h4>\n<p>检查appid所在集群是否正确执行<code>bucky deploy</code>部署了代码</p>\n<h4>平台日志保存限额</h4>\n<p>现阶段中国区和美国区的日志最大存储限额均为 300000 条。如果超过 150000 条，会回收 warn 级别以下的日志，只保留关键日志。用户需要注意日志产生频率，防止过多的无意义的 warn 级别以上日志冲掉关键的出错信息。</p>\n<h4>安装buckyos出现权限失败问题</h4>\n<p>sudo npm install -g --unsafe-perm=true --allow-root buckyos</p>\n<h4>在linux上出现/var/blog目录权限问题</h4>\n<p>此时需要需要使用sudo执行，或者配置/var/blog目录权限</p>\n",
      "id": 13
    },
    {
      "path": "4.例子/4.1.使用MySQL.md",
      "url": "4.例子/4.1.使用MySQL.html",
      "content": "# 使用MySQL\n\nBucky框架下，通过Drivers的方式，提供了对MySQL、MongoDB、Redis等的支持。本节介绍MySQL的配置与使用。\n\n## 创建一个mysql测试目录：\n```\n mkdir mysql\n cd mysql\n```\n\n## 使用bucky命令行初始化，创建mysql demo：\n![](images/mysql-init.jpg)\n\n## MySQL的配置\n\n在一个Bucky的XARPackage里使用MySQL需要两个地方做配置：\n1. 根目录下`knownledges.json`下配置MySQL数据库实例及其schema配置。\n2. 在XARPackage的config.json里配置对mysql的knowledges依赖。\n\n这两个文件的位置如图：\n\n![](images/mysql-depend.jpg)\n\n## MySQL数据库实例及其schema的配置\n\n在knowlegeds.json的global.mysql.instances以及global.mysql.shemas两个节点下分别配置：\n\n![](images/mysql-knowledge.jpg)\n\n注意，global.mysql.schemas里面，object.onCreate也支持文件相对路径的方式把sql语句拆分出去：\n```\n{\n\t...\n\t\"global.mysql.schemas\": {\n        \"type\": 0,\n        \"object\": {\n            \"user_exp_schema\": {\n                \"onCreate\": \"db/user_exp_oncreate.sql\"\n            }\n        }\n    },\n}\n```\n\n## XARPackage里添加对MySQL Knowledge的依赖\n\n在XARPackage的config.json里配置：\n\n![](images/mysql-config-json.jpg)\n\n## 通过MySQL Driver操作数据库\n\n参考packages/userexp/下的两个模块代码：\n\n1. packages/userexp/userexp.js\n\n2. packages/userexp/datablock.js\n\nMySQL Driver的查询接口，请参考mysqljs库的官方文档：\n\n* [https://github.com/mysqljs/mysql](https://github.com/mysqljs/mysql)\n\n# MySQL数据库维护\n\n## 如何修改表结构\n\n需要说明的是global.mysql.schemas中配置的onCreate，只会在首次get mysql instance时执行，且只执行一次。所以要想修改表结构有下面两种做法。\n\n1. 直接修改instance id和onCreate。适用于开发阶段。\n2. 添加一个新的instance，修改onCreate，做数据迁移。适用于线上环境。\n\n## 命令行工具\n\nbucky工具还支持交互式mysql，类似mysql client。\n\n0. 在项目执行`bucky deploy`和`bucky k -reset`之后：\n1. 初始化mysql交互式环境，一个项目只需要初始化一次：bucky mysql -init \n2. 进入mysql交互式环境：bucky mysql \n3. 在交互式环境里有两个命令：\n   - `show instances` 显示有哪些mysql数据库\n   - `use instanceid` 切换到某个具体的instanceid的数据库\n   - 然后执行查询语句。\n4. 例子：\n    - 切换到test数据库：`use test`;\n    - 查询address表：`select * from address`;\n\n## 使用phpmyadmin\n\nphpmyadmin是知名度很高的图形化mysql client。支持mysql所有操作。\n\nurl:[https://phpmyadmin.buckycloud.com/](https://phpmyadmin.buckycloud.com/)\n\n使用方法：\n1. 输入buckycloud.com的用户名和密码，点击获取AppID\n2. 选择要操作的AppID，点击获取MySQL Instance\n3. 选择要操作的Instance，点击最后的按钮PHPMyAdmin\n\n就会转到熟悉的phpmyadmin管理页面\n\n--全文完--\n\n\n",
      "html": "<h1>使用MySQL</h1>\n<p>Bucky框架下，通过Drivers的方式，提供了对MySQL、MongoDB、Redis等的支持。本节介绍MySQL的配置与使用。</p>\n<h2>创建一个mysql测试目录：</h2>\n<pre><code> mkdir mysql\n cd mysql\n</code></pre>\n<h2>使用bucky命令行初始化，创建mysql demo：</h2>\n<p><img src=\"images/mysql-init.jpg\" alt=\"\"></p>\n<h2>MySQL的配置</h2>\n<p>在一个Bucky的XARPackage里使用MySQL需要两个地方做配置：</p>\n<ol>\n<li>根目录下<code>knownledges.json</code>下配置MySQL数据库实例及其schema配置。</li>\n<li>在XARPackage的config.json里配置对mysql的knowledges依赖。</li>\n</ol>\n<p>这两个文件的位置如图：</p>\n<p><img src=\"images/mysql-depend.jpg\" alt=\"\"></p>\n<h2>MySQL数据库实例及其schema的配置</h2>\n<p>在knowlegeds.json的global.mysql.instances以及global.mysql.shemas两个节点下分别配置：</p>\n<p><img src=\"images/mysql-knowledge.jpg\" alt=\"\"></p>\n<p>注意，global.mysql.schemas里面，object.onCreate也支持文件相对路径的方式把sql语句拆分出去：</p>\n<pre><code>{\n\t...\n\t&quot;global.mysql.schemas&quot;: {\n        &quot;type&quot;: 0,\n        &quot;object&quot;: {\n            &quot;user_exp_schema&quot;: {\n                &quot;onCreate&quot;: &quot;db/user_exp_oncreate.sql&quot;\n            }\n        }\n    },\n}\n</code></pre>\n<h2>XARPackage里添加对MySQL Knowledge的依赖</h2>\n<p>在XARPackage的config.json里配置：</p>\n<p><img src=\"images/mysql-config-json.jpg\" alt=\"\"></p>\n<h2>通过MySQL Driver操作数据库</h2>\n<p>参考packages/userexp/下的两个模块代码：</p>\n<ol>\n<li>\n<p>packages/userexp/userexp.js</p>\n</li>\n<li>\n<p>packages/userexp/datablock.js</p>\n</li>\n</ol>\n<p>MySQL Driver的查询接口，请参考mysqljs库的官方文档：</p>\n<ul>\n<li><a href=\"https://github.com/mysqljs/mysql\">https://github.com/mysqljs/mysql</a></li>\n</ul>\n<h1>MySQL数据库维护</h1>\n<h2>如何修改表结构</h2>\n<p>需要说明的是global.mysql.schemas中配置的onCreate，只会在首次get mysql instance时执行，且只执行一次。所以要想修改表结构有下面两种做法。</p>\n<ol>\n<li>直接修改instance id和onCreate。适用于开发阶段。</li>\n<li>添加一个新的instance，修改onCreate，做数据迁移。适用于线上环境。</li>\n</ol>\n<h2>命令行工具</h2>\n<p>bucky工具还支持交互式mysql，类似mysql client。</p>\n<ol start=\"0\">\n<li>在项目执行<code>bucky deploy</code>和<code>bucky k -reset</code>之后：</li>\n<li>初始化mysql交互式环境，一个项目只需要初始化一次：bucky mysql -init</li>\n<li>进入mysql交互式环境：bucky mysql</li>\n<li>在交互式环境里有两个命令：\n<ul>\n<li><code>show instances</code> 显示有哪些mysql数据库</li>\n<li><code>use instanceid</code> 切换到某个具体的instanceid的数据库</li>\n<li>然后执行查询语句。</li>\n</ul>\n</li>\n<li>例子：\n<ul>\n<li>切换到test数据库：<code>use test</code>;</li>\n<li>查询address表：<code>select * from address</code>;</li>\n</ul>\n</li>\n</ol>\n<h2>使用phpmyadmin</h2>\n<p>phpmyadmin是知名度很高的图形化mysql client。支持mysql所有操作。</p>\n<p>url:<a href=\"https://phpmyadmin.buckycloud.com/\">https://phpmyadmin.buckycloud.com/</a></p>\n<p>使用方法：</p>\n<ol>\n<li>输入buckycloud.com的用户名和密码，点击获取AppID</li>\n<li>选择要操作的AppID，点击获取MySQL Instance</li>\n<li>选择要操作的Instance，点击最后的按钮PHPMyAdmin</li>\n</ol>\n<p>就会转到熟悉的phpmyadmin管理页面</p>\n<p>–全文完–</p>\n",
      "id": 14
    },
    {
      "path": "4.例子/4.2.使用OSS读写文件.md",
      "url": "4.例子/4.2.使用OSS读写文件.html",
      "content": "本节介绍创建bucky开发环境下使用阿里云OSS实现简单读写文件。\n\n## 创建并运行示例项目\n\n#### 通过交互式命令行创建简单文件读写的示例项目:\n\n```\nmkdir fileserver\nbucky init -i\n```\n\n选择从`示例`创建解决方案：\n```\n解决方案类型：\n────────────────────\n1. 示例\n2. 新建解决方案\n\n$请选择解决方案类型[1/2]：1\n```\n\n选择`fileserver`这个示例解决方案：\n```\n示例项目列表：\n────────────────────\n1. HelloBucky\n2. ReactNative\n3. fileserver\n4. minichat\n5. mysql\n\n$选择示例项目序号：3\n```\n\n#### 配置本地调试账号\n\n首先，向bucky平台申请一个阿里云OSS的本地测试账号，包含如下信息：\n```\naccessKeyId: \naccessKeySecret: \nroleArn:\nbucket:\nregion: \n```\n\n其次，通过命令`bucky config -localdebug`配置本地OSS驱动:\n```\n请选择要配置的本地驱动\n────────────────────\n1. MYSQL\n2. MONGO\n3. OSS\n$请输入序号：3\n```\n\n则，会交互式的请求输入OSS本地驱动的配置参数：\n```\n$请输入accessKeyId: exampleAccessKeyId\n\n$请输入accessKeySecret: exampleSecret\n\n$请输入roleArn: exampleArn\n\n$请输入bucket: example-bucket\n\n$请输入region: example-region\n```\n\n询问是否继续配置其他驱动，选择n：\n```\n继续配置本地驱动？ [y/n]: n\n```\n\n#### 本地测试\n\n使用命令行本地测试\n```\nbucky build\nbucky deploy\nbucky k -reset\nbucky debug -main test/filemanager/test.js\n```\n\n#### 线上运行\n```\nbucky build\nbucky deploy\nbucky k -reset\nbucky run -main test/filemanager/test.js\n```\n\n#### 代码分析\n\n1. `src/test/filemanager/filemanager.js` 里通过使用OSS驱动给需要读写的文件签名并返回签名的URL\n    注意，这个XARPackage的config.json里配置了允许使用oss驱动`bx.oss.client`\n2. `test/filemanager/test.js`里通过调用filemanager这个XARPackage的签名接口获得签名URL后，\n   使用`bucky.OSSUtil.putData`和`bucky.OSSUtil.getData`进行读写文件。\n\n## 新建项目\n\n新建项目要使用OSS驱动的XARPackage注意事项：\n1. 在选择运行环境的时候必须选择，只允许在后端运行\n2. 在选择是否使用OSS驱动的时候，要选择y\n\n--全文完--\n\n\n\n\n\n\n\n\n",
      "html": "<p>本节介绍创建bucky开发环境下使用阿里云OSS实现简单读写文件。</p>\n<h2>创建并运行示例项目</h2>\n<h4>通过交互式命令行创建简单文件读写的示例项目:</h4>\n<pre><code>mkdir fileserver\nbucky init -i\n</code></pre>\n<p>选择从<code>示例</code>创建解决方案：</p>\n<pre><code>解决方案类型：\n────────────────────\n1. 示例\n2. 新建解决方案\n\n$请选择解决方案类型[1/2]：1\n</code></pre>\n<p>选择<code>fileserver</code>这个示例解决方案：</p>\n<pre><code>示例项目列表：\n────────────────────\n1. HelloBucky\n2. ReactNative\n3. fileserver\n4. minichat\n5. mysql\n\n$选择示例项目序号：3\n</code></pre>\n<h4>配置本地调试账号</h4>\n<p>首先，向bucky平台申请一个阿里云OSS的本地测试账号，包含如下信息：</p>\n<pre><code>accessKeyId: \naccessKeySecret: \nroleArn:\nbucket:\nregion: \n</code></pre>\n<p>其次，通过命令<code>bucky config -localdebug</code>配置本地OSS驱动:</p>\n<pre><code>请选择要配置的本地驱动\n────────────────────\n1. MYSQL\n2. MONGO\n3. OSS\n$请输入序号：3\n</code></pre>\n<p>则，会交互式的请求输入OSS本地驱动的配置参数：</p>\n<pre><code>$请输入accessKeyId: exampleAccessKeyId\n\n$请输入accessKeySecret: exampleSecret\n\n$请输入roleArn: exampleArn\n\n$请输入bucket: example-bucket\n\n$请输入region: example-region\n</code></pre>\n<p>询问是否继续配置其他驱动，选择n：</p>\n<pre><code>继续配置本地驱动？ [y/n]: n\n</code></pre>\n<h4>本地测试</h4>\n<p>使用命令行本地测试</p>\n<pre><code>bucky build\nbucky deploy\nbucky k -reset\nbucky debug -main test/filemanager/test.js\n</code></pre>\n<h4>线上运行</h4>\n<pre><code>bucky build\nbucky deploy\nbucky k -reset\nbucky run -main test/filemanager/test.js\n</code></pre>\n<h4>代码分析</h4>\n<ol>\n<li><code>src/test/filemanager/filemanager.js</code> 里通过使用OSS驱动给需要读写的文件签名并返回签名的URL\n注意，这个XARPackage的config.json里配置了允许使用oss驱动<code>bx.oss.client</code></li>\n<li><code>test/filemanager/test.js</code>里通过调用filemanager这个XARPackage的签名接口获得签名URL后，\n使用<code>bucky.OSSUtil.putData</code>和<code>bucky.OSSUtil.getData</code>进行读写文件。</li>\n</ol>\n<h2>新建项目</h2>\n<p>新建项目要使用OSS驱动的XARPackage注意事项：</p>\n<ol>\n<li>在选择运行环境的时候必须选择，只允许在后端运行</li>\n<li>在选择是否使用OSS驱动的时候，要选择y</li>\n</ol>\n<p>–全文完–</p>\n",
      "id": 15
    },
    {
      "path": "4.例子/4.3.创建ReactNative项目.md",
      "url": "4.例子/4.3.创建ReactNative项目.html",
      "content": "\n本节介绍创建ReactNative项目，并在ReactNative环境里调用bucky。\n\n## 准备ReactNative开发环境。\n\n参考教程：\n1. 非官方中文教程（翻译）：https://reactnative.cn/docs/0.39/getting-started.html\n2. 官方教程：https://facebook.github.io/react-native/docs/getting-started.html\n  - QuickStart 或\n  - Build Projects With Native Code\n\n例如参考链接1，选择Mac+Andoird环境，step by step搭建开发环境。\n\n## 创建ReactNative示例。\n\n在搭建好ReactNative开发环境后，可以开始使用bucky命令行创建示例项目并运行。\n\n首先，选择从示例项目初始化解决方案，并选择`ReactNative`这个示例：\n```\nbucky init -i\n```\n\n其次，构建项目，并初始化konwledges：\n```\nbucky build\nbucky deploy\nbucky k -reset\n```\n\n最后，进入`src/client`目录，使用`react-native`命令运行react-native示例项目。注意andoird手机要连接上电脑，推荐直接用真机开发。\n```\ncd src/client\nreact-native run-android\n```\n\n可以看下目录结构：\n```\n.\n├── dist\n│   ├── ...\n├── knowledges.json\n├── solution.json\n├── src\n│   ├── client\n│   │   ├── App.js\n│   │   ├── __tests__\n│   │   ├── android\n│   │   ├── app.json\n│   │   ├── core\n│   │   │   ├── bucky_meta.js\n│   │   │   ├── rn_core.js\n│   │   │   └── rn_ld_core.js\n│   │   ├── index.js\n│   │   ├── ios\n│   │   └── package.json\n│   └── server\n│       └── calc\n│           ├── calc.js\n│           ├── config.json\n│           └── onload.js\n└── test\n    └── calc\n        └── test.js\n```\n\n项目结构：\n- src/server是bucky项目，下面的calc是个bucky XARPackage。\n- src/client是ReactNative项目，而src/client/core下放了bucky的ReactNative适配运行时。\n\n在ReactNative的程序入口src/client/App.js里包含了初始化bucky并对`calc:calc`模块发起RPC调用\n的示例代码。\n\n\n## 新建ReactNative新项目。\n\nbucky命令行工具支持在解决方案内创建一个新的ReactNative项目，步骤如下：\n\n1. 创建一个创建ReactNative项目\n2. 创建bucky项目，添加XARPackage\n3. 在ReactNative项目里调用bucky项目的XARPackage的接口。\n\n#### 创建ReactNative项目\n首先，创建目录，并进入交互式初始化，选择`新建解决方案`:\n```\nmkdir rntest\ncd rntest\nbucky init -i \n```\n\n接着，添加新项目，输入项目目录： `src/client/`。\n\n然后是，选择项目类型为: \n```\n请选择项目类型：\n────────────────────\n1. bucky项目（后台）\n2. HTML5（前端）\n3. 微信小程序（前端）\n4. ReactNative（前端）\n```\n\n选择序号4，创建ReactNative（前端）项目。此时，bucky会调用`react-native init ${projectName}`初始化\nReactNative项目，耐心等待中。\n\n\n#### 创建bucky项目\n创建完毕后，提示`$继续添加project？ [y/n]:`，输入`y`,继续创建一个bucky项目:\n- 输入项目路径为`src/server`\n- 选择`bucky项目（后台）`类型\n- 输入packagename为`calc`\n- 选择`示例package`,\n- 选择`只允许后端`\n- 一路输入n\n\n#### 在ReactNative项目里调用bucky项目的XARPackage的接口。\n\n打开src/client/App.js 可以看到BuckyTest类，根据注释的提示，编写调用calc:calc模块的div2接口的代码。\n\n#### 构建，运行\n\n最后，使用bucky工具构建，并在android上运行react native。注意andoird手机要连接上电脑，推荐直接用真机开发。\n```\nbucky build\nbucky deploy\nbucky k -reset\ncd src/client\nnpm install \nreact-native run-android\n```\n\n#### 常见问题\n\nF: \n出现错误：\n```\nUnable to load scripts from assets 'index.android.bundle'. Make sure your bundle is packaged correctly or you're running a packager server.\n```\n\nA: \n手机摇一摇，RN App上会出现菜单，选择`Dev Setting`，点击`Debug server host&port for device`，设置你的PC电脑上启动的RN服务的IP和端口，默认端口是8081，例如：`192.168.100.1:8081`，然后返回，并重新执行\n```\nreact-native run-android\n```\n\nF: \n出现错误:\n```\nload file failed, status=404 url=https://dev.buckycloud.com/services/repository/1001/loadfile?appid=...,resp={\"resykt\":9}\n```\n\nA: \n执行`bucky deploy`,然后在App里双击`Reload(R,R)`.\n\n--全文完--\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",
      "html": "<p>本节介绍创建ReactNative项目，并在ReactNative环境里调用bucky。</p>\n<h2>准备ReactNative开发环境。</h2>\n<p>参考教程：</p>\n<ol>\n<li>非官方中文教程（翻译）：<a href=\"https://reactnative.cn/docs/0.39/getting-started.html\">https://reactnative.cn/docs/0.39/getting-started.html</a></li>\n<li>官方教程：<a href=\"https://facebook.github.io/react-native/docs/getting-started.html\">https://facebook.github.io/react-native/docs/getting-started.html</a></li>\n</ol>\n<ul>\n<li>QuickStart 或</li>\n<li>Build Projects With Native Code</li>\n</ul>\n<p>例如参考链接1，选择Mac+Andoird环境，step by step搭建开发环境。</p>\n<h2>创建ReactNative示例。</h2>\n<p>在搭建好ReactNative开发环境后，可以开始使用bucky命令行创建示例项目并运行。</p>\n<p>首先，选择从示例项目初始化解决方案，并选择<code>ReactNative</code>这个示例：</p>\n<pre><code>bucky init -i\n</code></pre>\n<p>其次，构建项目，并初始化konwledges：</p>\n<pre><code>bucky build\nbucky deploy\nbucky k -reset\n</code></pre>\n<p>最后，进入<code>src/client</code>目录，使用<code>react-native</code>命令运行react-native示例项目。注意andoird手机要连接上电脑，推荐直接用真机开发。</p>\n<pre><code>cd src/client\nreact-native run-android\n</code></pre>\n<p>可以看下目录结构：</p>\n<pre><code>.\n├── dist\n│   ├── ...\n├── knowledges.json\n├── solution.json\n├── src\n│   ├── client\n│   │   ├── App.js\n│   │   ├── __tests__\n│   │   ├── android\n│   │   ├── app.json\n│   │   ├── core\n│   │   │   ├── bucky_meta.js\n│   │   │   ├── rn_core.js\n│   │   │   └── rn_ld_core.js\n│   │   ├── index.js\n│   │   ├── ios\n│   │   └── package.json\n│   └── server\n│       └── calc\n│           ├── calc.js\n│           ├── config.json\n│           └── onload.js\n└── test\n    └── calc\n        └── test.js\n</code></pre>\n<p>项目结构：</p>\n<ul>\n<li>src/server是bucky项目，下面的calc是个bucky XARPackage。</li>\n<li>src/client是ReactNative项目，而src/client/core下放了bucky的ReactNative适配运行时。</li>\n</ul>\n<p>在ReactNative的程序入口src/client/App.js里包含了初始化bucky并对<code>calc:calc</code>模块发起RPC调用\n的示例代码。</p>\n<h2>新建ReactNative新项目。</h2>\n<p>bucky命令行工具支持在解决方案内创建一个新的ReactNative项目，步骤如下：</p>\n<ol>\n<li>创建一个创建ReactNative项目</li>\n<li>创建bucky项目，添加XARPackage</li>\n<li>在ReactNative项目里调用bucky项目的XARPackage的接口。</li>\n</ol>\n<h4>创建ReactNative项目</h4>\n<p>首先，创建目录，并进入交互式初始化，选择<code>新建解决方案</code>:</p>\n<pre><code>mkdir rntest\ncd rntest\nbucky init -i \n</code></pre>\n<p>接着，添加新项目，输入项目目录： <code>src/client/</code>。</p>\n<p>然后是，选择项目类型为:</p>\n<pre><code>请选择项目类型：\n────────────────────\n1. bucky项目（后台）\n2. HTML5（前端）\n3. 微信小程序（前端）\n4. ReactNative（前端）\n</code></pre>\n<p>选择序号4，创建ReactNative（前端）项目。此时，bucky会调用<code>react-native init ${projectName}</code>初始化\nReactNative项目，耐心等待中。</p>\n<h4>创建bucky项目</h4>\n<p>创建完毕后，提示<code>$继续添加project？ [y/n]:</code>，输入<code>y</code>,继续创建一个bucky项目:</p>\n<ul>\n<li>输入项目路径为<code>src/server</code></li>\n<li>选择<code>bucky项目（后台）</code>类型</li>\n<li>输入packagename为<code>calc</code></li>\n<li>选择<code>示例package</code>,</li>\n<li>选择<code>只允许后端</code></li>\n<li>一路输入n</li>\n</ul>\n<h4>在ReactNative项目里调用bucky项目的XARPackage的接口。</h4>\n<p>打开src/client/App.js 可以看到BuckyTest类，根据注释的提示，编写调用calc:calc模块的div2接口的代码。</p>\n<h4>构建，运行</h4>\n<p>最后，使用bucky工具构建，并在android上运行react native。注意andoird手机要连接上电脑，推荐直接用真机开发。</p>\n<pre><code>bucky build\nbucky deploy\nbucky k -reset\ncd src/client\nnpm install \nreact-native run-android\n</code></pre>\n<h4>常见问题</h4>\n<p>F:\n出现错误：</p>\n<pre><code>Unable to load scripts from assets 'index.android.bundle'. Make sure your bundle is packaged correctly or you're running a packager server.\n</code></pre>\n<p>A:\n手机摇一摇，RN App上会出现菜单，选择<code>Dev Setting</code>，点击<code>Debug server host&amp;port for device</code>，设置你的PC电脑上启动的RN服务的IP和端口，默认端口是8081，例如：<code>192.168.100.1:8081</code>，然后返回，并重新执行</p>\n<pre><code>react-native run-android\n</code></pre>\n<p>F:\n出现错误:</p>\n<pre><code>load file failed, status=404 url=https://dev.buckycloud.com/services/repository/1001/loadfile?appid=...,resp={&quot;resykt&quot;:9}\n</code></pre>\n<p>A:\n执行<code>bucky deploy</code>,然后在App里双击<code>Reload(R,R)</code>.</p>\n<p>–全文完–</p>\n",
      "id": 16
    },
    {
      "path": "4.例子/4.4.创建微信小程序项目.md",
      "url": "4.例子/4.4.创建微信小程序项目.html",
      "content": "本节介绍结合微信小程序与Bucky构建项目\n\n## 准备微信小程序环境\n\n下载最新版微信小程序IDE：https://mp.weixin.qq.com/cgi-bin/wx\n申请微信小程序AppID\n\n## 创建微信小程序示例\n\n微信小程序的例子包含两个：\n* wechat 是微信小程序例子\n* wegame 是微信小游戏例子\n\n首先，选择从示例项目初始化解决方案，并选择`wechat`这个示例：\n```\nbucky init -i\n```\n\n其次，构建项目，并初始化konwledges：\n```\nbucky build\nbucky deploy\nbucky k -reset\n```\n\n测试rpc调用：\n```\nbucky run -main test/calc/test_calc.js\n```\n\n使用微信小程序IDE打开目录`src/client/`\n等待小程序界面加载完成，可以看到小程序界面上显示了加载bucky cloud上的calc计算除法的结果。\n\n可以看下目录结构：\n```\n├── dist\n│   ├── ...\n├── knowledges.json\n├── solution.json\n├── src\n│   ├── client\n│   │   ├── app.js\n│   │   ├── app.json\n│   │   ├── app.wxss\n│   │   ├── core\n│   │   │   ├── bucky_meta.js\n│   │   │   ├── bucky_packages.js\n│   │   │   ├── lib\n│   │   │   ├── packages\n│   │   │   ├── wx_core.js\n│   │   │   └── wx_ld_core.js\n│   │   ├── pages\n│   │   │   ├── index\n│   │   │   └── logs\n│   │   ├── project.config.json\n│   │   └── utils\n│   │       └── util.js\n│   └── server\n│       └── calc\n│           ├── calc.js\n│           ├── config.json\n│           └── onload.js\n└── test\n    └── calc\n        └── test_calc.js\n```\n\n项目结构：\n- src/server是bucky项目，下面的calc是个bucky XARPackage。\n- src/client是微信小程序项目，而src/client/core下放了bucky的微信小程序适配运行时。\n- src/client/core是bucky工具在build时生成的，包含\n    - bucky核心库`wx_core.js`\n    - bucky初始化需要的配置文件`bucky_meta.js`,`bucky_packages.js`\n    - `src/client/core/packages/`目录下是`src/server/`下的XAR Package的代理\n\n在微信小程序的程序入口src/client/pages/index.js里包含了初始化bucky并对`calc:calc`模块发起RPC调用的示例代码。\n\n\n## 新建微信小程序新项目。\n\nbucky命令行工具支持在解决方案内创建一个新的微信小程序项目，步骤如下：\n\n1. 创建一个微信小程序项目\n2. 创建bucky项目，添加XARPackage\n3. 在微信小程序项目里调用bucky项目的XARPackage的接口。\n\n#### 创建微信小程序项目\n首先，创建目录，并进入交互式初始化，选择`新建解决方案`:\n```\nmkdir wxtest\ncd wxtest\nbucky init -i \n```\n\n接着，添加新项目，输入项目目录： `src/client/wx`。\n\n然后是，选择项目类型为: \n```\n请选择项目类型：\n────────────────────\n1. bucky项目（后台）\n2. HTML5（前端）\n3. 微信小程序（前端）\n4. ReactNative（前端）\n```\n\n此处选择序号3，创建包含bucky初始化的微信小程序代码。\n\n接着会提示选择微信小程序对子类别：\n```\n微信小程序子类别：\n────────────────────\n1. 微信小程序\n2. 微信小游戏\n```\n\n按需选择。\n\n#### 创建bucky项目\n创建完毕后，提示`$继续添加project？ [y/n]:`，输入`y`,继续创建一个bucky项目:\n- 输入项目路径为`src/server`\n- 选择`bucky项目（后台）`类型\n- 输入packagename为`calc`\n- 选择`示例package`,\n- 选择`只允许后端`\n- 一路输入n\n\n#### 在ReactNative项目里调用bucky项目的XARPackage的接口。\n\n打开src/client/wx/pages/index.js 可以看到Bucky初始化的函数已经准备好了，根据注释的提示，编写调用calc:calc模块的div2接口的代码。\n\n亦可参考本节上面的微信小程序demo\n\n#### 构建，运行\n\n最后，使用bucky工具构建，测试\n```\nbucky build\nbucky deploy\nbucky k -reset\nbucky run -main test/calc/test_calc.js\n```\n\n使用微信小程序IDE打开src/client/wx调试微信小程序.\n\n--全文完--",
      "html": "<p>本节介绍结合微信小程序与Bucky构建项目</p>\n<h2>准备微信小程序环境</h2>\n<p>下载最新版微信小程序IDE：<a href=\"https://mp.weixin.qq.com/cgi-bin/wx\">https://mp.weixin.qq.com/cgi-bin/wx</a>\n申请微信小程序AppID</p>\n<h2>创建微信小程序示例</h2>\n<p>微信小程序的例子包含两个：</p>\n<ul>\n<li>wechat 是微信小程序例子</li>\n<li>wegame 是微信小游戏例子</li>\n</ul>\n<p>首先，选择从示例项目初始化解决方案，并选择<code>wechat</code>这个示例：</p>\n<pre><code>bucky init -i\n</code></pre>\n<p>其次，构建项目，并初始化konwledges：</p>\n<pre><code>bucky build\nbucky deploy\nbucky k -reset\n</code></pre>\n<p>测试rpc调用：</p>\n<pre><code>bucky run -main test/calc/test_calc.js\n</code></pre>\n<p>使用微信小程序IDE打开目录<code>src/client/</code>\n等待小程序界面加载完成，可以看到小程序界面上显示了加载bucky cloud上的calc计算除法的结果。</p>\n<p>可以看下目录结构：</p>\n<pre><code>├── dist\n│   ├── ...\n├── knowledges.json\n├── solution.json\n├── src\n│   ├── client\n│   │   ├── app.js\n│   │   ├── app.json\n│   │   ├── app.wxss\n│   │   ├── core\n│   │   │   ├── bucky_meta.js\n│   │   │   ├── bucky_packages.js\n│   │   │   ├── lib\n│   │   │   ├── packages\n│   │   │   ├── wx_core.js\n│   │   │   └── wx_ld_core.js\n│   │   ├── pages\n│   │   │   ├── index\n│   │   │   └── logs\n│   │   ├── project.config.json\n│   │   └── utils\n│   │       └── util.js\n│   └── server\n│       └── calc\n│           ├── calc.js\n│           ├── config.json\n│           └── onload.js\n└── test\n    └── calc\n        └── test_calc.js\n</code></pre>\n<p>项目结构：</p>\n<ul>\n<li>src/server是bucky项目，下面的calc是个bucky XARPackage。</li>\n<li>src/client是微信小程序项目，而src/client/core下放了bucky的微信小程序适配运行时。</li>\n<li>src/client/core是bucky工具在build时生成的，包含\n<ul>\n<li>bucky核心库<code>wx_core.js</code></li>\n<li>bucky初始化需要的配置文件<code>bucky_meta.js</code>,<code>bucky_packages.js</code></li>\n<li><code>src/client/core/packages/</code>目录下是<code>src/server/</code>下的XAR Package的代理</li>\n</ul>\n</li>\n</ul>\n<p>在微信小程序的程序入口src/client/pages/index.js里包含了初始化bucky并对<code>calc:calc</code>模块发起RPC调用的示例代码。</p>\n<h2>新建微信小程序新项目。</h2>\n<p>bucky命令行工具支持在解决方案内创建一个新的微信小程序项目，步骤如下：</p>\n<ol>\n<li>创建一个微信小程序项目</li>\n<li>创建bucky项目，添加XARPackage</li>\n<li>在微信小程序项目里调用bucky项目的XARPackage的接口。</li>\n</ol>\n<h4>创建微信小程序项目</h4>\n<p>首先，创建目录，并进入交互式初始化，选择<code>新建解决方案</code>:</p>\n<pre><code>mkdir wxtest\ncd wxtest\nbucky init -i \n</code></pre>\n<p>接着，添加新项目，输入项目目录： <code>src/client/wx</code>。</p>\n<p>然后是，选择项目类型为:</p>\n<pre><code>请选择项目类型：\n────────────────────\n1. bucky项目（后台）\n2. HTML5（前端）\n3. 微信小程序（前端）\n4. ReactNative（前端）\n</code></pre>\n<p>此处选择序号3，创建包含bucky初始化的微信小程序代码。</p>\n<p>接着会提示选择微信小程序对子类别：</p>\n<pre><code>微信小程序子类别：\n────────────────────\n1. 微信小程序\n2. 微信小游戏\n</code></pre>\n<p>按需选择。</p>\n<h4>创建bucky项目</h4>\n<p>创建完毕后，提示<code>$继续添加project？ [y/n]:</code>，输入<code>y</code>,继续创建一个bucky项目:</p>\n<ul>\n<li>输入项目路径为<code>src/server</code></li>\n<li>选择<code>bucky项目（后台）</code>类型</li>\n<li>输入packagename为<code>calc</code></li>\n<li>选择<code>示例package</code>,</li>\n<li>选择<code>只允许后端</code></li>\n<li>一路输入n</li>\n</ul>\n<h4>在ReactNative项目里调用bucky项目的XARPackage的接口。</h4>\n<p>打开src/client/wx/pages/index.js 可以看到Bucky初始化的函数已经准备好了，根据注释的提示，编写调用calc:calc模块的div2接口的代码。</p>\n<p>亦可参考本节上面的微信小程序demo</p>\n<h4>构建，运行</h4>\n<p>最后，使用bucky工具构建，测试</p>\n<pre><code>bucky build\nbucky deploy\nbucky k -reset\nbucky run -main test/calc/test_calc.js\n</code></pre>\n<p>使用微信小程序IDE打开src/client/wx调试微信小程序.</p>\n<p>–全文完–</p>\n",
      "id": 17
    },
    {
      "path": "5.知识库/5.1.理解async.md",
      "url": "5.知识库/5.1.理解async.html",
      "content": "\n如果对JavaScript的async的使用不清楚，可以逐个阅读下面的例子理解:\n\n* [understanding_async_01](https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_01.js)\n* [understanding_async_02](https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_02.js)\n* [understanding_async_03](https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_03.js)\n* [understanding_async_04](https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_04.js)\n* [understanding_async_05](https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_05.js)\n* [understanding_async_06](https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_06.js)\n* [understanding_async_07](https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_07.js)\n* [understanding_async_08](https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_08.js)\n* [understanding_async_09](https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_09.js)\n* [understanding_async_10](https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_10.js)\n* [understanding_async_11](https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_11.js)\n* [understanding_async_12](https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_12.js)\n* [understanding_async_13](https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_13.js)\n* [understanding_async_14](https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_14.js)\n",
      "html": "<p>如果对JavaScript的async的使用不清楚，可以逐个阅读下面的例子理解:</p>\n<ul>\n<li><a href=\"https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_01.js\">understanding_async_01</a></li>\n<li><a href=\"https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_02.js\">understanding_async_02</a></li>\n<li><a href=\"https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_03.js\">understanding_async_03</a></li>\n<li><a href=\"https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_04.js\">understanding_async_04</a></li>\n<li><a href=\"https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_05.js\">understanding_async_05</a></li>\n<li><a href=\"https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_06.js\">understanding_async_06</a></li>\n<li><a href=\"https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_07.js\">understanding_async_07</a></li>\n<li><a href=\"https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_08.js\">understanding_async_08</a></li>\n<li><a href=\"https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_09.js\">understanding_async_09</a></li>\n<li><a href=\"https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_10.js\">understanding_async_10</a></li>\n<li><a href=\"https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_11.js\">understanding_async_11</a></li>\n<li><a href=\"https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_12.js\">understanding_async_12</a></li>\n<li><a href=\"https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_13.js\">understanding_async_13</a></li>\n<li><a href=\"https://github.com/fanfeilong/jsasync/blob/master/code/understanding_async/understanding_async_14.js\">understanding_async_14</a></li>\n</ul>\n",
      "id": 18
    },
    {
      "path": "5.知识库/5.2.模块接口.md",
      "url": "5.知识库/5.2.模块接口.html",
      "content": "## 什么是模块\n\n在XARPackage里，在config.json里配置modules，可以导出XARPackage下的js模块。\n\n假设calc是一个XARPacakge，目录结构如下:\n```\n├── src\n│   └── test\n│       └── calc\n│           ├── calc.js\n│           ├── config.json\n│           └── onload.js\n```\n\n则导出calc.js的方式是在config.json里配置modules字段：\n```json\n// src/test/calc/config.json\n{\n    ...\n    \"modules\": {\"calc\": \"calc.js\"},\n    ...\n}\n```\n\n则`calc:calc` 就代表calc这个XARPackage下的calc模块，`{xarpacakge_name}:{module_name}`是模块的id\n\n## 如何默认导出XARPackage内的所有js模块\n\n上面的方式每导出一个模块，都需要在config.json/modules里增加一条配置，如果希望默认导出包内所有的js模块，则\n可以配置config.json/moduleAccess字段，例如：\n```json\n// src/test/calc/config.json\n{\n    ...\n    moduleAccess:'public', // 模块导出级别, 可选值是'public','protect', 'private'，默认是'public'\n    ...\n}\n```\n\n不过，从工程的角度来说，不建议默认全部导出，通过导出设计过的特定模块是一个更合理的做法。\n\n## 什么是模块的导出接口\n\n在上面的calc.js里，通过module.exports导出的函数就是模块的导出接口，例如calc:calc的模块接口如下，其中的注释\n简要解释了模块导出接口的输入输出参数要求:\n\n```javascript\nfunction main(){\n    // 模块加载后先执行main函数\n}\n\n//\n// bucky package 导出函数的两种声明方式\n// ===============\n//\n// ## 方式1: 回调方式，\n// 1. 采用最后一个回调函数退出函数\n// 2. 该回调只接受一个参数\n// \n// ## 方式2: async/await方式\n// 1. 返回值是个只能有一个元素的数组，(i.e: [result])\n// 2. 由于bucky框架不使用异常机制，不能返回异常\n// 3. 如果返回Promise，则只使用resolve，不能使用reject，原因如上\n//\n// 下面的示例两种方式导出包接口，测试代码请见test目录\n// \n\n\n/**\n * 示例：采用方式一导出函数\n * \n * 导出div1函数，接受两个参数x和y，通过调用onComplete退出\n *\n * @param {double} x 除数\n * @param {double} y 被除数，y不应该为零\n * @param {function} onComplete(result) 退出回调，只接受一个参数\n */\nfunction div1(x,y,onComplete) {\n\n    /** onComplete只接受一个参数，因此需要把返回结果打包在一个对象内 */\n    let result = {\n        err:null,\n        value:null\n    };\n\n    try{\n        let value = x/y;\n        result.value = value;\n\n        /** 正确执行，返回计算结果 */\n        onComplete(result);\n    }catch(err){\n        /** 执行异常，返回出错信息 */\n\n        result.err = err;\n        onComplete(result);\n    }\n}\n\n/**\n * 示例：采用方式二导出函数\n * \n * 导出div2函数，接受两个参数x和y，通过resolve返回结果（正确/错误 通过错误码标识）\n *\n * @param {double} x 除数\n * @param {double} y 被除数，y不应该为零\n */\nasync function div2(x,y) {\n\n    /** onComplete只接受一个参数，因此需要把返回结果打包在一个对象内 */\n    let result = {\n        err:null,\n        value:null\n    };\n\n    return new Promise((resolve, reject) => {\n        try{\n            let value = x/y;\n            result.value = value;\n\n            /** 正确执行，返回计算结果 */\n            resolve([result]);\n        }catch(err){\n            /** 执行异常，返回出错信息 */\n\n            result.err = err;\n            resolve([result]);\n        }\n    });\n}\n\n/** 导出接口 */\nmodule.exports = {\n    __main:main,\n    div1,\n    div2\n};\n```\n\n\n## 模块接口函数的返回值怎么返回\n\nBucky的模块接口函数有两种形式。\n\n形式1，导出普通函数，最后一个回调参数onComplete返回，onComplete只接受一个参数:\n```javascript\nfunction test(arg1,arg2,onComplete){\n    ...\n    onComplete(result);\n}\n```\n\n形式2，导出async函数，返回值必须是个只含有一个值的数组：\n```javascript\nasync funciton test(arg1,arg2){\n    ....\n    return [result];\n}\n```\n或者：\n```javascript\nasync funciton test(arg1,arg2){\n    return new Promise((resolve,reject)=>{\n        ...\n        resolve([result]);\n    });\n}\n```\n\n## 为什么模块接口函数的返回值必须是单个值\n\n1. JavaScript语言的函数是单返回值语义的\n2. Bucky的导出函数支持下面两种形式：\n  1. 使用最后一个回调函数onComplete返回结果值\n  2. 导出函数是一个async函数\n\n综合考虑设计上的一致性，bucky的模块接口函数的返回值必须是单个值\n\n\n## 为什么模块接口函数不使用异常\n\n1. Bucky认为使用异常应该在语言提供完整的Checked Exception机制的情况下使用。\n2. Bucky整个框架都是使用错误码返回错误信息，尽量避免异常的传播。",
      "html": "<h2>什么是模块</h2>\n<p>在XARPackage里，在config.json里配置modules，可以导出XARPackage下的js模块。</p>\n<p>假设calc是一个XARPacakge，目录结构如下:</p>\n<pre><code>├── src\n│   └── test\n│       └── calc\n│           ├── calc.js\n│           ├── config.json\n│           └── onload.js\n</code></pre>\n<p>则导出calc.js的方式是在config.json里配置modules字段：</p>\n<pre><code class=\"language-json\">// src/test/calc/config.json\n{\n    ...\n    &quot;modules&quot;: {&quot;calc&quot;: &quot;calc.js&quot;},\n    ...\n}\n</code></pre>\n<p>则<code>calc:calc</code> 就代表calc这个XARPackage下的calc模块，<code>{xarpacakge_name}:{module_name}</code>是模块的id</p>\n<h2>如何默认导出XARPackage内的所有js模块</h2>\n<p>上面的方式每导出一个模块，都需要在config.json/modules里增加一条配置，如果希望默认导出包内所有的js模块，则\n可以配置config.json/moduleAccess字段，例如：</p>\n<pre><code class=\"language-json\">// src/test/calc/config.json\n{\n    ...\n    moduleAccess:'public', // 模块导出级别, 可选值是'public','protect', 'private'，默认是'public'\n    ...\n}\n</code></pre>\n<p>不过，从工程的角度来说，不建议默认全部导出，通过导出设计过的特定模块是一个更合理的做法。</p>\n<h2>什么是模块的导出接口</h2>\n<p>在上面的calc.js里，通过module.exports导出的函数就是模块的导出接口，例如calc:calc的模块接口如下，其中的注释\n简要解释了模块导出接口的输入输出参数要求:</p>\n<pre><code class=\"language-javascript\">function main(){\n    // 模块加载后先执行main函数\n}\n\n//\n// bucky package 导出函数的两种声明方式\n// ===============\n//\n// ## 方式1: 回调方式，\n// 1. 采用最后一个回调函数退出函数\n// 2. 该回调只接受一个参数\n// \n// ## 方式2: async/await方式\n// 1. 返回值是个只能有一个元素的数组，(i.e: [result])\n// 2. 由于bucky框架不使用异常机制，不能返回异常\n// 3. 如果返回Promise，则只使用resolve，不能使用reject，原因如上\n//\n// 下面的示例两种方式导出包接口，测试代码请见test目录\n// \n\n\n/**\n * 示例：采用方式一导出函数\n * \n * 导出div1函数，接受两个参数x和y，通过调用onComplete退出\n *\n * @param {double} x 除数\n * @param {double} y 被除数，y不应该为零\n * @param {function} onComplete(result) 退出回调，只接受一个参数\n */\nfunction div1(x,y,onComplete) {\n\n    /** onComplete只接受一个参数，因此需要把返回结果打包在一个对象内 */\n    let result = {\n        err:null,\n        value:null\n    };\n\n    try{\n        let value = x/y;\n        result.value = value;\n\n        /** 正确执行，返回计算结果 */\n        onComplete(result);\n    }catch(err){\n        /** 执行异常，返回出错信息 */\n\n        result.err = err;\n        onComplete(result);\n    }\n}\n\n/**\n * 示例：采用方式二导出函数\n * \n * 导出div2函数，接受两个参数x和y，通过resolve返回结果（正确/错误 通过错误码标识）\n *\n * @param {double} x 除数\n * @param {double} y 被除数，y不应该为零\n */\nasync function div2(x,y) {\n\n    /** onComplete只接受一个参数，因此需要把返回结果打包在一个对象内 */\n    let result = {\n        err:null,\n        value:null\n    };\n\n    return new Promise((resolve, reject) =&gt; {\n        try{\n            let value = x/y;\n            result.value = value;\n\n            /** 正确执行，返回计算结果 */\n            resolve([result]);\n        }catch(err){\n            /** 执行异常，返回出错信息 */\n\n            result.err = err;\n            resolve([result]);\n        }\n    });\n}\n\n/** 导出接口 */\nmodule.exports = {\n    __main:main,\n    div1,\n    div2\n};\n</code></pre>\n<h2>模块接口函数的返回值怎么返回</h2>\n<p>Bucky的模块接口函数有两种形式。</p>\n<p>形式1，导出普通函数，最后一个回调参数onComplete返回，onComplete只接受一个参数:</p>\n<pre><code class=\"language-javascript\">function test(arg1,arg2,onComplete){\n    ...\n    onComplete(result);\n}\n</code></pre>\n<p>形式2，导出async函数，返回值必须是个只含有一个值的数组：</p>\n<pre><code class=\"language-javascript\">async funciton test(arg1,arg2){\n    ....\n    return [result];\n}\n</code></pre>\n<p>或者：</p>\n<pre><code class=\"language-javascript\">async funciton test(arg1,arg2){\n    return new Promise((resolve,reject)=&gt;{\n        ...\n        resolve([result]);\n    });\n}\n</code></pre>\n<h2>为什么模块接口函数的返回值必须是单个值</h2>\n<ol>\n<li>JavaScript语言的函数是单返回值语义的</li>\n<li>Bucky的导出函数支持下面两种形式：</li>\n<li>使用最后一个回调函数onComplete返回结果值</li>\n<li>导出函数是一个async函数</li>\n</ol>\n<p>综合考虑设计上的一致性，bucky的模块接口函数的返回值必须是单个值</p>\n<h2>为什么模块接口函数不使用异常</h2>\n<ol>\n<li>Bucky认为使用异常应该在语言提供完整的Checked Exception机制的情况下使用。</li>\n<li>Bucky整个框架都是使用错误码返回错误信息，尽量避免异常的传播。</li>\n</ol>\n",
      "id": 19
    },
    {
      "path": "5.知识库/5.3.模块加载.md",
      "url": "5.知识库/5.3.模块加载.html",
      "content": "## 如何加载XARPackage里的导出模块？\n\nXARPackage的模块，有以下几种被加载的环境：\n* XARPackage内部\n* node环境\n* html5(h5)环境\n* react-native(rn)环境\n* 微信小程序(wx)环境 \n\n在这些环境下，都可以使用 `bucky.getCurrentRuntime().loadModule('{xarpacakge_name}:{module_name}')`\n的方式去加载模块，例如：\n\n```javascript\nlet runtime = bucky.getCurrentRuntime();\nlet [err1, thecalc] = await runtime.loadModule('test:test');\n```\n\n其中，在XARPackage内部，可以使用require方式简化代码，参考下面的说明\n\n## 如何**在XARPackage内**加载其他XARPackage的导出模块？\n\n此时，除了使用上面的方式，也可以使用静态`require`方式。\n\n假设有两个XARPackage: pacakgeA和packageB，需要在packageB里加载packageA里的模块，则可以直接在packageB使用相对路径对packageA的test模块require:\n```javascript\n// packageB\nlet theModule = require('../pacakgeA/test');\n```\n\n## 包之间循环依赖\n需要说明的是XARPackage包之间不能循环依赖，即不能同时：\n1. a包里面依赖b包的模块，`require('../b/b')` 同时\n2. b包里面依赖a包的模块 `require('../a/a')`\n\n此时，需要将某一个require改成在函数里面动态loadModule\n```javascript\n// b/b.js\nasync function test(){\n    const [,moduleA] = getCurrentRuntime().loadModule('a:a');\n}\nmodule.exports = {\n    test\n}\n```\n\n## 如何**在XARPackage内**加载同XAR的其他js模块？\n\nXARPackage内部模块的加载，也使用相对路径，例如同一个包内有test1.js和test2.js两个文件.\n\n```javascript\n// test1.js\nasync function hello(){\n    console.log('hello');\n    return 'hello';\n}\nmodule.exports = {\n    hello\n};\n```\n\n```javascript\n// test2.js\n// 相对路径方式加载其他js模块\nconst test1Module = require('./test1');\n\nasync function test(){\n    await test1Module.hello();\n}\n\nmodule.exports = {\n    test\n};\n```\n\n## 如何在XARPacakge内加载node的库\n\nnode库采用白名单机制，当前白名单如下，如果有其他需求，请联系buckycloud技术支持。\n```json\n{\n    \"util\":true,\n    \"path\":true,\n    \"assert\":true,\n    \"crypto\":true,\n    \"url\":true,\n    \"dns\":true,\n    \"events\":true,\n    \"http\":true,\n    \"https\":true,\n    \"http2\":true,\n\n    \"xmlhttprequest\":true,\n    \"mysql\":true,\n    \"mongodb\":true,\n    \"ws\":true,\n    \"request\":true,\n    \"moment\":true,\n    \"twilio\":true,\n    \"authorizenet\":true,\n    \"passport-facebook\":true,\n    \"pg\":true,\n    \"uuid\":true,\n    \"async\":true,\n    \"node-jose\":true\n}\n```\n",
      "html": "<h2>如何加载XARPackage里的导出模块？</h2>\n<p>XARPackage的模块，有以下几种被加载的环境：</p>\n<ul>\n<li>XARPackage内部</li>\n<li>node环境</li>\n<li>html5(h5)环境</li>\n<li>react-native(rn)环境</li>\n<li>微信小程序(wx)环境</li>\n</ul>\n<p>在这些环境下，都可以使用 <code>bucky.getCurrentRuntime().loadModule('{xarpacakge_name}:{module_name}')</code>\n的方式去加载模块，例如：</p>\n<pre><code class=\"language-javascript\">let runtime = bucky.getCurrentRuntime();\nlet [err1, thecalc] = await runtime.loadModule('test:test');\n</code></pre>\n<p>其中，在XARPackage内部，可以使用require方式简化代码，参考下面的说明</p>\n<h2>如何<strong>在XARPackage内</strong>加载其他XARPackage的导出模块？</h2>\n<p>此时，除了使用上面的方式，也可以使用静态<code>require</code>方式。</p>\n<p>假设有两个XARPackage: pacakgeA和packageB，需要在packageB里加载packageA里的模块，则可以直接在packageB使用相对路径对packageA的test模块require:</p>\n<pre><code class=\"language-javascript\">// packageB\nlet theModule = require('../pacakgeA/test');\n</code></pre>\n<h2>包之间循环依赖</h2>\n<p>需要说明的是XARPackage包之间不能循环依赖，即不能同时：</p>\n<ol>\n<li>a包里面依赖b包的模块，<code>require('../b/b')</code> 同时</li>\n<li>b包里面依赖a包的模块 <code>require('../a/a')</code></li>\n</ol>\n<p>此时，需要将某一个require改成在函数里面动态loadModule</p>\n<pre><code class=\"language-javascript\">// b/b.js\nasync function test(){\n    const [,moduleA] = getCurrentRuntime().loadModule('a:a');\n}\nmodule.exports = {\n    test\n}\n</code></pre>\n<h2>如何<strong>在XARPackage内</strong>加载同XAR的其他js模块？</h2>\n<p>XARPackage内部模块的加载，也使用相对路径，例如同一个包内有test1.js和test2.js两个文件.</p>\n<pre><code class=\"language-javascript\">// test1.js\nasync function hello(){\n    console.log('hello');\n    return 'hello';\n}\nmodule.exports = {\n    hello\n};\n</code></pre>\n<pre><code class=\"language-javascript\">// test2.js\n// 相对路径方式加载其他js模块\nconst test1Module = require('./test1');\n\nasync function test(){\n    await test1Module.hello();\n}\n\nmodule.exports = {\n    test\n};\n</code></pre>\n<h2>如何在XARPacakge内加载node的库</h2>\n<p>node库采用白名单机制，当前白名单如下，如果有其他需求，请联系buckycloud技术支持。</p>\n<pre><code class=\"language-json\">{\n    &quot;util&quot;:true,\n    &quot;path&quot;:true,\n    &quot;assert&quot;:true,\n    &quot;crypto&quot;:true,\n    &quot;url&quot;:true,\n    &quot;dns&quot;:true,\n    &quot;events&quot;:true,\n    &quot;http&quot;:true,\n    &quot;https&quot;:true,\n    &quot;http2&quot;:true,\n\n    &quot;xmlhttprequest&quot;:true,\n    &quot;mysql&quot;:true,\n    &quot;mongodb&quot;:true,\n    &quot;ws&quot;:true,\n    &quot;request&quot;:true,\n    &quot;moment&quot;:true,\n    &quot;twilio&quot;:true,\n    &quot;authorizenet&quot;:true,\n    &quot;passport-facebook&quot;:true,\n    &quot;pg&quot;:true,\n    &quot;uuid&quot;:true,\n    &quot;async&quot;:true,\n    &quot;node-jose&quot;:true\n}\n</code></pre>\n",
      "id": 20
    },
    {
      "path": "5.知识库/5.4.公共类库.md",
      "url": "5.知识库/5.4.公共类库.html",
      "content": "\n## 如何在多个XARPackage间共享辅助类库\n\n多个XARPackage开发的情况下，公共辅助类库是一个常见的需求。\n\n首先，通过交互式方式创建一个package名为`lib`, pacakge类型为`静态库`的包：\n```\nbucky@bucky ~/D/d/b/calc> bucky add -i\n...\n请选择要操作的项目：\n────────────────────\n1. 新建项目\n2. src/test\n\n$请输入序号：2\n\n◎ 添加新package到项目src/test....\n\n$请输入package名字：lib\n\n选择package类型：\n────────────────────\n1. 新建package\n2. 示例package\n3. 静态库\n\n$请输入序号：3\n\n```\n\n则，增加了一个静态类库包src/test/lib：\n```\n├── src\n│   └── test\n│       ├── calc\n│       │   ├── calc.js\n│       │   ├── config.json\n│       │   └── onload.js\n│       └── lib\n│           ├── config.lib.json\n│           ├── lib.js\n│           └── onload.js\n```\n\n注意lib下只有一个config.lib.json，而不是config.json，我们在lib下增加一个util.js：\n```\n// src/test/lib/util.js\nfunction hello(info){\n\tBX_INFO(info);\n}\n\nmodule.exports = {\n\thello\n};\n```\n\n则，目录结构变为：\n```\n├── src\n│   └── test\n│       ├── calc\n│       │   ├── calc.js\n│       │   ├── config.json\n│       │   └── onload.js\n│       └── lib\n│           ├── config.lib.json\n│           ├── lib.js\n│           ├── util.js\n│           └── onload.js\n```\n\n则，我们可以在src/test/calc/calc.js里直接使用相对路径require该util模块:\n\n```\n// src/test/calc/calc.js\nvar Util = require('../lib/util');\n\nasync function div(){\n\tUtil.hello('call util here');\n}\n\nmodule.exports={\n\t__main,\n\tdiv,\n};\n```\n",
      "html": "<h2>如何在多个XARPackage间共享辅助类库</h2>\n<p>多个XARPackage开发的情况下，公共辅助类库是一个常见的需求。</p>\n<p>首先，通过交互式方式创建一个package名为<code>lib</code>, pacakge类型为<code>静态库</code>的包：</p>\n<pre><code>bucky@bucky ~/D/d/b/calc&gt; bucky add -i\n...\n请选择要操作的项目：\n────────────────────\n1. 新建项目\n2. src/test\n\n$请输入序号：2\n\n◎ 添加新package到项目src/test....\n\n$请输入package名字：lib\n\n选择package类型：\n────────────────────\n1. 新建package\n2. 示例package\n3. 静态库\n\n$请输入序号：3\n\n</code></pre>\n<p>则，增加了一个静态类库包src/test/lib：</p>\n<pre><code>├── src\n│   └── test\n│       ├── calc\n│       │   ├── calc.js\n│       │   ├── config.json\n│       │   └── onload.js\n│       └── lib\n│           ├── config.lib.json\n│           ├── lib.js\n│           └── onload.js\n</code></pre>\n<p>注意lib下只有一个config.lib.json，而不是config.json，我们在lib下增加一个util.js：</p>\n<pre><code>// src/test/lib/util.js\nfunction hello(info){\n\tBX_INFO(info);\n}\n\nmodule.exports = {\n\thello\n};\n</code></pre>\n<p>则，目录结构变为：</p>\n<pre><code>├── src\n│   └── test\n│       ├── calc\n│       │   ├── calc.js\n│       │   ├── config.json\n│       │   └── onload.js\n│       └── lib\n│           ├── config.lib.json\n│           ├── lib.js\n│           ├── util.js\n│           └── onload.js\n</code></pre>\n<p>则，我们可以在src/test/calc/calc.js里直接使用相对路径require该util模块:</p>\n<pre><code>// src/test/calc/calc.js\nvar Util = require('../lib/util');\n\nasync function div(){\n\tUtil.hello('call util here');\n}\n\nmodule.exports={\n\t__main,\n\tdiv,\n};\n</code></pre>\n",
      "id": 21
    },
    {
      "path": "5.知识库/5.5.系统事件.md",
      "url": "5.知识库/5.5.系统事件.html",
      "content": "\n## 系统事件(System Event)\n\n传统的分布式系统通常会在一些主机上通过系统的Cron服务来跑一些定时任务。这类任务从逻辑上和事件很接近，\n不同之处在于事件并不由某端用户代码出发，而是通过给系统一个控制命令后，由系统触发。我们把这一类事件\n称作System Event。Bucky目前支持的系统事件有两种: 系统定时器(System Timer)和系统任务(System Task)。\n\n## 系统定时器(System Timer)\n\n一个系统定时器的能力是定时执行指定XARPackage的指定接口。系统定时器的使用很简单：\n1. 创建一个XARPackage，导出需要定时执行的接口\n2. 在kowledges.json里配置\"system.timer\"字段\n\n其中，在konwledges.json里配置一个系统定时器system.timers如下:\n\n```json\n{\n  ...\n\n  \"system.timers\": {\n    \"type\": 0,\n    \"object\": {\n      // 以key-value配置多个systemtimer\n      \"example-system-timer-name\": {\n\n        // 配置要定时调用的函数\n        \"exec\": {\n\n          // 函数全称，格式是 \n          // xarpackagename:modulename::functionname\n          \"function\": \"example-xar-package-name:example-xar-module::onTimer\",\n\n          // 函数参数\n          \"args\": [],\n\n          // 用以selectruntime的packageInfo，\n          // 如果没有指定，那么直接在当前runtime运行timer\n          \"package\" : {   \n            // 必选字段\n            \"package_id\" : \"\",\n            \"runtime_type\" : \"\",\n\n            // 可选字段\n            \"ability\" : [],\n            \"drivers\" : [],\n            \"storages\" : [],\n            \"tags\" : [],\n          },\n\n          // 参考CallChain一节的介绍，\n          \"cc\": {}\n        },\n\n        // 定时器执行的间隔，最低时间间隔为1s\n        \"interval\": 30000,\n\n        // 选项，目前只有一个'immediate'，\n        // 代表是否先执行一次再定时等待\n        \"options\": [\n          \"immediate\"\n        ],\n\n        // 是否被禁止执行，可以通过bucky的工具动态修改该值\n        \"disable\" : true/false, \n      },\n    }\n  }\n  ...\n}\n```\n\n则在应用程序构建并启动后，bucky会根据上述Knowledge配置动态触发应用程序的系统定时器。例如实现一个应用程序内的积分系统，实现一个复式记账模块，则在事务（Transaction）的实现里，就可以使用System Timer来驱动Undo/Redo逻辑的实现。\n\n## 系统任务(System Task)\n\n一个系统任务的能力是在根据unix的Cron规则，在匹配的时间点执行指定XARPackage的指定接口。系统任务的使用很简单：\n1. 创建一个XARPackage，导出需要定时执行的接口\n2. 在kowledges.json里配置\"system.task\"字段\n\n其中，在konwledges.json里配置一个系统任务示例如下:\n```json\n{\n  ...\n  \"system.tasks\" : {\n    \"type\" : 0,\n    \"object\" : {\n\n      // 以key-value配置多个systemtask\n      \"test1\" : {\n        \"exec\" : {\n          // 函数全称，格式是 \n          // xarpackagename:modulename::functionname\n          \"function\" : \"calculater:calculater::onSystemTask\",\n\n          // 函数参数\n          \"args\" : [],\n\n          // 参考CallChain一节的介绍，\n          \"cc\" : {}\n        },\n        \"cronexp\" : \"* * * * *\"\n      }\n    }\n  }\n  ...\n}\n```\n\n其中`cronexp`遵守unix的Cron规范：\n- 参考文档：https://en.wikipedia.org/wiki/Cron\n- cronexp生成器：https://www.freeformatter.com/cron-expression-generator-quartz.html\n\n\n",
      "html": "<h2>系统事件(System Event)</h2>\n<p>传统的分布式系统通常会在一些主机上通过系统的Cron服务来跑一些定时任务。这类任务从逻辑上和事件很接近，\n不同之处在于事件并不由某端用户代码出发，而是通过给系统一个控制命令后，由系统触发。我们把这一类事件\n称作System Event。Bucky目前支持的系统事件有两种: 系统定时器(System Timer)和系统任务(System Task)。</p>\n<h2>系统定时器(System Timer)</h2>\n<p>一个系统定时器的能力是定时执行指定XARPackage的指定接口。系统定时器的使用很简单：</p>\n<ol>\n<li>创建一个XARPackage，导出需要定时执行的接口</li>\n<li>在kowledges.json里配置&quot;system.timer&quot;字段</li>\n</ol>\n<p>其中，在konwledges.json里配置一个系统定时器system.timers如下:</p>\n<pre><code class=\"language-json\">{\n  ...\n\n  &quot;system.timers&quot;: {\n    &quot;type&quot;: 0,\n    &quot;object&quot;: {\n      // 以key-value配置多个systemtimer\n      &quot;example-system-timer-name&quot;: {\n\n        // 配置要定时调用的函数\n        &quot;exec&quot;: {\n\n          // 函数全称，格式是 \n          // xarpackagename:modulename::functionname\n          &quot;function&quot;: &quot;example-xar-package-name:example-xar-module::onTimer&quot;,\n\n          // 函数参数\n          &quot;args&quot;: [],\n\n          // 用以selectruntime的packageInfo，\n          // 如果没有指定，那么直接在当前runtime运行timer\n          &quot;package&quot; : {   \n            // 必选字段\n            &quot;package_id&quot; : &quot;&quot;,\n            &quot;runtime_type&quot; : &quot;&quot;,\n\n            // 可选字段\n            &quot;ability&quot; : [],\n            &quot;drivers&quot; : [],\n            &quot;storages&quot; : [],\n            &quot;tags&quot; : [],\n          },\n\n          // 参考CallChain一节的介绍，\n          &quot;cc&quot;: {}\n        },\n\n        // 定时器执行的间隔，最低时间间隔为1s\n        &quot;interval&quot;: 30000,\n\n        // 选项，目前只有一个'immediate'，\n        // 代表是否先执行一次再定时等待\n        &quot;options&quot;: [\n          &quot;immediate&quot;\n        ],\n\n        // 是否被禁止执行，可以通过bucky的工具动态修改该值\n        &quot;disable&quot; : true/false, \n      },\n    }\n  }\n  ...\n}\n</code></pre>\n<p>则在应用程序构建并启动后，bucky会根据上述Knowledge配置动态触发应用程序的系统定时器。例如实现一个应用程序内的积分系统，实现一个复式记账模块，则在事务（Transaction）的实现里，就可以使用System Timer来驱动Undo/Redo逻辑的实现。</p>\n<h2>系统任务(System Task)</h2>\n<p>一个系统任务的能力是在根据unix的Cron规则，在匹配的时间点执行指定XARPackage的指定接口。系统任务的使用很简单：</p>\n<ol>\n<li>创建一个XARPackage，导出需要定时执行的接口</li>\n<li>在kowledges.json里配置&quot;system.task&quot;字段</li>\n</ol>\n<p>其中，在konwledges.json里配置一个系统任务示例如下:</p>\n<pre><code class=\"language-json\">{\n  ...\n  &quot;system.tasks&quot; : {\n    &quot;type&quot; : 0,\n    &quot;object&quot; : {\n\n      // 以key-value配置多个systemtask\n      &quot;test1&quot; : {\n        &quot;exec&quot; : {\n          // 函数全称，格式是 \n          // xarpackagename:modulename::functionname\n          &quot;function&quot; : &quot;calculater:calculater::onSystemTask&quot;,\n\n          // 函数参数\n          &quot;args&quot; : [],\n\n          // 参考CallChain一节的介绍，\n          &quot;cc&quot; : {}\n        },\n        &quot;cronexp&quot; : &quot;* * * * *&quot;\n      }\n    }\n  }\n  ...\n}\n</code></pre>\n<p>其中<code>cronexp</code>遵守unix的Cron规范：</p>\n<ul>\n<li>参考文档：<a href=\"https://en.wikipedia.org/wiki/Cron\">https://en.wikipedia.org/wiki/Cron</a></li>\n<li>cronexp生成器：<a href=\"https://www.freeformatter.com/cron-expression-generator-quartz.html\">https://www.freeformatter.com/cron-expression-generator-quartz.html</a></li>\n</ul>\n",
      "id": 22
    },
    {
      "path": "5.知识库/5.6.全局事件.md",
      "url": "5.知识库/5.6.全局事件.html",
      "content": "## Global Event\n\nGlobal Event是bukcy框架提供的一个非常有用的组件。实现了一个逻辑上非常常用的功能：\n>“在系统中定义一个全局事件，然后在任何一段代码中都可以Attach这个事件，在任何一段代码中都可以Fire这个事件。”   \n\n在传统的后台开发中，我们常常使用消息中间件来达到类似功能。从过去的经验来看，我们鼓励应用尽量使用端到端的无状态事件系统（即系统允许丢失事件），当然bucky也允许用户开发自己的有状态事件系统。    \n\nGlobal Event的使用也很简单：\n\n#### 创建并监听事件\n```javascript\nlet em = getCurrentRuntime().getGlobalEventManager();\nlet eventCategory = 'myEventCatagory';\nlet eventID = 'myMessage';\n\n// create an event catagory\nem.create(eventCategory, (result) => {\n\n\t// attach an eventID on the event catagory\n\tconsole.log(`${eventCategory} is created:${result}`);\n\tem.attach(eventCategory, eventID, (msg) => {\n\t\tconsole.log(`${eventCategory}/${eventID} is fired:`, msg);\n\t};\n});\n```\n\n#### 触发事件\n```javascript\nlet em = getCurrentRuntime().getGlobalEventManager();\nlet eventCategory = 'myEventCatagory';\nlet eventID = 'myMessage';\n\n// Fire the eventID on the event catagory\nlet msg = JSON.stringify({\n    cmd:'echo',\n    body:{\n        text:'Real-world programming, however, requires care, expertise, and wisdom.',\n        from:'SICP'\n    }\n});\nem.activeEvent(eventCategory, eventID, msg);\n```\n\n#### 代码说明\n从上述代码中我们可以看出来：\n1. Global Event的操作接口都是异步的。\n2. Global Event的由event catagory/eventID两层结构构成，方便用户组织事件的名字空间。\n3. Global Event的参数是一个字符串，我们一般鼓励在里面填写一个stringfiy后的JSON.\n4. Global Event是应用程序全局的，所以具有分布式系统在一致性方面的特性：attach成功之后存在与事件控制器失去链接的可能，而系统并不会保证在失去连接的这段时间内产生的事件不会丢失。",
      "html": "<h2>Global Event</h2>\n<p>Global Event是bukcy框架提供的一个非常有用的组件。实现了一个逻辑上非常常用的功能：</p>\n<blockquote>\n<p>“在系统中定义一个全局事件，然后在任何一段代码中都可以Attach这个事件，在任何一段代码中都可以Fire这个事件。”</p>\n</blockquote>\n<p>在传统的后台开发中，我们常常使用消息中间件来达到类似功能。从过去的经验来看，我们鼓励应用尽量使用端到端的无状态事件系统（即系统允许丢失事件），当然bucky也允许用户开发自己的有状态事件系统。</p>\n<p>Global Event的使用也很简单：</p>\n<h4>创建并监听事件</h4>\n<pre><code class=\"language-javascript\">let em = getCurrentRuntime().getGlobalEventManager();\nlet eventCategory = 'myEventCatagory';\nlet eventID = 'myMessage';\n\n// create an event catagory\nem.create(eventCategory, (result) =&gt; {\n\n\t// attach an eventID on the event catagory\n\tconsole.log(`${eventCategory} is created:${result}`);\n\tem.attach(eventCategory, eventID, (msg) =&gt; {\n\t\tconsole.log(`${eventCategory}/${eventID} is fired:`, msg);\n\t};\n});\n</code></pre>\n<h4>触发事件</h4>\n<pre><code class=\"language-javascript\">let em = getCurrentRuntime().getGlobalEventManager();\nlet eventCategory = 'myEventCatagory';\nlet eventID = 'myMessage';\n\n// Fire the eventID on the event catagory\nlet msg = JSON.stringify({\n    cmd:'echo',\n    body:{\n        text:'Real-world programming, however, requires care, expertise, and wisdom.',\n        from:'SICP'\n    }\n});\nem.activeEvent(eventCategory, eventID, msg);\n</code></pre>\n<h4>代码说明</h4>\n<p>从上述代码中我们可以看出来：</p>\n<ol>\n<li>Global Event的操作接口都是异步的。</li>\n<li>Global Event的由event catagory/eventID两层结构构成，方便用户组织事件的名字空间。</li>\n<li>Global Event的参数是一个字符串，我们一般鼓励在里面填写一个stringfiy后的JSON.</li>\n<li>Global Event是应用程序全局的，所以具有分布式系统在一致性方面的特性：attach成功之后存在与事件控制器失去链接的可能，而系统并不会保证在失去连接的这段时间内产生的事件不会丢失。</li>\n</ol>\n",
      "id": 23
    }
  ]
}