node框架LoopBack教程

ES6的出品为JS成为企业级语言扫清障碍,与之配套的,我们需要一个真正的企业级框架。快递像一个精巧的微内核,不足以支撑起一个大项目。以下是LoopBack的一些入门知识,它是一个真正的企业级框架,随着使用的深入,读者将会发现它更多的用法和优秀的特性。本篇将对它的主要用法做一个详细的介绍。

LoopBack是建立在Express基础上的企业级Node.js框架,这个框架支持

  • 只需要编写少量代码就能创建动态端到端的REST API
  • 支持主流的数据源,例如Mongodb,SOAP,MySQL等和REST API的数据。
  • 一致化的模型关系和对API访问的权限控制
  • 可使用内置的用于移动应用场景下的地理定位,文件服务以及消息推送
  • 提供Android,iOS和JavaScript的SDK,轻松创建客户端应用程序
  • 支持在云端或者本地部署服务

它可以像Express那样被使用。除此之外,LoopBack作为一个面向企业级的Web框架,提供了更丰富的功能,这在我们添加模型,权限控制,连接数据源等操作时,极大的提升我们的效率。例如可以通过修改配置增加模型,并指定模型的数据源。它默认提供了一些基础模型,例如用户这个模型包含了注册登录等逻辑。我们可以非常方便的继承这些内建模型,实现个性化的定制。它还提供了Hook编程的机制。它同时提供了可视化的调试页面,自动生成对应的前端SDK。这些功能在开发大型Web服务的时候,将帮助我们更容易查看和管理项目。本篇将会详细的介绍LoopBack的使用。

安装与运行

StrongLoop是生成LoopBack框架的工具程序,我们首先安装它。运行

<span style="color:#333333"><code><span style="color:#000000">npm install </span><span style="color:#666600">-</span><span style="color:#000000">g strongloop</span></code></span>

安装完成之后,可以运行slc -v查看是否安装成功(需要事先建立slc的软链接)。

紧接着,我们运行slc loopback,这是一个交互式的命令,首先提示用户输入项目名称,这里就输入环回。接下来根据引导,按步骤填写相应信息即可。输入项目名称之后,接下来的步骤我们可以直接敲回车即可。最后strongloop会帮助我们创建loopback目录,并且在目录下创建默认的项目文件。我们进入loopback文件夹,运行slc loopback:model,创建一个模型。我们可以随意输一个模块名,例如酷。接下来要求选择数据源,这里先选择默认值db(memory),敲回车即可。下一步要求选择模型的基类,也选用默认值PersistedModel,代表此模型与持久化数据源连接。接下来,会出现

通过REST API公开炫酷?是

当我们选择'Y',LoopBack会为我们的模型生成REST API的代码。之后直接点击回车完成步骤即可。我们查看一下loopback目录都包含哪些文件<br>

LoopBack符合模型(M) - 视图(V) - 控制器©的设计规范。上图中的服务器文件夹,包含了程序的启动代码,配置信息。路由部分的逻辑也在服务器目录下。快速支持将路由分组,因此服务器目录可以对应MVC的C.client目录包含给用户展示的前端代码,也包含由后台处理的用于生成页面的模板。这个目录对应MVC的V.common目录下有一个模特文件夹,这里的代码处理具体的业务逻辑和数据,对应M.

我们进入服务器文件夹,运行节点server.js,可以看到如下信息

Web服务器监听:http://0.0.0.0 : 3000 浏览您的REST API,网址为http://0.0.0.0:3000/explorer

在本地打开浏览器访问http://0.0.0.0:3000/explorer,可以看到如下界面<br> <br>

这是LoopBack集成的一个非常棒的功能,它列出了所有对外的模型和每一个模型的接口.LoopBack默认生成的接口都是REST API风格。点击某一个接口,界面会展开,展开的界面提供了测试功能。我们可以将构造好的参数填入输入框,然后查看接口的返回结果。

LoopBack为模型默认生成的接口包括

读系列

  1. 存在 - 模型的数据源中对应id项是否存在
  2. findById - 根据id返回数据源对应的项
  3. find - 返回所有满足匹配查询条件的项
  4. count - 返回满足匹配查询条件的项目个数

写系列

  1. 创造 - 创建新项
  2. upsert - 更新项
  3. destroyById - 删除为指定id的项

默认这些REST API可以被访问。如果需要屏蔽某一个,可以在模型的JS文件内部,例如cool.js,内部增加调用

<span style="color:#333333"><code><span style="color:#000088">module</span><span style="color:#666600">.</span><span style="color:#000000">exports </span><span style="color:#666600">=</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#660066">Cool</span><span style="color:#666600">)</span> <span style="color:#666600">{</span>
  <span style="color:#660066">Cool</span><span style="color:#666600">.</span><span style="color:#000000">disableRemoteMethod</span><span style="color:#666600">(</span><span style="color:#008800">'findById'</span><span style="color:#666600">,</span> <span style="color:#000088">true</span><span style="color:#666600">);</span>
  <span style="color:#880000">// 省略...</span></code></span>

这样就能屏蔽掉了findById这个接口。

当LoopBack服务启动的时候,它会按照文件名的字符串顺序,加载位于/ server / root里面的所有后缀名为.js的文件。这提供了一个初始化整个系统的机会。例如我们可以利用这个机制挂载模块,或者将初始化数据库的代码放到这个目录。

在浏览器中打开explorer调试接口虽然方便,但在实际项目中,别人随意可以查看这个界面存在着一定的风险。这时候就可以利用LoopBack加载服务器/ root里面JS文件的机制,为explorer的访问增加权限控制。接下来在服务器/ root里新建一个文件,起名为explorer.js,这个文件的内容是

<span style="color:#333333"><code><span style="color:#000088">module</span><span style="color:#666600">.</span><span style="color:#000000">exports </span><span style="color:#666600">=</span> <span style="color:#000088">function</span><span style="color:#000000"> mountLoopBackExplorer</span><span style="color:#666600">(</span><span style="color:#000000">server</span><span style="color:#666600">)</span> <span style="color:#666600">{</span>
  <span style="color:#000088">var</span><span style="color:#000000"> explorer</span><span style="color:#666600">;</span>
  <span style="color:#000088">try</span> <span style="color:#666600">{</span><span style="color:#000000">
    explorer </span><span style="color:#666600">=</span> <span style="color:#000088">require</span><span style="color:#666600">(</span><span style="color:#008800">'loopback-component-explorer'</span><span style="color:#666600">);</span>
  <span style="color:#666600">}</span> <span style="color:#000088">catch</span><span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">)</span> <span style="color:#666600">{</span>
    <span style="color:#880000">// Print the message only when the app was started via `server.listen()`.</span>
    <span style="color:#880000">// Do not print any message when the project is used as a component.</span><span style="color:#000000">
    server</span><span style="color:#666600">.</span><span style="color:#000000">once</span><span style="color:#666600">(</span><span style="color:#008800">'started'</span><span style="color:#666600">,</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#000000">baseUrl</span><span style="color:#666600">)</span> <span style="color:#666600">{</span><span style="color:#000000">
      console</span><span style="color:#666600">.</span><span style="color:#000000">error</span><span style="color:#666600">(</span>
        <span style="color:#008800">'Run `npm install loopback-component-explorer` to enable the LoopBack explorer'</span>
      <span style="color:#666600">);</span>
    <span style="color:#666600">});</span>
    <span style="color:#000088">return</span><span style="color:#666600">;</span>
  <span style="color:#666600">}</span>
  <span style="color:#880000">//用户名 test 密码 123456</span><span style="color:#000000">
  server</span><span style="color:#666600">.</span><span style="color:#000088">use</span><span style="color:#666600">(</span><span style="color:#008800">'/explorer'</span><span style="color:#666600">,</span> <span style="color:#000088">require</span><span style="color:#666600">(</span><span style="color:#008800">'node-basicauth'</span><span style="color:#666600">)({</span><span style="color:#008800">'test'</span><span style="color:#666600">:</span> <span style="color:#008800">'123456'</span> <span style="color:#666600">}));</span><span style="color:#000000">

  server</span><span style="color:#666600">.</span><span style="color:#000088">use</span><span style="color:#666600">(</span><span style="color:#008800">'/explorer'</span><span style="color:#666600">,</span><span style="color:#000000"> explorer</span><span style="color:#666600">.</span><span style="color:#000000">routes</span><span style="color:#666600">(</span><span style="color:#000000">server</span><span style="color:#666600">,</span> <span style="color:#666600">{</span><span style="color:#000000"> basePath</span><span style="color:#666600">:</span><span style="color:#000000"> server</span><span style="color:#666600">.</span><span style="color:#000088">get</span><span style="color:#666600">(</span><span style="color:#008800">'restApiRoot'</span><span style="color:#666600">)</span> <span style="color:#666600">}));</span><span style="color:#000000">

  server</span><span style="color:#666600">.</span><span style="color:#000000">once</span><span style="color:#666600">(</span><span style="color:#008800">'started'</span><span style="color:#666600">,</span> <span style="color:#000088">function</span><span style="color:#666600">()</span> <span style="color:#666600">{</span>
    <span style="color:#000088">var</span><span style="color:#000000"> baseUrl </span><span style="color:#666600">=</span><span style="color:#000000"> server</span><span style="color:#666600">.</span><span style="color:#000088">get</span><span style="color:#666600">(</span><span style="color:#008800">'url'</span><span style="color:#666600">).</span><span style="color:#000000">replace</span><span style="color:#666600">(</span><span style="color:#008800">/\/$/</span><span style="color:#666600">,</span> <span style="color:#008800">''</span><span style="color:#666600">);</span><span style="color:#000000">
    console</span><span style="color:#666600">.</span><span style="color:#000000">log</span><span style="color:#666600">(</span><span style="color:#008800">'查看你的 REST API  %s%s'</span><span style="color:#666600">,</span><span style="color:#000000"> baseUrl</span><span style="color:#666600">,</span> <span style="color:#008800">'/explorer'</span><span style="color:#666600">);</span>
  <span style="color:#666600">});</span>
<span style="color:#666600">};</span></code></span>

以上代码使用了一个新的模块node-basicauth,因此在启动服务前需要先安装好,回到loopback目录运行

npm install node-basicauth

然后还需要修改server / component-config.json文件的内容,将默认的配置去除,或者直接删除这个文件。在服务器目录下重新启动服务,然后在本地用浏览器打开网址http://0.0.0.0 :3000 / explorer,出现提示,要输入用户名和密码。<br> <br> mountLoopBackExplorer函数的参数服务器是LoopBack传进来的,这个对象代表LoopBack程序本身。它在server.js文件开头创建

<span style="color:#333333"><code><span style="color:#000088">var</span><span style="color:#000000"> app </span><span style="color:#666600">=</span> <span style="color:#000088">module</span><span style="color:#666600">.</span><span style="color:#000000">exports </span><span style="color:#666600">=</span><span style="color:#000000"> loopback</span><span style="color:#666600">();</span></code></span>

app.models包含了所有的模型,假如我们希望访问酷这个模型,可以通过如下形式

app.models.cool

得到此模型对象,之后便可以调用这个对象的函数。

路由与权限控制

LoopBack添加路由的方式与Express一致.LoopBack实现了MVC模型,在这个框架下,它提供了另外一种添加模块并导出API的方式。我们先来看Express添加路由的方法。默认生成的服务器/服务器的.js文件不大,大致内容为

