瀏覽代碼

重构完成,完成REST API单元测试

Sand 8 年之前
父節點
當前提交
7a5dc1dbe4
共有 42 個文件被更改,包括 2339 次插入3895 次删除
  1. 530 0
      doc/api/overview.md
  2. 37 2
      readme.md
  3. 36 39
      src/doctor/app.js
  4. 5 2
      src/doctor/bin/spawn.script.js
  5. 2 2
      src/doctor/controllers/index.js
  6. 34 0
      src/doctor/endpoints/application.endpoint.js
  7. 814 0
      src/doctor/endpoints/chats.endpoint.js
  8. 74 0
      src/doctor/endpoints/management.endpoint.js
  9. 0 48
      src/doctor/endpoints/management.js
  10. 0 793
      src/doctor/endpoints/messages.js
  11. 130 0
      src/doctor/endpoints/users.endpoint.js
  12. 0 93
      src/doctor/endpoints/users.js
  13. 38 1
      src/doctor/include/commons.js
  14. 44 18
      src/doctor/include/endpoints.js
  15. 0 10
      src/doctor/include/pages.js
  16. 60 0
      src/doctor/models/group.js
  17. 6 42
      src/doctor/models/msg.group.js
  18. 18 0
      src/doctor/models/msg.notify.js
  19. 0 37
      src/doctor/models/msg.p2p.js
  20. 25 0
      src/doctor/models/msg.private.js
  21. 13 35
      src/doctor/models/msg.stat.js
  22. 3 3
      src/doctor/models/msg.system.js
  23. 0 14
      src/doctor/models/push_notify.js
  24. 38 15
      src/doctor/models/user.js
  25. 3 1
      src/doctor/package.json
  26. 1 1
      src/doctor/repository/im.repo.js
  27. 9 9
      src/doctor/resources/schema/im_schema.sql
  28. 9 9
      src/doctor/resources/schema/talk_group_schema.sql
  29. 51 0
      src/doctor/test.js
  30. 12 8
      src/doctor/util/dbUtil.js
  31. 20 19
      src/doctor/util/log.js
  32. 0 108
      src/doctor/util/mime.js
  33. 0 2586
      src/doctor/util/mime.types.json
  34. 12 0
      src/doctor/util/objectUtil.js
  35. 3 0
      test/doctor/config.js
  36. 26 0
      test/doctor/endpoints/application.endpoint.Test.js
  37. 151 0
      test/doctor/endpoints/chats.endpoint.Test.js
  38. 26 0
      test/doctor/endpoints/management.endpoint.Test.js
  39. 76 0
      test/doctor/endpoints/users.endpoint.Test.js
  40. 9 0
      test/doctor/package.json
  41. 5 0
      test/doctor/util/dbUtil.Test.js
  42. 19 0
      test/doctor/util/objectUtil.Test.js

+ 530 - 0
doc/api/overview.md

@ -0,0 +1,530 @@
API
====================
> 作者:Sand,2017.11.07
概述
---------------------
本文档为REST API调用规范,旨在为第三方应用提供访统一的问平台调用与交互方式,通过这些接口可以访问患者的健康数据,医疗资源及医疗机构等数据。
所有API都是通过向平台的REST服务器发送HTTP请求来实现,REST服务器支持gzip,可有效降低网络开销,建议第三方应用加入gzip支持。
 
术语解释
---------------------
- Client ID: 在平台注册App时,由平台提供的唯一App标识。
- Client Secret:在平台注册App时为其分配的应用密码。
- 用户名:在平台注册用户时,由平台提供的唯一用户标识。
- 用户密码:与用户ID相匹配的用户密码,由用户保管。
- 机构代码:在平台注册机构时,使用此机构在国家机构数据库中注册的代码。
- 机构公钥:在平台注册机构时,平台为机构提供的公钥。
版本
---------------------
平台API均被版本化,当前版本为v1.0,版本号通过URL指定,例如: 
	api/v1.0/user?age=20
	
请务必在请求中包含版本号以便请求能够得到正确响应。
请求与响应通用模式
---------------------
所有API请求均通过HTTPS协议。API服务器地址:
- [医生端](https://ehr.yihu.com/api)
- [患者端](https://da.yihu.com/api)
数据发送与接收均以JSON作为载体。例如:数组参数array。普通字符串直接使用URL。
	curl -i https://ehr.yihu.com/api/v1.0/users
    
    HTTP/1.1 200 OK
    Server: nginx
    Date: Fri, 12 Oct 2012 23:33:14 GMT
    Content-Type: application/json; charset=utf-8
    Connection: keep-alive
    Status: 200 OK
    ETag: "a00049ba79152d03380c34652f2cb612"
    Content-Length: 5
    Cache-Control: max-age=0, private, must-revalidate
    
JSON结构中字段为空的时候,使用null表示,而不是将其丢弃。所有时间戳数据均以ISO 8601格式返回:
	YYYY-MM-DDTHH:MM:SSZ
	
### 资源摘要
当请求资源列表时,返回值是特定资源属性的一个子集,而非所有属性,即“资源摘要”(某些资源的属性会占用过多的系统资源),
鉴于性能原因应用默认不返回这些属性。若要获取这些属性,通过资源的详细信息来获取。
例如,当请求用户列表的时候,你将得到用户列表,其中只有部分属性有返回值
	GET /users
	
### 资源详情
当请求单个资源时,平台将会返回这个资源的所有属性,即“资源详情”(注意,用于不同的用户权限,其返回值可能有所不同)。
例如,当请求某个用户的详细信息时,将会返回这个用户的所有数据:
	GET /users/1024
参数
---------------------
很多API都含有可选参数。例如,对于GET请求,非路径变量参数可以通过HTTP查询参数指定:
	curl -i "https://ehr.yihu.com/v1/users/1024?age=20"
	
在这个示例中,用户的年龄参数通过*:age*查询字符串来指定。
对于POST, PUT与DELETE请求,不在URL中指定的参数必须封装成JSON,并指定Content-Type为'application/json':
	curl -i -u username -d '{"age":["20"]}' https://ehr.yihu.com/users
全局入口
---------------------
通过向根路径发送GET请求,将会得到平台所有支持的API列表:
	curl https://ehr.yihu.com/api
可以从返回的JSON得到你所需要的API列表。入口处显示平台API列表可以点击进去。列表只显示各API的第一个GET方法链接。若含有多个GET方法会被忽略。
GET方法点击进去之后,根据方法的实际情况:
- 若方法不需要认证,可以修改URL中的查询参数就可以得到结果
- 若方法需要认证,此时返回API的错误信息。结构如下
	{
        message: "需要认证",
        documentation_url: http://ehr.yihu.com/docs/help.html
    }
    
另外,平台提供了[Swagger](https://ehr.yihu.com/swagger-ui.html)工具,用户可以直接访问API列表。
客户端错误
---------------------
客户端请求时,服务端可能会返回以下三种类型的API错误,具体的错误信息在响应体中:
- 若请求参数是一个无效的JSON,服务端将返回400错误:
	HTTP/1.1 400 Bad Request
    Content-Length: 35
    
    {"message":"Problems parsing JSON"}
    
- 若客户端发送的是一个错误的JSON值,服务端将返回400错误:
	HTTP/1.1 400 Bad Request
    Content-Length: 40
    
    {"message":"Body should be a JSON object"}
    
- 若客户端发送的数据包含无效的字段,服务端将返回422错误:
	HTTP/1.1 422 Unprocessable Entity
    Content-Length: 149
    
    
    {
      "message": "Validation Failed",
      "errors": [
        {
          "resource": "Issue",
          "field": "title",
          "code": "missing_field"
        }
      ]
    }
    
    
所有的错误对象都含有相应的资源与属性字段,用以告诉客户端可能的错误原因。另外,错误对象中还有一个错误代码,用于告诉哪个字段发生错误。
错误代码列表如下:
<table>
	<tr>
		<td>错误名称</td>
		<td>描述</td>
	</tr>
	<tr>
		<td>missing</td>
		<td>资源不存在</td>
	</tr>
	<tr>
		<td>missing_field</td>
		<td>请求的资源不含请求的字段</td>
	</tr>
	<tr>
		<td>invalid</td>
		<td>字段的格式无效。请参考指定的资源文档以获取详细信息</td>
	</tr>
	<tr>
		<td>already_exists</td>
		<td>此资源的字段值与另一个资源一样。这种情况大多发生在资源的主键重复,例如:对象ID</td>
	</tr>
</table>
以上是通用的错误对象,资源也可能返回一些自定义错误消息,自定义错误消息包含一个message字段描述该错误,且大多数错误消息都
含有一个documentation_url字段指向可能对解决问题有帮助的页面。
HTTP重定向
---------------------
平台API在适当的时候会使用HTTP重定义功能。客户端应该假设所有的请求都有可能发生重定向。客户端接收到HTTP重定向时,
这不是一个错误并且客户端需要跟随重定向链接。重定向链接包含在URI资源的返回头*Location*字段中。
<table>
	<tr>
		<td>HTTP状态码</td>
		<td>描述</td>
	</tr>
	<tr>
		<td>301</td>
		<td>永久重定向。所请求的资源已被永久转移到*Location*指定的位置,客户端应该使用该URI重新请求该资源。</td>
	</tr>
	<tr>
		<td>302,307</td>
		<td>临时重定向。所请求的资源暂时转移到*Location*指定的位置,客户端应该使用该URI重新请求该资源。</td>
	</tr>
</table>
HTTP动词
---------------------
平台使用HTTP动词代表一个操作,目前支持以下动词
<table>
	<tr>
		<td>动词</td>
		<td>描述</td>
	</tr>
	<tr>
		<td>GET</td>
		<td>获取资源</td>
	</tr>
	<tr>
		<td>POST</td>
		<td>创建资源</td>
	</tr>
	<tr>
		<td>PUT</td>
		<td>更新一个或多个资源</td>
	</tr>
	<tr>
		<td>DELETE</td>
		<td>删除资源</td>
	</tr>
</table>
授权
---------------------
平台API有以下两种鉴权模式。大多数情况下,若不请求中不包含token会返回 *403 Forbidden*,但有些API出于保护资源的目的可能会返回*404 Not Found*错误。
- OAuth2 Token(包含在请求头中)
 
	curl -H "Authorization: token OAUTH-TOKEN" https://ehr.yihu.com/api
	
- OAuth2 Token(作为请求参数的一部分)
	curl https://ehr.yihu.com/api/resource?access_token=OAUTH-TOKEN
	
更多关于OAuth2的信息,请参考(OAuth2规范)[https://developer.github.com/v3/oauth/]。OAuth2 Token获取请查询授权接口。
- OAuth2 Id/Secret获取
	curl 'https://ehr.yihu.com/api/users/whatever?client_id=xxxx&client_secret=yyyy'
	
该请求返回的Token仅能用于服务端-服务端的场景,请不要部署两个一样的应用,然后分别向服务端请求Token,会互相覆盖。
请保管好应用Secret,不要泄露。
- 登录失败处理
使用非法凭据来请求资源将会返回*401 Unauthorized*错误:
	curl https://ehr.yihu.com/api/resource?access_token=OAUTH-TOKEN
    HTTP/1.1 401 Unauthorized
    
    {
      "message": "Bad credentials",
      "documentation_url": "https://ehr.yihu.com/api"
    }
如果在短时间内,服务端接收到多个使用非法凭据的请求,后续该开发者的所有API请求在一段时间内都将被拒绝(包括使用合法凭据的请求),返回的错误为*403 Forbidden*
	curl -i https://ehr.yihu.com/api/resource?access_token=INVALID-OAUTH-TOKEN
    HTTP/1.1 403 Forbidden
    
    {
      "message": "Maximum number of login attempts exceeded. Please try again later.",
      "documentation_url": "https://ehr.yihu.com/api"
    }
    
超媒体
---------------------
所有的资源都含有一个或多个\*_url的属性,以链接到其他资源。这样做可以帮助客户端减少自己构建其关联资源的构建代码。强烈建议客户端使用这些URL。
这样做对以后开发者升级API会有帮助。所有的URL都符合 [RFC 6570](https://github.com/hannesg/uri_template)URI 模板。
	>> tmpl = URITemplate.new('/notifications{?since,all,participating}')
    >> tmpl.expand
    => "/notifications"
    
    >> tmpl.expand :all => 1
    => "/notifications?all=1"
    
    >> tmpl.expand :all => 1, :participating => 1
    => "/notifications?all=1&participating=1"
分页
---------------------
当请求的资源过多时,会默认以15条/页的数据返回。可以指定新的页码*page*与页大小*size*以返回更多的数据。
	curl 'https://aehr.yihu.com/api/users?page=2&per_page=100'
	
注意,分布起始值为1,若请求中不提供page参数,默认返回第一页的数据。
### Link头
分页信息包含在响应头的Link字段中。建议使用此字段的值作为资源导航,而不是自己构建URL。某些情况下,分页信息会使用SHA1并且不提供页码。
	Link: <https://ehr.yihu.com/api/user/repos?page=3&per_page=100>; rel="next",
      <https://ehr.yihu.com/api/repos?page=50&per_page=100>; rel="last"
      
Link字段包含一个或多个超媒体链接关系。*rel*可能值如下:
<table>
	<tr>
		<td>名称</td>
		<td>描述</td>
	</tr>
	<tr>
		<td>next</td>
		<td>下一页结果</td>
	</tr>
	<tr>
		<td>last</td>
		<td>最后一页结果</td>
	</tr>
	<tr>
		<td>first</td>
		<td>第一页结果</td>
	</tr>
	<tr>
		<td>previous</td>
		<td>前一页结果</td>
	</tr>
</table>
请求频率与限制
---------------------
对认证过的请求,客户端每个小时的请求频率限制为5000次,对于未认证过的请求,客户端的请求频率限制为60次/小时。未认证的请求与IP绑定而不是与用户名绑定。
此情况对于搜索类型的API不适用,其频率限制有自身的规则。
通过检查HTTP响应头可以得到当前的请求频率限制:
	curl -i https://ehr.yihu.com/api/users/whatever
    HTTP/1.1 200 OK
    Date: Mon, 01 Jul 2013 17:27:06 GMT
    Status: 200 OK
    X-RateLimit-Limit: 60
    X-RateLimit-Remaining: 56
    X-RateLimit-Reset: 1372700873
    
从请求头中你可以得知你所需要的信息:
<table>
	<tr>
		<td>字段名称</td>
		<td>描述</td>
	</tr>
	<tr>
		<td>X-RateLimit-Limit</td>
		<td>每小时最大请求次数</td>
	</tr>
	<tr>
		<td>X-RateLimit-Remaining</td>
		<td>当前时间内剩余请求次数</td>
	</tr>
	<tr>
		<td>X-RateLimit-Reset</td>
		<td>下一次请求次数重置时间,UTC epoch秒数</td>
	</tr>
</table>
若想要将X-RateLimit-Reset时间转换为可读的,可以使用以下代码转换:
	new Date(1372700873 * 1000)
    // => Mon Jul 01 2013 13:47:53 GMT-0400 (EDT)
一旦请求次数用完,你将得到以下错误消息:
	HTTP/1.1 403 Forbidden
    Date: Tue, 20 Aug 2013 14:50:41 GMT
    Status: 403 Forbidden
    X-RateLimit-Limit: 60
    X-RateLimit-Remaining: 0
    X-RateLimit-Reset: 1377013266
    
    {
       "message": "xxx.xxx.xxx.xxx的API请求超过限制(好消息:认证提限优惠大酬宾,活动参见链接)",
       "documentation_url": "https://ehr.yihu.com/docs/api/v1/#rate-limiting"
    }
    
- 增加未授权的应用请求次数
若想增加未授权的应用请求次数,你需要在查询字段串中传递客户端的Client Id与Secret:
	curl -i 'https://ehr.yihu.com/api/v1/users/whatever?client_id=xxxx&client_secret=yyyy'
    HTTP/1.1 200 OK
    Date: Mon, 01 Jul 2013 17:27:06 GMT
    Status: 200 OK
    X-RateLimit-Limit: 5000
    X-RateLimit-Remaining: 4966
    X-RateLimit-Reset: 1372700873
    
这个方法应该仅用于服务端-服务端的请求,请务必不要泄漏App Secret。
使用User Agent
---------------------
所有的API都需要在请求头中包含User-Agent。没有User-Agent的请求将被拒绝。请在此字段中使用您的用户名或Client名称。
这样做可以在出现问题的时候方便联系你们。
例如:
	
	User-Agent: client 1FtXTrSL8D
	
	User-Agent: user developer
	
如果提供的User-Agent是无效值,你将收到以下错误信息:
	curl -iH 'User-Agent: ' https://ehr.yihu.com/api/v1/meta
	
    HTTP/1.0 403 Forbidden
    Connection: close
    Content-Type: text/html
    
    Request forbidden by administrative rules.
    Please make sure your request has a User-Agent header.
    Check https://ehr.yihu.com/api for other possible causes.
    
条件式请求
---------------------
	
未实现。
跨域资源共享(Cross-Origin Resource Sharing,CORS)
---------------------
未实现。
JSON-P回调
---------------------
你可以在GET请求参数中添加?callback参数以将请求结果包装成JSON函数,这种方式经常用在向第三方Web页面中嵌入健康档案平台的内容(跨域)。
平台将会把回调结果中的HTTP头作为API返回值的一部分:
	curl https://ehr.yihu.com/api/v1?callback=foo
    
    foo({
      "meta": {
        "status": 200,
        "X-RateLimit-Limit": "5000",
        "X-RateLimit-Remaining": "4966",
        "X-RateLimit-Reset": "1372700873",
        "Link": [ // pagination headers and other links
          ["https://ehr.yihu.com/api/v1?page=2", {"rel": "next"}]
        ]
      },
      "data": {
        // 回调的实际响应值
      }
    })
    
你可以编写JavaScript代码处理这种回调结果。以下是一个简单的示例:
	<html>
    <head>
    <script type="text/javascript">
    function foo(response) {
      var meta = response.meta;
      var data = response.data;
      console.log(meta);
      console.log(data);
    }
    
    var script = document.createElement('script');
    script.src = 'https://ehr.yihu.com?callback=foo';
    
    document.getElementsByTagName('head')[0].appendChild(script);
    </script>
    </head>
    
    <body>
      <p>Open up your browser's console.</p>
    </body>
    </html>
    
所有的响应头内容均与原HTTP响应头一致,除了LInk字段。此处的Link字段已经提前为你解析好,并包装为*[url, options]*数组。
例如,正常的HTTP响应头中Link结构如下:
	Link: <url1>; rel="next", <url2>; rel="foo"; bar="baz"
	
JSON-P中的Link结构如下:
	{
      "Link": [
        [
          "url1",
          {
            "rel": "next"
          }
        ],
        [
          "url2",
          {
            "rel": "foo",
            "bar": "baz"
          }
        ]
      ]
    }
    
时区
---------------------
部分API需要提供时间戳参数,部分API会返回含时区的时间戳。我们根据以下规则(优先级从大到小)决定API的时区信息:
**显示使用ISO 8601格式的时间戳,即包含时区信息的时间戳**
对于允许显示指定时间戳的API,我们将使用指定的时区。这种时间戳看起像这样:*2016-04-26T08:25:07.504+0800*。
**在HTTP请求头中使用Time-Zone参数**
在HTTP请求头中附加Time-Zone参数,其值使用[时区数据库中的名称](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)。
	curl -H "Time-Zone: Asia/Shanghai" -X POST https://ehr.yihu.com/linguist/contents/new_file.md
**使用上一次的已知时区信息**
如果找不到Time-Zone参数,并且你生成了一个授权请求,我们将使用上次授权用户所在的时区。每次你浏览健康档案的时候都会更新这个值。
**UTC**
如果上述几种情况中均不包含任何时区信息,我们将使用UTC。

+ 37 - 2
readme.md

@ -1,4 +1,4 @@
# 医生端IM服务器
# IM服务器
此项目是医生端即时消息服务器,与患者端不同。患者端的即时消息服务器使用C++编写,并且不具有消息存储功能,医生端与患者端已经连通,参见im.patient项目的说明。以后可能使用node.js框架统一二者。
此项目是医生端即时消息服务器,与患者端不同。患者端的即时消息服务器使用C++编写,并且不具有消息存储功能,医生端与患者端已经连通,参见im.patient项目的说明。以后可能使用node.js框架统一二者。
@ -18,9 +18,44 @@
![IM活动图](./doc/images/activity.png)
![IM活动图](./doc/images/activity.png)
## 错误处理
Node.js支持同步与异步调用,也导致了异常错误处理与众不同。一个给定的函数,它处理异常的方式要么是同步(用 throw方式)要么是异步的(用 callback 或者 EventEmitter),不会两者兼具。
用户可以在回调函数里处理错误,也可以使用 try/catch捕获异常 ,但是不能一起用。
实际上,使用throw并且期望调用者使用 try/catch 是很罕见的,因为 NodeJS里的同步函数通常不会产生运行失败(主要的例外是类似于JSON.parse的用户输入验证函数)。
## 消息格式
## 消息格式
https://my.oschina.net/antianlu/blog/190191
客户端向服务端发送:
    
    {
        channel: 0, 1, 2,
        body: {
            from: sand,
            to: Rose,
            contentType: "text/plain",
            content: "abc"
        }
    }
服务端向客户端发送:
    {
        time: 2016-11-08 12:12:01
        channel: 0, 1, 2,
        body: {
            from: sand,
            to: Rose,
            contentType: "text/plain",
            content: "abc"
        }
    }
    
## 测试
测试框架使用mocha。REST客户端使用supertest, should模块。
## 部署
## 部署

+ 36 - 39
src/doctor/app.js

@ -1,3 +1,7 @@
/**
 * 应用程序入口。
 */
// general dependencies
// general dependencies
var express = require('express');
var express = require('express');
var path = require('path');
var path = require('path');
@ -5,25 +9,22 @@ var favicon = require('serve-favicon');
var logger = require('morgan');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var bodyParser = require('body-parser');
var log = require('./util/log');
// server config dependencies
// server config dependencies
var endpoints = require('./include/endpoints').END_POINTS;
var pages = require('./include/pages').PAGES;
var APIv1 = require('./include/endpoints').APIv1;
var PAGES = require('./include/endpoints').PAGES;
var configFile = require('./include/commons').CONFIG_FILE;
var configFile = require('./include/commons').CONFIG_FILE;
var config = require('./resources/config/' + configFile);
var config = require('./resources/config/' + configFile);
// routes dependencies
// routes dependencies
var index = require('./controllers/index');
var index = require('./controllers/index');
var users = require('./endpoints/users');
var messages = require('./endpoints/messages');
var management = require('./endpoints/management');
console.log('==============================================');
console.log('');
console.log(new Date().toLocaleString() + ': Starting IM server, version ' + config.version);
console.log('');
console.log('==============================================');
var application = require('./endpoints/application.endpoint');
var users = require('./endpoints/users.endpoint');
var chats = require('./endpoints/chats.endpoint');
var management = require('./endpoints/management.endpoint');
// Application entry point
// Application entry point
var app = express();
var app = express();
@ -35,46 +36,42 @@ app.set('view engine', 'jade');
app.use(favicon(__dirname + '/public/favicon.ico', null));
app.use(favicon(__dirname + '/public/favicon.ico', null));
app.use(logger('dev'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.urlencoded({extended: false}));
app.use(cookieParser());
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'public')));
app.use(pages.Home.Index, index);
app.use(PAGES.Home.Index, index);
app.use(APIv1.Application.Base, application);
app.use(APIv1.Chats.Base, chats);
app.use(APIv1.Users.Base, users);
app.use(APIv1.Management.Base, management);
app.use(endpoints.Msg.Base, messages);
app.use(endpoints.Users.Base, users);
app.use(endpoints.Management.Base, management);
// Error handler, only handle the sync call exception
app.use(function (err, req, res, next) {
    if (err) {
        res
            .status(err.httpStatus || 500)
            .send({message: err.message});
    }
// 捕捉404错误,并转发至错误处理器
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
    next(err);
});
});
// In development mode, error handler will print stacktrace
// In development mode, error handler will print stacktrace, only handle the sync call exception
if (app.get('env') === 'development') {
if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
      message: err.message,
      error: err
    app.use(function (err, req, res, next) {
        log.error(err);
        next(err);
    });
    });
  });
}
}
// In production mode, error handler no stack traces leaked to users
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
    message: err.message,
    error: {}
  });
});
// Only handle the sync call exception
process.on('uncaughtException', function (err) {
process.on('uncaughtException', function (err) {
  console.log(err);
  console.log(err.stack);
    console.error(err);
});
});
log.info(new Date().toLocaleString() + ': Starting IM server, version ' + config.version);
module.exports = app;
module.exports = app;