<span style="color:#333333"><code><span style="color:#000088">var</span><span style="color:#000000"> loopback </span><span style="color:#666600">=</span> <span style="color:#000088">require</span><span style="color:#666600">(</span><span style="color:#008800">'loopback'</span><span style="color:#666600">);</span>
<span style="color:#000088">var</span><span style="color:#000000"> boot </span><span style="color:#666600">=</span> <span style="color:#000088">require</span><span style="color:#666600">(</span><span style="color:#008800">'loopback-boot'</span><span style="color:#666600">);</span>
<span style="color:#000088">var</span><span style="color:#000000"> app </span><span style="color:#666600">=</span> <span style="color:#000088">module</span><span style="color:#666600">.</span><span style="color:#000000">exports </span><span style="color:#666600">=</span><span style="color:#000000"> loopback</span><span style="color:#666600">();</span><span style="color:#000000">

app</span><span style="color:#666600">.</span><span style="color:#000000">start </span><span style="color:#666600">=</span> <span style="color:#000088">function</span><span style="color:#666600">()</span> <span style="color:#666600">{</span>
  <span style="color:#880000">// start the web server</span>
  <span style="color:#000088">return</span><span style="color:#000000"> app</span><span style="color:#666600">.</span><span style="color:#000000">listen</span><span style="color:#666600">(</span><span style="color:#000088">function</span><span style="color:#666600">()</span> <span style="color:#666600">{</span><span style="color:#000000">
    app</span><span style="color:#666600">.</span><span style="color:#000000">emit</span><span style="color:#666600">(</span><span style="color:#008800">'started'</span><span style="color:#666600">);</span>
    <span style="color:#000088">var</span><span style="color:#000000"> baseUrl </span><span style="color:#666600">=</span><span style="color:#000000"> app</span><span style="color:#666600">.</span><span style="color:#000088">get</span><span style="color:#666600">(</span><span style="color:#008800">'url'</span><span style="color:#666600">).</span><span style="color:#000000">replace</span><span style="color:#666600">(</span><span style="color:#008800">/\/$/</span><span style="color:#666600">,</span> <span style="color:#008800">''</span><span style="color:#666600">);</span><span style="color:#000000">
    console</span><span style="color:#666600">.</span><span style="color:#000000">log</span><span style="color:#666600">(</span><span style="color:#008800">'Web server listening at: %s'</span><span style="color:#666600">,</span><span style="color:#000000"> baseUrl</span><span style="color:#666600">);</span>
    <span style="color:#000088">if</span> <span style="color:#666600">(</span><span style="color:#000000">app</span><span style="color:#666600">.</span><span style="color:#000088">get</span><span style="color:#666600">(</span><span style="color:#008800">'loopback-component-explorer'</span><span style="color:#666600">))</span> <span style="color:#666600">{</span>
      <span style="color:#000088">var</span><span style="color:#000000"> explorerPath </span><span style="color:#666600">=</span><span style="color:#000000"> app</span><span style="color:#666600">.</span><span style="color:#000088">get</span><span style="color:#666600">(</span><span style="color:#008800">'loopback-component-explorer'</span><span style="color:#666600">).</span><span style="color:#000000">mountPath</span><span style="color:#666600">;</span><span style="color:#000000">
      console</span><span style="color:#666600">.</span><span style="color:#000000">log</span><span style="color:#666600">(</span><span style="color:#008800">'Browse your REST API at %s%s'</span><span style="color:#666600">,</span><span style="color:#000000"> baseUrl</span><span style="color:#666600">,</span><span style="color:#000000"> explorerPath</span><span style="color:#666600">);</span>
    <span style="color:#666600">}</span>
  <span style="color:#666600">});</span>
<span style="color:#666600">};</span>

<span style="color:#880000">// Bootstrap the application, configure models, datasources and middleware.</span>
<span style="color:#880000">// Sub-apps like REST API are mounted via boot scripts.</span><span style="color:#000000">
boot</span><span style="color:#666600">(</span><span style="color:#000000">app</span><span style="color:#666600">,</span><span style="color:#000000"> __dirname</span><span style="color:#666600">,</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">)</span> <span style="color:#666600">{</span>
  <span style="color:#000088">if</span> <span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">)</span> <span style="color:#000088">throw</span><span style="color:#000000"> err</span><span style="color:#666600">;</span>

  <span style="color:#880000">// start the server if `$ Node server.js`</span>
  <span style="color:#000088">if</span> <span style="color:#666600">(</span><span style="color:#000088">require</span><span style="color:#666600">.</span><span style="color:#000000">main </span><span style="color:#666600">===</span> <span style="color:#000088">module</span><span style="color:#666600">)</span><span style="color:#000000">
    app</span><span style="color:#666600">.</span><span style="color:#000000">start</span><span style="color:#666600">();</span>
<span style="color:#666600">});</span>
</code></span>

在server.js控制路由的逻辑中,应该将路由分类,以后方便管理。在服务器目录中新建一个文件夹,命名为routes,然后新建一个test.js的文件,内容为

<span style="color:#333333"><code><span style="color:#000088">var</span><span style="color:#000000"> router </span><span style="color:#666600">=</span> <span style="color:#000088">module</span><span style="color:#666600">.</span><span style="color:#000000">exports</span><span style="color:#666600">.</span><span style="color:#000000">test_router </span><span style="color:#666600">=</span> <span style="color:#000088">require</span><span style="color:#666600">(</span><span style="color:#008800">'loopback'</span><span style="color:#666600">).</span><span style="color:#660066">Router</span><span style="color:#666600">();</span><span style="color:#000000">

router</span><span style="color:#666600">.</span><span style="color:#000088">get</span><span style="color:#666600">(</span><span style="color:#008800">'/name'</span><span style="color:#666600">,</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#000000">req</span><span style="color:#666600">,</span><span style="color:#000000"> res</span><span style="color:#666600">,</span> <span style="color:#000088">next</span><span style="color:#666600">)</span> <span style="color:#666600">{</span><span style="color:#000000">
  res</span><span style="color:#666600">.</span><span style="color:#000000">send</span><span style="color:#666600">(</span><span style="color:#008800">'visit test/name'</span><span style="color:#666600">);</span>
<span style="color:#666600">});</span><span style="color:#000000">

router</span><span style="color:#666600">.</span><span style="color:#000088">get</span><span style="color:#666600">(</span><span style="color:#008800">'/'</span><span style="color:#666600">,</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#000000">req</span><span style="color:#666600">,</span><span style="color:#000000"> res</span><span style="color:#666600">)</span> <span style="color:#666600">{</span><span style="color:#000000">
  res</span><span style="color:#666600">.</span><span style="color:#000000">send</span><span style="color:#666600">(</span><span style="color:#008800">'visit test root'</span><span style="color:#666600">);</span>
<span style="color:#666600">});</span></code></span>

启动服务后,用户访问/ test /或者/ test / name的时候要能正确返回。因此需要修改server.js,建立test的路由,以下是修改之后的server.js内容

<span style="color:#333333"><code><span style="color:#000088">var</span><span style="color:#000000"> loopback </span><span style="color:#666600">=</span> <span style="color:#000088">require</span><span style="color:#666600">(</span><span style="color:#008800">'loopback'</span><span style="color:#666600">);</span>
<span style="color:#000088">var</span><span style="color:#000000"> boot </span><span style="color:#666600">=</span> <span style="color:#000088">require</span><span style="color:#666600">(</span><span style="color:#008800">'loopback-boot'</span><span style="color:#666600">);</span>
<span style="color:#000088">var</span><span style="color:#000000"> path </span><span style="color:#666600">=</span> <span style="color:#000088">require</span><span style="color:#666600">(</span><span style="color:#008800">'path'</span><span style="color:#666600">);</span>
<span style="color:#000088">var</span><span style="color:#000000"> app </span><span style="color:#666600">=</span> <span style="color:#000088">module</span><span style="color:#666600">.</span><span style="color:#000000">exports </span><span style="color:#666600">=</span><span style="color:#000000"> loopback</span><span style="color:#666600">();</span><span style="color:#000000">

app</span><span style="color:#666600">.</span><span style="color:#000000">start </span><span style="color:#666600">=</span> <span style="color:#000088">function</span><span style="color:#666600">()</span> <span style="color:#666600">{</span>
  <span style="color:#880000">// start the web server</span>
  <span style="color:#000088">return</span><span style="color:#000000"> app</span><span style="color:#666600">.</span><span style="color:#000000">listen</span><span style="color:#666600">(</span><span style="color:#000088">function</span><span style="color:#666600">()</span> <span style="color:#666600">{</span><span style="color:#000000">
    app</span><span style="color:#666600">.</span><span style="color:#000000">emit</span><span style="color:#666600">(</span><span style="color:#008800">'started'</span><span style="color:#666600">);</span>
    <span style="color:#000088">var</span><span style="color:#000000"> baseUrl </span><span style="color:#666600">=</span><span style="color:#000000"> app</span><span style="color:#666600">.</span><span style="color:#000088">get</span><span style="color:#666600">(</span><span style="color:#008800">'url'</span><span style="color:#666600">).</span><span style="color:#000000">replace</span><span style="color:#666600">(</span><span style="color:#008800">/\/$/</span><span style="color:#666600">,</span> <span style="color:#008800">''</span><span style="color:#666600">);</span><span style="color:#000000">
    console</span><span style="color:#666600">.</span><span style="color:#000000">log</span><span style="color:#666600">(</span><span style="color:#008800">'Web server listening at: %s'</span><span style="color:#666600">,</span><span style="color:#000000"> baseUrl</span><span style="color:#666600">);</span>
    <span style="color:#000088">if</span> <span style="color:#666600">(</span><span style="color:#000000">app</span><span style="color:#666600">.</span><span style="color:#000088">get</span><span style="color:#666600">(</span><span style="color:#008800">'loopback-component-explorer'</span><span style="color:#666600">))</span> <span style="color:#666600">{</span>
      <span style="color:#000088">var</span><span style="color:#000000"> explorerPath </span><span style="color:#666600">=</span><span style="color:#000000"> app</span><span style="color:#666600">.</span><span style="color:#000088">get</span><span style="color:#666600">(</span><span style="color:#008800">'loopback-component-explorer'</span><span style="color:#666600">).</span><span style="color:#000000">mountPath</span><span style="color:#666600">;</span><span style="color:#000000">
      console</span><span style="color:#666600">.</span><span style="color:#000000">log</span><span style="color:#666600">(</span><span style="color:#008800">'Browse your REST API at %s%s'</span><span style="color:#666600">,</span><span style="color:#000000"> baseUrl</span><span style="color:#666600">,</span><span style="color:#000000"> explorerPath</span><span style="color:#666600">);</span>
    <span style="color:#666600">}</span>
  <span style="color:#666600">});</span>
<span style="color:#666600">};</span><span style="color:#000000">

app</span><span style="color:#666600">.</span><span style="color:#000088">use</span><span style="color:#666600">(</span><span style="color:#008800">'/test'</span><span style="color:#666600">,</span><span style="color:#000088">require</span><span style="color:#666600">(</span><span style="color:#000000">path</span><span style="color:#666600">.</span><span style="color:#000000">resolve</span><span style="color:#666600">(</span><span style="color:#000000">__dirname</span><span style="color:#666600">,</span> <span style="color:#008800">'./routes/test.js'</span><span style="color:#666600">)).</span><span style="color:#000000">test_router</span><span style="color:#666600">);</span>