+ 5 - 2
src/doctor/bin/spawn.script.js

@ -1,10 +1,11 @@
#!/usr/bin/env node
#!/usr/bin/env node
var app = require('../app');
var app = require('../app');
var log = require('../util/log');
var debug = require('debug')('im.doctor:server');
var debug = require('debug')('im.doctor:server');
var http = require('http');
var http = require('http');
var commons = require('../include/commons');
var config = require(commons.CONFIG_FILE);
var configFile = require('../include/commons').CONFIG_FILE;
var config = require('../resources/config/' + configFile);
/**
/**
 * Get port from environment and store in Express.
 * Get port from environment and store in Express.
@ -80,3 +81,5 @@ function onListening() {
    : 'port ' + addr.port;
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
  debug('Listening on ' + bind);
}
}
log.info('Server running on: http://127.0.0.1:3000');

+ 2 - 2
src/doctor/controllers/index.js

@ -4,10 +4,10 @@
var express = require('express');
var express = require('express');
var router = express.Router();
var router = express.Router();
var pages = require('../include/pages').PAGES;
var pages = require('../include/endpoints').PAGES;
router.get(pages.Home.Index, function(req, res) {
router.get(pages.Home.Index, function(req, res) {
  res.render('index', { title: 'IM.Doctor' });
  res.render('index', { title: 'IM.User' });
});
});
module.exports = router;
module.exports = router;

+ 34 - 0
src/doctor/endpoints/application.endpoint.js

@ -0,0 +1,34 @@
var express = require('express');
var router = express.Router();
var log = require('../util/log.js');
var APIv1 = require('../include/endpoints').APIv1;
var msgStats = require("../models/msg.stat");
/**
 * 获取应用角标数。
 *
 * chats/badge_no?user_id=sand
 *
 * 参数:
 * uid:用户id
 */
router.get(APIv1.Application.BadgeNo, function (req, res) {
    var userId = req.query.user_id;
    msgStats.getBadgeNumber(userId, function (err, result) {
        if (err) {
            console.error("Get badge number failed: ", err);
            res.status(500).send({message: "Get badge number failed."});
            return;
        }
        var data = {userId: userId, badge: result};
        res.send(data);
    });
});
module.exports = router;

+ 814 - 0
src/doctor/endpoints/chats.endpoint.js

@ -0,0 +1,814 @@
/**
 * 消息端点。
 *
 * 此控制器处理点对点,组及消息消息。为三类消息提供发送及查询功能。
 */
var http = require('http');
var getui = require('getui');
var log = require('../util/log.js');
var objectUtil = require('../util/objectUtil');
var express = require('express');
var router = express.Router();
var systemMsg = require("../models/msg.system");
var privateMsg = require('../models/msg.private');
var groupMsg = require("../models/msg.group");
var notifyMsg = require("../models/msg.notify");
var msgStats = require("../models/msg.stat");
var user = require("../models/user");
var group = require("../models/group");
var APIv1 = require('../include/endpoints').APIv1;
var CHANNELS = require('../include/commons').CHANNELS;
var PLATFORMS = require('../include/commons').PLATFORM;
var MAX_INT = 9007199254740992;
//--------------------------------------------------------------//
//----------------------------消息发送----------------------------//
//--------------------------------------------------------------//
/**
 * 发送System消息。
 *
 * 消息格式:
 *  {
 *      to: "Rose",
 *      title: "System Message",
 *      summary: "You have new job",
 *      contentType: "1",
 *      content: "The patient has been followed in the scheduler, please make new follow plan as soon as possible."
 *  }
 *
 * @param message
 */
router.post(APIv1.Chats.SM, function (req, res) {
    // 检查消息体及消息格式是否正确
    var message = req.body;
    if (!objectUtil.isJsonObject(message)) {
        throw {httpStatus: 406, message: 'Problems parsing JSON.'}
    }
    // 字段判断
    if (message.to == null || message.title == null) {
        throw {httpStatus: 406, message: 'Missing fields.'}
    }
    // 消息处理
    user.getUserStatus(message.to, function (err, userStatus) {
        if (err) {
            console.log("Lookup system message receiver failed: ", err);
            
            throw {httpStatus: 500, message: "Lookup system message receiver failed: "};
        }
        if(userStatus.length == 0){
            throw {httpStatus: 404, message: "User not found."};
        }
        res.writeHead(200);
        var pushable = userStatus.length > 0 && userStatus[0].is_online;
        var notifyMessage = JSON.stringify({type: message.contentType, content: message.content});
        // 保存该条推送信息
        systemMsg.save(message.to,
            message.contentType,
            message.title,
            message.summary,
            message.content,
            function (err, result) {
                if (err) {
                    log.error("Save system message failed, ", err);
                    throw {httpStatus: 500, message: "Save system message failed."};
                } else {
                    // 保存通知到数据库中
                    notifyMessage.saveMessage(message.to,
                        message.contentType,
                        message.title,
                        message.summary,
                        notifyMessage,
                        pushable,
                        function (err, result) {
                            if (err) {
                                log.error("Save system message failed, ", err);
                                throw {httpStatus: 500, message: "Save system notify message failed."};
                            } else {
                                if (pushable == true) {
                                    if (userStatus.platform == PLATFORMS.iOS) {
                                        getui.pushAPN(message.to,
                                            userStatus.token,
                                            message.contentType,
                                            message.title,
                                            message.content,
                                            notifyMessage,
                                            function (err, result) {
                                                err != null ? console.log(err) : console.log(result);
                                            });
                                    } else if (PLATFORMS.iOS.Android) {
                                        getui.pushAndroid(userStatus.client_id,
                                            message.contentType,
                                            message.title,
                                            message.content,
                                            notifyMessage,
                                            userStatus.status,
                                            function (err, result) {
                                                err != null ? console.log(err) : console.log(result);
                                            });
                                    }
                                }
                            }
                        });
                }
            });
    });
    res.writeHead(200);
});
/**
 * 处理Private消息。处理流程分:
 * 1 解析消息,并保存到数据库
 * 2 更新消息统计数据
 * 3 获取目标的状态并构建通知消息,如果用户在线就推送通知消息
 *
 * 消息格式:
 *  {
 *      from: sand,
 *      to: Rose,
 *      contentType: "1",
 *      content: "Please follow the patient as soon as possible."
 *  }
 *
 * @param message
 */
router.post(APIv1.Chats.PM, function (req, res) {
    // 检查消息体及消息格式是否正确
    var message = req.body;
    if (!objectUtil.isJsonObject(message)) {
        throw {httpStatus: 406, message: 'Problems parsing JSON.'}
    }
    // 字段判断
    if (message.from == null || message.to == null || message.contentType == null || message.content == null) {
        throw {httpStatus: 406, message: 'Missing fields.'}
    }
    // 处理消息
    user.isExist(message.to, function (err, result) {
        if (err) {
            console.log('Lookup receiving users failed: ', err);
            throw {httpStatus: 500, message: 'Lookup receiving users failed.'};
        }
        if (result.length == 0) {
            throw {httpStatus: 404, message: 'Receiving users not found.'};
        }
        // 保存消息
        privateMsg.save(message.to, message.from, message.contentType, message.content, function (err, result) {
            if (err) {
                throw {httpStatus: 500, message: 'Save private message failed.'};
            }
            res.writeHead(200);
            // 更新自身的聊天统计信息
            msgStats.updateP2PChatInfo(message.from,
                message.to,
                message.from,
                CHANNELS.Private,
                message.content,
                function (err, result) {
                    if (err) log.error(err);
                });
            // 更新对端的聊天统计信息
            msgStats.updateP2PChatInfo(message.to,
                message.from,
                message.from,
                message.contentType,
                message.content,
                function (err, result) {
                    if (err) log.error(err);
                });
            // 获取对方状态,即对端的系统平台,token等信息,并推送通知消息给对端
            user.getUserStatus(message.to, function (err, result) {
                if (err) {
                    log.error('Get users status failed: ' + message.to);
                    throw {httpStatus: 500, message: 'Find target users failed.'};
                }
                // 构建通知消息
                var title = '新消息';
                var content = '';
                if (message.contentType == 1) {
                    content = message.content;
                } else if (message.contentType == 2) {
                    content = '[图片]';
                } else if (message.contentType == 3) {
                    content = '[语音]';
                } else {
                    content = '接收到一条新消息';
                }
                var pushable = false;
                var target;
                if (result.length > 0) {
                    target = result[0];
                    if (target.is_online) {
                        pushable = true;
                    }
                }
                var notifyMessage = JSON.stringify({type: 'p2p_msg', from: message.from});
                // 保存通知消息到数据库中并根据用户在线状态推送此消息
                notifyMsg.saveNotifyMessage(message.to,
                    message.contentType,
                    title,
                    content,
                    notifyMessage,
                    pushable,
                    function (err, result) {
                        if (err) {
                            log.error('Save private notify message failed, ', err);
                        } else {
                            if (pushable === true) {
                                if (data.platform === PLATFORMS.iOS) {
                                    getui.pushAPN(message.to,
                                        data.token,
                                        message.contentType,
                                        title,
                                        content,
                                        notifyMessage,
                                        function (err, result) {
                                            if (err != null) {
                                                console.log(err);
                                            } else {
                                                console.log(result);
                                            }
                                        });
                                } else if (data.platform === PLATFORMS.Android) {
                                    getui.pushAndroid(data.client_id,
                                        message.contentType,
                                        title,
                                        content,
                                        notifyMessage,
                                        data.status,
                                        function (err, result) {
                                            if (err != null) {
                                                console.log(err);
                                            } else {
                                                console.log(result);
                                            }
                                        });
                                }
                            }
                        }
                    });
            });
        });
    });
    res.writeHead(200);
});
/**
 * 处理讨论组消息。
 *
 * 消息格式:
 *  {
 *      from: "sand",                       // 发送者id
 *      at: "Rose",                         // @人
 *      group: "DiscussionGroupId",         // 所在组
 *      groupType: "1 or 2",                // 组类型:行政团队或讨论组
 *      contentType: "1,2,3",               // 内容类型
 *      content: "The patient mess me up"   // 内容
 *  }
 *
 * @param message
 */
router.post(APIv1.Chats.GM, function (req, res) {
    // 检查消息体及消息格式是否正确
    var message = req.body;
    if (!objectUtil.isJsonObject(message)) {
        throw {httpStatus: 406, message: 'Problems parsing JSON.'};
    }
    // 字段判断
    if (message.from == null || message.group == null || message.contentType == null || message.content == null) {
        throw {httpStatus: 406, message: 'Missing fields.'}
    }
    // 消息处理
    group.isGroupMember(message.group, message.groupType, message.from, function (err, result) {
        if (err) {
            throw {httpStatus: 500, message: 'Check group member failed.'};
        }
        if (result.length == 0) {
            throw {httpStatus: 404, message: 'Group member not found. Member id: ' + message.from};
        }
        // 保存群组消息
        groupMsg.saveMessage(message.from, message.group, message.at, message.contentType, message.content, function (err, result) {
            if (err) {
                throw {httpStatus: 500, message: 'Save group message failed.'};
            }
            res.writeHead(200);
            // 统计信息
            msgStats.updateGroupChatInfo(message.from,
                message.group,
                message.from,
                0,
                message.contentType,
                message.content,
                false,
                function (err, result) {
                    if (err) log.error(err);
                });
            // 推送通知消息给群组成员
            group.getMembers(message.group, message.groupType, function (err, result) {
                if (err) {
                    log.error('Get group members failed: ', err);
                    throw {httpStatus: 500, message: 'Get group members failed.'};
                }
                if (result.length == 0) {
                    log.warn('No members in group ', message.group);
                    return;
                }
                // 逐个推送通知
                for (var nIndex = 0; nIndex < result.length; nIndex++) {
                    var member = result[nIndex];
                    if (member.member_code === message.from) continue;
                    user.getUserStatus(member.member_code, function (err, result) {
                        if (err) {
                            console.error('Get group member status failed: ', err);
                            return;
                        }
                        var title = '';
                        var content = '';
                        if (message.contentType === 1) {
                            title = '群组消息';
                            content = message.content;
                        } else if (message.contentType === 2) {
                            title = '群组消息';
                            content = '[图片]';
                        } else if (message.contentType === 3) {
                            title = '群组消息';
                            content = '[语音]';
                        } else {
                            title = '群组消息';
                            content = '接收到一条新消息';
                        }
                        var pushable = result.length > 0 && result[0].is_online === 1;
                        var notifyMessage = JSON.stringify({type: 'group_msg', groupId: message.group});
                        // 发送并保存通知到数据库中
                        if (pushable) {
                            if (data.platform === PLATFORMS.iOS) {
                                getui.pushAPN(member.member_code,
                                    data.token,
                                    message.contentType,
                                    title,
                                    content,
                                    notifyMessage,
                                    function (err, result) {
                                        err != null ? console.error(err) : console.log(result);
                                        // 这段代码重复
                                        notifyMsg.saveNotifyMessage(member.member_code,
                                            message.contentType,
                                            title,
                                            content,
                                            notifyMessage,
                                            err != null ? 0 : 1,
                                            function (err, result) {
                                                if (err) {
                                                    console.log('Save group notify message failed: ', err);
                                                } else {
                                                }
                                            });
                                    });
                            } else if (data.platform === PLATFORMS.Android) {
                                getui.pushAndroid(data.client_id,
                                    message.contentType,
                                    title,
                                    content,
                                    notifyMessage,
                                    data.status,
                                    function (err, result) {
                                        err != null ? console.error(err) : console.log(result);
                                        // 这段代码重复
                                        notifyMsg.saveNotifyMessage(member.member_code,
                                            message.contentType,
                                            title,
                                            content,
                                            notifyMessage,
                                            err != null ? 0 : 1,
                                            function (err, result) {
                                                if (err) {
                                                    console.log('Save group notify message failed: ', err);
                                                } else {
                                                }
                                            });
                                    });
                            }
                        } else {
                            // 这段代码重复
                            notifyMsg.saveNotifyMessage(member.member_code,
                                message.contentType,
                                title,
                                content,
                                notifyMessage,
                                0,
                                function (err, result) {
                                    if (err) {
                                        console.log('Save group notify message failed: ', err);
                                    } else {
                                    }
                                });
                        }
                    });
                    // 统计信息
                    var at = 0;
                    if (message.at == member.member_code) {
                        at = 1;
                    }
                    msgStats.updateGroupChatInfo(member.member_code,
                        message.group,
                        message.from,
                        at,
                        message.contentType,
                        message.content,
                        true,
                        function (err, result) {
                            if (err) console.log(err);
                        });
                }
            });
        });
    });
});
//--------------------------------------------------------------//
//----------------------------消息获取----------------------------//
//--------------------------------------------------------------//
/**
 * 按时间倒序获取私信。
 *
 * 参数:
 *  user_id 必须,医生ID
 *  peer_id 必须,对方医生ID
 *  message_start_id 可选,消息的起始ID,如果为空从最新的一条开始获取
 *  count 可选,消息数量,如果不指定、小于零或大于50,默认为50条
 *
 * 请求URL:
 *  /pm?user_id=sand&peer_id=Rose&message_start_id=0&count=20
 */
router.get(APIv1.Chats.PM, function (req, res)  {
    var userId = req.query.user_id;
    var peerId = req.query.peer_id;
    var msgStartId = req.query.message_start_id === null ? MAX_INT : parseInt(req.query.message_start_id);
    var count = req.query.count == null ? 50 : parsetInt(req.query.count);
    if (userId == null) {
        throw {httpStatus: 400, message: "Missing field."};
    }
    privateMsg.findMessage(userId, peerId, msgStartId, count, function (err, rows) {
        if (err) {
            log.error("Get private message failed, ", err);
            throw {httpStatus: 500, message: "Get private messages failed."};
        }
        var messages = {};
        messages.startId = msgStartId;
        messages.count = rows.length;
        messages.records = [];
        for (var nIndex = 0; nIndex < rows.length; nIndex++) {
            rows[nIndex].timestamp = Date.parse(new Date(rows[nIndex].timestamp));
            messages.records.notifyMsg(rows[nIndex]);
        }
        res.writeHead(200);
        res.write(messages);
        // 清空统计信息
        msgStats.clearP2PChatInfo(userId, peerId, function (err, result) {
            if (err) console.log(err);
        });
    });
});
/**
 * 按时间倒序获取群消息。
 *
 * 参数:
 *  group_id 必须,组ID
 *  user_id 必须,医生ID
 *  message_start_id 可选,消息的起始ID,如果为空从最新的一条开始获取
 *  count 可选,消息数量,如果不指定、小于零或大于50,默认为50条
 *
 * 请求URL:
 *  /gm?user_id=sand&group_id=discussionGroup&message_start_id=0&count=20
 */
router.get(APIv1.Chats.GM, function (req, res)  {
    var groupId = req.query.group_id;
    var userId = req.query.user_id;
    var msgStartId = req.query.message_start_id === null ? MAX_INT : parseInt(req.query.message_start_id);
    var count = req.query.count == null ? 50 : parsetInt(req.query.count);
    if (groupId == null) {
        throw {httpStatus: 400, message: "Missing field."};
    }
    groupMsg.findMessage(groupId, msgStartId, count, function (err, rows) {
        if (err) {
            console.log('Get group message failed: ', err);
            throw {httpStatus: 500, message: 'Get group message failed.'};
        }
        var messages = {};
        messages.startId = msgStartId;
        messages.count = rows.length;
        messages.records = [];
        for (var nIndex = 0; nIndex < rows.length; nIndex++) {
            rows[nIndex].timestamp = Date.parse(new Date(rows[nIndex].timestamp));
            messages.records.push(rows[nIndex]);
        }
        res.writeHead(200);
        res.write(messages);
        // 清空统计信息
        msgStats.clearGroupChatInfo(req.query.uid,
            userId,
            function (err, result) {
                if (err) console.log(err);
            });
    });
});
//--------------------------------------------------------------//
//----------------------------消息统计----------------------------//
//--------------------------------------------------------------//
/**
 * 获取参与的聊天列表,包括:点对点,@我,参与的讨论组。
 *
 * 请求URL:
 *  /api/v1/chats?user_id=sand
 */
router.get(APIv1.Chats.List, function (req, res) {
    var userId = req.query.user_id;
    if (userId === null) {
        throw {httpStatus: 406, message: 'Missing fields.'};
    }
    msgStats.getChatList(userId, function (err, rows) {
        if (err) {
            log.error('Get users chat list failed: ', err);
            res.status(500).send({message: 'Get users chat list failed.'});
            return;
        }
        if (rows.length == 0) {
            res.status(200).send([]);
            return;
        }
        var chats = new Array(rows.length);
        for (var i = 0; i < rows.length; i++) {
            var row = rows[i];
            var element = {
                userId: row.uid,
                peerId: row.from_uid,
                groupId: row.from_gid === undefined ? "" : row.from_gid,
                atMe: row.at_me === 1,
                messageType: row.msg_type,
                lastContentType: row.last_content_type,
                lastContent: row.last_content,
                newMessageCount: row.new_msg_count,
                timestamp: Date.parse(new Date(row.timestamp))
            };
            chats[i] = element;
        }
        res.status(200).send(chats);
    });
});
/**
 * 获取所有群组未读消息总数。
 *
 * 请求URL:
 *  /gm/unread_count?user_id=sand
 *
 * 参数:
 *  user_id:医生ID
 */
router.get(APIv1.Chats.GMUnreadCount, function (req, res) {
    var userId = req.query.user_id;
    if (userId === null) {
        throw {httpStatus: 406, message: 'Missing fields.'};
    }
    msgStats.getGroupChatAllUnRead(userId, function (err, result) {
        if (err) {
            console.log('Get all unread messages failed: ', err);
            res.status(500).send({message: 'Get all unread messages failed.'});
        }
        var data = {
            userId: userId,
            messageType: 2,
            newMessageCount: 0
        };
        if (result.length > 0) {
            for (var index = 0; index < result.length; index++) {
                data.newMessageCount += result[index].new_msg_count;
            }
        }
        res.send(data);
    });
});
/**
 * 获取特定群组消息统计情况。
 *
 * /gm/statistic?group_id=GGG&&user_id=sand
 *
 * 参数:
 *  user_id:信息所有者id
 *  group_id:群组id
 */