<span style="color:#880000">// Bootstrap the application, configure models, datasources and middleware.</span>
<span style="color:#880000">// Sub-apps like REST API are mounted via boot scripts.</span><span style="color:#000000">
boot</span><span style="color:#666600">(</span><span style="color:#000000">app</span><span style="color:#666600">,</span><span style="color:#000000"> __dirname</span><span style="color:#666600">,</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">)</span> <span style="color:#666600">{</span>
  <span style="color:#000088">if</span> <span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">)</span> <span style="color:#000088">throw</span><span style="color:#000000"> err</span><span style="color:#666600">;</span>

  <span style="color:#880000">// start the server if `$ Node server.js`</span>
  <span style="color:#000088">if</span> <span style="color:#666600">(</span><span style="color:#000088">require</span><span style="color:#666600">.</span><span style="color:#000000">main </span><span style="color:#666600">===</span> <span style="color:#000088">module</span><span style="color:#666600">)</span><span style="color:#000000">
    app</span><span style="color:#666600">.</span><span style="color:#000000">start</span><span style="color:#666600">();</span>
<span style="color:#666600">});</span><span style="color:#000000">

process</span><span style="color:#666600">.</span><span style="color:#000000">on</span><span style="color:#666600">(</span><span style="color:#008800">'uncaughtException'</span><span style="color:#666600">,</span> <span style="color:#000088">function</span> <span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">){</span><span style="color:#000000">
  console</span><span style="color:#666600">.</span><span style="color:#000000">error</span><span style="color:#666600">(</span><span style="color:#008800">'uncaughtException: %s'</span><span style="color:#666600">,</span><span style="color:#000000"> err</span><span style="color:#666600">.</span><span style="color:#000000">message</span><span style="color:#666600">);</span>
<span style="color:#666600">});</span></code></span>

重新启动服务,使用浏览器访问http://0.0.0.0:3000/test/namehttp://0.0.0.0:3000/test/可以看到返回的结果。以上代码除了添加了一个test的路由,还监听了uncaughtException这个事件,后面的部分在讲解cluster模式的时候,我们将会看到对这个事件更合理的处理。

按照上述方式添加路由非常简单,但这些导出的API无法在explorer页面中查看和调试,也难以对API进行权限控制等操作。好在LoopBack框架提供了一套机制,通过修改配置文件就能增加模型和导出REST API,并且能够方便的对接口进行权限控制。之前在common / models文件夹里,我们用slc生成了一个模型cool,这个目录下包含两个文件

<span style="color:#333333"><code><span style="color:#000000">cool</span><span style="color:#666600">.</span><span style="color:#000000">js  cool</span><span style="color:#666600">.</span><span style="color:#000000">json</span></code></span>

cool.json是对这个模型的配置,这个文件包含的内容是

<span style="color:#333333"><code><span style="color:#666600">{</span>
  <span style="color:#008800">"name"</span><span style="color:#666600">:</span> <span style="color:#008800">"cool"</span><span style="color:#666600">,</span>
  <span style="color:#008800">"base"</span><span style="color:#666600">:</span> <span style="color:#008800">"PersistedModel"</span><span style="color:#666600">,</span>
  <span style="color:#008800">"idInjection"</span><span style="color:#666600">:</span> <span style="color:#000088">true</span><span style="color:#666600">,</span>
  <span style="color:#008800">"options"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
    <span style="color:#008800">"validateUpsert"</span><span style="color:#666600">:</span> <span style="color:#000088">true</span>
  <span style="color:#666600">},</span>
  <span style="color:#008800">"properties"</span><span style="color:#666600">:</span> <span style="color:#666600">{},</span>
  <span style="color:#008800">"validations"</span><span style="color:#666600">:</span> <span style="color:#666600">[],</span>
  <span style="color:#008800">"relations"</span><span style="color:#666600">:</span> <span style="color:#666600">{},</span>
  <span style="color:#008800">"acls"</span><span style="color:#666600">:</span> <span style="color:#666600">[],</span>
  <span style="color:#008800">"methods"</span><span style="color:#666600">:</span> <span style="color:#666600">{}</span>
<span style="color:#666600">}</span></code></span>

这个文件定义了几个字段,base代表cool模型的基类,acls字段用于权限控制.relations定义了模型之间的关系,属性定义了模型对应的持久化字段.cool.js中包含这个模型的处理逻辑,这个文件的初始内容是

<span style="color:#333333"><code><span style="color:#000088">module</span><span style="color:#666600">.</span><span style="color:#000000">exports </span><span style="color:#666600">=</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#660066">Cool</span><span style="color:#666600">)</span> <span style="color:#666600">{</span>
<span style="color:#666600">};</span></code></span>

现在我们给cool添加一个get请求,并且这个API添加不同类型的权限。要添加新接口,需要在cool.js中编写新接口的代码,例如我们添加一个名字为test的接口,这个接口接收一个字符串,然后返回这个字符串,代码大致如下

<span style="color:#333333"><code><span style="color:#000088">module</span><span style="color:#666600">.</span><span style="color:#000000">exports </span><span style="color:#666600">=</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#660066">Cool</span><span style="color:#666600">)</span> <span style="color:#666600">{</span>

  <span style="color:#660066">Cool</span><span style="color:#666600">.</span><span style="color:#000000">test </span><span style="color:#666600">=</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#000000">content</span><span style="color:#666600">,</span><span style="color:#000000"> cb</span><span style="color:#666600">){</span><span style="color:#000000">
  	cb</span><span style="color:#666600">(</span><span style="color:#000088">null</span><span style="color:#666600">,</span><span style="color:#000000"> content</span><span style="color:#666600">);</span>
  <span style="color:#666600">};</span>

  <span style="color:#660066">Cool</span><span style="color:#666600">.</span><span style="color:#000000">remoteMethod</span><span style="color:#666600">(</span>
    <span style="color:#008800">'test'</span>
    <span style="color:#666600">,{</span><span style="color:#000000">
      description</span><span style="color:#666600">:</span> <span style="color:#008800">'输入一个字符串,返回它'</span>
      <span style="color:#666600">,</span><span style="color:#000000">accepts</span><span style="color:#666600">:</span> <span style="color:#666600">[</span>
      			  <span style="color:#666600">{</span><span style="color:#000000">arg</span><span style="color:#666600">:</span> <span style="color:#008800">'content'</span><span style="color:#666600">,</span><span style="color:#000000"> type</span><span style="color:#666600">:</span> <span style="color:#008800">'string'</span><span style="color:#666600">,</span><span style="color:#000000">required</span><span style="color:#666600">:</span> <span style="color:#000088">true</span><span style="color:#666600">}</span>
                <span style="color:#666600">]</span>
             <span style="color:#666600">,</span><span style="color:#000000">http</span><span style="color:#666600">:</span> <span style="color:#666600">{</span><span style="color:#000000">path</span><span style="color:#666600">:</span><span style="color:#008800">'/test'</span><span style="color:#666600">,</span><span style="color:#000000"> verb</span><span style="color:#666600">:</span> <span style="color:#008800">'get'</span><span style="color:#666600">}</span>
             <span style="color:#666600">,</span><span style="color:#000000">returns </span><span style="color:#666600">:</span> <span style="color:#666600">{</span><span style="color:#000000"> arg</span><span style="color:#666600">:</span> <span style="color:#008800">'ret'</span><span style="color:#666600">,</span><span style="color:#000000"> type</span><span style="color:#666600">:</span><span style="color:#008800">"string"</span><span style="color:#666600">,</span><span style="color:#000000"> root</span><span style="color:#666600">:</span> <span style="color:#000088">true</span><span style="color:#666600">,</span><span style="color:#000000">required</span><span style="color:#666600">:</span> <span style="color:#000088">true</span><span style="color:#666600">}</span>
    <span style="color:#666600">}</span>
  <span style="color:#666600">);</span>
<span style="color:#666600">};</span></code></span>

LoopBack是一个优秀而易用的框架,代码就是最好的教科书。经过修改之后,我们重新启动服务,用浏览器打开explorer,测试我们新添加的接口如下图<br> <br>

我们在输入框随意输入一个字符串,点击测试按钮,可以立即查看返回结果。可见LoopBack框架内,给模型添加一个接口非常方便,新接口添加完毕,浏览器打开页面就可以直接调试。下面我们修改cool.json文件,来实现对这个接口的权限控制。我们为acls这个字段添加如下内容

<span style="color:#333333"><code><span style="color:#008800">"acls"</span><span style="color:#666600">:</span> <span style="color:#666600">[</span>
    <span style="color:#666600">{</span>
      <span style="color:#008800">"principalType"</span><span style="color:#666600">:</span> <span style="color:#008800">"ROLE"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"principalId"</span><span style="color:#666600">:</span> <span style="color:#008800">"$everyone"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"permission"</span><span style="color:#666600">:</span> <span style="color:#008800">"DENY"</span>
    <span style="color:#666600">}</span>
  <span style="color:#666600">]</span></code></span>

保存文件之后重启服务,在explorer内重新测试,我们发现接口已经不可访问

 
<span style="color:#333333"><code><span style="color:#666600">{</span>
    <span style="color:#008800">"error"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
    <span style="color:#008800">"name"</span><span style="color:#666600">:</span> <span style="color:#008800">"Error"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"status"</span><span style="color:#666600">:</span> <span style="color:#006666">401</span><span style="color:#666600">,</span>
    <span style="color:#008800">"message"</span><span style="color:#666600">:</span> <span style="color:#008800">"Authorization Required"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"statusCode"</span><span style="color:#666600">:</span> <span style="color:#006666">401</span><span style="color:#666600">,</span>
    <span style="color:#008800">"code"</span><span style="color:#666600">:</span> <span style="color:#008800">"AUTHORIZATION_REQUIRED"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"stack"</span><span style="color:#666600">:</span> <span style="color:#008800">"Error: Authorization Required"</span>
  <span style="color:#666600">}</span>
<span style="color:#666600">}</span></code></span>

principalId是指对谁进行权限控制。在LoopBack中,我们常用的几个取值包括

$ everyone $ owner $ authenticated自定义角色,例如admin

$ everyone按照字面意思比较好理解。$ owner和$ authenticated以及自定义角色在启用用户Token的情况下使用。例如一个登录用户,在访问REST API时会带上他的令牌信息,$ owner代表这个用户只能访问自己的信息,而对其他用户的数据没有访问权限。如果换成$ authenticated,那么只要用户的令牌信息合法,就可以调用这个接口。下面我们继续修改acls这个键,使得测试接口重新可访问,我们添加一个针对test接口的访问控制项

 
<span style="color:#333333"><code><span style="color:#008800">"acls"</span><span style="color:#666600">:</span> <span style="color:#666600">[</span>
    <span style="color:#666600">{</span>
      <span style="color:#008800">"principalType"</span><span style="color:#666600">:</span> <span style="color:#008800">"ROLE"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"principalId"</span><span style="color:#666600">:</span> <span style="color:#008800">"$everyone"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"permission"</span><span style="color:#666600">:</span> <span style="color:#008800">"DENY"</span>
    <span style="color:#666600">}</span>
    <span style="color:#666600">,{</span>
      <span style="color:#008800">"accessType"</span><span style="color:#666600">:</span> <span style="color:#008800">"EXECUTE"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"principalType"</span><span style="color:#666600">:</span> <span style="color:#008800">"ROLE"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"principalId"</span><span style="color:#666600">:</span> <span style="color:#008800">"$everyone"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"permission"</span><span style="color:#666600">:</span> <span style="color:#008800">"ALLOW"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"property"</span><span style="color:#666600">:</span> <span style="color:#008800">"test"</span> 
    <span style="color:#666600">}</span>
  <span style="color:#666600">]</span></code></span>

principalId设置为对所有的人进行访问控制,权限字段设置为允许。重启服务后,这个接口变得可访问.accessType的取值有三个,分别是READ,WRITE和EXECUTE。一般来讲,我们自定义的接口accessType使用EXECUTE修饰,principalId使用$ everyone或者$ authenticated修饰。对于每一个模型,LoopBack框架会自动生成一系列固定模式的REST API,用于存取模型数据。这部分接口的accessType常会用到READ和WRITE。接下来,我们基于LoopBack的一个内建模型用户,建立一个用户体系,允许使用者创建新用户,生成用户Token,然后再进一步讨论LoopBack的权限控制,之后本章还将讨论LoopBack中模型之间的关系。

添加新模型

Ouser,并建立服务的用户体系。进入服务器目录,其中有一个配置。

 
<span style="color:#333333"><code>  <span style="color:#008800">"cool"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
    <span style="color:#008800">"dataSource"</span><span style="color:#666600">:</span> <span style="color:#008800">"db"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"public"</span><span style="color:#666600">:</span> <span style="color:#000088">true</span>
  <span style="color:#666600">},</span>
  <span style="color:#008800">"Ouser"</span><span style="color:#666600">:{</span>
    <span style="color:#008800">"dataSource"</span><span style="color:#666600">:</span> <span style="color:#008800">"db"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"public"</span><span style="color:#666600">:</span> <span style="color:#000088">true</span>
  <span style="color:#666600">}</span></code></span>

之后,在common / models目录下,新建两个文件

<span style="color:#333333"><code><span style="color:#000000">ouser</span><span style="color:#666600">.</span><span style="color:#000000">js      ouser</span><span style="color:#666600">.</span><span style="color:#000000">json</span></code></span>

下面我们编辑ouser.json文件的内容,如下

<span style="color:#333333"><code><span style="color:#666600">{</span>
  <span style="color:#008800">"name"</span><span style="color:#666600">:</span> <span style="color:#008800">"Ouser"</span><span style="color:#666600">,</span>
  <span style="color:#008800">"plural"</span><span style="color:#666600">:</span> <span style="color:#008800">"ousers"</span><span style="color:#666600">,</span>
  <span style="color:#008800">"base"</span><span style="color:#666600">:</span> <span style="color:#008800">"User"</span><span style="color:#666600">,</span>
  <span style="color:#008800">"idInjection"</span><span style="color:#666600">:</span> <span style="color:#000088">true</span><span style="color:#666600">,</span>
  <span style="color:#008800">"properties"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
    <span style="color:#008800">"nickname"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
      <span style="color:#008800">"type"</span><span style="color:#666600">:</span> <span style="color:#008800">"string"</span>
    <span style="color:#666600">}</span>
  <span style="color:#666600">},</span>
  <span style="color:#008800">"validations"</span><span style="color:#666600">:</span> <span style="color:#666600">[],</span>
  <span style="color:#008800">"relations"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
  <span style="color:#666600">},</span>
  <span style="color:#008800">"acls"</span><span style="color:#666600">:</span> <span style="color:#666600">[</span>
    <span style="color:#666600">{</span>
      <span style="color:#008800">"principalType"</span><span style="color:#666600">:</span> <span style="color:#008800">"ROLE"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"principalId"</span><span style="color:#666600">:</span> <span style="color:#008800">"$everyone"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"permission"</span><span style="color:#666600">:</span> <span style="color:#008800">"DENY"</span>
    <span style="color:#666600">},</span>
    <span style="color:#666600">{</span>
      <span style="color:#008800">"accessType"</span><span style="color:#666600">:</span> <span style="color:#008800">"*"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"principalType"</span><span style="color:#666600">:</span> <span style="color:#008800">"ROLE"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"principalId"</span><span style="color:#666600">:</span> <span style="color:#008800">"$owner"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"permission"</span><span style="color:#666600">:</span> <span style="color:#008800">"ALLOW"</span>
    <span style="color:#666600">},</span>
    <span style="color:#666600">{</span>
      <span style="color:#008800">"accessType"</span><span style="color:#666600">:</span> <span style="color:#008800">"*"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"principalType"</span><span style="color:#666600">:</span> <span style="color:#008800">"ROLE"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"principalId"</span><span style="color:#666600">:</span> <span style="color:#008800">"admin"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"permission"</span><span style="color:#666600">:</span> <span style="color:#008800">"ALLOW"</span>
    <span style="color:#666600">}</span>
  <span style="color:#666600">],</span>
  <span style="color:#008800">"methods"</span><span style="color:#666600">:</span> <span style="color:#666600">{}</span>
<span style="color:#666600">}</span></code></span>

Ouser继承自用户,对应的JS文件在LoopBack模块目录的公共/模型文件夹中。用户代表了对用户操作的模型,包含了注册,登录等逻辑。接着编写ouser.js文件内容,如下

<span style="color:#333333"><code><span style="color:#000088">module</span><span style="color:#666600">.</span><span style="color:#000000">exports </span><span style="color:#666600">=</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#660066">Ouser</span><span style="color:#666600">)</span> <span style="color:#666600">{</span>
	
<span style="color:#666600">};</span>
</code></span>

因为模型Ouser继承自用户,因此源文件中可以调用用户定义的方法,用户对外的接口也被Ouser模型继承。在model-config.json文件中,我们屏蔽掉用户,使这个模型的接口不对外。

<span style="color:#333333"><code><span style="color:#008800">"User"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
    <span style="color:#008800">"dataSource"</span><span style="color:#666600">:</span> <span style="color:#008800">"db"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"public"</span><span style="color:#666600">:</span><span style="color:#000088">false</span>
  <span style="color:#666600">}</span></code></span>

接下来,我们重启服务,浏览器打开explorer,可以看到,刚才新添加的模型Ouser已经存在,并且包含了一系列REST API。这些API是LoopBack自动添加的,根据英文注释,不难理解每一个接口的含义。我们可以直接在explorer的界面中,创建一个新用户,先找到创建用户的接口<br>

在输入框中填写如下内容

<span style="color:#333333"><code><span style="color:#666600">{</span>
<span style="color:#008800">"nickname"</span><span style="color:#666600">:</span><span style="color:#008800">"Json"</span>
<span style="color:#666600">,</span><span style="color:#008800">"email"</span><span style="color:#666600">:</span><span style="color:#008800">"1234@qq.com"</span>
<span style="color:#666600">,</span><span style="color:#008800">"password"</span><span style="color:#666600">:</span><span style="color:#008800">"12345"</span>
<span style="color:#666600">}</span></code></span>

昵称是模型Ouser的一个属性。另外两个是基类用户自带的属性。然后点击尝试一下,返回如下内容

 
<span style="color:#333333"><code><span style="color:#666600">{</span>
  <span style="color:#008800">"nickname"</span><span style="color:#666600">:</span> <span style="color:#008800">"Json"</span><span style="color:#666600">,</span>
  <span style="color:#008800">"email"</span><span style="color:#666600">:</span> <span style="color:#008800">"1234@qq.com"</span><span style="color:#666600">,</span>
  <span style="color:#008800">"id"</span><span style="color:#666600">:</span> <span style="color:#006666">1</span>
<span style="color:#666600">}</span></code></span>

这个表新创建了一个用户。接下来调用Ouser的/ ousers / login方法,试着尝试使用邮箱和密码登录。在凭证输入框输入如下内容

<span style="color:#333333"><code><span style="color:#666600">{</span><span style="color:#008800">"email"</span><span style="color:#666600">:</span><span style="color:#008800">"1234@qq.com"</span>
<span style="color:#666600">,</span><span style="color:#008800">"password"</span><span style="color:#666600">:</span><span style="color:#008800">"12345"</span>
<span style="color:#666600">}</span></code></span>

点击发送按钮,将返回如下数据

 
<span style="color:#333333"><code><span style="color:#666600">{</span>
  <span style="color:#008800">"id"</span><span style="color:#666600">:</span> <span style="color:#008800">"F7IliK3irck8ILWkAdEucYGoXw67j50GTYKIsurYx1EuZb61QcohEsAxcqLw0RMS"</span><span style="color:#666600">,</span>
  <span style="color:#008800">"ttl"</span><span style="color:#666600">:</span> <span style="color:#006666">1209600</span><span style="color:#666600">,</span>
  <span style="color:#008800">"created"</span><span style="color:#666600">:</span> <span style="color:#008800">"2016-07-24T06:20:28.436Z"</span><span style="color:#666600">,</span>
  <span style="color:#008800">"userId"</span><span style="color:#666600">:</span> <span style="color:#006666">1</span>
<span style="color:#666600">}</span></code></span>

这个表我们已经登录成功,并返回一个此用户的令牌信息。目前服务使用的是基于memory存储方案,服务重启,数据丢失。复制这个令牌信息,将他拷贝到如下图所示的输入框,然后点击设置访问令牌按钮<br> <br>接着,我们点开get / ousers / {id}这个接口,在id对应的输入框输入1,点击试试按钮,将返回这个id为1的用户对应的信息。

以上过程演示了注册,登录和根据有效Token访问用户信息的步骤。而真正用于实际的步骤比这个要复杂一些。现在回过头再来看看,模型是怎么添加的,在model-config.json,我们添加了如下内容

<span style="color:#333333"><code>  <span style="color:#008800">"Ouser"</span><span style="color:#666600">:{</span>
    <span style="color:#008800">"dataSource"</span><span style="color:#666600">:</span> <span style="color:#008800">"db"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"public"</span><span style="color:#666600">:</span> <span style="color:#000088">true</span>
  <span style="color:#666600">}</span></code></span>

dataSource字段的内容是db,表示Ouser使用名称为db的数据源。这个数据源在同级目录的datasources.json中定义,我们看一下这个文件的内容

 
<span style="color:#333333"><code><span style="color:#666600">{</span>
  <span style="color:#008800">"db"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
    <span style="color:#008800">"name"</span><span style="color:#666600">:</span> <span style="color:#008800">"db"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"connector"</span><span style="color:#666600">:</span> <span style="color:#008800">"memory"</span>
  <span style="color:#666600">}</span>
<span style="color:#666600">}</span></code></span>

连接器字段的值为memory,它代表基于内存的持久化。刚才创建的新用户和对数据的任何修改,服务重启之后都将消失。在实际的使用中,服务的数据源应该来自可持久化的数据库。例如可修改为一个使用mongodb存储的数据源,为这个文件添加如下内容

<span style="color:#333333"><code><span style="color:#666600">{</span>
  <span style="color:#008800">"db"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
    <span style="color:#008800">"name"</span><span style="color:#666600">:</span> <span style="color:#008800">"db"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"connector"</span><span style="color:#666600">:</span> <span style="color:#008800">"memory"</span>
  <span style="color:#666600">},</span>
  <span style="color:#008800">"mongods"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
    <span style="color:#008800">"host"</span><span style="color:#666600">:</span> <span style="color:#008800">"localhost"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"port"</span><span style="color:#666600">:</span> <span style="color:#006666">27017</span><span style="color:#666600">,</span>
    <span style="color:#008800">"url"</span><span style="color:#666600">:</span> <span style="color:#008800">"mongodb://name:pass@localhost:27017/dbname"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"database"</span><span style="color:#666600">:</span> <span style="color:#008800">"dbname"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"username"</span><span style="color:#666600">:</span> <span style="color:#008800">"name"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"password"</span><span style="color:#666600">:</span> <span style="color:#008800">"pass"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"name"</span><span style="color:#666600">:</span> <span style="color:#008800">"mongods"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"connector"</span><span style="color:#666600">:</span> <span style="color:#008800">"mongodb"</span>
  <span style="color:#666600">}</span>