router.get(APIv1.Chats.GMStats, function (req, res) {
    var userId = req.query.user_id;
    var groupId = req.query.group_id;
    if (userId === null || groupId === null) {
        throw {httpStatus: 406, message: 'Miss fields.'};
    }
    msgStats.getGroupChatInfo(userId, groupId, function (err, result) {
        if (err) {
            console.log('Get group stats failed: ', err);
            throw {httpStatus: 500, message: 'Get group stats failed.'};
        }
        if (result.length == 0) {
            var data = {
                "userId": userId,
                "from": "",
                "groupId": groupId,
                "atMe": 0,
                "lastContentType": 1,
                "lastContent": "",
                "newMessageCount": 0,
                "timestamp": 0
            };
            res.write(data);
            return;
        }
        result[0].timestamp = Date.parse(new Date(result[0].timestamp));
        res.write(result[0]);
    });
});
/**
 * 获取与某人的私信统计。
 *
 * /pm/statistic?user_id=sand&&peer_id=rose
 *
 * 参数:
 *  user_id:信息所有者id
 *  peer_id:聊天对端id
 */
router.get(APIv1.Chats.PMStats, function (req, res) {
    var userId =  req.query.user_id;
    var peerId =  req.query.peer_id;
    if (userId == null || peerId == null) {
        throw {httpStatus: 406, message: "Missing fields."};
    }
    msgStats.getP2PChatInfo(userId, peerId, function (err, result) {
        if (err) {
            console.log("Get private messages stats failed: ", err);
            throw {httpStatus: 500, message: "Get private messages stats failed."};
        }
        if (result.length == 0) {
            var data = {
                "userId": userId,
                "peerId": peerId,
                "lastCContentType": 1,
                "lastContent": "",
                "newMessageCount": 0,
                "timestamp": 0
            };
            res.write(data);
            return;
        }
        result[0].timestamp = Date.parse(new Date(result[0].timestamp));
        res.write(result[0]);
    });
});
/**
 * 获取所有未读私信总数。
 *
 * /pm/unread_count?user_id=sand
 *
 * 参数:
 * uid:信息所有者id
 */
router.get(APIv1.Chats.PMUnreadCount, function (req, res) {
    var userId = req.query.user_id;
    msgStats.getP2PChatAllUnRead(userId, function (err, result) {
        if (err) {
            console.log("Get unread private message count failed: ", err);
            res.status(500).send({message: "Get unread private message count failed."});
        }
        var data = {userId: userId, messageType: 1, newMessageCount: 0};
        if (result.length > 0) {
            for (var i = 0; i < result.length; i++) {
                data.newMessageCount += result[i].new_msg_count;
            }
        }
        res.send(data);
    });
});
/**
 * 所有聊天消息未读数。
 *
 * 请求URL:
 *  /chats/unread_count?user_id=sand
 *
 * 参数:
 *  user_id:信息所有者id
 */
router.get(APIv1.Chats.UnreadMsgCount, function (req, res) {
    var userId = req.query.user_id;
    if (userId === null) {
        throw {httpStatus: 406, message: "Missing fields."};
    }
    msgStats.getChatAllUnRead(userId, function (err, result) {
        if (err) {
            console.error("Get all unread message count failed: ", err);
            res.status(500).send({message: "Get all unread message count failed."});
        }
        var data = {userId: userId, messageType: 0, newMessageCount: 0};
        if (result.length > 0) {
            for (var index = 0; index < result.length; index++) {
                data.newMessageCount += result[index].new_msg_count;
            }
        }
        res.send(data);
    });
});
module.exports = router;

+ 74 - 0
src/doctor/endpoints/management.endpoint.js

@ -0,0 +1,74 @@
/**
 * 管理端点。负责数据库,服务器状态等内容反馈。
 */
var express = require('express');
var router = express.Router();
var configFile = require('../include/commons').CONFIG_FILE;
var config = require('../resources/config/' + configFile);
var wlyyRepo = require('../repository/wlyy.repo');
var imRepo = require('../repository/im.repo');
var log = require('../util/log');
var APIv1 = require('../include/endpoints').APIv1;
var _ = require('underscore');
function getWlyyTables(handler) {
    wlyyRepo.execQuery({
        "sql": "SELECT table_name FROM information_schema.TABLES WHERE TABLE_SCHEMA = ?",
        "args": [config.wlyyDbConfig.database],
        "handler": handler
    });
}
function getImTables(handler) {
    imRepo.execQuery({
        "sql": "SELECT table_name FROM information_schema.TABLES WHERE TABLE_SCHEMA = ?",
        "args": [config.imDbConfig.database],
        "handler": handler
    });
}
function makeResponse(dbStatus, dbName, err, result) {
    if (err) {
        dbStatus.push({
            name: dbName,
            status: 'Failed',
            message: err
        });
    } else {
        var tableList = new Array();
        _.each(result, function (row) {
            tableList.push(row.table_name);
        });
        var status = {
            name: dbName,
            status: 'OK',
            tables: tableList
        };
        dbStatus.push(status);
    }
}
/**
 * 数据库检查,包括所有表,连接状态。
 */
router.get(APIv1.Management.DbStatus, function (req, res, next) {
    var dbStatus = new Array(0);
    getImTables(function (err, result) {
        makeResponse(dbStatus, config.imDbConfig.database, err, result);
        getWlyyTables(function (err, result) {
            makeResponse(dbStatus, config.wlyyDbConfig.database, err, result);
            res.json(dbStatus);
        });
    });
});
module.exports = router;

+ 0 - 48
src/doctor/endpoints/management.js

@ -1,48 +0,0 @@
/**
 * 管理控制器。
 */
var express = require('express');
var router = express.Router();
var endpoints = require('../include/endpoints').END_POINTS;
var wlyyRepo = require("../repository/wlyy.repo");
var imRepo = require("../repository/im.repo");
function getWlyyTables(handler) {
    wlyyRepo.execQuery({
        "sql": "SELECT table_name FROM information_schema.TABLES",
        "args": [],
        "handler": handler
    });
}
function getImTables(handler) {
    imRepo.execQuery({
        "sql": "SELECT table_name FROM information_schema.TABLES",
        "args": [],
        "handler": handler
    });
}
/**
 * 数据库检查,包括所有表,连接状态。
 */
router.get(endpoints.Management.DbStatus, function(req, res, next) {
    getWlyyTables(function(err, result) {
        if (err) {
            res.send({errno: -1, errmsg: 'open wlyy db error'});
            getImTables(function(err, result) {
                if (err) {
                    res.send({errno: -1, errmsg: 'open im db error'});
                } else {
                    res.send({errno: 0, errmsg: 'open im db success'});
                }
            });
        } else {
            res.send({errno: 0, errmsg: 'open wlyy db success'});
        }
    });
});
module.exports = router;

+ 0 - 793
src/doctor/endpoints/messages.js

@ -1,793 +0,0 @@
/**
 * 消息控制器。
 *
 * 此控制器处理点对点,组及消息消息。为三类消息提供发送及查询功能。
 */
var http = require('http');
var qs = require('querystring');
var getui = require('getui');
var endpoints = require('../include/endpoints').END_POINTS;
var express = require('express');
var router = express.Router();
var groupMsg = require("../models/msg.group");
var privateMsg = require('../models/msg.p2p');
var StatMsg = require("../models/msg.stat");
var systemMsg = require("../models/msg.system");
var user = require("../models/user");
var push = require("../models/push_notify");
/**
 * 发送P2P消息。
 *
 * from_uid=x&to_uid=xx&content=xxx&type=1
 *
 * 参数:
 * from_uid:发送者id
 * to_uid:接收者id
 * content:消息内容
 * type:消息类型:1文本,2图片,3语音
 */
router.post(endpoints.Msg.Privates, function (req, res, next) {
    if (req.query.from_uid == null
        || req.query.to_uid == null
        || req.query.content == null
        || req.query.type == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    privateMsg.isUserExist(req.query.to_uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get users from db error'});
            return;
        }
        if (result.length == 0) {
            res.send({errno: 2, errmsg: 'no receive users'});
            return;
        }
        // 保存一对一消息
        privateMsg.saveP2PMsg(req.query.to_uid, req.query.from_uid, req.query.type, req.query.content, function (err, result) {
            if (err) {
                res.send({errno: 3, errmsg: 'save msg to db error'});
                return;
            }
            res.send({errno: 0, errmsg: 'send p2p msg success'});
            // 更新自身的聊天统计信息
            StatMsg.updateP2PChatInfo(req.query.from_uid,
                req.query.to_uid,
                req.query.from_uid,
                req.query.type,
                req.query.content,
                function (err, result) {
                    if (err) {
                        console.log(err);
                    }
                });
            // 更新对端的聊天统计信息
            StatMsg.updateP2PChatInfo(req.query.to_uid,
                req.query.from_uid,
                req.query.from_uid,
                req.query.type,
                req.query.content,
                function (err, result) {
                    if (err) {
                        console.log(err);
                    }
                });
            // 推送通知消息给对端
            user.getUserbyID(req.query.to_uid, function (err, result) {
                if (err) {
                    console.log('group msg:get users by id from db failed');
                    return;
                }
                var title = '';
                var content = '';
                if (req.query.type == 1) {
                    title = '新消息';
                    content = req.query.content;
                } else if (req.query.type == 2) {
                    title = '新消息';
                    content = '接收到 [图片]';
                } else if (req.query.type == 3) {
                    title = '新消息';
                    content = '接收到 [语音]';
                } else {
                    title = '新消息';
                    content = '接收到一条新消息';
                }
                var bMustPush = 0;
                var data;
                if (result.length > 0) {
                    data = result[0];
                    if (data.is_online) {
                        bMustPush = 1;
                    }
                }
                var push_data = JSON.stringify({
                    type: 'p2p_msg',
                    from_uid: req.query.from_uid
                });
                // 保存通知到数据库中
                push.savePushNotify(req.query.to_uid,
                    req.query.type,
                    title,
                    content,
                    push_data,
                    bMustPush,
                    function (err, result) {
                        if (err) {
                            // 保存失败
                            console.log('save msg to db failed');
                        } else {
                            console.log('save msg to db success');
                            if (bMustPush == true) {
                                if (data.platform == 0) {// iOS
                                    getui.pushAPN(req.query.to_uid,
                                        data.token,
                                        req.query.type,
                                        title,
                                        content,
                                        push_data,
                                        function (err, result) {
                                            if (err != null) {
                                                console.log(err);
                                            } else {
                                                console.log(result);
                                            }
                                        });
                                } else {// Android
                                    getui.pushAndroid(data.client_id,
                                        req.query.type,
                                        title,
                                        content,
                                        push_data,
                                        data.status,
                                        function (err, result) {
                                            if (err != null) {
                                                console.log(err);
                                            } else {
                                                console.log(result);
                                            }
                                        });
                                }
                            }
                        }
                    });
            });
        });
    });
});
/**
 * 获取P2P消息。
 *
 * p2p/getmsg.im?uid=x&peer_uid=xx&start=0&count=20
 *
 * 参数:
 * uid:用户id
 * peer_uid:对端id
 * start;分页查询起始条目
 * count:查询条数
 * 备注:按时间倒序
 */
router.get(endpoints.Msg.Privates, function (req, res, next) {
    if (req.query.uid == null
        || req.query.peer_uid == null
        || req.query.start == null
        || req.query.count == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    privateMsg.getP2PMsg(req.query.uid, req.query.peer_uid, req.query.start, req.query.count, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get group msg from db error'});
            return;
        }
        var data = {};
        data.start = parseInt(req.query.start);
        data.count = result.length;
        data.list = new Array();
        for (var nIndex = 0; nIndex < result.length; nIndex++) {
            result[nIndex].timestamp = Date.parse(new Date(result[nIndex].timestamp));
            data.list.push(result[nIndex]);
        }
        res.send(data);
        // 清空统计信息
        StatMsg.clearP2PChatInfo(req.query.uid,
            req.query.peer_uid,
            function (err, result) {
                if (err) {
                    console.log(err);
                }
            });
    });
});
/**
 * 发送群组消息。
 *
 * group/sendmsg.im?from_uid=x&to_gid=xx&content=xxx&type=1&at_uid=xxxx&group_type
 *
 * 参数:
 * from_uid:发送者id
 * to_gid:群组id
 * content:消息内容
 * type:消息类型:1文本,2图片,3语音
 * at_uid:@用户id:1为行政组,null或者其他值为自定义组
 * group_type:区分是行政组还是自定义组,从不同表中查找组成员
 */
router.post(endpoints.Msg.Groups, function (req, res, next) {
    if (req.query.from_uid == null
        || req.query.to_gid == null
        || req.query.content == null
        || req.query.type == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    groupMsg.isGroupUser(req.query.from_uid, req.query.to_gid, req.query.group_type, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get users from db error'});
            return;
        }
        if (result.length == 0) {
            res.send({errno: 2, errmsg: 'no receive users'});
            return;
        }
        var at_uid = '';
        if (req.query.at_uid != null) {
            at_uid = req.query.at_uid;
        }
        // 保存群组消息
        groupMsg.saveGroupMsg(req.query.from_uid, req.query.to_gid, at_uid, req.query.type, req.query.content, function (err, result) {
            if (err) {
                res.send({errno: 3, errmsg: 'save msg to db error'});
                return;
            }
            res.send({errno: 0, errmsg: 'send group msg success'});
            // 统计信息
            StatMsg.updateGroupChatInfo(req.query.from_uid,
                req.query.to_gid,
                req.query.from_uid,
                0,
                req.query.type,
                req.query.content,
                false,
                function (err, result) {
                    if (err) {
                        console.log(err);
                    }
                });
            // 推送通知消息给群组各成员
            groupMsg.getGroupUsers(req.query.to_gid, req.query.group_type, function (err, result) {
                if (err) {
                    console.log('get users from db error');
                    return;
                }
                if (result.length == 0) {
                    console.log('no receive users');
                    return;
                }
                // 推送通知
                for (var nIndex = 0; nIndex < result.length; nIndex++) {
                    if (result[nIndex].member_code == req.query.from_uid) {
                        continue;
                    }
                    (function () {
                        var toUserID = result[nIndex].member_code;
                        user.getUserbyID(toUserID, function (err, result) {
                            var tmp = toUserID;
                            if (err) {
                                console.log('group msg:get users by id from db failed');
                                return;
                            }
                            var title = '';
                            var content = '';
                            if (req.query.type == 1) {
                                title = '群组消息';
                                content = req.query.content;
                            } else if (req.query.type == 2) {
                                title = '群组消息';
                                content = '接收到 [图片]';
                            } else if (req.query.type == 3) {
                                title = '群组消息';
                                content = '接收到 [语音]';
                            } else {
                                title = '群组消息';
                                content = '接收到一条新消息';
                            }
                            var bMustPush = 0;
                            var data;
                            if (result.length > 0) {
                                data = result[0];
                                if (data.is_online) {
                                    bMustPush = 1;
                                }
                            }
                            var push_data = JSON.stringify({
                                type: 'group_msg',
                                gid: req.query.to_gid
                            });
                            // 保存通知到数据库中
                            push.savePushNotify(toUserID,
                                req.query.type,
                                title,
                                content,
                                push_data,
                                bMustPush,
                                function (err, result) {
                                    if (err) {
                                        // 保存失败
                                        console.log('save msg to db failed');
                                    } else {
                                        console.log('save msg to db success');
                                        if (bMustPush == true) {
                                            if (data.platform == 0) {// iOS
                                                getui.pushAPN(toUserID,
                                                    data.token,
                                                    req.query.type,
                                                    title,
                                                    content,
                                                    push_data,
                                                    function (err, result) {
                                                        if (err != null) {
                                                            console.log(err);
                                                        } else {
                                                            console.log(result);
                                                        }
                                                    });
                                            } else {// Android
                                                getui.pushAndroid(data.client_id,
                                                    req.query.type,
                                                    title,
                                                    content,
                                                    push_data,
                                                    data.status,
                                                    function (err, result) {
                                                        if (err != null) {
                                                            console.log(err);
                                                        } else {
                                                            console.log(result);
                                                        }
                                                    });
                                            }
                                        }
                                    }
                                });
                        });
                        // 统计信息
                        var at_me = 0;
                        if (at_uid == toUserID) {
                            at_me = 1;
                        }
                        StatMsg.updateGroupChatInfo(toUserID,
                            req.query.to_gid,
                            req.query.from_uid,
                            at_me,
                            req.query.type,
                            req.query.content,
                            true,
                            function (err, result) {
                                if (err) {
                                    console.log(err);
                                }
                            });
                    })();
                }
            });
        });
    });
});
/**
 * 读取群组消息。
 *
 * :group/getmsg.im?uid=x&gid=xx&start=0&count=20
 *
 * 参数:
 * uid:用户id
 * gid:群组id
 * start;分页查询起始条目
 * count:查询条数
 * 备注:按时间倒序
 */
router.get(endpoints.Msg.Groups, function (req, res, next) {
    if (req.query.uid == null
        || req.query.gid == null
        || req.query.start == null
        || req.query.count == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    groupMsg.getGroupMsg(req.query.gid, req.query.start, req.query.count, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get group msg from db error'});
            return;
        }
        var data = {};
        data.start = parseInt(req.query.start);
        data.count = result.length;
        data.list = new Array();
        for (var nIndex = 0; nIndex < result.length; nIndex++) {
            result[nIndex].timestamp = Date.parse(new Date(result[nIndex].timestamp));
            data.list.push(result[nIndex]);
        }
        res.send(data);
        // 清空统计信息
        StatMsg.clearGroupChatInfo(req.query.uid,
            req.query.gid,
            function (err, result) {
                if (err) {
                    console.log(err);
                }
            });
    });
});
/**
 * 发送系统消息。
 *
 * system/sendmsg.im?to_uid=x&type=xx&title=xxx&content=xxxx&data=xxxxx
 *
 * 参数:
 * to_uid:消息接收者ID
 * type:消息类型
 * title:消息标题
 * content:推送消息提示内容
 * data:推送消息内容
 */
router.get(endpoints.Msg.System, function (req, res, next) {
    if (req.query.to_uid == null
        || req.query.title == null
        || req.query.type == null
        || req.query.content == null
        || req.query.data == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    user.getUserbyID(req.query.to_uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get users by id from db failed'});
            return;
        }
        var bMustPush = 0;
        var data;
        if (result.length > 0) {
            data = result[0];
            if (data.is_online) {
                bMustPush = 1;
            }
        }
        var push_data = JSON.stringify({
            type: req.query.type,
            data: req.query.data
        });
        // 保存该条推送信息
        systemMsg.saveSystemMsg(req.query.to_uid,
            req.query.type,
            req.query.title,
            req.query.content,
            req.query.data,
            function (err, result) {
                if (err) {
                    // 保存失败
                    res.send({errno: 2, errmsg: 'save msg to db failed'});
                } else {
                    // 保存通知到数据库中
                    push.savePushNotify(req.query.to_uid,
                        req.query.type,
                        req.query.title,
                        req.query.content,
                        push_data,
                        bMustPush,
                        function (err, result) {
                            if (err) {
                                // 保存失败
                                res.send({errno: 3, errmsg: 'save msg to db failed'});
                            } else {
                                res.send({errno: 0, errmsg: 'save msg to db success'});
                                if (bMustPush == true) {
                                    if (data.platform == 0) {// iOS
                                        getui.pushAPN(req.query.to_uid,
                                            data.token,
                                            req.query.type,
                                            req.query.title,
                                            req.query.content,
                                            push_data,
                                            function (err, result) {
                                                if (err != null) {
                                                    console.log(err);
                                                } else {
                                                    console.log(result);
                                                }
                                            });
                                    } else {// Android
                                        getui.pushAndroid(data.client_id,
                                            req.query.type,
                                            req.query.title,
                                            req.query.content,
                                            push_data,
                                            data.status,
                                            function (err, result) {
                                                if (err != null) {
                                                    console.log(err);
                                                } else {
                                                    console.log(result);
                                                }
                                            });
                                    }
                                }
                            }
                        });
                }
            });
    });
});
/**
 * 获取群组消息统计。
 *
 * statistic/getgroupchatinfo.im?uid=x&gid=xx
 *
 * 参数:
 * uid:信息所有者id
 * gid:群组id
 */
router.get(endpoints.Msg.GroupStat, function (req, res, next) {
    if (req.query.uid == null
        || req.query.gid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    StatMsg.getGroupChatInfo(req.query.uid, req.query.gid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get statistic from db error'});
            return;
        }
        if (result.length == 0) {
            var data = {"uid":req.query.uid,"from_uid":"","from_gid":req.query.gid,"at_me":0,"last_content_type":1,"last_content":"","new_msg_count":0,"timestamp":0};
            res.send(data);
            return;
        }
        result[0].timestamp = Date.parse(new Date(result[0].timestamp));
        res.send(result[0]);
    });
});
/**
 * 获取点对点消息统计。
 *
 * statistic/getp2pchatinfo.im?uid=x&peer_uid=xx
 *
 * 参数:
 * uid:信息所有者id
 * peer_uid:聊天对端id
 */
router.get(endpoints.Msg.PrivateStat, function (req, res, next) {
    if (req.query.uid == null
        || req.query.peer_uid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    StatMsg.getP2PChatInfo(req.query.uid, req.query.peer_uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get statistic from db error'});
            return;
        }
        if (result.length == 0) {
            var data = {"uid":req.query.uid,"from_uid":req.query.peer_uid,"last_content_type":1,"last_content":"","new_msg_count":0,"timestamp":0};
            res.send(data);
            return;
        }
        result[0].timestamp = Date.parse(new Date(result[0].timestamp));
        res.send(result[0]);
    });
});
/**
 * 获取参与的聊天列表,包括:点对点,@我,参与的讨论组等。
 *
 * statistic/getchatlist.im?uid=x
 *
 * 参数:
 * uid:信息所有者id
 */