<span style="color:#666600">}</span></code></span>

url字段中的名称,pass和dbname以实际的为准.database字段代表数据库名称,用户名代表mongodb的用户名,密码是数据库连接密码。使用mongodb做存储,需要先安装mongodb的连接器,在工程根目录下运行

npm install --save loopback-connector-mongodb

这样,在服务启动后,LoopBack根据这个配置文件给出的连接url,自动去连接mongodb数据库。我们希望所有的模型使用mongodb作为数据源,那就需要全面的修改model-config.json。修改后如下

<span style="color:#333333"><code><span style="color:#666600">{</span>
  <span style="color:#008800">"_meta"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
    <span style="color:#008800">"sources"</span><span style="color:#666600">:</span> <span style="color:#666600">[</span>
      <span style="color:#008800">"loopback/common/models"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"loopback/server/models"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"../common/models"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"./models"</span>
    <span style="color:#666600">],</span>
    <span style="color:#008800">"mixins"</span><span style="color:#666600">:</span> <span style="color:#666600">[</span>
      <span style="color:#008800">"loopback/common/mixins"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"loopback/server/mixins"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"../common/mixins"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"./mixins"</span>
    <span style="color:#666600">]</span>
  <span style="color:#666600">},</span>
  <span style="color:#008800">"User"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
    <span style="color:#008800">"dataSource"</span><span style="color:#666600">:</span> <span style="color:#008800">"mongods"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"public"</span><span style="color:#666600">:</span><span style="color:#000088">false</span>
  <span style="color:#666600">},</span>
  <span style="color:#008800">"AccessToken"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
    <span style="color:#008800">"dataSource"</span><span style="color:#666600">:</span> <span style="color:#008800">"mongods"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"public"</span><span style="color:#666600">:</span> <span style="color:#000088">false</span>
  <span style="color:#666600">},</span>
  <span style="color:#008800">"ACL"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
    <span style="color:#008800">"dataSource"</span><span style="color:#666600">:</span> <span style="color:#008800">"mongods"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"public"</span><span style="color:#666600">:</span> <span style="color:#000088">false</span>
  <span style="color:#666600">},</span>
  <span style="color:#008800">"RoleMapping"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
    <span style="color:#008800">"dataSource"</span><span style="color:#666600">:</span> <span style="color:#008800">"mongods"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"public"</span><span style="color:#666600">:</span> <span style="color:#000088">false</span>
  <span style="color:#666600">},</span>
  <span style="color:#008800">"Role"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
    <span style="color:#008800">"dataSource"</span><span style="color:#666600">:</span> <span style="color:#008800">"mongods"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"public"</span><span style="color:#666600">:</span> <span style="color:#000088">false</span>
  <span style="color:#666600">},</span>
  <span style="color:#008800">"cool"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
    <span style="color:#008800">"dataSource"</span><span style="color:#666600">:</span> <span style="color:#008800">"mongods"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"public"</span><span style="color:#666600">:</span> <span style="color:#000088">true</span>
  <span style="color:#666600">},</span>
  <span style="color:#008800">"Ouser"</span><span style="color:#666600">:{</span>
    <span style="color:#008800">"dataSource"</span><span style="color:#666600">:</span> <span style="color:#008800">"mongods"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"public"</span><span style="color:#666600">:</span> <span style="color:#000088">true</span>
  <span style="color:#666600">}</span>
<span style="color:#666600">}</span></code></span>

可见,修改数据源只需要这些模型的dataSource都改为mongods。


loopback-connector-mongodb模块依赖Mongodb的官方Node.js驱动mongodb模块。在程序中,我们可以直接使用官方驱动操作数据库,这也极为方便。下例是使用mongodb模块连接数据库并创建集合的例子

<span style="color:#333333"><code><span style="color:#880000">// A simple example showing the creation of a collection.</span>

<span style="color:#000088">var</span> <span style="color:#660066">MongoClient</span> <span style="color:#666600">=</span><span style="color:#000000"> require</span><span style="color:#666600">(</span><span style="color:#008800">'mongodb'</span><span style="color:#666600">).</span><span style="color:#660066">MongoClient</span><span style="color:#666600">,</span><span style="color:#000000">
  test </span><span style="color:#666600">=</span><span style="color:#000000"> require</span><span style="color:#666600">(</span><span style="color:#008800">'assert'</span><span style="color:#666600">);</span>
<span style="color:#660066">MongoClient</span><span style="color:#666600">.</span><span style="color:#000000">connect</span><span style="color:#666600">(</span><span style="color:#008800">'mongodb://localhost:27017/test'</span><span style="color:#666600">,</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">,</span><span style="color:#000000"> db</span><span style="color:#666600">)</span> <span style="color:#666600">{</span><span style="color:#000000">
  test</span><span style="color:#666600">.</span><span style="color:#000000">equal</span><span style="color:#666600">(</span><span style="color:#000088">null</span><span style="color:#666600">,</span><span style="color:#000000"> err</span><span style="color:#666600">);</span>

  <span style="color:#880000">// Create a capped collection with a maximum of 1000 documents</span><span style="color:#000000">
  db</span><span style="color:#666600">.</span><span style="color:#000000">createCollection</span><span style="color:#666600">(</span><span style="color:#008800">"a_simple_collection"</span><span style="color:#666600">,</span> <span style="color:#666600">{</span><span style="color:#000000">capped</span><span style="color:#666600">:</span><span style="color:#000088">true</span><span style="color:#666600">,</span><span style="color:#000000"> size</span><span style="color:#666600">:</span><span style="color:#006666">10000</span><span style="color:#666600">,</span><span style="color:#000000"> max</span><span style="color:#666600">:</span><span style="color:#006666">1000</span><span style="color:#666600">,</span><span style="color:#000000"> w</span><span style="color:#666600">:</span><span style="color:#006666">1</span><span style="color:#666600">},</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">,</span><span style="color:#000000"> collection</span><span style="color:#666600">)</span> <span style="color:#666600">{</span><span style="color:#000000">
    test</span><span style="color:#666600">.</span><span style="color:#000000">equal</span><span style="color:#666600">(</span><span style="color:#000088">null</span><span style="color:#666600">,</span><span style="color:#000000"> err</span><span style="color:#666600">);</span>

    <span style="color:#880000">// Insert a document in the capped collection</span><span style="color:#000000">
    collection</span><span style="color:#666600">.</span><span style="color:#000000">insertOne</span><span style="color:#666600">({</span><span style="color:#000000">a</span><span style="color:#666600">:</span><span style="color:#006666">1</span><span style="color:#666600">},</span> <span style="color:#666600">{</span><span style="color:#000000">w</span><span style="color:#666600">:</span><span style="color:#006666">1</span><span style="color:#666600">},</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">,</span><span style="color:#000000"> result</span><span style="color:#666600">)</span> <span style="color:#666600">{</span><span style="color:#000000">
      test</span><span style="color:#666600">.</span><span style="color:#000000">equal</span><span style="color:#666600">(</span><span style="color:#000088">null</span><span style="color:#666600">,</span><span style="color:#000000"> err</span><span style="color:#666600">);</span><span style="color:#000000">

      db</span><span style="color:#666600">.</span><span style="color:#000000">close</span><span style="color:#666600">();</span>
    <span style="color:#666600">});</span>
  <span style="color:#666600">});</span>
<span style="color:#666600">});</span></code></span>

官方驱动原始支持Promise和ES6 generator,其官网API文档对每一个接口的说明非常详尽。建议读者访问http://mongodb.github.io/node-mongodb-native/2.1/api/了解更多。


初始化数据库

使用mongodb作为可持久化的数据源,最开始启动服务的时候,这个数据库为空。还记得LoopBack在启动时会到服务器/根目录下依次加载JS文件。因此也可以将初始化数据库的代码放入这个目录内。当启动服务时,JS需要注意的是,这类初始化代码只需要执行一次,因此当数据库初始化完毕之后,要把文件名后缀的js去掉,防止以后重复执行。在server / root目录下,新添加一个文件initmongo.js,内容为

<span style="color:#333333"><code><span style="color:#000088">module</span><span style="color:#666600">.</span><span style="color:#000000">exports </span><span style="color:#666600">=</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#000000">app</span><span style="color:#666600">)</span> <span style="color:#666600">{</span>
  <span style="color:#000088">var</span><span style="color:#000000"> mongoDs </span><span style="color:#666600">=</span><span style="color:#000000"> app</span><span style="color:#666600">.</span><span style="color:#000000">dataSources</span><span style="color:#666600">.</span><span style="color:#000000">mongods</span><span style="color:#666600">;</span><span style="color:#000000">
  mongoDs</span><span style="color:#666600">.</span><span style="color:#000000">automigrate</span><span style="color:#666600">(</span><span style="color:#008800">'AccessToken'</span><span style="color:#666600">,</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">){</span>
    <span style="color:#000088">if</span><span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">)</span> <span style="color:#000088">throw</span><span style="color:#000000"> err</span><span style="color:#666600">;</span>
  <span style="color:#666600">});</span><span style="color:#000000">
  mongoDs</span><span style="color:#666600">.</span><span style="color:#000000">automigrate</span><span style="color:#666600">(</span><span style="color:#008800">'Ouser'</span><span style="color:#666600">,</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">){</span>
    <span style="color:#000088">if</span><span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">)</span> <span style="color:#000088">throw</span><span style="color:#000000"> err</span><span style="color:#666600">;</span>

    <span style="color:#000088">var</span> <span style="color:#660066">Ouser</span> <span style="color:#666600">=</span><span style="color:#000000"> app</span><span style="color:#666600">.</span><span style="color:#000000">models</span><span style="color:#666600">.</span><span style="color:#660066">Ouser</span><span style="color:#666600">;</span>
    <span style="color:#000088">var</span> <span style="color:#660066">Role</span> <span style="color:#666600">=</span><span style="color:#000000"> app</span><span style="color:#666600">.</span><span style="color:#000000">models</span><span style="color:#666600">.</span><span style="color:#660066">Role</span><span style="color:#666600">;</span>
    <span style="color:#000088">var</span> <span style="color:#660066">RoleMapping</span> <span style="color:#666600">=</span><span style="color:#000000"> app</span><span style="color:#666600">.</span><span style="color:#000000">models</span><span style="color:#666600">.</span><span style="color:#660066">RoleMapping</span><span style="color:#666600">;</span>

    <span style="color:#660066">Ouser</span><span style="color:#666600">.</span><span style="color:#000000">create</span><span style="color:#666600">([</span>
      <span style="color:#666600">{</span><span style="color:#000000">username</span><span style="color:#666600">:</span> <span style="color:#008800">'admin'</span><span style="color:#666600">,</span><span style="color:#000000"> email</span><span style="color:#666600">:</span> <span style="color:#008800">'admin@e.com'</span><span style="color:#666600">,</span><span style="color:#000000"> password</span><span style="color:#666600">:</span> <span style="color:#008800">'12345'</span><span style="color:#666600">,</span><span style="color:#000000"> emailVerified</span><span style="color:#666600">:</span> <span style="color:#000088">true</span><span style="color:#666600">}</span>
      <span style="color:#666600">],</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">,</span><span style="color:#000000"> users</span><span style="color:#666600">)</span> <span style="color:#666600">{</span>
      <span style="color:#000088">if</span> <span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">)</span> <span style="color:#000088">throw</span><span style="color:#000000"> err</span><span style="color:#666600">;</span><span style="color:#000000">
      mongoDs</span><span style="color:#666600">.</span><span style="color:#000000">automigrate</span><span style="color:#666600">(</span><span style="color:#008800">'Role'</span><span style="color:#666600">,</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">){</span>
        <span style="color:#000088">if</span><span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">)</span> <span style="color:#000088">throw</span><span style="color:#000000"> err</span><span style="color:#666600">;</span><span style="color:#000000">
        mongoDs</span><span style="color:#666600">.</span><span style="color:#000000">automigrate</span><span style="color:#666600">(</span><span style="color:#008800">'RoleMapping'</span><span style="color:#666600">,</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">){</span>
          <span style="color:#000088">if</span><span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">)</span> <span style="color:#000088">throw</span><span style="color:#000000"> err</span><span style="color:#666600">;</span>
          <span style="color:#000088">var</span><span style="color:#000000"> userid </span><span style="color:#666600">=</span><span style="color:#000000"> users</span><span style="color:#666600">[</span><span style="color:#006666">0</span><span style="color:#666600">].</span><span style="color:#000000">id</span><span style="color:#666600">;</span>
          <span style="color:#660066">Role</span><span style="color:#666600">.</span><span style="color:#000000">create</span><span style="color:#666600">({</span><span style="color:#000000">
          name</span><span style="color:#666600">:</span> <span style="color:#008800">'admin'</span>
          <span style="color:#666600">},</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">,</span><span style="color:#000000"> role</span><span style="color:#666600">)</span> <span style="color:#666600">{</span><span style="color:#000000">
            console</span><span style="color:#666600">.</span><span style="color:#000000">log</span><span style="color:#666600">(</span><span style="color:#008800">'Created role:'</span><span style="color:#666600">,</span><span style="color:#000000"> role</span><span style="color:#666600">);</span><span style="color:#000000">

            role</span><span style="color:#666600">.</span><span style="color:#000000">principals</span><span style="color:#666600">.</span><span style="color:#000000">create</span><span style="color:#666600">({</span><span style="color:#000000">
            principalType</span><span style="color:#666600">:</span> <span style="color:#660066">RoleMapping</span><span style="color:#666600">.</span><span style="color:#000000">USER
            </span><span style="color:#666600">,</span><span style="color:#000000"> principalId</span><span style="color:#666600">:</span><span style="color:#000000"> userid
            </span><span style="color:#666600">},</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">,</span><span style="color:#000000"> principal</span><span style="color:#666600">)</span> <span style="color:#666600">{</span>
            <span style="color:#000088">if</span> <span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">)</span> <span style="color:#000088">throw</span><span style="color:#000000"> err</span><span style="color:#666600">;</span><span style="color:#000000">
              console</span><span style="color:#666600">.</span><span style="color:#000000">log</span><span style="color:#666600">(</span><span style="color:#008800">'Created principal:'</span><span style="color:#666600">,</span><span style="color:#000000"> principal</span><span style="color:#666600">);</span>
            <span style="color:#666600">});</span>
          <span style="color:#666600">});</span>
        <span style="color:#666600">});</span>
      <span style="color:#666600">});</span>
    <span style="color:#666600">});</span>
  <span style="color:#666600">});</span>
<span style="color:#666600">};</span></code></span>

上面这段代码创建了AccessToken,Role,RoleMapping和Ouser这几张表。前三个模型是LoopBack预定义的.AccessToken用于保存用户登录后的Token信息.Role和RoleMapping用于权限控制。上述代码创建了角色表,并添加了一个角色admin。在RoleMapping中,将权限角色admin与Ouser表中新创建的用户关联起来。此用户登录成功之后,就可以访问用户限定的接口。

<span style="color:#333333"><code><span style="color:#666600">{</span>
  <span style="color:#008800">"accessType"</span><span style="color:#666600">:</span> <span style="color:#008800">"EXECUTE"</span><span style="color:#666600">,</span>
  <span style="color:#008800">"principalType"</span><span style="color:#666600">:</span> <span style="color:#008800">"ROLE"</span><span style="color:#666600">,</span>
  <span style="color:#008800">"principalId"</span><span style="color:#666600">:</span> <span style="color:#008800">"admin"</span><span style="color:#666600">,</span>
  <span style="color:#008800">"permission"</span><span style="color:#666600">:</span> <span style="color:#008800">"ALLOW"</span><span style="color:#666600">,</span>
  <span style="color:#008800">"property"</span><span style="color:#666600">:</span> <span style="color:#008800">"test"</span> 
<span style="color:#666600">}</span></code></span>

用户登录成功之后,服务端向浏览器返回其有效Token,程序中可以将这个Token保存到域名所在的cookie中,这样以后的http访问请求就会自带这个令牌信息,LoopBack根据这个令牌信息,从AccessToken中反查出用户id,如果Token有效,此用户就拥有了$ authenticated角色,可以访问被认证限定的接口。例如可以修改ouser.js,在登录成功后,把这个cookie植如用户浏览器。

<span style="color:#333333"><code><span style="color:#000088">module</span><span style="color:#666600">.</span><span style="color:#000000">exports </span><span style="color:#666600">=</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#660066">Ouser</span><span style="color:#666600">)</span> <span style="color:#666600">{</span>
    <span style="color:#660066">Ouser</span><span style="color:#666600">.</span><span style="color:#000000">afterRemote</span><span style="color:#666600">(</span><span style="color:#008800">'login'</span><span style="color:#666600">,</span> <span style="color:#000088">function</span> <span style="color:#666600">(</span><span style="color:#000000">context</span><span style="color:#666600">,</span><span style="color:#000000"> result</span><span style="color:#666600">,</span> <span style="color:#000088">next</span><span style="color:#666600">)</span> <span style="color:#666600">{</span>
      <span style="color:#000088">var</span><span style="color:#000000"> res </span><span style="color:#666600">=</span><span style="color:#000000"> context</span><span style="color:#666600">.</span><span style="color:#000000">res</span><span style="color:#666600">;</span>
      <span style="color:#000088">if</span> <span style="color:#666600">(</span><span style="color:#000000"> result </span><span style="color:#666600">&&</span><span style="color:#000000"> result</span><span style="color:#666600">.</span><span style="color:#000000">id </span><span style="color:#666600">)</span> <span style="color:#666600">{</span><span style="color:#000000">
          res</span><span style="color:#666600">.</span><span style="color:#000000">cookie</span><span style="color:#666600">(</span><span style="color:#008800">'authorization'</span><span style="color:#666600">,</span><span style="color:#000000"> result</span><span style="color:#666600">.</span><span style="color:#000000">id</span><span style="color:#666600">,</span> <span style="color:#666600">{</span><span style="color:#000000"> maxAge</span><span style="color:#666600">:</span> <span style="color:#006666">1000</span><span style="color:#666600">*</span><span style="color:#006666">60</span><span style="color:#666600">*</span><span style="color:#006666">60</span><span style="color:#666600">*</span><span style="color:#006666">24</span><span style="color:#666600">*</span><span style="color:#006666">14</span><span style="color:#666600">*</span><span style="color:#006666">6</span><span style="color:#666600">,</span><span style="color:#000000"> httpOnly</span><span style="color:#666600">:</span> <span style="color:#000088">true</span>
                  <span style="color:#666600">,</span><span style="color:#000088">signed</span><span style="color:#666600">:</span> <span style="color:#000088">true</span><span style="color:#666600">,</span><span style="color:#000000"> domain</span><span style="color:#666600">:</span> <span style="color:#008800">'.domain.com'</span> <span style="color:#666600">});</span>
      <span style="color:#666600">}</span>
      <span style="color:#000088">return</span> <span style="color:#000088">next</span><span style="color:#666600">();</span>
    <span style="color:#666600">});</span>
<span style="color:#666600">};</span></code></span>

当然,处于安全考虑,应该对保存在用户本地的cookie信息加密。这可以使用cookie-parser这个中间件来完成。

钩子机制

上一节结尾的代码用到了LoopBack的钩子机制.LoopBack的钩子分为两种

  1. 接口调用执行前和执行后,分别对应beforeRemote和afterRemote;
  2. CRUD操作前或后,注册的方法被执行。主要有
 

删除之前保存之前保存,删除之后删除之前

CRUD是增加,读取查询,更新,删除的简称。这两种钩子使用起来都不复杂。上面的代码afterRemote就是使用第一种钩子的场景。在本章前面的部分,用例子演示了注册登录的过程。在实际的邮箱注册逻辑中,用户点击注册之后,应该给用户注册时填写的邮箱发送一封邮件。用户收到邮件后,点击连接,才能激活这个账户。而发送邮件的时机,应该是把用户的注册信息写到表Ouser之后。我们可以利用钩子的机制,在创建用户之后,执行一个函数,发送一封确认邮件。

<span style="color:#333333"><code><span style="color:#660066">Ouser</span><span style="color:#666600">.</span><span style="color:#000000">afterRemote</span><span style="color:#666600">(</span><span style="color:#008800">'create'</span><span style="color:#666600">,</span> <span style="color:#000088">function</span> <span style="color:#666600">(</span><span style="color:#000000">ctx</span><span style="color:#666600">,</span><span style="color:#000000"> result</span><span style="color:#666600">,</span> <span style="color:#000088">next</span><span style="color:#666600">)</span> <span style="color:#666600">{</span>
   <span style="color:#000088">if</span><span style="color:#666600">(!</span><span style="color:#000000">ctx</span><span style="color:#666600">.</span><span style="color:#000000">result</span><span style="color:#666600">.</span><span style="color:#000000">emailVerified </span><span style="color:#666600">&&</span> <span style="color:#666600">!</span><span style="color:#000000">ctx</span><span style="color:#666600">.</span><span style="color:#000000">result</span><span style="color:#666600">.</span><span style="color:#000000">username</span><span style="color:#666600">){</span><span style="color:#000000">
      let subject </span><span style="color:#666600">=</span> <span style="color:#008800">'注册邮件'</span><span style="color:#666600">;</span><span style="color:#000000">
      let </span><span style="color:#000088">template</span> <span style="color:#666600">=</span><span style="color:#000000"> path</span><span style="color:#666600">.</span><span style="color:#000000">resolve</span><span style="color:#666600">(</span><span style="color:#000000">path</span><span style="color:#666600">.</span><span style="color:#000000">join</span><span style="color:#666600">(</span><span style="color:#000000">__dirname</span><span style="color:#666600">,</span> <span style="color:#008800">'..'</span><span style="color:#666600">,</span> <span style="color:#008800">'..'</span><span style="color:#666600">,</span> <span style="color:#008800">'client'</span><span style="color:#666600">,</span><span style="color:#008800">'templates'</span><span style="color:#666600">,</span> <span style="color:#008800">'verify.ejs'</span><span style="color:#666600">));</span><span style="color:#000000">
      ctx</span><span style="color:#666600">.</span><span style="color:#000000">result</span><span style="color:#666600">.</span><span style="color:#000000">verify</span><span style="color:#666600">({</span><span style="color:#000000">
      type</span><span style="color:#666600">:</span><span style="color:#008800">'email'</span><span style="color:#666600">,</span>
      <span style="color:#000088">from</span><span style="color:#666600">:</span><span style="color:#008800">'admin@domain.com'</span><span style="color:#666600">,</span> <span style="color:#880000">//发送邮箱</span><span style="color:#000000">
      to</span><span style="color:#666600">:</span><span style="color:#000000">ctx</span><span style="color:#666600">.</span><span style="color:#000000">result</span><span style="color:#666600">.</span><span style="color:#000000">email</span><span style="color:#666600">,</span> <span style="color:#880000">//用户邮箱</span><span style="color:#000000">
      subject</span><span style="color:#666600">:</span><span style="color:#000000">subject</span><span style="color:#666600">,</span>
      <span style="color:#000088">template</span><span style="color:#666600">:</span> <span style="color:#000088">template</span>
      <span style="color:#666600">},</span> <span style="color:#000088">function</span> <span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">,</span><span style="color:#000000"> data</span><span style="color:#666600">){</span>
        <span style="color:#000088">if</span><span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">){</span><span style="color:#000000">
          console</span><span style="color:#666600">.</span><span style="color:#000000">error</span><span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">);</span>
        <span style="color:#666600">}</span>
        <span style="color:#000088">next</span><span style="color:#666600">();</span>
      <span style="color:#666600">});</span>
   <span style="color:#666600">}</span><span style="color:#000088">else</span><span style="color:#666600">{</span>
      <span style="color:#000088">next</span><span style="color:#666600">();</span>
   <span style="color:#666600">}</span>