router.get(endpoints.Users.ChatList, function (req, res, next) {
    if (req.query.uid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    StatMsg.getChatList(req.query.uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get statistic from db error'});
            return;
        }
        if (result.length == 0) {
            var data = {"uid":req.query.uid,"from_uid":req.query.peer_uid,"last_content_type":1,"last_content":"","new_msg_count":0,"timestamp":0};
            res.send(data);
            return;
        }
        //result[0].timestamp = Date.parse(new Date(result[0].timestamp));
        for (var index = 0; index < result.length; index++) {
            result[index].timestamp = Date.parse(new Date(result[index].timestamp));
        }
        res.send(result);
    });
});
/**
 * 群组聊天消息所有未读数。
 *
 * statistic/getgroupunreadcount.im?uid=x
 *
 * 参数:
 * uid:信息所有者id
 */
router.get(endpoints.Users.GroupUnreadMsgCount, function (req, res, next) {
    if (req.query.uid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    StatMsg.getGroupChatAllUnRead(req.query.uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get statistic from db error'});
            return;
        }
        var data = {"uid":req.query.uid,"msg_type":2,"new_msg_count":0};
        if (result.length == 0) {
            res.send(data);
            return;
        }
        var count = 0;
        var index = 0;
        var length = result.length;
        for (; index < length; index++) {
            count += result[index].new_msg_count;
        }
        data.new_msg_count = count;
        res.send(data);
    });
});
/**
 * 一对一聊天消息所有未读数。
 *
 * statistic/getp2punreadcount.im?uid=x
 *
 * 参数:
 * uid:信息所有者id
 */
router.get(endpoints.Users.PrivateUnreadMsgCount, function (req, res, next) {
    if (req.query.uid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    StatMsg.getP2PChatAllUnRead(req.query.uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get statistic from db error'});
            return;
        }
        var data = {"uid":req.query.uid,"msg_type":1,"new_msg_count":0};
        if (result.length == 0) {
            res.send(data);
            return;
        }
        var count = 0;
        var index = 0;
        var length = result.length;
        for (; index < length; index++) {
            count += result[index].new_msg_count;
        }
        data.new_msg_count = count;
        res.send(data);
    });
});
/**
 * 所有聊天消息未读数。
 *
 * statistic/getallunreadmsgcount.im?uid=x
 *
 * 参数:
 * uid:信息所有者id
 */
router.get(endpoints.Users.UnreadMsgCount, function (req, res, next) {
    if (req.query.uid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    StatMsg.getChatAllUnRead(req.query.uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get statistic from db error'});
            return;
        }
        var data = {"uid":req.query.uid,"msg_type":0,"new_msg_count":0};
        if (result.length == 0) {
            res.send(data);
            return;
        }
        var count = 0;
        var index = 0;
        var length = result.length;
        for (; index < length; index++) {
            count += result[index].new_msg_count;
        }
        data.new_msg_count = count;
        res.send(data);
    });
});
/**
 * 获取角标数。
 *
 * statistic/getbadgenum.im?uid=x
 *
 * 参数:
 * uid:用户id
 */
router.get('/getbadgenum.im', function (req, res, next) {
    if (req.query.uid == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    StatMsg.getBadgeNumber(req.query.uid, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'get badge error'});
            return;
        }
        var data = {"uid":req.query.uid,"badge":result};
        res.send(data);
    });
});
module.exports = router;

+ 130 - 0
src/doctor/endpoints/users.endpoint.js

@ -0,0 +1,130 @@
/**
 * 用户端点。
 */
var express = require('express');
var router = express.Router();
var user = require("../models/user");
var http = require('http');
var configFile = require('../include/commons').CONFIG_FILE;
var config = require('../resources/config/' + configFile);
var log = require("../util/log.js");
var objectUtil = require('../util/objectUtil');
var APIv1 = require('../include/endpoints').APIv1;
/**
 * 登录。用户登录后,更新数据库中的在线状态。
 *
 * 请求URL:
 *  /users/login?user_id=sand&token=0PFWlKmLBN9YzhCfFWVgYA&client_id=H6FYbDejks6VjMmW3uH7V6&platform=0
 *
 * 参数说明:
 *  user_id:用户ID
 *  token:个推的token
 *  client_id:个推的client id
 *  platform:平台类型,0为iOS,1为Android
 */
router.get(APIv1.Users.Login, function (req, res) {
    var userId = req.query.user_id;
    var token = req.query.token;
    var clientId = req.query.client_id;
    var platform = req.query.platform;
    if (userId == null) {
        throw {httpStatus: 406, message: 'Missing field "user_id".'};
    }
    if (token == null) {
        throw {httpStatus: 406, message: 'Missing field "token".'};
    }
    if (clientId == null) {
        throw {httpStatus: 406, message: 'Missing field "client_id".'};
    }
    if (platform == null) {
        throw {httpStatus: 406, message: 'Missing field "platform".'};
    }
    user.deleteToken(token, function (err, result) {
        if (err) {
            log.error('Error occurs when user login and delete token: ', err);
            res.status(500).send({message: 'Error occurs when user login and delete token.'});
        } else {
            user.login(userId,
                token,
                clientId,
                platform,
                function (err, result) {
                    if (err) {
                        log.error('Error occurs when user login and delete token: ', err);
                        res.status(500).send({message: 'Error occurs when user login.'});
                    }
                    res.status(200).end();
                });
        }
    });
});
/**
 * 退出。
 *
 * 请求URL:
 *  /users/logout?user_id=sand
 *
 * 参数:
 *  user_id:用户ID
 */
router.get(APIv1.Users.Logout, function (req, res) {
    if (req.query.user_id == null) {
        throw {httpStatus: 406, message: 'Validation Failed. Missing user id field.'};
    }
    user.logout(req.query.user_id,
        function (err, result) {
            if (err) {
                log.error(err.message);
                res.status(416).send({message: 'Logout failed.'});
            }
            res.status(200).end();
        });
});
/**
 * 更新客户端状态。
 *
 * 请求URL:
 *  /users/:user_id/status
 *
 * POST参数格式:
 *  {status: 1}, app状态,0在后台,1在前台
 */
router.post(APIv1.Users.UserStatus, function (req, res) {
    var userId = req.param('user_id');
    var status = req.body;
    if (!objectUtil.isJsonObject(status)) {
        throw {httpStatus: 406, message: "Problems parsing json."};
    }
    if (userId === null || (status.status != 0 && status.status != 1)) {
        throw {httpStatus: 406, message: 'Validation Failed. Missing fields.'};
    }
    user.updateStatus(userId, status.status,
        function (err, result) {
            if (err) {
                log.error("Update user status failed: ", err.message);
                res.status(500).send({message: 'Update users status failed.'});
            }
            res.status(200).end();
        });
});
module.exports = router;

+ 0 - 93
src/doctor/endpoints/users.js

@ -1,93 +0,0 @@
/**
 * 用户控制器。
 */
var express = require('express');
var router = express.Router();
var user = require("../models/user");
var http = require('http');
var qs = require('querystring');
/**
 * 登录:/users/login.im?user_id=x&token=xx&client_id=xxx&platform=0
 * 参数:
 * user_id:用户ID
 * token:个推的token
 * client_id:个推的clientid
 * platform:平台类型:0为ios,1为android
 */