<span style="color:#666600">});</span></code></span>

为了能够收发邮件,需要使用LoopBack的一个基础模型电子邮件并增加相应的邮件配置,在model-config.json文件中增加

<span style="color:#333333"><code><span style="color:#008800">"Email"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
    <span style="color:#008800">"dataSource"</span><span style="color:#666600">:</span> <span style="color:#008800">"emailds"</span>
  <span style="color:#666600">}</span></code></span>

然后在datasources.json中增加邮件配置信息

<span style="color:#333333"><code><span style="color:#008800">"emailds"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
    <span style="color:#008800">"name"</span><span style="color:#666600">:</span> <span style="color:#008800">"emailds"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"connector"</span><span style="color:#666600">:</span> <span style="color:#008800">"mail"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"transports"</span><span style="color:#666600">:</span> <span style="color:#666600">[</span>
      <span style="color:#666600">{</span>
        <span style="color:#008800">"type"</span><span style="color:#666600">:</span> <span style="color:#008800">"smtp"</span><span style="color:#666600">,</span>
        <span style="color:#008800">"host"</span><span style="color:#666600">:</span> <span style="color:#008800">"the email host"</span><span style="color:#666600">,</span>
        <span style="color:#008800">"secure"</span><span style="color:#666600">:</span> <span style="color:#000088">false</span><span style="color:#666600">,</span>
        <span style="color:#008800">"port"</span><span style="color:#666600">:</span> <span style="color:#006666">25</span><span style="color:#666600">,</span>
        <span style="color:#008800">"auth"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
          <span style="color:#008800">"user"</span><span style="color:#666600">:</span> <span style="color:#008800">"your email"</span><span style="color:#666600">,</span>
          <span style="color:#008800">"pass"</span><span style="color:#666600">:</span> <span style="color:#008800">"your pass"</span>
        <span style="color:#666600">}</span>
      <span style="color:#666600">}</span>
    <span style="color:#666600">]</span>
  <span style="color:#666600">}</span></code></span>

中间件

server目录下有一个配置文件middleware.json,LoobBack增加了中间件执行序列的概念,这可以严格的定义中间件函数的调用顺序.LoopBack预定义的阶段包含

 

initial - 中间件最早在这个阶段执行会话 - 准备会话对象auth - 权限认证解析 - 解析请求体路由 - 路由请求文件 - 对静态文件的请求final - 错误处理

每一个阶段又可分成三个子阶段,例如身份验证阶段,可分为

 

“auth:before”:{}“auth”:{}“auth:after”:{}

在一次请求中,这些阶段自上而下依次执行。我们可以举一个例子,来说明如何在这个文件中添加中间件。对于404错误,我们希望返回一个404页面.final用来处理错误,因此可以在这个阶段,添加一个处理404错误的中间件。

<span style="color:#333333"><code><span style="color:#008800">"final"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
    <span style="color:#008800">"./error404.js"</span><span style="color:#666600">:{}</span>
  <span style="color:#666600">}</span></code></span>

在同级目录下,新建这个文件,文件的内容为

<span style="color:#333333"><code><span style="color:#000088">module</span><span style="color:#666600">.</span><span style="color:#000000">exports </span><span style="color:#666600">=</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#000000">options</span><span style="color:#666600">)</span> <span style="color:#666600">{</span>
  <span style="color:#000088">return</span> <span style="color:#000088">function</span><span style="color:#000000"> raiseUrlNotFoundError</span><span style="color:#666600">(</span><span style="color:#000000">req</span><span style="color:#666600">,</span><span style="color:#000000"> res</span><span style="color:#666600">,</span> <span style="color:#000088">next</span><span style="color:#666600">)</span> <span style="color:#666600">{</span>
    <span style="color:#000088">var</span><span style="color:#000000"> error </span><span style="color:#666600">=</span> <span style="color:#000088">new</span> <span style="color:#660066">Error</span><span style="color:#666600">(</span><span style="color:#008800">'Cannot '</span> <span style="color:#666600">+</span><span style="color:#000000"> req</span><span style="color:#666600">.</span><span style="color:#000000">method </span><span style="color:#666600">+</span> <span style="color:#008800">' '</span> <span style="color:#666600">+</span><span style="color:#000000"> req</span><span style="color:#666600">.</span><span style="color:#000000">url</span><span style="color:#666600">);</span><span style="color:#000000">
    error</span><span style="color:#666600">.</span><span style="color:#000000">status </span><span style="color:#666600">=</span> <span style="color:#006666">404</span><span style="color:#666600">;</span>
<span style="color:#880000">//------------------- max custom 404 ------//</span>
   <span style="color:#000088">if</span> <span style="color:#666600">(</span><span style="color:#000000">req</span><span style="color:#666600">.</span><span style="color:#000000">accepts</span><span style="color:#666600">(</span><span style="color:#008800">'html, text/html'</span><span style="color:#666600">))</span> <span style="color:#666600">{</span><span style="color:#000000">
        console</span><span style="color:#666600">.</span><span style="color:#000000">log</span><span style="color:#666600">(</span> <span style="color:#008800">"404 ERR! "</span> <span style="color:#666600">);</span>
       <span style="color:#000088">return</span><span style="color:#000000"> res</span><span style="color:#666600">.</span><span style="color:#000000">sendFile</span><span style="color:#666600">(</span><span style="color:#008800">'404.html'</span><span style="color:#666600">,</span> <span style="color:#666600">{</span><span style="color:#000000"> root</span><span style="color:#666600">:</span><span style="color:#000000"> __dirname </span><span style="color:#666600">+</span> <span style="color:#008800">'./../client/public/html/'</span> <span style="color:#666600">});</span>
    <span style="color:#666600">}</span>
<span style="color:#880000">//---------------------------------------//</span>
    <span style="color:#000088">next</span><span style="color:#666600">(</span><span style="color:#000000">error</span><span style="color:#666600">);</span>
  <span style="color:#666600">};</span>
<span style="color:#666600">}</span></code></span>

如此做之后,不要忘了在client / public / html目录下包含一个404.html的文件。

再比如,在解析请求体阶段,可以添加自动对json或urlencoded编码的字符串进行解析的中间件

<span style="color:#333333"><code><span style="color:#008800">"parse"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
    <span style="color:#008800">"body-parser#json"</span><span style="color:#666600">:</span> <span style="color:#666600">{},</span>
    <span style="color:#008800">"body-parser#urlencoded"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span><span style="color:#008800">"params"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span> <span style="color:#008800">"extended"</span><span style="color:#666600">:</span> <span style="color:#000088">true</span> <span style="color:#666600">}}</span>
    <span style="color:#666600">}</span></code></span>

模型关系

在程序中可以定义很多模型,这些模型可能存在一些关系。例如一个用户可能在多处登录,因此可以存在多个有效的令牌信息。也就是说用户模型的一个用户对应AccessToken模型的多份数据,而AccessToken中里面的任意一个元素只属于User中某一个用户.User和AccessToken这两个模型是LoopBack自带的,我们可以进入LoopBack模块文件夹的common / models目录下,查看这两个模型的json文件,在user.json文件末尾,我们可以看到如下内容

<span style="color:#333333"><code><span style="color:#008800">"relations"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
  <span style="color:#008800">"accessTokens"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
	 <span style="color:#008800">"type"</span><span style="color:#666600">:</span> <span style="color:#008800">"hasMany"</span><span style="color:#666600">,</span>
	 <span style="color:#008800">"model"</span><span style="color:#666600">:</span> <span style="color:#008800">"AccessToken"</span><span style="color:#666600">,</span>
	 <span style="color:#008800">"foreignKey"</span><span style="color:#666600">:</span> <span style="color:#008800">"userId"</span><span style="color:#666600">,</span>
	 <span style="color:#008800">"options"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
	    <span style="color:#008800">"disableInclude"</span><span style="color:#666600">:</span> <span style="color:#000088">true</span>
     <span style="color:#666600">}</span>
  <span style="color:#666600">}</span>
<span style="color:#666600">}</span></code></span>

type字段的hasMany代表User与AccessToken是一对多的关系,User是主模型.foreignKey代表了这两个模型之间的关联键。也就是用户表的id作为AccessToken的外键,名称是userId.access -token.json文件末尾,我们看到类似的内容

<span style="color:#333333"><code><span style="color:#008800">"relations"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
    <span style="color:#008800">"user"</span><span style="color:#666600">:</span> <span style="color:#666600">{</span>
      <span style="color:#008800">"type"</span><span style="color:#666600">:</span> <span style="color:#008800">"belongsTo"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"model"</span><span style="color:#666600">:</span> <span style="color:#008800">"User"</span><span style="color:#666600">,</span>
      <span style="color:#008800">"foreignKey"</span><span style="color:#666600">:</span> <span style="color:#008800">"userId"</span>
    <span style="color:#666600">}</span>
  <span style="color:#666600">}</span></code></span>

belongsTo代表它是用户的从模型,userId作为外键,其值为对应用户元素的id。

一旦定义了模型之间的关系,LoopBack会为我们自动生成一系列的REST API接口,例如可以使用Ouser模型中的接口,得到AccessToken模型的数据。下图显示了这些生成的接口<br>

例如我们想获取某一个用户id的所有Token信息,就可以使用上图展示的第一个接口获取