router.get('/login.im', function (req, res, next) {
    if (req.query.user_id == null
        || req.query.token == null
        || req.query.client_id == null
        || req.query.platform == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    user.deleteToken(req.query.token, function (err, result) {
        if (err) {
            res.send({errno: 1, errmsg: 'delete token error'});
            return;
        }
        user.login(req.query.user_id,
            req.query.token,
            req.query.client_id,
            req.query.platform,
            function (err, result) {
                if (err) {
                    res.send({errno: 2, errmsg: 'update users status error'});
                    return;
                }
                res.send({errno: 0, errmsg: 'login successful'});
            });
        });
});
/**
 * 登出:/users/logout.im?user_id=x
 * 参数:
 * user_id:用户ID
 */
router.get('/logout.im', function (req, res, next) {
    if (req.query.user_id == null) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    user.logout(req.query.user_id,
        function (err, result) {
            if (err) {
                res.send({errno: 255, errmsg: 'update users status error'});
                return;
            }
            res.send({errno: 0, errmsg: 'logout successful'});
        });
});
/**
 * 更新app状态:/users/updatestatus.im?user_id=x&status=1
 * 参数:
 * user_id:用户ID
 * status:App状态,0在后台,1在前台
 */
router.get('/updatestatus.im', function (req, res, next) {
    if (req.query.user_id == null || (req.query.status != 0 && req.query.status != 1)) {
        res.send({errno: -1, errmsg: 'parameter error'});
        return;
    }
    user.updateStatus(req.query.user_id, req.query.status,
        function (err, result) {
            if (err) {
                res.send({errno: 255, errmsg: 'update users status error'});
                return;
            }
            res.send({errno: 0, errmsg: 'update status successful'});
        });
});
module.exports = router;

+ 38 - 1
src/doctor/include/commons.js

@ -1,3 +1,12 @@
/**
 * 此文件内容为常用的系统枚举及变量。
 */
/**
 * 系统部署时的配置文件。
 *
 * @type {string}
 */
var configFile = "config.";
var configFile = "config.";
if (process.env.prod !== undefined) {
if (process.env.prod !== undefined) {
@ -8,4 +17,32 @@ if (process.env.prod !== undefined) {
    configFile += "dev";
    configFile += "dev";
}
}
exports.CONFIG_FILE = configFile;
/**
 *  消息类型定义。
 */
var channels = {
    System: "0",
    Private: "1",
    Group: "2"
};
/**
 * 客户端平台。
 */
var platform = {
    iOS: "0",
    Android: "1"
};
/**
 * 组类型。
 */
var groupType = {
    AdminTeam: "1",
    DiscussionGroup: "2"
};
exports.CHANNELS = channels;
exports.CONFIG_FILE = configFile;
exports.PLATFORM = platform;
exports.GROUP_TYPE = groupType;

+ 44 - 18
src/doctor/include/endpoints.js

@ -3,32 +3,58 @@
 *
 *
 * 若想访问包含页面的内容,请在相应的API后面添加.html。
 * 若想访问包含页面的内容,请在相应的API后面添加.html。
 */
 */
var endpoints = {
    Msg: {
        Base: "/message",
        Privates: '/privates',
        PrivateStat: '/privates/stat',
        Groups: '/groups',
        GroupStat: '/groups/stat',
        System: '/system',
var APIv1 = {
    Application: {
        Base: '/api/v1/application',
        BadgeNo: '/badge_no'
        BadgeNo: '/badge_no'
    },
    },
    Chats: {
        Base: "/api/v1/chats",
        List: "/list",
        // 未读消息数
        UnreadMsgCount: '/unread_count',
        // 私信
        PM: '/pm',
        PMUnreadCount: '/pm/unread_count',
        PMStats: '/pm/statistic',
        // 组信
        GM: '/gm',
        GMUnreadCount: '/gm/unread_count',
        GMStats: '/gm/statistic',
        //系统消息
        SM: '/sm'
    },
    Users: {
    Users: {
        Base: '/users',
        Base: '/api/v1/users',
        Login: '/login',
        Logout: '/logout',
        User: '/users/:user_id',
        ChatList: '/users/:user_id/chat_list',
        GroupUnreadMsgCount: '/users/:user_id/groups/:group_id/unread_count',
        PrivateUnreadMsgCount: '/users/:user_id/private/unread_count',
        UnreadMsgCount: '/users/:user_id/message/unread_count'
        User: '/:user_id',
        UserStatus: '/:user_id/status'
    },
    },
    Management: {
    Management: {
        Base: '/management',
        Base: '/api/v1/management',
        Health: '/health',
        DbStatus: '/db'
    }
};
        DbStatus: "/db"
var pages = {
    Home: {
        Index: '/'
    }
    }
};
};
module.exports.END_POINTS = endpoints;
module.exports.PAGES = pages;
module.exports.APIv1 = APIv1;

+ 0 - 10
src/doctor/include/pages.js

@ -1,10 +0,0 @@
/**
 * 所有的页面URL。提供部分对REST API返回值的可视化展示或便捷操作页面入口。
 */
var pages = {
    Home: {
        Index: '/'
    }
};
exports.PAGES = pages;

+ 60 - 0
src/doctor/models/group.js

@ -0,0 +1,60 @@
/**
 * 群组模型。此数据来源于家庭医生平台数据库的行政团队。
 *
 * 实际团队数据分为两部分:行政团队与临时讨论组,具体的与业务相关。医生在讨论组里,
 * 会出现两种:在行政团队内聊天,也可以在临时讨论组里聊天。
 */
"use strict";
var wlyyRepo = require("../repository/wlyy.repo");
var groupTypes = require('../include/commons').GROUP_TYPE;
/**
 * 判断是否为团队成员。
 *
 * @param groupId
 * @param groupType
 * @param doctorId
 * @param handler
 */
function isGroupMember(groupId, groupType, doctorId, handler){
    if(groupType == groupTypes.AdminTeam) {
        wlyyRepo.execQuery({
            "sql": "SELECT doctor_code from wlyy_admin_team_member WHERE team_id=? and doctor_code=?",
            "args": [groupId, doctorId],
            "handler": handler
        });
    }else {
        wlyyRepo.execQuery({
            "sql": "SELECT member_code from wlyy_talk_group_member WHERE group_code=? and member_code=?",
            "args": [groupId, doctorId],
            "handler": handler
        });
    }
}
/**
 * 获取团队成员。
 *
 * @param groupId
 * @param groupType
 * @param handler
 */
function getMembers(groupId, groupType, handler) {
    if(groupType == groupTypes.AdminTeam) {
        wlyyRepo.execQuery({
            "sql": "SELECT doctor_code from wlyy_admin_team_member WHERE team_id=? AND available = 1",
            "args": [groupId],
            "handler": handler
        });
    } else {
        wlyyRepo.execQuery({
            "sql": "SELECT member_code from wlyy_talk_group_member WHERE group_code=?",
            "args": [groupId],
            "handler": handler
        });
    }
}
exports.isGroupMember = isGroupMember;
exports.getMembers = getMembers;

+ 6 - 42
src/doctor/models/msg.group.js

@ -1,62 +1,26 @@
"use strict";
"use strict";
var log = require('../util/log');
var wlyyRepo = require("../repository/wlyy.repo");
var imRepo = require("../repository/im.repo");
var imRepo = require("../repository/im.repo");
function isGroupUser(user_id, group_id, group_type, handler) {
	if (group_type == 1) {
		wlyyRepo.execQuery({
			"sql": "SELECT doctor_code from wlyy_admin_team_member WHERE team_id=? and doctor_code=?",
			"args": [group_id, user_id],
			"handler": handler
		});
	} else {
		wlyyRepo.execQuery({
			"sql": "SELECT member_code from wlyy_talk_group_member WHERE group_code=? and member_code=?",
			"args": [group_id, user_id],
			"handler": handler
		});
	}
}
function getGroupUsers(group_id, group_type, handler) {
	if (group_type == 1) {
		wlyyRepo.execQuery({
			"sql": "SELECT doctor_code from wlyy_admin_team_member WHERE team_id=?",
			"args": [group_id],
			"handler": handler
		});
	} else {
		wlyyRepo.execQuery({
			"sql": "SELECT member_code from wlyy_talk_group_member WHERE group_code=?",
			"args": [group_id],
			"handler": handler
		});
	}
}
function saveGroupMsg(user_id, group_id, at_uid, type, content, handler) {
function saveMessage(from, groupId, at, contentType, content, handler) {
	imRepo.execQuery({
	imRepo.execQuery({
		"sql": "INSERT INTO groups (to_gid,from_uid,at_uid,type,content) VALUES (?,?,?,?,?)",
		"sql": "INSERT INTO groups (to_gid,from_uid,at_uid,type,content) VALUES (?,?,?,?,?)",
		"args": [group_id, user_id, at_uid, type, content],
		"args": [groupId, from, at, contentType, content],
		"handler": handler
		"handler": handler
	});
	});
}
}
function getGroupMsg(group_id, start, count, handler) {
function findMessage(groupId, start, count, handler) {
	var sql = "SELECT from_uid,at_uid,type,content,timestamp from groups WHERE to_gid = ? GROUP BY timestamp DESC LIMIT ";
	var sql = "SELECT from_uid,at_uid,type,content,timestamp from groups WHERE to_gid = ? GROUP BY timestamp DESC LIMIT ";
	sql += start;
	sql += start;
	sql += ",";
	sql += ",";
	sql += count;
	sql += count;
	imRepo.execQuery({
	imRepo.execQuery({
		"sql": sql,
		"sql": sql,
		"args": [group_id],
		"args": [groupId],
		"handler": handler
		"handler": handler
	});
	});
}
}
exports.isGroupUser = isGroupUser;
exports.getGroupUsers = getGroupUsers;
exports.saveGroupMsg = saveGroupMsg;
exports.getGroupMsg = getGroupMsg;
exports.saveMessage = saveMessage;
exports.findMessage = findMessage;

+ 18 - 0
src/doctor/models/msg.notify.js

@ -0,0 +1,18 @@
/**
 * 保存服务端向客户端发送的“推送消息”。当用户A向用户B发送消息,服务器会保存此消息,并构建一条通知消息保存到数据库,
 * 并通过个推系统向B发送此消息。
 */
"use strict";
var log = require('../util/log');
var imRepo = require("../repository/im.repo");
function saveNotifyMessage(to, contentType, title, content, message, has_pushed, handler) {
    imRepo.execQuery({
        "sql": "INSERT INTO push_notify (to_uid,type,title,content,data,has_pushed) VALUES (?,?,?,?,?,?)",
        "args": [to, contentType, title, content, message, has_pushed],
        "handler": handler
    });
}
exports.saveNotifyMessage = saveNotifyMessage;

+ 0 - 37
src/doctor/models/msg.p2p.js

@ -1,37 +0,0 @@
"use strict";
var log = require('../util/log');
var wlyyRepo = require("../repository/wlyy.repo");
var imRepo = require("../repository/im.repo");
function isUserExist(to_uid, handler) {
	wlyyRepo.execQuery({
		"sql": "SELECT count(*) from wlyy_doctor WHERE code=?",
		"args": [to_uid],
		"handler": handler
	});
}
function saveP2PMsg(to_uid, from_uid, type, content, handler) {
	imRepo.execQuery({
		"sql": "INSERT INTO privates (to_uid,from_uid,type,content) VALUES (?,?,?,?)",
		"args": [to_uid, from_uid, type, content],
		"handler": handler
	});
}
function getP2PMsg(to_uid, from_uid, start, count, handler) {
	var sql = "SELECT to_uid,from_uid,type,content,timestamp from privates WHERE (to_uid=? AND from_uid=?) OR (to_uid=? AND from_uid=?) GROUP BY timestamp DESC LIMIT ";
	sql += start;
	sql += ",";
	sql += count;
	imRepo.execQuery({
		"sql": sql,
		"args": [to_uid, from_uid, from_uid, to_uid],
		"handler": handler
	});
}
exports.isUserExist = isUserExist;
exports.saveP2PMsg = saveP2PMsg;
exports.getP2PMsg = getP2PMsg;

+ 25 - 0
src/doctor/models/msg.private.js

@ -0,0 +1,25 @@
"use strict";
var imRepo = require("../repository/im.repo");
function saveMessage(to, from, type, content, handler) {
	imRepo.execQuery({
		"sql": "INSERT INTO privates (to_uid,from_uid,type,content) VALUES (?,?,?,?)",
		"args": [to, from, type, content],
		"handler": handler
	});
}
function findMessage(to, from, start, count, handler) {
	var sql = "SELECT id, to_uid, from_uid, type, content, timestamp from privates " +
        "WHERE (to_uid=? AND from_uid=?) OR (to_uid=? AND from_uid=?) AND id < ? GROUP BY timestamp DESC LIMIT ?";
	imRepo.execQuery({
		"sql": sql,
		"args": [to, from, from, to, start, count],
		"handler": handler
	});
}
exports.save = saveMessage;
exports.findMessage = findMessage;

+ 13 - 35
src/doctor/models/msg.stat.js

@ -14,13 +14,13 @@ function updateGroupChatInfo(user_id, group_id, from_uid, at_me, type, content,
	var uuid = user_id + '_' + group_id;
	var uuid = user_id + '_' + group_id;
    if (msg_count_plus_one) {
    if (msg_count_plus_one) {
        imRepo.execQuery({
        imRepo.execQuery({
            "sql": "INSERT INTO statistic (uid,uuid,from_uid,from_gid,at_me,msg_type,last_content_type,last_content,new_msg_count) VALUES (?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE from_uid=?,at_me=?,last_content_type=?,last_content=?,new_msg_count=new_msg_count+1",
            "sql": "INSERT INTO msg_statistic (uid,uuid,from_uid,from_gid,at_me,msg_type,last_content_type,last_content,new_msg_count) VALUES (?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE from_uid=?,at_me=?,last_content_type=?,last_content=?,new_msg_count=new_msg_count+1",
            "args": [user_id, uuid, from_uid, group_id, at_me, 2, type, content, 1, from_uid, at_me, type, content],
            "args": [user_id, uuid, from_uid, group_id, at_me, 2, type, content, 1, from_uid, at_me, type, content],
            "handler": handler
            "handler": handler
        });
        });
    } else {
    } else {
        imRepo.execQuery({
        imRepo.execQuery({
            "sql": "INSERT INTO statistic (uid,uuid,from_uid,from_gid,at_me,msg_type,last_content_type,last_content,new_msg_count) VALUES (?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE from_uid=?,at_me=?,last_content_type=?,last_content=?",
            "sql": "INSERT INTO msg_statistic (uid,uuid,from_uid,from_gid,at_me,msg_type,last_content_type,last_content,new_msg_count) VALUES (?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE from_uid=?,at_me=?,last_content_type=?,last_content=?",
            "args": [user_id, uuid, from_uid, group_id, at_me, 2, type, content, 0, from_uid, at_me, type, content],
            "args": [user_id, uuid, from_uid, group_id, at_me, 2, type, content, 0, from_uid, at_me, type, content],
            "handler": handler
            "handler": handler
        });
        });
@ -32,14 +32,14 @@ function updateP2PChatInfo(user_id, peer_uid, from_uid, type, content, handler)
    if (user_id == from_uid) {
    if (user_id == from_uid) {
        // 更新自身的统计信息
        // 更新自身的统计信息
        imRepo.execQuery({
        imRepo.execQuery({
            "sql": "INSERT INTO statistic (uid,uuid,from_uid,peer_uid,msg_type,last_content_type,last_content,new_msg_count) VALUES (?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE peer_uid=?,last_content_type=?,last_content=?",
            "sql": "INSERT INTO msg_statistic (uid,uuid,from_uid,peer_uid,msg_type,last_content_type,last_content,new_msg_count) VALUES (?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE peer_uid=?,last_content_type=?,last_content=?",
            "args": [user_id, uuid, from_uid, peer_uid, 1, type, content, 0, peer_uid, type, content],
            "args": [user_id, uuid, from_uid, peer_uid, 1, type, content, 0, peer_uid, type, content],
            "handler": handler
            "handler": handler
        });
        });
    } else {
    } else {
        // 更新对端的统计信息
        // 更新对端的统计信息
        imRepo.execQuery({
        imRepo.execQuery({
            "sql": "INSERT INTO statistic (uid,uuid,from_uid,peer_uid,msg_type,last_content_type,last_content,new_msg_count) VALUES (?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE peer_uid=?,last_content_type=?,last_content=?,new_msg_count=new_msg_count+1",
            "sql": "INSERT INTO msg_statistic (uid,uuid,from_uid,peer_uid,msg_type,last_content_type,last_content,new_msg_count) VALUES (?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE peer_uid=?,last_content_type=?,last_content=?,new_msg_count=new_msg_count+1",
            "args": [user_id, uuid, from_uid, peer_uid, 1, type, content, 1, peer_uid, type, content],
            "args": [user_id, uuid, from_uid, peer_uid, 1, type, content, 1, peer_uid, type, content],
            "handler": handler
            "handler": handler
        });
        });
@ -49,7 +49,7 @@ function updateP2PChatInfo(user_id, peer_uid, from_uid, type, content, handler)
function clearGroupChatInfo(user_id, group_id, handler) {
function clearGroupChatInfo(user_id, group_id, handler) {
	var uuid = user_id + '_' + group_id;
	var uuid = user_id + '_' + group_id;
	imRepo.execQuery({
	imRepo.execQuery({
		"sql": "UPDATE statistic SET new_msg_count='0' WHERE uuid=?",
		"sql": "UPDATE msg_statistic SET new_msg_count='0' WHERE uuid=?",
		"args": [uuid],
		"args": [uuid],
		"handler": handler
		"handler": handler
	});
	});
@ -58,7 +58,7 @@ function clearGroupChatInfo(user_id, group_id, handler) {
function clearP2PChatInfo(user_id, peer_uid, handler) {
function clearP2PChatInfo(user_id, peer_uid, handler) {
	var uuid = user_id + '_' + peer_uid;
	var uuid = user_id + '_' + peer_uid;
	imRepo.execQuery({
	imRepo.execQuery({
		"sql": "UPDATE statistic SET new_msg_count='0' WHERE uuid=?",
		"sql": "UPDATE msg_statistic SET new_msg_count='0' WHERE uuid=?",
		"args": [uuid],
		"args": [uuid],
		"handler": handler
		"handler": handler
	});
	});
@ -67,7 +67,7 @@ function clearP2PChatInfo(user_id, peer_uid, handler) {
function getGroupChatInfo(user_id, group_id, handler) {
function getGroupChatInfo(user_id, group_id, handler) {
	var uuid = user_id + '_' + group_id;
	var uuid = user_id + '_' + group_id;
	imRepo.execQuery({
	imRepo.execQuery({
		"sql": "SELECT uid,from_uid,from_gid,at_me,last_content_type,last_content,new_msg_count,timestamp from statistic WHERE uuid = ?",
		"sql": "SELECT uid,from_uid,from_gid,at_me,last_content_type,last_content,new_msg_count,timestamp from msg_statistic WHERE uuid = ?",
		"args": [uuid],
		"args": [uuid],
		"handler": handler
		"handler": handler
	});
	});
@ -76,7 +76,7 @@ function getGroupChatInfo(user_id, group_id, handler) {
function getP2PChatInfo(user_id, peer_uid, handler) {
function getP2PChatInfo(user_id, peer_uid, handler) {
	var uuid = user_id + '_' + peer_uid;
	var uuid = user_id + '_' + peer_uid;
	imRepo.execQuery({
	imRepo.execQuery({
		"sql": "SELECT uid,from_uid,last_content_type,last_content,new_msg_count,timestamp from statistic WHERE uuid = ?",
		"sql": "SELECT uid,from_uid,last_content_type,last_content,new_msg_count,timestamp from msg_statistic WHERE uuid = ?",
		"args": [uuid],
		"args": [uuid],
		"handler": handler
		"handler": handler
	});
	});
@ -84,7 +84,7 @@ function getP2PChatInfo(user_id, peer_uid, handler) {
function getChatList(user_id, handler) {
function getChatList(user_id, handler) {
    imRepo.execQuery({
    imRepo.execQuery({
        "sql": "SELECT uid,from_uid,from_gid,peer_uid,at_me,msg_type,last_content_type,last_content,new_msg_count,timestamp from statistic WHERE uid = ?",
        "sql": "SELECT uid,from_uid,from_gid,peer_uid,at_me,msg_type,last_content_type,last_content,new_msg_count,timestamp from msg_statistic WHERE uid = ?",
        "args": [user_id],
        "args": [user_id],
        "handler": handler
        "handler": handler
    });
    });
@ -92,7 +92,7 @@ function getChatList(user_id, handler) {
function getGroupChatAllUnRead(user_id, handler) {
function getGroupChatAllUnRead(user_id, handler) {
	imRepo.execQuery({
	imRepo.execQuery({
		"sql": "SELECT new_msg_count from statistic WHERE uid=? AND msg_type=2 AND new_msg_count>0",
		"sql": "SELECT new_msg_count from msg_statistic WHERE uid=? AND msg_type=2 AND new_msg_count>0",
		"args": [user_id],
		"args": [user_id],
		"handler": handler
		"handler": handler
	});
	});
@ -100,7 +100,7 @@ function getGroupChatAllUnRead(user_id, handler) {
function getP2PChatAllUnRead(user_id, handler) {
function getP2PChatAllUnRead(user_id, handler) {
	imRepo.execQuery({
	imRepo.execQuery({
		"sql": "SELECT new_msg_count from statistic WHERE uid=? AND msg_type=1 AND new_msg_count>0",
		"sql": "SELECT new_msg_count from msg_statistic WHERE uid=? AND msg_type=1 AND new_msg_count>0",
		"args": [user_id],
		"args": [user_id],
		"handler": handler
		"handler": handler
	});
	});
@ -108,7 +108,7 @@ function getP2PChatAllUnRead(user_id, handler) {
function getChatAllUnRead(user_id, handler) {
function getChatAllUnRead(user_id, handler) {
	imRepo.execQuery({
	imRepo.execQuery({
		"sql": "SELECT new_msg_count from statistic WHERE uid=? AND new_msg_count>0",
		"sql": "SELECT new_msg_count from msg_statistic WHERE uid=? AND new_msg_count>0",
		"args": [user_id],
		"args": [user_id],
		"handler": handler
		"handler": handler
	});
	});
@ -116,7 +116,7 @@ function getChatAllUnRead(user_id, handler) {
function getAppMsgAmount(user_id, handler) {
function getAppMsgAmount(user_id, handler) {
    wlyyRepo.execQuery({
    wlyyRepo.execQuery({
        "sql": "SELECT imei,token from wlyy_token WHERE users=?",
        "sql": "SELECT imei,token from wlyy_token WHERE user=?",
        "args": [user_id],
        "args": [user_id],
        "handler": function(err, result) {
        "handler": function(err, result) {
            if (err) {
            if (err) {
@ -158,29 +158,7 @@ function getAppMsgAmount(user_id, handler) {
function getBadgeNumber(user_id, handler) {
function getBadgeNumber(user_id, handler) {
    async.parallel([
    async.parallel([
            function(callback) {
            function(callback) {
                // 此版本app界面未显示im的未读条数
                callback(null, 0);
                callback(null, 0);
                /*
                getChatAllUnRead(user_id, function(err, result) {
                    if (err) {
                        callback(null, 0);
                        return;
                    }
                    if (result.length == 0) {
                        callback(null, 0);
                        return;
                    }
                    var count = 0;
                    var index = 0;
                    var length = result.length;
                    for (; index < length; index++) {
                        count += result[index].new_msg_count;
                    }
                    callback(null, count);
                });
                */
            },
            },
            function(callback) {
            function(callback) {

+ 3 - 3
src/doctor/models/msg.system.js

@ -3,12 +3,12 @@
var log = require('../util/log');
var log = require('../util/log');
var imRepo = require("../repository/im.repo");
var imRepo = require("../repository/im.repo");
function saveSystemMsg(to_uid, type, title, content, data, handler) {
function save(to, contentType, title, summary, content, handler) {
    imRepo.execQuery({
    imRepo.execQuery({
        "sql": "INSERT INTO system (to_uid,type,title,content,data) VALUES (?,?,?,?,?)",
        "sql": "INSERT INTO system (to_uid,type,title,content,data) VALUES (?,?,?,?,?)",
        "args": [to_uid, type, title, content, data],
        "args": [to, contentType, title, summary, content],
        "handler": handler
        "handler": handler
    });
    });
}
}
exports.saveSystemMsg = saveSystemMsg;
exports.save = save;

+ 0 - 14
src/doctor/models/push_notify.js

@ -1,14 +0,0 @@
"use strict";
var log = require('../util/log');
var imRepo = require("../repository/im.repo");
function savePushNotify(to_uid, type, title, content, data, has_pushed, handler) {
    imRepo.execQuery({
        "sql": "INSERT INTO push_notify (to_uid,type,title,content,data,has_pushed) VALUES (?,?,?,?,?,?)",
        "args": [to_uid, type, title, content, data, has_pushed],
        "handler": handler
    });
}
exports.savePushNotify = savePushNotify;

+ 38 - 15
src/doctor/models/user.js

@ -1,50 +1,73 @@
"use strict";
/**
 * 医生模型。医生真实数据存在于家庭医生平台数据库,即医生的ID,姓名,年龄等详细内容。而IM平台的user表只包含用户当前的在线状态,
 * 即用户的准实时状态,token等内容,不包含用户具体的详细信息。
 */
"use strict";
var log = require('../util/log');
var log = require('../util/log');
var imRepo = require("../repository/im.repo");
var imRepo = require("../repository/im.repo");
var wlyyRepo = require("../repository/wlyy.repo");
function login(user_id, token, client_id, platform, handler) {
/**
 * 判断用户是否存在。数据从家庭医生平台获取,而不是IM库的user表。
 *
 * @param user
 * @param handler
 */
function isExist(user, handler) {
    wlyyRepo.execQuery({
        "sql": "SELECT count(*) from wlyy_doctor WHERE code=?",
        "args": [user],
        "handler": handler
    });
}
function getUserStatus(userId, handler) {
    imRepo.execQuery({
    imRepo.execQuery({
        "sql": "INSERT INTO users (user_id,token,client_id,platform,is_online,status) VALUES (?,?,?,?,1,1) ON DUPLICATE KEY UPDATE token=?,client_id=?,platform=?,is_online=1,status=1",
        "args": [user_id, token, client_id, platform, token, client_id, platform],
        "sql": "SELECT platform,token,client_id,is_online,status from user WHERE user_id = ?",
        "args": [userId],
        "handler": handler
        "handler": handler
    });
    });
}
}
function logout(user_id, handler) {
function login(userId, token, client_id, platform, handler) {
    imRepo.execQuery({
    imRepo.execQuery({
        "sql": "UPDATE users SET is_online='0',status='0' WHERE user_id=?",
        "args": [user_id],
        "sql": "INSERT INTO user (user_id,token,client_id,platform,is_online,status) VALUES (?,?,?,?,1,1) ON" +
        " DUPLICATE KEY UPDATE token=?,client_id=?,platform=?,is_online=1,status=1",
        "args": [userId, token, client_id, platform, token, client_id, platform],
        "handler": handler
        "handler": handler
    });
    });
}
}
function getUserbyID(user_id, handler) {
function logout(userId, handler) {
    imRepo.execQuery({
    imRepo.execQuery({
        "sql": "SELECT platform,token,client_id,is_online,status from users WHERE user_id = ?",
        "args": [user_id],
        "sql": "UPDATE user SET is_online='0',status='0' WHERE user_id=?",
        "args": [userId],
        "handler": handler
        "handler": handler
    });
    });
}
}
function deleteToken(token, handler) {
function deleteToken(token, handler) {
    imRepo.execQuery({
    imRepo.execQuery({
        "sql": "DELETE FROM users WHERE token=?",
        "sql": "DELETE FROM user WHERE token=?",
        "args": [token],
        "args": [token],
        "handler": handler
        "handler": handler
    });
    });
}
}
function updateStatus(user_id, status, handler) {
function updateStatus(userId, status, handler) {
    imRepo.execQuery({
    imRepo.execQuery({
        "sql": "UPDATE users SET status=? WHERE user_id=?",
        "args": [status, user_id],
        "sql": "UPDATE user SET status=? WHERE user_id=?",
        "args": [status, userId],
        "handler": handler
        "handler": handler
    });
    });
}
}
exports.isExist = isExist;
exports.getUserStatus = getUserStatus;
exports.login = login;
exports.login = login;
exports.logout = logout;
exports.logout = logout;
exports.getUserbyID = getUserbyID;
exports.deleteToken = deleteToken;
exports.deleteToken = deleteToken;
exports.updateStatus = updateStatus;
exports.updateStatus = updateStatus;

+ 3 - 1
src/doctor/package.json

@ -15,9 +15,11 @@
    "enum": "~2.3.0",
    "enum": "~2.3.0",
    "express": "~4.12.4",
    "express": "~4.12.4",
    "jade": "~1.9.2",
    "jade": "~1.9.2",
    "mocha": "~3.1.2",
    "morgan": "~1.5.3",
    "morgan": "~1.5.3",
    "mysql": "~2.5.3",
    "mysql": "~2.5.3",
    "serve-favicon": "~2.2.1",
    "serve-favicon": "~2.2.1",
    "socket.io": "~1.5.1"
    "socket.io": "~1.5.1",
    "underscore": "~1.8.3"
  }
  }
}
}

+ 1 - 1
src/doctor/repository/im.repo.js

@ -12,5 +12,5 @@ var pool = mysql.createPool(config.imDbConfig);
 * 执行查询
 * 执行查询
 */
 */
exports.execQuery = function(options) {
exports.execQuery = function(options) {
	dbUtil.execQuery(pool, options);
    dbUtil.execQuery(pool, options);
};
};

+ 9 - 9
src/doctor/resources/schema/im_schema.sql

@ -1,16 +1,16 @@
/*
/*
Navicat MySQL Data Transfer
	Navicat MySQL Data Transfer
Source Server         : wjw(im)
Source Server Version : 50549
Source Host           : localhost:3306
Source Database       : im
	Source Server         : wjw(im)
	Source Server Version : 50549
	Source Host           : localhost:3306
	Source Database       : im
Target Server Type    : MYSQL
Target Server Version : 50549
File Encoding         : 65001
	Target Server Type    : MYSQL
	Target Server Version : 50549
	File Encoding         : 65001
Date: 2016-07-19 20:56:59
	Date: 2016-07-19 20:56:59
*/
*/
SET FOREIGN_KEY_CHECKS=0;
SET FOREIGN_KEY_CHECKS=0;

+ 9 - 9
src/doctor/resources/schema/talk_group_schema.sql

@ -1,16 +1,16 @@
/*
/*
Navicat MySQL Data Transfer
	Navicat MySQL Data Transfer
Source Server         : 172.19.103.77
Source Server Version : 50629
Source Host           : 172.19.103.77:3306
Source Database       : wlyy
	Source Server         : 172.19.103.77
	Source Server Version : 50629
	Source Host           : 172.19.103.77:3306
	Source Database       : wlyy
Target Server Type    : MYSQL
Target Server Version : 50629
File Encoding         : 65001
	Target Server Type    : MYSQL
	Target Server Version : 50629
	File Encoding         : 65001
Date: 2016-11-03 16:14:21
	Date: 2016-11-03 16:14:21
*/
*/
SET FOREIGN_KEY_CHECKS=0;
SET FOREIGN_KEY_CHECKS=0;

+ 51 - 0
src/doctor/test.js

@ -0,0 +1,51 @@
var express = require('express'),
    app = express(),
    mysql = require('mysql'),
    connectionpool = mysql.createPool({
        host: 'localhost',
        user: 'root',
        password: 'secret',
        database: 'rest_demo'
    });
app.get('/:table', function (req, res) {
    connectionpool.getConnection(function (err, connection) {
        if (err) {
            console.error('CONNECTION error: ', err);
            res.statusCode = 503;
            res.send({
                result: 'error',
                err: err.code
            });
        } else {
            connection.query('SELECT * FROM ' + req.params.table + ' ORDER BY id DESC LIMIT 20', req.params.id, function (err, rows, fields) {
                if (err) {
                    console.error(err);
                    res.statusCode = 500;
                    res.send({
                        result: 'error',
                        err: err.code
                    });
                }
                res.send({
                    result: 'success',
                    err: '',
                    fields: fields,
                    json: rows,
                    length: rows.length
                });
                connection.release();
            });
        }
    });
});
app.get('/:table/:id', function (req, res) {
});
app.post('/:table', function (req, res) {
});
app.put('/:table/:id', function (req, res) {
});
app.delete('/:table/:id', function (req, res) {
});
app.listen(3000);
console.log('Rest Demo Listening on port 3000');

+ 12 - 8
src/doctor/util/dbUtil.js

@ -1,6 +1,6 @@
"use strict";
"use strict";
var log = require('../util/log');
var log = require('./log');
/**
/**
 * 数据库查询工具,使用数据库连接池获取连接,执行查询,之后将连接返回连接池。
 * 数据库查询工具,使用数据库连接池获取连接,执行查询,之后将连接返回连接池。
@ -13,28 +13,32 @@ exports.execQuery = function (pool, options) {
        var handler = options['handler'];
        var handler = options['handler'];
        if (err) {
        if (err) {
            log.error('db-getConnection err:' + err);
            log.error('Database - get connection failed, ' + err);
            handler(err, 'db-getConnection');
            handler(err, 'db-getConnection');
            return;
            return;
        }
        }
        // 执行查询
        // 执行查询
        if (!args) {
            var query = connection.query(sql, function (err, results) {
        if (args) {
            var query = connection.query(sql, args, function (err, results) {
                if (err) {
                if (err) {
                    log.error('db-query err:' + err);
                    log.error('Database - execute query failed, ' + err);
                    handler(err, results);
                    handler(err, results);
                    return;
                    return;
                }
                }
                // 处理结果
                // 处理结果
                handler(err, results);
                handler(err, results);
            });
            });
        } else {
        } else {
            var query = connection.query(sql, args, function (err, results) {
            var query = connection.query(sql, function (err, results) {
                if (err) {
                if (err) {
                    log.error('db-query err:' + err);
                    log.error('Database - execute query failed, ' + err);
                    handler(err, results);
                    handler(err, results);
                    return;
                    return;
                }
                }
@ -46,7 +50,7 @@ exports.execQuery = function (pool, options) {
        // 返回连接池
        // 返回连接池
        connection.release(function (err) {
        connection.release(function (err) {
            if (error) {
            if (error) {
                log.error('db-release err:' + err);
                log.error('Database - release connection failed, ' + err);
            }
            }
        });
        });
    });
    });

+ 20 - 19
src/doctor/util/log.js

@ -5,19 +5,19 @@
    // log level
    // log level
    var LEVEL = {
    var LEVEL = {
        ALL:Infinity,
        INFO:3,
        WARN:2,
        ERROR:1,
        NONE:-Infinity
        ALL: Infinity,
        INFO: 3,
        WARN: 2,
        ERROR: 1,
        NONE: -Infinity
    };
    };
    // log color
    // log color
    var COLOR = {
    var COLOR = {
        RESET:'\u001b[0m',
        INFO:'\u001b[32m', // green
        WARN:'\u001b[33m', // yellow
        ERROR:'\u001b[31m' // red
        RESET: '\u001b[0m',
        INFO: '\u001b[32m', // green
        WARN: '\u001b[33m', // yellow
        ERROR: '\u001b[31m' // red
    };
    };
    // global log level
    // global log level
@ -66,10 +66,11 @@
        var caller = structuredStack[2];
        var caller = structuredStack[2];
        var lineSep = process.platform == 'win32' ? '\\' : '/';
        var lineSep = process.platform == 'win32' ? '\\' : '/';
        var fileNameSplited = caller.getFileName().split(lineSep);
        var fileName = fileNameSplited[fileNameSplited.length - 1];
        var fileNameTokens = caller.getFileName().split(lineSep);
        var fileName = fileNameTokens[fileNameTokens.length - 1];
        var lineNumber = caller.getLineNumber();
        var lineNumber = caller.getLineNumber();
        var columnNumber = caller.getColumnNumber();
        var columnNumber = caller.getColumnNumber();
        // function name may be empty if it is a global call
        // function name may be empty if it is a global call
        // var functionName = caller.getFunctionName();
        // var functionName = caller.getFunctionName();
        var levelString;
        var levelString;
@ -88,8 +89,8 @@
                break;
                break;
        }
        }
        var output = util.format('%s %s(%d,%d): %s',
        var output = util.format('%s %s(%d,%d): %s',
            levelString, fileName, lineNumber, columnNumber, message
        );
            levelString, fileName, lineNumber, columnNumber, message);
        if (!coloredOutput) {
        if (!coloredOutput) {
            process.stdout.write(output + '\n');
            process.stdout.write(output + '\n');
        } else {
        } else {
@ -110,12 +111,12 @@
    }
    }
    module.exports = {
    module.exports = {
        info:info,
        warn:warn,
        error:error,
        LEVEL:LEVEL,
        setLevel:setLevel,
        setColoredOutput:setColoredOutput
        info: info,
        warn: warn,
        error: error,
        LEVEL: LEVEL,
        setLevel: setLevel,
        setColoredOutput: setColoredOutput
    };
    };
}());
}());

+ 0 - 108
src/doctor/util/mime.js

@ -1,108 +0,0 @@
var path = require('path');
var fs = require('fs');
function Mime() {
    // Map of extension -> mime type
    this.types = Object.create(null);
    // Map of mime type -> extension
    this.extensions = Object.create(null);
}
/**
 * Define mimetype -> extension mappings.  Each key is a mime-type that maps
 * to an array of extensions associated with the type.  The first extension is
 * used as the default extension for the type.
 *
 * e.g. mime.define({'audio/ogg', ['oga', 'ogg', 'spx']});
 *
 * @param map (Object) type definitions
 */
Mime.prototype.define = function (map) {
    for (var type in map) {
        var exts = map[type];
        for (var i = 0; i < exts.length; i++) {
            if (process.env.DEBUG_MIME && this.types[exts]) {
                console.warn(this._loading.replace(/.*\//, ''), 'changes "' + exts[i] + '" extension type from ' +
                    this.types[exts] + ' to ' + type);
            }
            this.types[exts[i]] = type;
        }
        // Default extension is the first one we encounter
        if (!this.extensions[type]) {
            this.extensions[type] = exts[0];
        }
    }
};
/**
 * Load an Apache2-style ".types" file
 *
 * This may be called multiple times (it's expected).  Where files declare
 * overlapping types/extensions, the last file wins.
 *
 * @param file (String) path of file to load.
 */
Mime.prototype.load = function(file) {
    this._loading = file;
    // Read file and split into lines
    var map = {},
        content = fs.readFileSync(file, 'ascii'),
        lines = content.split(/[\r\n]+/);
    lines.forEach(function(line) {
        // Clean up whitespace/comments, and split into fields
        var fields = line.replace(/\s*#.*|^\s*|\s*$/g, '').split(/\s+/);
        map[fields.shift()] = fields;
    });
    this.define(map);
    this._loading = null;
};
/**
 * Lookup a mime type based on extension
 */
Mime.prototype.lookup = function(path, fallback) {
    var ext = path.replace(/.*[\.\/\\]/, '').toLowerCase();
    return this.types[ext] || fallback || this.default_type;
};
/**
 * Return file extension associated with a mime type
 */
Mime.prototype.extension = function(mimeType) {
    var type = mimeType.match(/^\s*([^;\s]*)(?:;|\s|$)/)[1].toLowerCase();
    return this.extensions[type];
};
// Default instance
var mime = new Mime();
// Define built-in types
mime.define(require('./types.json'));
// Default type
mime.default_type = mime.lookup('bin');
//
// Additional API specific to the default instance
//
mime.Mime = Mime;
/**
 * Lookup a charset based on mime type.
 */
mime.charsets = {
    lookup: function(mimeType, fallback) {
        // Assume text types are utf8
        return (/^text\//).test(mimeType) ? 'UTF-8' : fallback;
    }
};
module.exports = mime;

+ 0 - 2586
src/doctor/util/mime.types.json

@ -1,2586 +0,0 @@
{
  "application/andrew-inset": [
    "ez"
  ],
  "application/applixware": [
    "aw"
  ],
  "application/atom+xml": [
    "atom"
  ],
  "application/atomcat+xml": [
    "atomcat"
  ],
  "application/atomsvc+xml": [
    "atomsvc"
  ],
  "application/ccxml+xml": [
    "ccxml"
  ],
  "application/cdmi-capability": [
    "cdmia"
  ],
  "application/cdmi-container": [
    "cdmic"
  ],
  "application/cdmi-domain": [
    "cdmid"
  ],
  "application/cdmi-object": [
    "cdmio"
  ],
  "application/cdmi-queue": [
    "cdmiq"
  ],
  "application/cu-seeme": [
    "cu"
  ],
  "application/dash+xml": [
    "mdp"
  ],
  "application/davmount+xml": [
    "davmount"
  ],
  "application/docbook+xml": [
    "dbk"
  ],
  "application/dssc+der": [
    "dssc"
  ],
  "application/dssc+xml": [
    "xdssc"
  ],
  "application/ecmascript": [
    "ecma"
  ],
  "application/emma+xml": [
    "emma"
  ],
  "application/epub+zip": [
    "epub"
  ],
  "application/exi": [
    "exi"
  ],
  "application/font-tdpfr": [
    "pfr"
  ],
  "application/font-woff": [
    "woff"
  ],
  "application/font-woff2": [
    "woff2"
  ],
  "application/gml+xml": [
    "gml"
  ],
  "application/gpx+xml": [
    "gpx"
  ],
  "application/gxf": [
    "gxf"
  ],
  "application/hyperstudio": [
    "stk"
  ],
  "application/inkml+xml": [
    "ink",
    "inkml"
  ],
  "application/ipfix": [
    "ipfix"
  ],
  "application/java-archive": [
    "jar"
  ],
  "application/java-serialized-object": [
    "ser"
  ],
  "application/java-vm": [
    "class"
  ],
  "application/javascript": [
    "js"
  ],
  "application/json": [
    "json",
    "map"
  ],
  "application/json5": [
    "json5"
  ],
  "application/jsonml+json": [
    "jsonml"
  ],
  "application/lost+xml": [
    "lostxml"
  ],
  "application/mac-binhex40": [
    "hqx"
  ],
  "application/mac-compactpro": [
    "cpt"
  ],
  "application/mads+xml": [
    "mads"
  ],
  "application/marc": [
    "mrc"
  ],
  "application/marcxml+xml": [
    "mrcx"
  ],
  "application/mathematica": [
    "ma",
    "nb",
    "mb"
  ],
  "application/mathml+xml": [
    "mathml"
  ],
  "application/mbox": [
    "mbox"
  ],
  "application/mediaservercontrol+xml": [
    "mscml"
  ],
  "application/metalink+xml": [
    "metalink"
  ],
  "application/metalink4+xml": [
    "meta4"
  ],
  "application/mets+xml": [
    "mets"
  ],
  "application/mods+xml": [
    "mods"
  ],
  "application/mp21": [
    "m21",
    "mp21"
  ],
  "application/mp4": [
    "mp4s",
    "m4p"
  ],
  "application/msword": [
    "doc",
    "dot"
  ],
  "application/mxf": [
    "mxf"
  ],
  "application/octet-stream": [
    "bin",
    "dms",
    "lrf",
    "mar",
    "so",
    "dist",
    "distz",
    "pkg",
    "bpk",
    "dump",
    "elc",
    "deploy",
    "buffer"
  ],
  "application/oda": [
    "oda"
  ],
  "application/oebps-package+xml": [
    "opf"
  ],
  "application/ogg": [
    "ogx"
  ],
  "application/omdoc+xml": [
    "omdoc"
  ],
  "application/onenote": [
    "onetoc",
    "onetoc2",
    "onetmp",
    "onepkg"
  ],
  "application/oxps": [
    "oxps"
  ],
  "application/patch-ops-error+xml": [
    "xer"
  ],
  "application/pdf": [
    "pdf"
  ],
  "application/pgp-encrypted": [
    "pgp"
  ],
  "application/pgp-signature": [
    "asc",
    "sig"
  ],
  "application/pics-rules": [
    "prf"
  ],
  "application/pkcs10": [
    "p10"
  ],
  "application/pkcs7-mime": [
    "p7m",
    "p7c"
  ],
  "application/pkcs7-signature": [
    "p7s"
  ],
  "application/pkcs8": [
    "p8"
  ],
  "application/pkix-attr-cert": [
    "ac"
  ],
  "application/pkix-cert": [
    "cer"
  ],
  "application/pkix-crl": [
    "crl"
  ],
  "application/pkix-pkipath": [
    "pkipath"
  ],
  "application/pkixcmp": [
    "pki"
  ],
  "application/pls+xml": [
    "pls"
  ],
  "application/postscript": [
    "ai",
    "eps",
    "ps"
  ],
  "application/prs.cww": [
    "cww"
  ],
  "application/pskc+xml": [
    "pskcxml"
  ],
  "application/rdf+xml": [
    "rdf"
  ],
  "application/reginfo+xml": [
    "rif"
  ],
  "application/relax-ng-compact-syntax": [
    "rnc"
  ],
  "application/resource-lists+xml": [
    "rl"
  ],
  "application/resource-lists-diff+xml": [
    "rld"
  ],
  "application/rls-services+xml": [
    "rs"
  ],
  "application/rpki-ghostbusters": [
    "gbr"
  ],
  "application/rpki-manifest": [
    "mft"
  ],
  "application/rpki-roa": [
    "roa"
  ],
  "application/rsd+xml": [
    "rsd"
  ],
  "application/rss+xml": [
    "rss"
  ],
  "application/rtf": [
    "rtf"
  ],
  "application/sbml+xml": [
    "sbml"
  ],
  "application/scvp-cv-request": [
    "scq"
  ],
  "application/scvp-cv-response": [
    "scs"
  ],
  "application/scvp-vp-request": [
    "spq"
  ],
  "application/scvp-vp-response": [
    "spp"
  ],
  "application/sdp": [
    "sdp"
  ],
  "application/set-payment-initiation": [
    "setpay"
  ],
  "application/set-registration-initiation": [
    "setreg"
  ],
  "application/shf+xml": [
    "shf"
  ],
  "application/smil+xml": [
    "smi",
    "smil"
  ],
  "application/sparql-query": [
    "rq"
  ],
  "application/sparql-results+xml": [
    "srx"
  ],
  "application/srgs": [
    "gram"
  ],
  "application/srgs+xml": [
    "grxml"
  ],
  "application/sru+xml": [
    "sru"
  ],
  "application/ssdl+xml": [
    "ssdl"
  ],
  "application/ssml+xml": [
    "ssml"
  ],
  "application/tei+xml": [
    "tei",
    "teicorpus"
  ],
  "application/thraud+xml": [
    "tfi"
  ],
  "application/timestamped-data": [
    "tsd"
  ],
  "application/vnd.3gpp.pic-bw-large": [
    "plb"
  ],
  "application/vnd.3gpp.pic-bw-small": [
    "psb"
  ],
  "application/vnd.3gpp.pic-bw-var": [
    "pvb"
  ],
  "application/vnd.3gpp2.tcap": [
    "tcap"
  ],
  "application/vnd.3m.post-it-notes": [
    "pwn"
  ],
  "application/vnd.accpac.simply.aso": [
    "aso"
  ],
  "application/vnd.accpac.simply.imp": [
    "imp"
  ],
  "application/vnd.acucobol": [
    "acu"
  ],
  "application/vnd.acucorp": [
    "atc",
    "acutc"
  ],
  "application/vnd.adobe.air-application-installer-package+zip": [
    "air"
  ],
  "application/vnd.adobe.formscentral.fcdt": [
    "fcdt"
  ],
  "application/vnd.adobe.fxp": [
    "fxp",
    "fxpl"
  ],
  "application/vnd.adobe.xdp+xml": [
    "xdp"
  ],
  "application/vnd.adobe.xfdf": [
    "xfdf"
  ],
  "application/vnd.ahead.space": [
    "ahead"
  ],
  "application/vnd.airzip.filesecure.azf": [
    "azf"
  ],
  "application/vnd.airzip.filesecure.azs": [
    "azs"
  ],
  "application/vnd.amazon.ebook": [
    "azw"
  ],
  "application/vnd.americandynamics.acc": [
    "acc"
  ],
  "application/vnd.amiga.ami": [
    "ami"
  ],
  "application/vnd.android.package-archive": [
    "apk"
  ],
  "application/vnd.anser-web-certificate-issue-initiation": [
    "cii"
  ],
  "application/vnd.anser-web-funds-transfer-initiation": [
    "fti"
  ],
  "application/vnd.antix.game-component": [
    "atx"
  ],
  "application/vnd.apple.installer+xml": [
    "mpkg"
  ],
  "application/vnd.apple.mpegurl": [
    "m3u8"
  ],
  "application/vnd.aristanetworks.swi": [
    "swi"
  ],
  "application/vnd.astraea-software.iota": [
    "iota"
  ],
  "application/vnd.audiograph": [
    "aep"
  ],
  "application/vnd.blueice.multipass": [
    "mpm"
  ],
  "application/vnd.bmi": [
    "bmi"
  ],
  "application/vnd.businessobjects": [
    "rep"
  ],
  "application/vnd.chemdraw+xml": [
    "cdxml"
  ],
  "application/vnd.chipnuts.karaoke-mmd": [
    "mmd"
  ],
  "application/vnd.cinderella": [
    "cdy"
  ],
  "application/vnd.claymore": [
    "cla"
  ],
  "application/vnd.cloanto.rp9": [
    "rp9"
  ],
  "application/vnd.clonk.c4group": [
    "c4g",
    "c4d",
    "c4f",
    "c4p",
    "c4u"
  ],
  "application/vnd.cluetrust.cartomobile-config": [
    "c11amc"
  ],
  "application/vnd.cluetrust.cartomobile-config-pkg": [
    "c11amz"
  ],
  "application/vnd.commonspace": [
    "csp"
  ],
  "application/vnd.contact.cmsg": [
    "cdbcmsg"
  ],
  "application/vnd.cosmocaller": [
    "cmc"
  ],
  "application/vnd.crick.clicker": [
    "clkx"
  ],
  "application/vnd.crick.clicker.keyboard": [
    "clkk"
  ],
  "application/vnd.crick.clicker.palette": [
    "clkp"
  ],
  "application/vnd.crick.clicker.template": [
    "clkt"
  ],
  "application/vnd.crick.clicker.wordbank": [
    "clkw"
  ],
  "application/vnd.criticaltools.wbs+xml": [
    "wbs"
  ],
  "application/vnd.ctc-posml": [
    "pml"
  ],
  "application/vnd.cups-ppd": [
    "ppd"
  ],
  "application/vnd.curl.car": [
    "car"
  ],
  "application/vnd.curl.pcurl": [
    "pcurl"
  ],
  "application/vnd.dart": [
    "dart"
  ],
  "application/vnd.data-vision.rdz": [
    "rdz"
  ],
  "application/vnd.dece.data": [
    "uvf",
    "uvvf",
    "uvd",
    "uvvd"
  ],
  "application/vnd.dece.ttml+xml": [
    "uvt",
    "uvvt"
  ],
  "application/vnd.dece.unspecified": [
    "uvx",
    "uvvx"
  ],
  "application/vnd.dece.zip": [
    "uvz",
    "uvvz"
  ],
  "application/vnd.denovo.fcselayout-link": [
    "fe_launch"
  ],
  "application/vnd.dna": [
    "dna"
  ],
  "application/vnd.dolby.mlp": [
    "mlp"
  ],
  "application/vnd.dpgraph": [
    "dpg"
  ],
  "application/vnd.dreamfactory": [
    "dfac"
  ],
  "application/vnd.ds-keypoint": [
    "kpxx"
  ],
  "application/vnd.dvb.ait": [
    "ait"
  ],
  "application/vnd.dvb.service": [
    "svc"
  ],
  "application/vnd.dynageo": [
    "geo"
  ],
  "application/vnd.ecowin.chart": [
    "mag"
  ],
  "application/vnd.enliven": [
    "nml"
  ],
  "application/vnd.epson.esf": [
    "esf"
  ],
  "application/vnd.epson.msf": [
    "msf"
  ],
  "application/vnd.epson.quickanime": [
    "qam"
  ],
  "application/vnd.epson.salt": [
    "slt"
  ],
  "application/vnd.epson.ssf": [
    "ssf"
  ],
  "application/vnd.eszigno3+xml": [
    "es3",
    "et3"
  ],
  "application/vnd.ezpix-album": [
    "ez2"
  ],
  "application/vnd.ezpix-package": [
    "ez3"
  ],
  "application/vnd.fdf": [
    "fdf"
  ],
  "application/vnd.fdsn.mseed": [
    "mseed"
  ],
  "application/vnd.fdsn.seed": [
    "seed",
    "dataless"
  ],
  "application/vnd.flographit": [
    "gph"
  ],
  "application/vnd.fluxtime.clip": [
    "ftc"
  ],
  "application/vnd.framemaker": [
    "fm",
    "frame",
    "maker",
    "book"
  ],
  "application/vnd.frogans.fnc": [
    "fnc"
  ],
  "application/vnd.frogans.ltf": [
    "ltf"
  ],
  "application/vnd.fsc.weblaunch": [
    "fsc"
  ],
  "application/vnd.fujitsu.oasys": [
    "oas"
  ],
  "application/vnd.fujitsu.oasys2": [
    "oa2"
  ],
  "application/vnd.fujitsu.oasys3": [
    "oa3"
  ],
  "application/vnd.fujitsu.oasysgp": [
    "fg5"
  ],
  "application/vnd.fujitsu.oasysprs": [
    "bh2"
  ],
  "application/vnd.fujixerox.ddd": [
    "ddd"
  ],
  "application/vnd.fujixerox.docuworks": [
    "xdw"
  ],
  "application/vnd.fujixerox.docuworks.binder": [
    "xbd"
  ],
  "application/vnd.fuzzysheet": [
    "fzs"
  ],
  "application/vnd.genomatix.tuxedo": [
    "txd"
  ],
  "application/vnd.geogebra.file": [
    "ggb"
  ],
  "application/vnd.geogebra.tool": [
    "ggt"
  ],
  "application/vnd.geometry-explorer": [
    "gex",
    "gre"
  ],
  "application/vnd.geonext": [
    "gxt"
  ],
  "application/vnd.geoplan": [
    "g2w"
  ],
  "application/vnd.geospace": [
    "g3w"
  ],
  "application/vnd.gmx": [
    "gmx"
  ],
  "application/vnd.google-earth.kml+xml": [
    "kml"
  ],
  "application/vnd.google-earth.kmz": [
    "kmz"
  ],
  "application/vnd.grafeq": [
    "gqf",
    "gqs"
  ],
  "application/vnd.groove-account": [
    "gac"
  ],
  "application/vnd.groove-help": [
    "ghf"
  ],
  "application/vnd.groove-identity-message": [
    "gim"
  ],
  "application/vnd.groove-injector": [
    "grv"
  ],
  "application/vnd.groove-tool-message": [
    "gtm"
  ],
  "application/vnd.groove-tool-template": [
    "tpl"
  ],
  "application/vnd.groove-vcard": [
    "vcg"
  ],
  "application/vnd.hal+xml": [
    "hal"
  ],
  "application/vnd.handheld-entertainment+xml": [
    "zmm"
  ],
  "application/vnd.hbci": [
    "hbci"
  ],
  "application/vnd.hhe.lesson-player": [
    "les"
  ],
  "application/vnd.hp-hpgl": [
    "hpgl"
  ],
  "application/vnd.hp-hpid": [
    "hpid"
  ],
  "application/vnd.hp-hps": [
    "hps"
  ],
  "application/vnd.hp-jlyt": [
    "jlt"
  ],
  "application/vnd.hp-pcl": [
    "pcl"
  ],
  "application/vnd.hp-pclxl": [
    "pclxl"
  ],
  "application/vnd.ibm.minipay": [
    "mpy"
  ],
  "application/vnd.ibm.modcap": [
    "afp",
    "listafp",
    "list3820"
  ],
  "application/vnd.ibm.rights-management": [
    "irm"
  ],
  "application/vnd.ibm.secure-container": [
    "sc"
  ],
  "application/vnd.iccprofile": [
    "icc",
    "icm"
  ],
  "application/vnd.igloader": [
    "igl"
  ],
  "application/vnd.immervision-ivp": [
    "ivp"
  ],
  "application/vnd.immervision-ivu": [
    "ivu"
  ],
  "application/vnd.insors.igm": [
    "igm"
  ],
  "application/vnd.intercon.formnet": [
    "xpw",
    "xpx"
  ],
  "application/vnd.intergeo": [
    "i2g"
  ],
  "application/vnd.intu.qbo": [
    "qbo"
  ],
  "application/vnd.intu.qfx": [
    "qfx"
  ],
  "application/vnd.ipunplugged.rcprofile": [
    "rcprofile"
  ],
  "application/vnd.irepository.package+xml": [
    "irp"
  ],
  "application/vnd.is-xpr": [
    "xpr"
  ],
  "application/vnd.isac.fcs": [
    "fcs"
  ],
  "application/vnd.jam": [
    "jam"
  ],
  "application/vnd.jcp.javame.midlet-rms": [
    "rms"
  ],
  "application/vnd.jisp": [
    "jisp"
  ],
  "application/vnd.joost.joda-archive": [
    "joda"
  ],
  "application/vnd.kahootz": [
    "ktz",
    "ktr"
  ],
  "application/vnd.kde.karbon": [
    "karbon"
  ],
  "application/vnd.kde.kchart": [
    "chrt"
  ],
  "application/vnd.kde.kformula": [
    "kfo"
  ],
  "application/vnd.kde.kivio": [
    "flw"
  ],
  "application/vnd.kde.kontour": [
    "kon"
  ],
  "application/vnd.kde.kpresenter": [
    "kpr",
    "kpt"
  ],
  "application/vnd.kde.kspread": [
    "ksp"
  ],
  "application/vnd.kde.kword": [
    "kwd",
    "kwt"
  ],
  "application/vnd.kenameaapp": [
    "htke"
  ],
  "application/vnd.kidspiration": [
    "kia"
  ],
  "application/vnd.kinar": [
    "kne",
    "knp"
  ],
  "application/vnd.koan": [
    "skp",
    "skd",
    "skt",
    "skm"
  ],
  "application/vnd.kodak-descriptor": [
    "sse"
  ],
  "application/vnd.las.las+xml": [
    "lasxml"
  ],
  "application/vnd.llamagraphics.life-balance.desktop": [
    "lbd"
  ],
  "application/vnd.llamagraphics.life-balance.exchange+xml": [
    "lbe"
  ],
  "application/vnd.lotus-1-2-3": [
    "123"
  ],
  "application/vnd.lotus-approach": [
    "apr"
  ],
  "application/vnd.lotus-freelance": [
    "pre"
  ],
  "application/vnd.lotus-notes": [
    "nsf"
  ],
  "application/vnd.lotus-organizer": [
    "org"
  ],
  "application/vnd.lotus-screencam": [
    "scm"
  ],
  "application/vnd.lotus-wordpro": [
    "lwp"
  ],
  "application/vnd.macports.portpkg": [
    "portpkg"
  ],
  "application/vnd.mcd": [
    "mcd"
  ],
  "application/vnd.medcalcdata": [
    "mc1"
  ],
  "application/vnd.mediastation.cdkey": [
    "cdkey"
  ],
  "application/vnd.mfer": [
    "mwf"
  ],
  "application/vnd.mfmp": [
    "mfm"
  ],
  "application/vnd.micrografx.flo": [
    "flo"
  ],
  "application/vnd.micrografx.igx": [
    "igx"
  ],
  "application/vnd.mif": [
    "mif"
  ],
  "application/vnd.mobius.daf": [
    "daf"
  ],
  "application/vnd.mobius.dis": [
    "dis"
  ],
  "application/vnd.mobius.mbk": [
    "mbk"
  ],
  "application/vnd.mobius.mqy": [
    "mqy"
  ],
  "application/vnd.mobius.msl": [
    "msl"
  ],
  "application/vnd.mobius.plc": [
    "plc"
  ],
  "application/vnd.mobius.txf": [
    "txf"
  ],
  "application/vnd.mophun.application": [
    "mpn"
  ],
  "application/vnd.mophun.certificate": [
    "mpc"
  ],
  "application/vnd.mozilla.xul+xml": [
    "xul"
  ],
  "application/vnd.ms-artgalry": [
    "cil"
  ],
  "application/vnd.ms-cab-compressed": [
    "cab"
  ],
  "application/vnd.ms-excel": [
    "xls",
    "xlm",
    "xla",
    "xlc",
    "xlt",
    "xlw"
  ],
  "application/vnd.ms-excel.addin.macroenabled.12": [
    "xlam"
  ],
  "application/vnd.ms-excel.sheet.binary.macroenabled.12": [
    "xlsb"
  ],
  "application/vnd.ms-excel.sheet.macroenabled.12": [
    "xlsm"
  ],
  "application/vnd.ms-excel.template.macroenabled.12": [
    "xltm"
  ],
  "application/vnd.ms-fontobject": [
    "eot"
  ],
  "application/vnd.ms-htmlhelp": [
    "chm"
  ],
  "application/vnd.ms-ims": [
    "ims"
  ],
  "application/vnd.ms-lrm": [
    "lrm"
  ],
  "application/vnd.ms-officetheme": [
    "thmx"
  ],
  "application/vnd.ms-pki.seccat": [
    "cat"
  ],
  "application/vnd.ms-pki.stl": [
    "stl"
  ],
  "application/vnd.ms-powerpoint": [
    "ppt",
    "pps",
    "pot"
  ],
  "application/vnd.ms-powerpoint.addin.macroenabled.12": [
    "ppam"
  ],
  "application/vnd.ms-powerpoint.presentation.macroenabled.12": [
    "pptm"
  ],
  "application/vnd.ms-powerpoint.slide.macroenabled.12": [
    "sldm"
  ],
  "application/vnd.ms-powerpoint.slideshow.macroenabled.12": [
    "ppsm"
  ],
  "application/vnd.ms-powerpoint.template.macroenabled.12": [
    "potm"
  ],
  "application/vnd.ms-project": [
    "mpp",
    "mpt"
  ],
  "application/vnd.ms-word.document.macroenabled.12": [
    "docm"
  ],
  "application/vnd.ms-word.template.macroenabled.12": [
    "dotm"
  ],
  "application/vnd.ms-works": [
    "wps",
    "wks",
    "wcm",
    "wdb"
  ],
  "application/vnd.ms-wpl": [
    "wpl"
  ],
  "application/vnd.ms-xpsdocument": [
    "xps"
  ],
  "application/vnd.mseq": [
    "mseq"
  ],
  "application/vnd.musician": [
    "mus"
  ],
  "application/vnd.muvee.style": [
    "msty"
  ],
  "application/vnd.mynfc": [
    "taglet"
  ],
  "application/vnd.neurolanguage.nlu": [
    "nlu"
  ],
  "application/vnd.nitf": [
    "ntf",
    "nitf"
  ],
  "application/vnd.noblenet-directory": [
    "nnd"
  ],
  "application/vnd.noblenet-sealer": [
    "nns"
  ],
  "application/vnd.noblenet-web": [
    "nnw"
  ],
  "application/vnd.nokia.n-gage.data": [
    "ngdat"
  ],
  "application/vnd.nokia.radio-preset": [
    "rpst"
  ],
  "application/vnd.nokia.radio-presets": [
    "rpss"
  ],
  "application/vnd.novadigm.edm": [
    "edm"
  ],
  "application/vnd.novadigm.edx": [
    "edx"
  ],
  "application/vnd.novadigm.ext": [
    "ext"
  ],
  "application/vnd.oasis.opendocument.chart": [
    "odc"
  ],
  "application/vnd.oasis.opendocument.chart-template": [
    "otc"
  ],
  "application/vnd.oasis.opendocument.database": [
    "odb"
  ],
  "application/vnd.oasis.opendocument.formula": [
    "odf"
  ],
  "application/vnd.oasis.opendocument.formula-template": [
    "odft"
  ],
  "application/vnd.oasis.opendocument.graphics": [
    "odg"
  ],
  "application/vnd.oasis.opendocument.graphics-template": [
    "otg"
  ],
  "application/vnd.oasis.opendocument.image": [
    "odi"
  ],
  "application/vnd.oasis.opendocument.image-template": [
    "oti"
  ],
  "application/vnd.oasis.opendocument.presentation": [
    "odp"
  ],
  "application/vnd.oasis.opendocument.presentation-template": [
    "otp"
  ],
  "application/vnd.oasis.opendocument.spreadsheet": [
    "ods"
  ],
  "application/vnd.oasis.opendocument.spreadsheet-template": [
    "ots"
  ],
  "application/vnd.oasis.opendocument.text": [
    "odt"
  ],
  "application/vnd.oasis.opendocument.text-master": [
    "odm"
  ],
  "application/vnd.oasis.opendocument.text-template": [
    "ott"
  ],
  "application/vnd.oasis.opendocument.text-web": [
    "oth"
  ],
  "application/vnd.olpc-sugar": [
    "xo"
  ],
  "application/vnd.oma.dd2+xml": [
    "dd2"
  ],
  "application/vnd.openofficeorg.extension": [
    "oxt"
  ],
  "application/vnd.openxmlformats-officedocument.presentationml.presentation": [
    "pptx"
  ],
  "application/vnd.openxmlformats-officedocument.presentationml.slide": [
    "sldx"
  ],
  "application/vnd.openxmlformats-officedocument.presentationml.slideshow": [
    "ppsx"
  ],
  "application/vnd.openxmlformats-officedocument.presentationml.template": [
    "potx"
  ],
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [
    "xlsx"
  ],
  "application/vnd.openxmlformats-officedocument.spreadsheetml.template": [
    "xltx"
  ],
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document": [
    "docx"
  ],
  "application/vnd.openxmlformats-officedocument.wordprocessingml.template": [
    "dotx"
  ],
  "application/vnd.osgeo.mapguide.package": [
    "mgp"
  ],
  "application/vnd.osgi.dp": [
    "dp"
  ],
  "application/vnd.osgi.subsystem": [
    "esa"
  ],
  "application/vnd.palm": [
    "pdb",
    "pqa",
    "oprc"
  ],
  "application/vnd.pawaafile": [
    "paw"
  ],
  "application/vnd.pg.format": [
    "str"
  ],
  "application/vnd.pg.osasli": [
    "ei6"
  ],
  "application/vnd.picsel": [
    "efif"
  ],
  "application/vnd.pmi.widget": [
    "wg"
  ],
  "application/vnd.pocketlearn": [
    "plf"
  ],
  "application/vnd.powerbuilder6": [
    "pbd"
  ],
  "application/vnd.previewsystems.box": [
    "box"
  ],
  "application/vnd.proteus.magazine": [
    "mgz"
  ],
  "application/vnd.publishare-delta-tree": [
    "qps"
  ],
  "application/vnd.pvi.ptid1": [
    "ptid"
  ],
  "application/vnd.quark.quarkxpress": [
    "qxd",
    "qxt",
    "qwd",
    "qwt",
    "qxl",
    "qxb"
  ],
  "application/vnd.realvnc.bed": [
    "bed"
  ],
  "application/vnd.recordare.musicxml": [
    "mxl"
  ],
  "application/vnd.recordare.musicxml+xml": [
    "musicxml"
  ],
  "application/vnd.rig.cryptonote": [
    "cryptonote"
  ],
  "application/vnd.rim.cod": [
    "cod"
  ],
  "application/vnd.rn-realmedia": [
    "rm"
  ],
  "application/vnd.rn-realmedia-vbr": [
    "rmvb"
  ],
  "application/vnd.route66.link66+xml": [
    "link66"
  ],
  "application/vnd.sailingtracker.track": [
    "st"
  ],
  "application/vnd.seemail": [
    "see"
  ],
  "application/vnd.sema": [
    "sema"
  ],
  "application/vnd.semd": [
    "semd"
  ],
  "application/vnd.semf": [
    "semf"
  ],
  "application/vnd.shana.informed.formdata": [
    "ifm"
  ],
  "application/vnd.shana.informed.formtemplate": [
    "itp"
  ],
  "application/vnd.shana.informed.interchange": [
    "iif"
  ],
  "application/vnd.shana.informed.package": [
    "ipk"
  ],
  "application/vnd.simtech-mindmapper": [
    "twd",
    "twds"
  ],
  "application/vnd.smaf": [
    "mmf"
  ],
  "application/vnd.smart.teacher": [
    "teacher"
  ],
  "application/vnd.solent.sdkm+xml": [
    "sdkm",
    "sdkd"
  ],
  "application/vnd.spotfire.dxp": [
    "dxp"
  ],
  "application/vnd.spotfire.sfs": [
    "sfs"
  ],
  "application/vnd.stardivision.calc": [
    "sdc"
  ],
  "application/vnd.stardivision.draw": [
    "sda"
  ],
  "application/vnd.stardivision.impress": [
    "sdd"
  ],
  "application/vnd.stardivision.math": [
    "smf"
  ],
  "application/vnd.stardivision.writer": [
    "sdw",
    "vor"
  ],
  "application/vnd.stardivision.writer-global": [
    "sgl"
  ],
  "application/vnd.stepmania.package": [
    "smzip"
  ],
  "application/vnd.stepmania.stepchart": [
    "sm"
  ],
  "application/vnd.sun.xml.calc": [
    "sxc"
  ],
  "application/vnd.sun.xml.calc.template": [
    "stc"
  ],
  "application/vnd.sun.xml.draw": [
    "sxd"
  ],
  "application/vnd.sun.xml.draw.template": [
    "std"
  ],
  "application/vnd.sun.xml.impress": [
    "sxi"
  ],
  "application/vnd.sun.xml.impress.template": [
    "sti"
  ],
  "application/vnd.sun.xml.math": [
    "sxm"
  ],
  "application/vnd.sun.xml.writer": [
    "sxw"
  ],
  "application/vnd.sun.xml.writer.global": [
    "sxg"
  ],
  "application/vnd.sun.xml.writer.template": [
    "stw"
  ],
  "application/vnd.sus-calendar": [
    "sus",
    "susp"
  ],
  "application/vnd.svd": [
    "svd"
  ],
  "application/vnd.symbian.install": [
    "sis",
    "sisx"
  ],
  "application/vnd.syncml+xml": [
    "xsm"
  ],
  "application/vnd.syncml.dm+wbxml": [
    "bdm"
  ],
  "application/vnd.syncml.dm+xml": [
    "xdm"
  ],
  "application/vnd.tao.intent-module-archive": [
    "tao"
  ],
  "application/vnd.tcpdump.pcap": [
    "pcap",
    "cap",
    "dmp"
  ],
  "application/vnd.tmobile-livetv": [
    "tmo"
  ],
  "application/vnd.trid.tpt": [
    "tpt"
  ],
  "application/vnd.triscape.mxs": [
    "mxs"
  ],
  "application/vnd.trueapp": [
    "tra"
  ],
  "application/vnd.ufdl": [
    "ufd",
    "ufdl"
  ],
  "application/vnd.uiq.theme": [
    "utz"
  ],
  "application/vnd.umajin": [
    "umj"
  ],
  "application/vnd.unity": [
    "unityweb"
  ],
  "application/vnd.uoml+xml": [
    "uoml"
  ],
  "application/vnd.vcx": [
    "vcx"
  ],
  "application/vnd.visio": [
    "vsd",
    "vst",
    "vss",
    "vsw"
  ],
  "application/vnd.visionary": [
    "vis"
  ],
  "application/vnd.vsf": [
    "vsf"
  ],
  "application/vnd.wap.wbxml": [
    "wbxml"
  ],
  "application/vnd.wap.wmlc": [
    "wmlc"
  ],
  "application/vnd.wap.wmlscriptc": [
    "wmlsc"
  ],
  "application/vnd.webturbo": [
    "wtb"
  ],
  "application/vnd.wolfram.player": [
    "nbp"
  ],
  "application/vnd.wordperfect": [
    "wpd"
  ],
  "application/vnd.wqd": [
    "wqd"
  ],
  "application/vnd.wt.stf": [
    "stf"
  ],
  "application/vnd.xara": [
    "xar"
  ],
  "application/vnd.xfdl": [
    "xfdl"
  ],
  "application/vnd.yamaha.hv-dic": [
    "hvd"
  ],
  "application/vnd.yamaha.hv-script": [
    "hvs"
  ],
  "application/vnd.yamaha.hv-voice": [
    "hvp"
  ],
  "application/vnd.yamaha.openscoreformat": [
    "osf"
  ],
  "application/vnd.yamaha.openscoreformat.osfpvg+xml": [
    "osfpvg"
  ],
  "application/vnd.yamaha.smaf-audio": [
    "saf"
  ],
  "application/vnd.yamaha.smaf-phrase": [
    "spf"
  ],
  "application/vnd.yellowriver-custom-menu": [
    "cmp"
  ],
  "application/vnd.zul": [
    "zir",
    "zirz"
  ],
  "application/vnd.zzazz.deck+xml": [
    "zaz"
  ],
  "application/voicexml+xml": [
    "vxml"
  ],
  "application/widget": [
    "wgt"
  ],
  "application/winhlp": [
    "hlp"
  ],
  "application/wsdl+xml": [
    "wsdl"
  ],
  "application/wspolicy+xml": [
    "wspolicy"
  ],
  "application/x-7z-compressed": [
    "7z"
  ],
  "application/x-abiword": [
    "abw"
  ],
  "application/x-ace-compressed": [
    "ace"
  ],
  "application/x-apple-diskimage": [
    "dmg"
  ],
  "application/x-authorware-bin": [
    "aab",
    "x32",
    "u32",
    "vox"
  ],
  "application/x-authorware-map": [
    "aam"
  ],
  "application/x-authorware-seg": [
    "aas"
  ],
  "application/x-bcpio": [
    "bcpio"
  ],
  "application/x-bittorrent": [
    "torrent"
  ],
  "application/x-blorb": [
    "blb",
    "blorb"
  ],
  "application/x-bzip": [
    "bz"
  ],
  "application/x-bzip2": [
    "bz2",
    "boz"
  ],
  "application/x-cbr": [
    "cbr",
    "cba",
    "cbt",
    "cbz",
    "cb7"
  ],
  "application/x-cdlink": [
    "vcd"
  ],
  "application/x-cfs-compressed": [
    "cfs"
  ],
  "application/x-chat": [
    "chat"
  ],
  "application/x-chess-pgn": [
    "pgn"
  ],
  "application/x-chrome-extension": [
    "crx"
  ],
  "application/x-conference": [
    "nsc"
  ],
  "application/x-cpio": [
    "cpio"
  ],
  "application/x-csh": [
    "csh"
  ],
  "application/x-debian-package": [
    "deb",
    "udeb"
  ],
  "application/x-dgc-compressed": [
    "dgc"
  ],
  "application/x-director": [
    "dir",
    "dcr",
    "dxr",
    "cst",
    "cct",
    "cxt",
    "w3d",
    "fgd",
    "swa"
  ],
  "application/x-doom": [
    "wad"
  ],
  "application/x-dtbncx+xml": [
    "ncx"
  ],
  "application/x-dtbook+xml": [
    "dtb"
  ],
  "application/x-dtbresource+xml": [
    "res"
  ],
  "application/x-dvi": [
    "dvi"
  ],
  "application/x-envoy": [
    "evy"
  ],
  "application/x-eva": [
    "eva"
  ],
  "application/x-font-bdf": [
    "bdf"
  ],
  "application/x-font-ghostscript": [
    "gsf"
  ],
  "application/x-font-linux-psf": [
    "psf"
  ],
  "application/x-font-otf": [
    "otf"
  ],
  "application/x-font-pcf": [
    "pcf"
  ],
  "application/x-font-snf": [
    "snf"
  ],
  "application/x-font-ttf": [
    "ttf",
    "ttc"
  ],
  "application/x-font-type1": [
    "pfa",
    "pfb",
    "pfm",
    "afm"
  ],
  "application/x-freearc": [
    "arc"
  ],
  "application/x-futuresplash": [
    "spl"
  ],
  "application/x-gca-compressed": [
    "gca"
  ],
  "application/x-glulx": [
    "ulx"
  ],
  "application/x-gnumeric": [
    "gnumeric"
  ],
  "application/x-gramps-xml": [
    "gramps"
  ],
  "application/x-gtar": [
    "gtar"
  ],
  "application/x-hdf": [
    "hdf"
  ],
  "application/x-install-instructions": [
    "install"
  ],
  "application/x-iso9660-image": [
    "iso"
  ],
  "application/x-java-jnlp-file": [
    "jnlp"
  ],
  "application/x-latex": [
    "latex"
  ],
  "application/x-lua-bytecode": [
    "luac"
  ],
  "application/x-lzh-compressed": [
    "lzh",
    "lha"
  ],
  "application/x-mie": [
    "mie"
  ],
  "application/x-mobipocket-ebook": [
    "prc",
    "mobi"
  ],
  "application/x-ms-application": [
    "application"
  ],
  "application/x-ms-shortcut": [
    "lnk"
  ],
  "application/x-ms-wmd": [
    "wmd"
  ],
  "application/x-ms-wmz": [
    "wmz"
  ],
  "application/x-ms-xbap": [
    "xbap"
  ],
  "application/x-msaccess": [
    "mdb"
  ],
  "application/x-msbinder": [
    "obd"
  ],
  "application/x-mscardfile": [
    "crd"
  ],
  "application/x-msclip": [
    "clp"
  ],
  "application/x-msdownload": [
    "exe",
    "dll",
    "com",
    "bat",
    "msi"
  ],
  "application/x-msmediaview": [
    "mvb",
    "m13",
    "m14"
  ],
  "application/x-msmetafile": [
    "wmf",
    "wmz",
    "emf",
    "emz"
  ],
  "application/x-msmoney": [
    "mny"
  ],
  "application/x-mspublisher": [
    "pub"
  ],
  "application/x-msschedule": [
    "scd"
  ],
  "application/x-msterminal": [
    "trm"
  ],
  "application/x-mswrite": [
    "wri"
  ],
  "application/x-netcdf": [
    "nc",
    "cdf"
  ],
  "application/x-nzb": [
    "nzb"
  ],
  "application/x-pkcs12": [
    "p12",
    "pfx"
  ],
  "application/x-pkcs7-certificates": [
    "p7b",
    "spc"
  ],
  "application/x-pkcs7-certreqresp": [
    "p7r"
  ],
  "application/x-rar-compressed": [
    "rar"
  ],
  "application/x-research-info-systems": [
    "ris"
  ],
  "application/x-sh": [
    "sh"
  ],
  "application/x-shar": [
    "shar"
  ],
  "application/x-shockwave-flash": [
    "swf"
  ],
  "application/x-silverlight-app": [
    "xap"
  ],
  "application/x-sql": [
    "sql"
  ],
  "application/x-stuffit": [
    "sit"
  ],
  "application/x-stuffitx": [
    "sitx"
  ],
  "application/x-subrip": [
    "srt"
  ],
  "application/x-sv4cpio": [
    "sv4cpio"
  ],
  "application/x-sv4crc": [
    "sv4crc"
  ],
  "application/x-t3vm-image": [
    "t3"
  ],
  "application/x-tads": [
    "gam"
  ],
  "application/x-tar": [
    "tar"
  ],
  "application/x-tcl": [
    "tcl"
  ],
  "application/x-tex": [
    "tex"
  ],
  "application/x-tex-tfm": [
    "tfm"
  ],
  "application/x-texinfo": [
    "texinfo",
    "texi"
  ],
  "application/x-tgif": [
    "obj"
  ],
  "application/x-ustar": [
    "ustar"
  ],
  "application/x-wais-source": [
    "src"
  ],
  "application/x-web-app-manifest+json": [
    "webapp"
  ],
  "application/x-x509-ca-cert": [
    "der",
    "crt"
  ],
  "application/x-xfig": [
    "fig"
  ],
  "application/x-xliff+xml": [
    "xlf"
  ],
  "application/x-xpinstall": [
    "xpi"
  ],
  "application/x-xz": [
    "xz"
  ],
  "application/x-zmachine": [
    "z1",
    "z2",
    "z3",
    "z4",
    "z5",
    "z6",
    "z7",
    "z8"
  ],
  "application/xaml+xml": [
    "xaml"
  ],
  "application/xcap-diff+xml": [
    "xdf"
  ],
  "application/xenc+xml": [
    "xenc"
  ],
  "application/xhtml+xml": [
    "xhtml",
    "xht"
  ],
  "application/xml": [
    "xml",
    "xsl",
    "xsd"
  ],
  "application/xml-dtd": [
    "dtd"
  ],
  "application/xop+xml": [
    "xop"
  ],
  "application/xproc+xml": [
    "xpl"
  ],
  "application/xslt+xml": [
    "xslt"
  ],
  "application/xspf+xml": [
    "xspf"
  ],
  "application/xv+xml": [
    "mxml",
    "xhvml",
    "xvml",
    "xvm"
  ],
  "application/yang": [
    "yang"
  ],
  "application/yin+xml": [
    "yin"
  ],
  "application/zip": [
    "zip"
  ],
  "audio/adpcm": [
    "adp"
  ],
  "audio/basic": [
    "au",
    "snd"
  ],
  "audio/midi": [
    "mid",
    "midi",
    "kar",
    "rmi"
  ],
  "audio/mp4": [
    "mp4a",
    "m4a"
  ],
  "audio/mpeg": [
    "mpga",
    "mp2",
    "mp2a",
    "mp3",
    "m2a",
    "m3a"
  ],
  "audio/ogg": [
    "oga",
    "ogg",
    "spx"
  ],
  "audio/s3m": [
    "s3m"
  ],
  "audio/silk": [
    "sil"
  ],
  "audio/vnd.dece.audio": [
    "uva",
    "uvva"
  ],
  "audio/vnd.digital-winds": [
    "eol"
  ],
  "audio/vnd.dra": [
    "dra"
  ],
  "audio/vnd.dts": [
    "dts"
  ],
  "audio/vnd.dts.hd": [
    "dtshd"
  ],
  "audio/vnd.lucent.voice": [
    "lvp"
  ],
  "audio/vnd.ms-playready.media.pya": [
    "pya"
  ],
  "audio/vnd.nuera.ecelp4800": [
    "ecelp4800"
  ],
  "audio/vnd.nuera.ecelp7470": [
    "ecelp7470"
  ],
  "audio/vnd.nuera.ecelp9600": [
    "ecelp9600"
  ],
  "audio/vnd.rip": [
    "rip"
  ],
  "audio/webm": [
    "weba"
  ],
  "audio/x-aac": [
    "aac"
  ],
  "audio/x-aiff": [
    "aif",
    "aiff",
    "aifc"
  ],
  "audio/x-caf": [
    "caf"
  ],
  "audio/x-flac": [
    "flac"
  ],
  "audio/x-matroska": [
    "mka"
  ],
  "audio/x-mpegurl": [
    "m3u"
  ],
  "audio/x-ms-wax": [
    "wax"
  ],
  "audio/x-ms-wma": [
    "wma"
  ],
  "audio/x-pn-realaudio": [
    "ram",
    "ra"
  ],
  "audio/x-pn-realaudio-plugin": [
    "rmp"
  ],
  "audio/x-wav": [
    "wav"
  ],
  "audio/xm": [
    "xm"
  ],
  "chemical/x-cdx": [
    "cdx"
  ],
  "chemical/x-cif": [
    "cif"
  ],
  "chemical/x-cmdf": [
    "cmdf"
  ],
  "chemical/x-cml": [
    "cml"
  ],
  "chemical/x-csml": [
    "csml"
  ],
  "chemical/x-xyz": [
    "xyz"
  ],
  "font/opentype": [
    "otf"
  ],
  "image/bmp": [
    "bmp"
  ],
  "image/cgm": [
    "cgm"
  ],
  "image/g3fax": [
    "g3"
  ],
  "image/gif": [
    "gif"
  ],
  "image/ief": [
    "ief"
  ],
  "image/jpeg": [
    "jpeg",
    "jpg",
    "jpe"
  ],
  "image/ktx": [
    "ktx"
  ],
  "image/png": [
    "png"
  ],
  "image/prs.btif": [
    "btif"
  ],
  "image/sgi": [
    "sgi"
  ],
  "image/svg+xml": [
    "svg",
    "svgz"
  ],
  "image/tiff": [
    "tiff",
    "tif"
  ],
  "image/vnd.adobe.photoshop": [
    "psd"
  ],
  "image/vnd.dece.graphic": [
    "uvi",
    "uvvi",
    "uvg",
    "uvvg"
  ],
  "image/vnd.djvu": [
    "djvu",
    "djv"
  ],
  "image/vnd.dvb.subtitle": [
    "sub"
  ],
  "image/vnd.dwg": [
    "dwg"
  ],
  "image/vnd.dxf": [
    "dxf"
  ],
  "image/vnd.fastbidsheet": [
    "fbs"
  ],
  "image/vnd.fpx": [
    "fpx"
  ],
  "image/vnd.fst": [
    "fst"
  ],
  "image/vnd.fujixerox.edmics-mmr": [
    "mmr"
  ],
  "image/vnd.fujixerox.edmics-rlc": [
    "rlc"
  ],
  "image/vnd.ms-modi": [
    "mdi"
  ],
  "image/vnd.ms-photo": [
    "wdp"
  ],
  "image/vnd.net-fpx": [
    "npx"
  ],
  "image/vnd.wap.wbmp": [
    "wbmp"
  ],
  "image/vnd.xiff": [
    "xif"
  ],
  "image/webp": [
    "webp"
  ],
  "image/x-3ds": [
    "3ds"
  ],
  "image/x-cmu-raster": [
    "ras"
  ],
  "image/x-cmx": [
    "cmx"
  ],
  "image/x-freehand": [
    "fh",
    "fhc",
    "fh4",
    "fh5",
    "fh7"
  ],
  "image/x-icon": [
    "ico"
  ],
  "image/x-mrsid-image": [
    "sid"
  ],
  "image/x-pcx": [
    "pcx"
  ],
  "image/x-pict": [
    "pic",
    "pct"
  ],
  "image/x-portable-anymap": [
    "pnm"
  ],
  "image/x-portable-bitmap": [
    "pbm"
  ],
  "image/x-portable-graymap": [
    "pgm"
  ],
  "image/x-portable-pixmap": [
    "ppm"
  ],
  "image/x-rgb": [
    "rgb"
  ],
  "image/x-tga": [
    "tga"
  ],
  "image/x-xbitmap": [
    "xbm"
  ],
  "image/x-xpixmap": [
    "xpm"
  ],
  "image/x-xwindowdump": [
    "xwd"
  ],
  "message/rfc822": [
    "eml",
    "mime"
  ],
  "model/iges": [
    "igs",
    "iges"
  ],
  "model/mesh": [
    "msh",
    "mesh",
    "silo"
  ],
  "model/vnd.collada+xml": [
    "dae"
  ],
  "model/vnd.dwf": [
    "dwf"
  ],
  "model/vnd.gdl": [
    "gdl"
  ],
  "model/vnd.gtw": [
    "gtw"
  ],
  "model/vnd.mts": [
    "mts"
  ],
  "model/vnd.vtu": [
    "vtu"
  ],
  "model/vrml": [
    "wrl",
    "vrml"
  ],
  "model/x3d+binary": [
    "x3db",
    "x3dbz"
  ],
  "model/x3d+vrml": [
    "x3dv",
    "x3dvz"
  ],
  "model/x3d+xml": [
    "x3d",
    "x3dz"
  ],
  "text/cache-manifest": [
    "appcache",
    "manifest"
  ],
  "text/calendar": [
    "ics",
    "ifb"
  ],
  "text/coffeescript": [
    "coffee"
  ],
  "text/css": [
    "css"
  ],
  "text/csv": [
    "csv"
  ],
  "text/hjson": [
    "hjson"
  ],
  "text/html": [
    "html",
    "htm"
  ],
  "text/jade": [
    "jade"
  ],
  "text/jsx": [
    "jsx"
  ],
  "text/less": [
    "less"
  ],
  "text/n3": [
    "n3"
  ],
  "text/plain": [
    "txt",
    "text",
    "conf",
    "def",
    "list",
    "log",
    "in",
    "ini"
  ],
  "text/prs.lines.tag": [
    "dsc"
  ],
  "text/richtext": [
    "rtx"
  ],
  "text/sgml": [
    "sgml",
    "sgm"
  ],
  "text/stylus": [
    "stylus",
    "styl"
  ],
  "text/tab-separated-values": [
    "tsv"
  ],
  "text/troff": [
    "t",
    "tr",
    "roff",
    "man",
    "me",
    "ms"
  ],
  "text/turtle": [
    "ttl"
  ],
  "text/uri-list": [
    "uri",
    "uris",
    "urls"
  ],
  "text/vcard": [
    "vcard"
  ],
  "text/vnd.curl": [
    "curl"
  ],
  "text/vnd.curl.dcurl": [
    "dcurl"
  ],
  "text/vnd.curl.mcurl": [
    "mcurl"
  ],
  "text/vnd.curl.scurl": [
    "scurl"
  ],
  "text/vnd.dvb.subtitle": [
    "sub"
  ],
  "text/vnd.fly": [
    "fly"
  ],
  "text/vnd.fmi.flexstor": [
    "flx"
  ],
  "text/vnd.graphviz": [
    "gv"
  ],
  "text/vnd.in3d.3dml": [
    "3dml"
  ],
  "text/vnd.in3d.spot": [
    "spot"
  ],
  "text/vnd.sun.j2me.app-descriptor": [
    "jad"
  ],
  "text/vnd.wap.wml": [
    "wml"
  ],
  "text/vnd.wap.wmlscript": [
    "wmls"
  ],
  "text/vtt": [
    "vtt"
  ],
  "text/x-asm": [
    "s",
    "asm"
  ],
  "text/x-c": [
    "c",
    "cc",
    "cxx",
    "cpp",
    "h",
    "hh",
    "dic"
  ],
  "text/x-component": [
    "htc"
  ],
  "text/x-fortran": [
    "f",
    "for",
    "f77",
    "f90"
  ],
  "text/x-handlebars-template": [
    "hbs"
  ],
  "text/x-java-source": [
    "java"
  ],
  "text/x-lua": [
    "lua"
  ],
  "text/x-markdown": [
    "markdown",
    "md",
    "mkd"
  ],
  "text/x-nfo": [
    "nfo"
  ],
  "text/x-opml": [
    "opml"
  ],
  "text/x-pascal": [
    "p",
    "pas"
  ],
  "text/x-sass": [
    "sass"
  ],
  "text/x-scss": [
    "scss"
  ],
  "text/x-setext": [
    "etx"
  ],
  "text/x-sfv": [
    "sfv"
  ],
  "text/x-uuencode": [
    "uu"
  ],
  "text/x-vcalendar": [
    "vcs"
  ],
  "text/x-vcard": [
    "vcf"
  ],
  "text/yaml": [
    "yaml",
    "yml"
  ],
  "video/3gpp": [
    "3gp"
  ],
  "video/3gpp2": [
    "3g2"
  ],
  "video/h261": [
    "h261"
  ],
  "video/h263": [
    "h263"
  ],
  "video/h264": [
    "h264"
  ],
  "video/jpeg": [
    "jpgv"
  ],
  "video/jpm": [
    "jpm",
    "jpgm"
  ],
  "video/mj2": [
    "mj2",
    "mjp2"
  ],
  "video/mp2t": [
    "ts"
  ],
  "video/mp4": [
    "mp4",
    "mp4v",
    "mpg4"
  ],
  "video/mpeg": [
    "mpeg",
    "mpg",
    "mpe",
    "m1v",
    "m2v"
  ],
  "video/ogg": [
    "ogv"
  ],
  "video/quicktime": [
    "qt",
    "mov"
  ],
  "video/vnd.dece.hd": [
    "uvh",
    "uvvh"
  ],
  "video/vnd.dece.mobile": [
    "uvm",
    "uvvm"
  ],
  "video/vnd.dece.pd": [
    "uvp",
    "uvvp"
  ],
  "video/vnd.dece.sd": [
    "uvs",
    "uvvs"
  ],
  "video/vnd.dece.video": [
    "uvv",
    "uvvv"
  ],
  "video/vnd.dvb.file": [
    "dvb"
  ],
  "video/vnd.fvt": [
    "fvt"
  ],
  "video/vnd.mpegurl": [
    "mxu",
    "m4u"
  ],
  "video/vnd.ms-playready.media.pyv": [
    "pyv"
  ],
  "video/vnd.uvvu.mp4": [
    "uvu",
    "uvvu"
  ],
  "video/vnd.vivo": [
    "viv"
  ],
  "video/webm": [
    "webm"
  ],
  "video/x-f4v": [
    "f4v"
  ],
  "video/x-fli": [
    "fli"
  ],
  "video/x-flv": [
    "flv"
  ],
  "video/x-m4v": [
    "m4v"
  ],
  "video/x-matroska": [
    "mkv",
    "mk3d",
    "mks"
  ],
  "video/x-mng": [
    "mng"
  ],
  "video/x-ms-asf": [
    "asf",
    "asx"
  ],
  "video/x-ms-vob": [
    "vob"
  ],
  "video/x-ms-wm": [
    "wm"
  ],
  "video/x-ms-wmv": [
    "wmv"
  ],
  "video/x-ms-wmx": [
    "wmx"
  ],
  "video/x-ms-wvx": [
    "wvx"
  ],
  "video/x-msvideo": [
    "avi"
  ],
  "video/x-sgi-movie": [
    "movie"
  ],
  "video/x-smv": [
    "smv"
  ],
  "x-conference/x-cooltalk": [
    "ice"
  ]
}

+ 12 - 0
src/doctor/util/objectUtil.js

@ -0,0 +1,12 @@
"use strict";
/**
 * 检查对象是否为JSON。
 *
 * @see http://stackoverflow.com/questions/11182924/how-to-check-if-javascript-object-is-json
 *
 * @param object
 */
exports.isJsonObject = function(object){
    return {}.constructor === object.constructor;
};

+ 3 - 0
test/doctor/config.js

@ -0,0 +1,3 @@
"use strict";
module.exports.host = 'http://127.0.0.1:3000';

+ 26 - 0
test/doctor/endpoints/application.endpoint.Test.js

@ -0,0 +1,26 @@
"use strict";
var testConfig = require('../config');
var APIv1 = require('../../../src/doctor/include/endpoints').APIv1;
var should = require("should");
var restTemplate = require('supertest').agent(testConfig.host);
var userId = '48832ecc339111e6badcfa163e789456';
describe('Chat api', function () {
    // 获取角标数
    describe('when get badge number', function () {
        it('shout return 200 and badge number', function (done) {
            var path = APIv1.Application.Base + APIv1.Application.BadgeNo + "?user_id=" + userId;
            restTemplate.get(path)
                .end(function (err, response) {
                    console.log("User " + userId + "'s badge number: \n", response.body);
                    response.status.should.equal(200);
                    done();
                });
        });
    });
});

+ 151 - 0
test/doctor/endpoints/chats.endpoint.Test.js

@ -0,0 +1,151 @@
"use strict";
var testConfig = require('../config');
var APIv1 = require('../../../src/doctor/include/endpoints').APIv1;
var should = require("should");
var restTemplate = require('supertest').agent(testConfig.host);
var userId = '48832ecc339111e6badcfa163e789456';
describe('Chat api', function () {
    // 发送系统消息
    describe('when send SYSTEM message correctly', function () {
        it('should return 200', function (done) {
            var path = APIv1.Chats.Base + APIv1.Chats.SM;
            restTemplate.post(path)
                .send({
                        to: "Rose",
                        title: "System Message",
                        summary: "You have new job",
                        contentType: "1",
                        content: "The patient has been followed in the scheduler, please make new follow plan as soon as possible."
                    }
                ).end(function (err, response) {
            });
        });
    });
    // 发送私信
    describe('when send PRIVATE message correctly', function () {
        it('should return 200', function (done) {
            var path = APIv1.Chats.Base + APIv1.Chats.PM;
            restTemplate.post(path)
                .send({
                    to: "Rose",
                    title: "System Message",
                    summary: "You have new job",
                    contentType: "1",
                    content: "The patient has been followed in the scheduler, please make new follow plan as soon as possible."
                })
                .end(function (err, response) {
                });
        });
    });
    // 发送群消息
    describe('when send PRIVATE message with bad message format', function () {
        it('should return 200', function (done) {
            var path = APIv1.Chats.Base + APIv1.Chats.PM;
            restTemplate.post(path)
                .send({
                    from: userId,
                    at: "Rose",
                    group: "DiscussionGroupId",
                    groupType: 1,
                    contentType: 1,
                    content: "The patient mess me up"
                })
                .end(function (err, response) {
                });
            restTemplate.post(path)
                .send({
                    from: userId,
                    at: "",
                    group: "DiscussionGroupId",
                    groupType: 1,
                    contentType: 1,
                    content: "The patient mess me up"
                }).end(function (err, response) {
            });
        });
    });
    // 所有聊天列表
    describe('when get ALL CHAT list', function () {
        it('should return 200 and all chat list', function (done) {
            var path = APIv1.Chats.Base + APIv1.Chats.List + "?user_id=" + userId;
            restTemplate.get(path)
                .end(function (err, response) {
                    console.log("User " + userId + "'s all chat list: \n", response.body);
                    response.status.should.equal(200);
                    done();
                });
        });
    });
    // 未读组数量
    describe('when get ALL GROUP unread chat count', function () {
        it('should return 200 and group chat count', function (done) {
            var path = APIv1.Chats.Base + APIv1.Chats.GMUnreadCount + "?user_id=" + userId;
            restTemplate.get(path)
                .end(function (err, response) {
                    console.log("User " + userId + "'s group chat count: \n", response.body);
                    response.status.should.equal(200);
                    done();
                });
        })
    });
    // 未读私信数量
    describe('when get ALL PRIVATE unread chat list', function () {
        it('should return 200 and private chat count', function (done) {
            var path = APIv1.Chats.Base + APIv1.Chats.PMUnreadCount + "?user_id=" + userId;
            restTemplate.get(path)
                .end(function (err, response) {
                    console.log("User " + userId + "'s private chat count: \n", response.body);
                    response.status.should.equal(200);
                    done();
                });
        })
    });
    // 所有未读消息数量
    describe('when get ALL unread message count', function () {
        it('should return 200 and all unread message count', function (done) {
            var path = APIv1.Chats.Base + APIv1.Chats.UnreadMsgCount + "?user_id=" + userId;
            restTemplate.get(path)
                .end(function (err, response) {
                    console.log("User " + userId + "'s all unread message count: \n", response.body);
                    response.status.should.equal(200);
                    done();
                });
        });
    });
    // 获取角标数
    describe('when get badge number', function () {
        it('shout return 200 and badge number', function (done) {
            var path = APIv1.Application.Base + APIv1.Application.BadgeNo + "?user_id=" + userId;
            restTemplate.get(path)
                .end(function (err, response) {
                    console.log("User " + userId + "'s badge number: \n", response.body);
                    response.status.should.equal(200);
                    done();
                });
        });
    });
});

+ 26 - 0
test/doctor/endpoints/management.endpoint.Test.js

@ -0,0 +1,26 @@
"use strict";
var testConfig = require('../config');
var APIv1 = require('../../../src/doctor/include/endpoints').APIv1;
var should = require("should");
var server = require('supertest').agent(testConfig.host);
describe('Management api', function () {
    describe('when require /db', function(){
        it('should return 200', function (done) {
            var path = APIv1.Management.Base + APIv1.Management.DbStatus;
            server.get(path)
                .expect(200, function (err, response) {
                    //console.log(response.body);
                    response.body.length.should.equal(2);
                    response.body[0].tables.length.should.greaterThan(5);
                    response.body[1].tables.length.should.greaterThan(5);
                    done();
                });
        });
    });
});

+ 76 - 0
test/doctor/endpoints/users.endpoint.Test.js

@ -0,0 +1,76 @@
"use strict";
var testConfig = require('../config');
var APIv1 = require('../../../src/doctor/include/endpoints').APIv1;
var should = require("should");
var server = require('supertest').agent(testConfig.host);
describe('User api', function () {
    describe('when login with correct params', function () {
        it('should return 200', function (done) {
            var path = APIv1.Users.Base + APIv1.Users.Login + "?user_id=sand&token=0PFWlKmLBN9YzhCfFWVgYA&client_id=H6FYbDejks6VjMmW3uH7V6&platform=0";
            server.get(path)
                .expect(200)
                .end(done);
        })
    });
    describe('when login without user_id', function () {
        it('should return 406', function (done) {
            var path = APIv1.Users.Base + APIv1.Users.Login + "?token=0PFWlKmLBN9YzhCfFWVgYA&client_id=H6FYbDejks6VjMmW3uH7V6&platform=0";
            server.get(path)
                .expect(406, done);
        });
    });
    describe('when login without client_id', function () {
        it('should return 406', function (done) {
            var path = APIv1.Users.Base + APIv1.Users.Login + "?user_id=sand&token=0PFWlKmLBN9YzhCfFWVgYA&platform=0";
            server.get(path)
                .expect(406, done);
        });
    });
    describe('when login without token', function () {
        it('should return 406', function (done) {
            var path = APIv1.Users.Base + APIv1.Users.Login + "?user_id=sand&token=0PFWlKmLBN9YzhCfFWVgYA&platform=0";
            server.get(path)
                .expect(406, done);
        });
    });
    describe('when login without platform', function () {
        it('should return 406', function (done) {
            var path = APIv1.Users.Base + APIv1.Users.Login + "?token=0PFWlKmLBN9YzhCfFWVgYA&client_id=H6FYbDejks6VjMmW3uH7V6";
            server.get(path)
                .expect(406, done);
        });
    });
    describe('when update user status', function () {
        it('should return 200', function (done) {
            var path = APIv1.Users.Base + "/sand/status";
            server.post(path)
                .set('Content-Type', 'application/json')
                .set('X-HTTP-Method-Override', 'POST')
                .send({status: 1})
                .expect(200, done);
        });
    });
    describe('when user logout', function () {
        it('should return 200', function (done) {
            var path = APIv1.Users.Base + APIv1.Users.Logout + "?user_id=sand";
            server.get(path)
                .expect(200, done);
        });
    });
});

+ 9 - 0
test/doctor/package.json

@ -0,0 +1,9 @@
{
  "name": "im.doctor.test",
  "version": "1.0.1",
  "devDependencies": {
    "mocha": "^2.2.5",
    "should": "^7.0.2",
    "supertest": "^1.0.1"
  }
}

+ 5 - 0
test/doctor/util/dbUtil.Test.js

@ -0,0 +1,5 @@
"use strict";
var dbUtil = require('../../../src/doctor/util/dbUtil');
var assert = require('assert');

+ 19 - 0
test/doctor/util/objectUtil.Test.js

@ -0,0 +1,19 @@
"use strict";
var objectUtil = require('../../../src/doctor/util/objectUtil');
var assert = require('assert');
describe('Object utilities', function(){
    describe('When test isJsonObject with "Array" object', function(){
        it('should return false', function(){
            assert.strictEqual(objectUtil.isJsonObject([1, 2]), false);
        })
    });
    describe('When test isJsonObject with "Dict" object', function () {
        it('should return true', function () {
            assert.strictEqual(objectUtil.isJsonObject({age: 2}), true);
        })
    });
});