<span style="color:#333333"><code><span style="color:#666600">[</span>
  <span style="color:#666600">{</span>
    <span style="color:#008800">"id"</span><span style="color:#666600">:</span> <span style="color:#008800">"9g9SCL6LAFPy20WLf7u0Q2KIAcgXv8Nfur3BxHs7xq1501UzBNcJYNlDRmbXSmrh"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"ttl"</span><span style="color:#666600">:</span> <span style="color:#006666">7257600</span><span style="color:#666600">,</span>
    <span style="color:#008800">"created"</span><span style="color:#666600">:</span> <span style="color:#008800">"2016-05-22T08:43:56.380Z"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"userId"</span><span style="color:#666600">:</span> <span style="color:#008800">"56e9853decfd499b641b82a1"</span>
  <span style="color:#666600">},</span>
  <span style="color:#666600">{</span>
    <span style="color:#008800">"id"</span><span style="color:#666600">:</span> <span style="color:#008800">"BtFnIenOd003UmGmFxJs6f6bcaeIBvcyD5q94zpxoQ5nv9ojUQqmRJ3rAbH9oU5n"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"ttl"</span><span style="color:#666600">:</span> <span style="color:#006666">7257600</span><span style="color:#666600">,</span>
    <span style="color:#008800">"created"</span><span style="color:#666600">:</span> <span style="color:#008800">"2016-05-22T08:44:38.014Z"</span><span style="color:#666600">,</span>
    <span style="color:#008800">"userId"</span><span style="color:#666600">:</span> <span style="color:#008800">"56e9853decfd499b641b82a1"</span>
  <span style="color:#666600">}</span>
<span style="color:#666600">]</span></code></span>

使用cluster模式运行服务

因为http是无状态的,因此可以启动多个平行的服务进程并行处理http请求.cluster-works模式的另一个好处是,主进程是所有work进程的父进程,work的异常退出,主进程都可以捕获到,并报警.cluster模块是节点原生支持的模块。我们在服务器目录下,添加一个文件cluster.js,文件内容为

<span style="color:#333333"><code><span style="color:#000088">var</span><span style="color:#000000"> cluster </span><span style="color:#666600">=</span> <span style="color:#000088">require</span><span style="color:#666600">(</span><span style="color:#008800">'cluster'</span><span style="color:#666600">);</span>
<span style="color:#000088">var</span><span style="color:#000000"> workers </span><span style="color:#666600">=</span> <span style="color:#666600">{};</span>

<span style="color:#000088">var</span> <span style="color:#660066">WorkersLen</span> <span style="color:#666600">=</span> <span style="color:#000088">function</span> <span style="color:#666600">(){</span>
  <span style="color:#000088">var</span><span style="color:#000000"> len </span><span style="color:#666600">=</span> <span style="color:#006666">0</span><span style="color:#666600">;</span>
  <span style="color:#000088">for</span><span style="color:#666600">(</span><span style="color:#000088">var</span><span style="color:#000000"> id </span><span style="color:#000088">in</span><span style="color:#000000"> workers</span><span style="color:#666600">){</span>
     <span style="color:#666600">++</span><span style="color:#000000">len</span><span style="color:#666600">;</span>
  <span style="color:#666600">}</span>
  <span style="color:#000088">return</span><span style="color:#000000"> len</span><span style="color:#666600">;</span>
<span style="color:#666600">};</span>

<span style="color:#000088">var</span><span style="color:#000000"> createWorker </span><span style="color:#666600">=</span> <span style="color:#000088">function</span> <span style="color:#666600">(){</span>
    <span style="color:#000088">var</span><span style="color:#000000"> worker </span><span style="color:#666600">=</span><span style="color:#000000"> cluster</span><span style="color:#666600">.</span><span style="color:#000000">fork</span><span style="color:#666600">();</span><span style="color:#000000">
    workers</span><span style="color:#666600">[</span><span style="color:#000000">worker</span><span style="color:#666600">.</span><span style="color:#000000">id</span><span style="color:#666600">]</span> <span style="color:#666600">=</span><span style="color:#000000"> worker</span><span style="color:#666600">;</span><span style="color:#000000">
    worker</span><span style="color:#666600">.</span><span style="color:#000000">on</span><span style="color:#666600">(</span><span style="color:#008800">'exit'</span><span style="color:#666600">,</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#000000">code</span><span style="color:#666600">){</span>
      <span style="color:#000088">delete</span><span style="color:#000000"> workers</span><span style="color:#666600">[</span><span style="color:#000000">worker</span><span style="color:#666600">.</span><span style="color:#000000">id</span><span style="color:#666600">];</span>
    <span style="color:#666600">});</span><span style="color:#000000">

    worker</span><span style="color:#666600">.</span><span style="color:#000000">on</span><span style="color:#666600">(</span><span style="color:#008800">'message'</span><span style="color:#666600">,</span> <span style="color:#000088">function</span><span style="color:#666600">(</span><span style="color:#000000">msg</span><span style="color:#666600">){</span>
      <span style="color:#000088">do</span> <span style="color:#666600">{</span>
          <span style="color:#000088">if</span><span style="color:#666600">(</span><span style="color:#000000">msg</span><span style="color:#666600">.</span><span style="color:#000000">cmd </span><span style="color:#666600">===</span> <span style="color:#008800">'suicide'</span><span style="color:#666600">){</span><span style="color:#000000">
            createWorker</span><span style="color:#666600">();</span>
            <span style="color:#000088">break</span><span style="color:#666600">;</span>
          <span style="color:#666600">}</span>
      <span style="color:#666600">}</span><span style="color:#000088">while</span><span style="color:#666600">(</span><span style="color:#000088">false</span><span style="color:#666600">);</span>
    <span style="color:#666600">});</span>
<span style="color:#666600">};</span>

<span style="color:#000088">function</span> <span style="color:#660066">StartWorkers</span><span style="color:#666600">()</span> <span style="color:#666600">{</span>
    <span style="color:#000088">var</span><span style="color:#000000"> n </span><span style="color:#666600">=</span> <span style="color:#006666">0</span><span style="color:#666600">;</span>
    <span style="color:#000088">require</span><span style="color:#666600">(</span><span style="color:#008800">'os'</span><span style="color:#666600">).</span><span style="color:#000000">cpus</span><span style="color:#666600">().</span><span style="color:#000000">forEach</span><span style="color:#666600">(</span><span style="color:#000088">function</span><span style="color:#666600">(){</span><span style="color:#000000">
      createWorker</span><span style="color:#666600">();</span>
    <span style="color:#666600">});</span>
<span style="color:#666600">}</span>

<span style="color:#000088">if</span><span style="color:#666600">(</span><span style="color:#000000">cluster</span><span style="color:#666600">.</span><span style="color:#000000">isMaster</span><span style="color:#666600">){</span>
  <span style="color:#660066">StartWorkers</span><span style="color:#666600">();</span><span style="color:#000000">
  process</span><span style="color:#666600">.</span><span style="color:#000000">on</span><span style="color:#666600">(</span><span style="color:#008800">'exit'</span><span style="color:#666600">,</span> <span style="color:#000088">function</span><span style="color:#666600">(){</span>
    <span style="color:#000088">for</span><span style="color:#666600">(</span><span style="color:#000088">var</span><span style="color:#000000"> id </span><span style="color:#000088">in</span><span style="color:#000000"> workers</span><span style="color:#666600">){</span><span style="color:#000000">
      workers</span><span style="color:#666600">[</span><span style="color:#000000">id</span><span style="color:#666600">].</span><span style="color:#000000">kill</span><span style="color:#666600">();</span>
    <span style="color:#666600">}</span>
  <span style="color:#666600">});</span>
<span style="color:#666600">}</span><span style="color:#000088">else</span><span style="color:#666600">{</span>
  <span style="color:#000088">require</span><span style="color:#666600">(</span><span style="color:#008800">'./server.js'</span><span style="color:#666600">).</span><span style="color:#000000">start</span><span style="color:#666600">();</span>
<span style="color:#666600">}</span></code></span>

以上代码包含了work进程与主进程的通信.Node进程之间使用Unix域套接字通信,这是一种非常高效的方式。主进程监听了消息事件,回调函数的参数是一个json对象。可以根据这个json对象的内容,区分这个事件的不同类型,然后分别处理。

接下来还需要稍微修改一下server.js文件。在文件末尾,曾为server.js添加了如下代码

<span style="color:#333333"><code><span style="color:#000000">process</span><span style="color:#666600">.</span><span style="color:#000000">on</span><span style="color:#666600">(</span><span style="color:#008800">'uncaughtException'</span><span style="color:#666600">,</span> <span style="color:#000088">function</span> <span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">){</span><span style="color:#000000">
  console</span><span style="color:#666600">.</span><span style="color:#000000">error</span><span style="color:#666600">(</span><span style="color:#008800">'uncaughtException: %s'</span><span style="color:#666600">,</span><span style="color:#000000"> err</span><span style="color:#666600">.</span><span style="color:#000000">message</span><span style="color:#666600">);</span>
<span style="color:#666600">});</span></code></span>

现在我们希望遇到这个未捕获异常,除了打印出异常信息之外,程序能够优雅的退出,而不是在某一个时刻崩溃掉。于是将上述代码修改为

<span style="color:#333333"><code><span style="color:#000000">process</span><span style="color:#666600">.</span><span style="color:#000000">on</span><span style="color:#666600">(</span><span style="color:#008800">'uncaughtException'</span><span style="color:#666600">,</span> <span style="color:#000088">function</span> <span style="color:#666600">(</span><span style="color:#000000">err</span><span style="color:#666600">){</span><span style="color:#000000">
  console</span><span style="color:#666600">.</span><span style="color:#000000">error</span><span style="color:#666600">(</span><span style="color:#008800">'worker uncaughtException: %s'</span><span style="color:#666600">,</span><span style="color:#000000"> err</span><span style="color:#666600">.</span><span style="color:#000000">message</span><span style="color:#666600">);</span>
  <span style="color:#000088">var</span><span style="color:#000000"> worker </span><span style="color:#666600">=</span> <span style="color:#000088">require</span><span style="color:#666600">(</span><span style="color:#008800">'cluster'</span><span style="color:#666600">).</span><span style="color:#000000">worker</span><span style="color:#666600">;</span>
  <span style="color:#000088">if</span><span style="color:#666600">(</span><span style="color:#000000">worker</span><span style="color:#666600">){</span><span style="color:#000000">
    process</span><span style="color:#666600">.</span><span style="color:#000000">send</span><span style="color:#666600">({</span><span style="color:#000000"> cmd</span><span style="color:#666600">:</span> <span style="color:#008800">'suicide'</span><span style="color:#666600">,</span><span style="color:#000000"> stack</span><span style="color:#666600">:</span><span style="color:#000000"> err</span><span style="color:#666600">.</span><span style="color:#000000">stack</span><span style="color:#666600">,</span><span style="color:#000000"> message</span><span style="color:#666600">:</span><span style="color:#000000">err</span><span style="color:#666600">.</span><span style="color:#000000">message</span><span style="color:#666600">});</span>
    <span style="color:#660066">Server</span><span style="color:#666600">.</span><span style="color:#000000">close</span><span style="color:#666600">(</span><span style="color:#000088">function</span><span style="color:#666600">(){</span><span style="color:#000000">
      process</span><span style="color:#666600">.</span><span style="color:#000088">exit</span><span style="color:#666600">(</span><span style="color:#006666">1</span><span style="color:#666600">);</span>
    <span style="color:#666600">});</span>
  <span style="color:#666600">}</span>
<span style="color:#666600">});</span></code></span>

当子进程收到一个未捕获异常时,就向父进程发送一个消息事件,并附上异常信息,500毫秒之后退出。父进程收到这个类型为自杀的消息事件之后,立即重启动一个工作。事实上,cluster.js文件内还可以处理更多的逻辑。但无论如何,cluster.js的代码都该越简单越好,它的稳定性应该与节点引擎一致。我们可以再结合pm2工具运行集群.js,pm2可以保证cluster.js的运行,这样可以进一步提供服务健壮性。

原文:https://cnodejs.org/topic/57e5b2859c495dce044f397c

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值