Jelajahi Sumber

完成消息发送单元测试及BUG修复;

Sand 8 tahun lalu
induk
melakukan
1df6685026
35 mengubah file dengan 823 tambahan dan 1618 penghapusan
  1. 15 24
      src/client/im.client.js
  2. 2 3
      src/server/app.js
  3. 23 9
      src/server/endpoints/url.initializer.js
  4. 0 34
      src/server/endpoints/v1/application.endpoint.js
  5. 0 702
      src/server/endpoints/v1/chats.endpoint.js
  6. 0 56
      src/server/endpoints/v1/groups.endpoint.js
  7. 0 27
      src/server/endpoints/v1/management.endpoint.js
  8. 0 108
      src/server/endpoints/v1/users.endpoint.js
  9. 3 12
      src/server/endpoints/v2/application.endpoint.js
  10. 78 60
      src/server/endpoints/v2/session.endpoint.js
  11. 1 9
      src/server/endpoints/v2/topic.endpoint.js
  12. 5 8
      src/server/endpoints/v2/user.endpoint.js
  13. 32 23
      src/server/include/commons.js
  14. 54 25
      src/server/models/messages/messages.js
  15. 1 1
      src/server/models/redis.model.js
  16. 50 44
      src/server/models/sessions/participants.js
  17. 1 1
      src/server/models/sessions/session.cleaner.js
  18. 187 178
      src/server/models/sessions/sessions.js
  19. 86 77
      src/server/models/sessions/topics.js
  20. 0 34
      src/server/models/stats.js
  21. 1 1
      src/server/models/user/patient.js
  22. 17 17
      src/server/models/user/users.js
  23. 4 12
      src/server/repository/mysql/message.repo.js
  24. 22 44
      src/server/repository/mysql/participant.repo.js
  25. 2 2
      src/server/repository/mysql/patient.repo.js
  26. 19 17
      src/server/repository/mysql/session.repo.js
  27. 1 5
      src/server/resources/config/config.dev.js
  28. 1 2
      src/server/resources/schema/ichat_1.2.8_table_schema.sql
  29. 1 1
      src/server/util/db.util.js
  30. 13 31
      test/client/im.client.application.Test.js
  31. 0 31
      test/client/im.client.health.Test.js
  32. 2 19
      test/client/im.client.session.Test.js
  33. 36 0
      test/client/im.client.session.muc.Test.js
  34. 165 0
      test/client/im.client.session.p2p.Test.js
  35. 1 1
      test/client/im.client.user.Test.js

+ 15 - 24
src/client/im.client.js

@ -29,7 +29,7 @@ var server = "http://127.0.0.1:3008/api/v2";
// 资源实体在URL中的占位符
var UserPath = ":user_id";
var SessionPath = ":session_id";
var TopicPath = ":topic+id";
var TopicPath = ":topic_id";
var ParticipantPath = ":participant_id";
// REST API
@ -49,6 +49,8 @@ var ENDPOINTS = {
        UserStatus: '/users/:user_id/status'
    },
    Sessions: {
        Sessions: '/sessions',
        Session: '/sessions/:session_id',
        SessionSticky: '/sessions/:session_id/sticky',
@ -74,15 +76,6 @@ var ENDPOINTS = {
    Search: {}
};
// 将API路径加上主机名称
/*(function regularEndpoints() {
 for (var catalog in ENDPOINTS) {
 for(var path in catalog){
 ENDPOINTS.getProperty(catalog).setProperty(path, server + path);
 }
 }
 })();*/
var httpClient = {
    get: function (endpoint, data, successCallback, errorCallback) {
        $.ajax({
@ -150,10 +143,8 @@ var httpClient = {
            }
        });
    },
    // 执行业务接口前,调用此函数判断当前用户是否在线。
    isOnline: function (callback, failure) {
        /**
         * 执行业务接口前,调用此函数判断当前用户是否在线。
         */
        httpClient.get(ENDPOINTS.Users.UserStatus, {}, null, function (res) {
            if (res.status == 200) {
                callback();
@ -214,23 +205,23 @@ var imClient = {
    Sessions: {
        // 创建MUC会话
        createMucSession: function (userId, peerId, success, failure) {
            httpClient.post(ENDPOINTS.Sessions,
                {session_type: 1, user_id: userId, peer_id: peerId},
            httpClient.post(ENDPOINTS.Sessions.Sessions,
                {session_type: 1, session_name: "咨询", participants: []},
                success,
                failure);
        },
        // 创建P2P会话
        createP2pSession: function (userId, peerId, success, failure) {
            httpClient.post(ENDPOINTS.Sessions,
                {session_type: 2, user_id: userId, peer_id: peerId},
            httpClient.post(ENDPOINTS.Sessions.Sessions,
                {session_type: 2, session_name: "P2P", participants: [userId + ":0", peerId + ":0"]},
                success,
                failure);
        },
        // 获取与患者发生的会话,实际上这些是MUC会话
        getSessionsWithPatient: function (userId, page, size, success, failure) {
            httpClient.get(ENDPOINTS.Sessions,
            httpClient.get(ENDPOINTS.Sessions.Sessions,
                {user_id: userId, session_type: "1", page: page, size: size},
                success,
                failure);
@ -238,8 +229,8 @@ var imClient = {
        // 获取与医生相关的会话,实际上这些是P2P,群聊和讨论组
        getSessionsWithDoctor: function (userId, page, size, success, failure) {
            httpClient.get(ENDPOINTS.Sessions,
                {user_id: userId, session_type: "2, 3", page: page, size: size},
            httpClient.get(ENDPOINTS.Sessions.Sessions,
                {user_id: userId, session_type: "2,3", page: page, size: size},
                success,
                failure);
        },
@ -310,9 +301,9 @@ var imClient = {
        },
        // 发送消息,不论是何种会话,均不需要指定会话对象是谁,只需要向会话本身发送消息即可
        sendMessage: function (sessionId, userId, content, contentType, success, failure) {
        sendMessage: function (sessionId, userId, userName, content, contentType, success, failure) {
            httpClient.post(ENDPOINTS.Sessions.Messages.replace(SessionPath, sessionId),
                {from: userId, content_type: contentType, content: content},
                {sender_id: userId, sender_name: userName, content_type: contentType, content: content},
                success,
                failure);
        },
@ -359,7 +350,7 @@ var imClient = {
        // 添加会话成员
        addParticipant: function (sessionId, participantId, success, failure) {
            httpClient.post(ENDPOINTS.Sessions.Participants.replace(SessionPath, sessionId),
            httpClient.post(ENDPOINTS.Sessions.SessionParticipants.replace(SessionPath, sessionId),
                {participant_id: participantId},
                success,
                failure);
@ -367,7 +358,7 @@ var imClient = {
        // 获取会话成员列表
        getParticipants: function (sessionId, success, failure) {
            httpClient.get(ENDPOINTS.Sessions.Participants.replace(SessionPath, sessionId),
            httpClient.get(ENDPOINTS.Sessions.SessionParticipants.replace(SessionPath, sessionId),
                {},
                success,
                failure);

+ 2 - 3
src/server/app.js

@ -45,9 +45,8 @@ UrlInitializer.initRestApi(app);
// 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});
        res.status(err.httpStatus || 500)
           .send({message: err.message});
    }
    next(err);

+ 23 - 9
src/server/endpoints/url.initializer.js

@ -8,8 +8,7 @@
 */
"use strict";
let PAGES = require('../include/endpoints').PAGES;
let APIv2 = require('../include/endpoints').APIv2;
let ObjectUtil = require("../util/object.util.js");
// pages
let index = require('../controllers/index');
@ -22,27 +21,42 @@ let sessions = require('./v2/session.endpoint');
let topics = require('./v2/topic.endpoint');
let management = require('./v2/management.endpoint');
class UrlInitializer{
    constructor(){
const PAGES = require('../include/endpoints').PAGES;
const APIv2 = require('../include/endpoints').APIv2;
class UrlInitializer {
    constructor() {
    }
    static initRestApi(app){
    static initRestApi(app) {
        // 旧版本API过滤
        app.use(function (req, res, next) {
            if(req.url.indexOf('/api/v1') >= 0){
            if (req.url.indexOf('/api/v1') >= 0) {
                res.status(403).send({message: "API v1 is invalid since 2.0.0, please use API v2."});
            } else {
                next();
            }
        });
        // POST, PUT请求必须使用JSON作为BODY
        app.use(function (req, res, next) {
            if(req.method === 'POST' || req.method === 'PUT'){
                if(!ObjectUtil.isJsonObject(req.body)){
                    throw {httpStatus: 406, message: 'Problems parsing JSON.'}
                }
            }
            next();
        });
        app.use(APIv2.Application.Base, application);
        app.use(APIv2.Management.Base, management);
        app.use(APIv2.Users.Base, users);
        app.use(APIv2.Sessions.Base,sessions);
        app.use(APIv2.Sessions.Topics,topics);
        app.use(APIv2.Sessions.Base, sessions);
        app.use(APIv2.Sessions.Topics, topics);
    }
    static initWebPages(app){
    static initWebPages(app) {
        app.use(PAGES.Home.Index, index);
        app.use(PAGES.Socket.Index, socket);
    }

+ 0 - 34
src/server/endpoints/v1/application.endpoint.js

@ -1,34 +0,0 @@
"use strict";
let express = require('express');
let router = express.Router();
const APIv1 = require('../include/endpoints').APIv1;
const MODEL_EVENTS = require('../include/commons').MODEL_EVENTS;
let StatsMessage = require("../models/stats");
/**
 * 获取应用角标数。
 *
 * /application/badge_no?user_id=sand
 *
 * 参数:
 * uid:用户id
 */
router.get(APIv1.Application.BadgeNo, function (req, res) {
    let userId = req.query.user_id;
    let statsMsg = new StatsMessage();
    statsMsg.on(MODEL_EVENTS.OK, function (data) {
        res.status(200).send(data);
    });
    statsMsg.on(MODEL_EVENTS.Error, function (data) {
        res.status(500).send(data);
    });
    statsMsg.getBadgeNumber(userId);
});
module.exports = router;

+ 0 - 702
src/server/endpoints/v1/chats.endpoint.js

@ -1,702 +0,0 @@
/**
 * 消息端点。
 *
 * 此控制器处理点对点,组及消息消息。为三类消息提供发送及查询功能。
 */
"use strict";
let express = require('express');
let router = express.Router();
let http = require('http');
let log = require('../util/log.js');
let ObjectUtil = require("../util/object.util.js");
let ControllerUtil = require('../util/controller.util');
let Patient = require("../models/user/patient");
let Doctor = require('../models/user/doctor');
let Group = require('../models/group');
let Search = require('../models/search');
let Sessions = require('../models/sessions/sessions');
const APIv1 = require('../include/endpoints').APIv1;
const CONTENT_TYPES = require('../include/commons').CONTENT_TYPE;
const MAX_INT = require('../include/commons').MAX_INT;
const DEFAULT_PAGE_SIZE = require('../include/commons').DEFAULT_PAGE_SIZE;
//--------------------------------------------------------------//
//----------------------------消息发送----------------------------//
//--------------------------------------------------------------//
/**
 * 发送System消息。
 *
 * 请求URL:
 *  /chats/sm
 *
 * 消息格式:
 *  {
 *      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.",
 *      delay: 986123465
 *  }
 *
 * @param message
 */
router.post(APIv1.Chats.SM, function (req, res) {
    // 检查消息体及消息格式是否正确
    let message = req.body;
    if (!ObjectUtil.isJsonObject(message)) {
        throw {httpStatus: 406, message: 'Problems parsing JSON.'}
    }
    // 字段判断
    let testing = ObjectUtil.fieldsCheck(message, "to", "title", "summary", "contentType", "content");
    if (!testing.pass) {
        throw {httpStatus: 406, message: testing.message}
    }
    // 消息处理
    let doctor = new Doctor();
    ControllerUtil.regModelEventHandler(doctor, res);
    doctor.sendSystemMessage(message);
});
router.get(APIv1.Chats.TEST,function(req,res){
    let test = req.query.test;
    //http://192.168.131.107:3008/api/v1/chats/test?test=1&page=0&pagesize=10&user=3121&sessionId=testsessionmsg1
    if(test==1){//获取会话消息列表
        let page = req.query.page;
        let pagesize = req.query.pagesize;
        let user = req.query.user;
        let sessionId =req.query.sessionId;
        let sessions = new Sessions();
        ControllerUtil.regModelEventHandler(sessions, res);
        sessions.getMessages(sessionId,user,page,pagesize);
    }
    //http://192.168.131.107:3008/api/v1/chats/test?test=2&page=0&pagesize=10&user=3121
    if(test==2){//获取用户会话
        let sessions = new Sessions();
        let page = req.query.page;
        let pagesize = req.query.pagesize;
        let user = req.query.user;
        ControllerUtil.regModelEventHandler(sessions, res);
        sessions.getUserSessions(user,page,pagesize);
    }
    //http://192.168.131.107:3008/api/v1/chats/test?test=3&sessionId=132312312&users=10,2,3&name=3121&sessionType=2
    if(test==3){
        let sessions = new Sessions();
        ControllerUtil.regModelEventHandler(sessions, res);
        let sessionId = req.query.sessionId;
        let users = req.query.users;
        let name = req.query.name;
        let sessionType = req.query.sessionType;
        sessions.createSession(sessionId,name,sessionType,users);
    }
    if(test==4){
        let sessions = new Sessions();
        ControllerUtil.regModelEventHandler(sessions, res);
        let sessionId = req.query.sessionId;
        let message ={};
        message.contentType =1;
        message.timestamp=new Date();
        message.content ="test send message";
        message.senderId="10";
        message.senderName="test1";
        sessions.saveMessageBySession(message,sessionId);
    }
    //http://192.168.131.107:3008/api/v1/chats/test?test=5&user=3121&sessionId=testsessionmsg1
    if(test==5){
        let sessions = new Sessions();
        ControllerUtil.regModelEventHandler(sessions, res);
        let sessionId = req.query.sessionId;
        let user = req.query.user;
        let type = req.query.type;
        if(type==1){
            //置顶
            sessions.stickSession(sessionId,user);
        }else{
            //取消置顶
            sessions.cancelStickSession(sessionId,user);
        }
    }
})
/**
 * 处理Private消息。处理流程分:
 * 1 解析消息,并保存到数据库
 * 2 更新消息统计数据
 * 3 获取目标的状态并构建通知消息,如果用户在线就推送通知消息
 *
 * 请求URL:
 *  /chats/pm
 *
 * 消息格式:
 *  {
 *      from: sand,
 *      to: Rose,
 *      contentType: "1,2,3,4",
 *      content: "Please follow the patient as soon as possible."
 *  }
 *
 * @param message
 */
router.post(APIv1.Chats.PM, function (req, res) {
    // 检查消息体及消息格式是否正确
    let message = req.body;
    if (!ObjectUtil.isJsonObject(message)) {
        throw {httpStatus: 406, message: 'Problems parsing JSON.'}
    }
    // 字段判断
    let testing = ObjectUtil.fieldsCheck(message, "from", "to", "contentType", "content");
    if (!testing.pass) {
        throw {httpStatus: 406, message: testing.message};
    }
    // 消息处理,患者与医生分开发送
    Patient.isPatientCode(message.to,
        function () {
            let patient = new Patient();
            ControllerUtil.regModelEventHandler(patient, res);
            patient.sendMessage(message);
        }, function () {
            let doctor = new Doctor();
            ControllerUtil.regModelEventHandler(doctor, res);
            doctor.sendMessage(message);
        });
});
/**
 * 处理讨论组消息。
 *
 * 请求URL:
 *  /chats/gm
 *
 * 消息格式:
 *  {
 *      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) {
    // 检查消息体及消息格式是否正确
    let message = req.body;
    if (!ObjectUtil.isJsonObject(message)) {
        throw {httpStatus: 406, message: 'Problems parsing JSON.'};
    }
    // 字段判断
    let testing = ObjectUtil.fieldsCheck(message, 'from', 'at', 'group', 'groupType', 'contentType', 'content');
    if (!testing.pass) {
        throw {httpStatus: 406, message: testing.message}
    }
    // 消息处理
    let group = new Group();
    ControllerUtil.regModelEventHandler(group, res);
    group.sendMessage(message);
});
//--------------------------------------------------------------//
//----------------------------消息提取----------------------------//
//--------------------------------------------------------------//
/**
 * 获取参与的聊天列表,包括:点对点,@我,参与的讨论组,系统消息等。
 *
 * 请求URL:
 *  /chats/list?user_id=sand
 */
router.get(APIv1.Chats.List, function (req, res) {
    let userId = req.query.user_id;
    if (userId === null) {
        throw {httpStatus: 406, message: 'Missing fields.'};
    }
    let doctor = new Doctor();
    ControllerUtil.regModelEventHandler(doctor, res);
    doctor.getChatList(userId);
});
/**
 * 获取与患者的聊天列表,包括:P2P,参与的讨论组和行政团队。
 *
 * 请求URL:
 *  /chats/list/patient
 */
router.get(APIv1.Chats.ListWithPatient, function (req, res) {
    let userId = req.query.user_id;
    if (userId === null) {
        throw {httpStatus: 406, message: 'Missing fields.'};
    }
    let doctor = new Doctor();
    ControllerUtil.regModelEventHandler(doctor, res);
    doctor.getChatsListWithPatient(userId);
});
/**
 * 获取与医生的聊天列表,包括:点对点,参与的讨论组。
 *
 * 请求URL:
 *  /chats/list/doctor
 */
router.get(APIv1.Chats.ListWithDoctor, function (req, res) {
    let userId = req.query.user_id;
    if (userId === null) {
        throw {httpStatus: 406, message: 'Missing fields.'};
    }
    let doctor = new Doctor();
    ControllerUtil.regModelEventHandler(doctor, res);
    doctor.getChatListWithDoctor(userId);
});
router.get(APIv1.Chats.MsgAmount, function (req, res) {
    let userId = req.query.user_id;
    if (userId === null) {
        throw {httpStatus: 406, message: 'Missing fields.'};
    }
    let doctor = new Doctor();
    ControllerUtil.regModelEventHandler(doctor, res);
    doctor.getChatListMsgAmount(userId);
});
/**
 * 获取最近聊天对象:包括患者,医生与讨论组。客户端自行根据需要提取患者、医生或讨论组数据。
 *
 * 请求URL:
 *  /chats/recent?user_id=0de7295862dd11e69faffa163e8aee56&days=7
 *
 * 参数:
 *  user_id: 用户ID
 *  target_type: 对象类型,1患者,2医生,3讨论组
 *  days: 最近天数,默认7天
 */
router.get(APIv1.Chats.Recent, function (req, res) {
    let userId = req.query.user_id;
    let days = req.query.days;
    if (userId === null) {
        throw {httpStatus: 406, message: 'Missing field: user_id'};
    }
    if (days === null) {
        days = 7;
    }
    let doctor = new Doctor();
    ControllerUtil.regModelEventHandler(doctor, res);
    doctor.getRecentChatList(userId, days);
});
/**
 * 获取私信。倒序排列。
 *
 * 参数:
 *  user_id 必须,医生ID
 *  peer_id 必须,对方医生ID
 *  content_type 必须,消息类型
 *  message_start_id 可选,消息的起始ID,如果为空从最新的一条开始获取
 *  message_end_id 可选,消息的结束ID,如果为空从第一条开始获取
 *  count 可选,消息数量,如果不指定、小于零或大于50,默认为100条。若message_start_id与message_end_id均不为空,则此参数无效,方法是设置为10000条
 *  closed_interval 消息范围是否使用闭区间
 *
 * 请求URL:
 *  /chats/pm?user_id=sand&peer_id=Rose&content_type=2&message_start_id=10000&message_end_id=0&count=20&closed_interval=false
 */
router.get(APIv1.Chats.PM, function (req, res) {
    let userId = req.query.user_id;
    let peerId = req.query.peer_id;
    let contentType = req.query.content_type;
    let msgStartId = !req.query.message_start_id ? MAX_INT : parseInt(req.query.message_start_id);
    let msgEndId = !req.query.message_end_id || req.query.message_end_id === 'null' ? 0 : parseInt(req.query.message_end_id);
    let count = req.query.count === undefined ? DEFAULT_PAGE_SIZE : parseInt(req.query.count);
    let closedInterval = (req.query.closed_interval != false && req.query.closed_interval === "true");
    if (contentType !== undefined && parseInt(contentType) === CONTENT_TYPES.Image) count = DEFAULT_PAGE_SIZE;
    if (req.query.message_start_id && req.query.message_end_id) count = 10000;
    if (!userId) {
        throw {httpStatus: 400, message: "Missing field: user_id."};
    }
    let doctor = new Doctor();
    ControllerUtil.regModelEventHandler(doctor, res);
    doctor.getPrivateMessages(userId, peerId, contentType, msgStartId, msgEndId, count, closedInterval);
});
/**
 * 获取未读私信。倒序排列。
 *
 * 参数:
 *  user_id 必须,医生ID
 *  peer_id 必须,对方医生ID
 *
 * 请求URL:
 *  /chats/pm/unread?user_id=sand&peer_id=Rose
 */
router.get(APIv1.Chats.PMUnread, function (req, res) {
    let userId = req.query.user_id;
    let peerId = req.query.peer_id;
    if (userId === undefined) {
        throw {httpStatus: 400, message: "Missing field: user_id."};
    }
    let doctor = new Doctor();
    ControllerUtil.regModelEventHandler(doctor, res);
    doctor.getUnreadPrivateMessages(userId, peerId);
});
/**
 * 按时间倒序获取群消息。
 *
 * 参数:
 *  user_id 必须,医生ID
 *  group_id 必须,组ID
 *  message_start_id 可选,消息的起始ID,如果为空从最新的一条开始获取
 *  count 可选,消息数量,如果不指定、小于零或大于50,默认为50条
 *
 * 请求URL:
 *  /chats/gm?user_id=D2016008240002&group_id=494&content_type=2&message_start_id=0&message_end_id=0&count=20
 */
router.get(APIv1.Chats.GM, function (req, res) {
    let groupId = req.query.group_id;
    let memberId = req.query.user_id;
    let contentType = req.query.content_type;
    let msgStartId = !req.query.message_start_id ? MAX_INT : parseInt(req.query.message_start_id);
    let msgEndId = !req.query.message_end_id ? 0 : parseInt(req.query.message_end_id);
    let count = req.query.count === undefined ? DEFAULT_PAGE_SIZE : parseInt(req.query.count);
    if (groupId === undefined) {
        throw {httpStatus: 400, message: "Missing field: group_id."};
    }
    if (contentType !== undefined && parseInt(contentType) === CONTENT_TYPES.Image) count = DEFAULT_PAGE_SIZE;
    if (req.query.message_start_id && req.query.message_end_id) count = 100000;
    let group = new Group();
    ControllerUtil.regModelEventHandler(group, res);
    group.getMessages(groupId, memberId, contentType, msgStartId, msgEndId, count);
});
/**
 * 获取未读群消息。
 *
 * 请求URL:
 *  /chats/gm/unread?group_id=discussionGroupId&user_id=sand
 */
router.get(APIv1.Chats.GMUnread, function (req, res) {
    let memberId = req.query.user_id;
    let groupId = req.query.group_id;
    if (memberId === undefined) {
        throw {httpStatus: 400, message: "Missing field: user_id."};
    }
    if (groupId === undefined) {
        throw {httpStatus: 400, message: "Missing field: group_id."};
    }
    let group = new Group();
    ControllerUtil.regModelEventHandler(group, res);
    group.getUnreadMessages(groupId, memberId);
});
//--------------------------------------------------------------//
//----------------------------消息统计----------------------------//
//--------------------------------------------------------------//
/**
 * 获取所有群组未读消息总数。
 *
 * 请求URL:
 *  /chats/gm/unread_count?user_id=sand
 *
 * 参数:
 *  user_id:医生ID
 */
router.get(APIv1.Chats.GMUnreadCount, function (req, res) {
    let memberId = req.query.user_id;
    if (memberId === null) {
        throw {httpStatus: 406, message: 'Missing fields.'};
    }
    let group = new Group();
    ControllerUtil.regModelEventHandler(group, res);
    group.getUnreadMessageCount(memberId);
});
/**
 * 获取特定群组消息统计情况。
 *
 * /chats/gm/statistic?group_id=GGG&&user_id=sand
 *
 * 参数:
 *  user_id:信息所有者id
 *  group_id:群组id
 */
router.get(APIv1.Chats.GMStats, function (req, res) {
    let memberId = req.query.user_id;
    let groupId = req.query.group_id;
    if (memberId === null || groupId === null) {
        throw {httpStatus: 406, message: 'Miss fields.'};
    }
    let group = new Group();
    ControllerUtil.regModelEventHandler(group, res);
    group.getChatSummary(groupId, memberId);
});
/**
 * 获取与某人的私信统计。
 *
 * /chats/pm/statistic?user_id=sand&&peer_id=rose
 *
 * 参数:
 *  user_id:信息所有者id
 *  peer_id:聊天对端id
 */
router.get(APIv1.Chats.PMStats, function (req, res) {
    let userId = req.query.user_id;
    let peerId = req.query.peer_id;
    if (userId == null || peerId == null) {
        throw {httpStatus: 406, message: "Missing fields."};
    }
    let doctor = new Doctor();
    ControllerUtil.regModelEventHandler(doctor, res);
    doctor.getChatSummary(userId, peerId);
});
/**
 * 获取所有未读私信总数。
 *
 * /chats/pm/unread_count?user_id=sand
 *
 * 参数:
 * uid:信息所有者id
 */
router.get(APIv1.Chats.PMUnreadCount, function (req, res) {
    let userId = req.query.user_id;
    let doctor = new Doctor();
    ControllerUtil.regModelEventHandler(doctor, res);
    doctor.getUnreadPrivateMessages(userId);
});
/**
 * 所有聊天消息未读数。
 *
 * 请求URL:
 *  /chats/chats/unread_count?user_id=sand
 *
 * 参数:
 *  user_id:信息所有者id
 */
router.get(APIv1.Chats.UnreadMsgCount, function (req, res) {
    let userId = req.query.user_id;
    if (userId === null) {
        throw {httpStatus: 406, message: "Missing fields."};
    }
    let doctor = new Doctor();
    ControllerUtil.regModelEventHandler(doctor, res);
    doctor.getAllUnreadMessageCount(userId);
});
/**
 * 搜索患者相关的数据,包括患者信息与相关的私信记录。关键词不支持空格拆分。
 *
 * 请求URL:
 *  http://192.168.131.107:3000/api/v1/chats/search/patient?user_id=D2016008240003&user_role=3&keyword=fa
 *
 * 参数:
 *  keywords: 关键词
 */
router.get(APIv1.Chats.SearchAboutPatient, function (req, res) {
    var userId = req.query.user_id;
    var userRole = req.query.user_role;
    var keyword = req.query.keyword;
    if (!userId) throw {httpStatus: 406, message: "Missing fields: user_id."};
    if (!userRole) throw {httpStatus: 406, message: "Missing fields: user_role."};
    if (!keyword) throw {httpStatus: 406, message: "Missing fields: keyword."};
    let search = new Search();
    ControllerUtil.regModelEventHandler(search, res);
    search.searchAboutPatient(userId, userRole, keyword);
});
/**
 * 获取某个聊天的关键字搜索记录列表
 * 请求URL:
 *http://192.168.131.107:3000/api/v1/chats/search/patient/list?user_id=D2016008240003&keyword=f&group_id=e2b695b9daf74d0faeb90a304ae587a0&type=1
 */
router.get(APIv1.Chats.SearchAboutPatientList, function (req, res) {
    var userId = req.query.user_id;
    var groupId = req.query.group_id;
    var keyword = req.query.keyword;
    var type = req.query.type;
    if (!userId) throw {httpStatus: 406, message: "Missing fields: user_id."};
    if (!groupId) throw {httpStatus: 406, message: "Missing fields: group_id."};
    if (!keyword) throw {httpStatus: 406, message: "Missing fields: keyword."};
    if (!type) throw {httpStatus: 406, message: "Missing fields: type."};
    let search = new Search();
    ControllerUtil.regModelEventHandler(search, res);
    search.searchAboutPatientList(userId, keyword,groupId,type);
});
/**
 * http://192.168.131.107:3000/api/v1/chats/search/patient/all?user_id=D2016008240003&keyword=f&type=2&user_role=3
 * 患者消息查询查看更多
 */
router.get(APIv1.Chats.SearchAbountPatientMore, function (req, res) {
    var userId = req.query.user_id;
    var keyword = req.query.keyword;
    var type = req.query.type;
    var userRole= req.query.user_role;
    if (!userId) throw {httpStatus: 406, message: "Missing fields: user_id."};
    if (!keyword) throw {httpStatus: 406, message: "Missing fields: keyword."};
    if (!type) throw {httpStatus: 406, message: "Missing fields: type."};
    if (!userRole) throw {httpStatus: 406, message: "Missing fields: userRole."};
    let search = new Search();
    ControllerUtil.regModelEventHandler(search, res);
    search.searchAboutPatientAll(userId,userRole, keyword,type);
});
/**
 * 搜索医生相关的数据,包括医生信息与相关的聊天记录,包括私信与群信。
 *
 * 请求URL:
 *  /search/doctor?user_id=D2016008240003&keyword=黄
 *
 * 参数:
 *  keywords: 关键词
 */
router.get(APIv1.Chats.SearchAboutDoctor, function (req, res) {
    let userId = req.query.user_id;
    let keyword = req.query.keyword;
    if (!userId) throw {httpStatus: 406, message: "Missing fields: user_id."};
    if (!keyword) throw {httpStatus: 406, message: "Missing fields: keyword."};
    let search = new Search();
    ControllerUtil.regModelEventHandler(search, res);
    search.searchAboutDoctor(userId, keyword);
});
/**
 * http://192.168.131.107:3000/api/v1/chats/search/doctor/list?user_id=D2016008240003&keyword=%E9%BB%84&type=2
 * 医生搜索查看更多
 * type = 1 查询聊过的医生
 * type = 2 查询群组标题和人员包含的群聊
 * type = 3 查询聊天关键字
 */
router.get(APIv1.Chats.SearchAboutDoctorList, function (req, res) {
    let userId = req.query.user_id;
    let keyword = req.query.keyword;
    let type = req.query.type;
    if (!userId) throw {httpStatus: 406, message: "Missing fields: user_id."};
    if (!keyword) throw {httpStatus: 406, message: "Missing fields: keyword."};
    if (!type) throw {httpStatus: 406, message: "Missing fields: type."};
    let search = new Search();
    ControllerUtil.regModelEventHandler(search, res);
    search.searchDoctorMore(userId, keyword,type);
});
/**
 * 查询医生聊天记录详情(单个聊天记录包含关键字的)
 *http://192.168.131.107:3000/api/v1/chats/search/doctor/content/list?user_id=D2016008240003&keyword=f&type=2&groupcode=0381288df51b434795b6946bb63d90b8
 */
router.get(APIv1.Chats.SearchAbountDoctorContentDetail, function (req, res) {
    let userId = req.query.user_id;
    let keyword = req.query.keyword;
    let type = req.query.type;
    let groupcode = req.query.groupcode;
    if (!userId) throw {httpStatus: 406, message: "Missing fields: user_id."};
    if (!keyword) throw {httpStatus: 406, message: "Missing fields: keyword."};
    if (!type) throw {httpStatus: 406, message: "Missing fields: type."};
    if (!groupcode) throw {httpStatus: 406, message: "Missing fields: groupcode."};
    let search = new Search();
    ControllerUtil.regModelEventHandler(search, res);
    search.searchDoctorContentDetail(userId, keyword,groupcode,type);
});
/**
 * 获取单条消息。
 *
 * URL:
 *  /chats/message?id=1234&type=1
 */
router.get(APIv1.Chats.Message, function (req, res) {
    let messageId = req.query.id;
    let messageType = req.query.type;
    let doctor = new Doctor();
    ControllerUtil.regModelEventHandler(doctor, res);
    doctor.getMessage(messageId, messageType);
});
/**
 * 判断当前会话是否已经结束。
 *
 * 请求URL:
 *  /chats/pm/finished?user_id=sand&peer_id=rose
 */
router.get(APIv1.Chats.PMFinished, function (req, res) {
    let doctorId = req.query.doctor_id;
    let patientId = req.query.patient_id;
    if (!doctorId) {
        throw {httpStatus: 406, message: "Missing field: doctor_id"};
    }
    if (!patientId) {
        throw {httpStatus: 406, message: "Missing field: patient_id"};
    }
    let doctor = new Doctor();
    ControllerUtil.regModelEventHandler(doctor, res);
    doctor.isConsultFinished(doctorId, patientId);
});
module.exports = router;

+ 0 - 56
src/server/endpoints/v1/groups.endpoint.js

@ -1,56 +0,0 @@
/**
 * Created by Sand on 2016/11/14.
 */
"use strict";
let express = require("express");
let router = express.Router();
const APIv1 = require('../include/endpoints').APIv1;
const MODEL_EVENTS = require('../include/commons').MODEL_EVENTS;
let Group = require('../models/group');
/**
 * 获取团队成员。
 *
 * 请求URL:
 *  /groups/:group_id/members?type=1
 */
router.get(APIv1.Groups.Members, function (req, res) {
    var groupId = req.params.group_id;
    var groupType = req.query.type;
    if(!groupType){
        throw {httpStatus: 406, message: "Missing fields: type"};
    }
});
/**
 * 获取成员头像。
 *
 * 请求URL:
 *  /groups/member/avatars?groups=433,10,63
 */
router.get(APIv1.Groups.MembersAvatar, function (req, res) {
    var groups = req.query.groups;
    if (!groups) {
        throw {httpStatus: 406, message: "Missing field: groups"};
    }
    var tokens = groups.split(",");
    groups = "'" + tokens.join("', '") + "'";
    let group = new Group();
    group.on(MODEL_EVENTS.OK, function (data) {
        res.status(200).send(data);
    });
    group.on(MODEL_EVENTS.Error, function (message) {
        res.status(500).send(message);
    });
    group.getMemberAvatars(groups);
});
module.exports = router;

+ 0 - 27
src/server/endpoints/v1/management.endpoint.js

@ -1,27 +0,0 @@
/**
 * 管理端点。负责数据库,服务器状态等内容反馈。
 */
"use strict";
let express = require('express');
let router = express.Router();
let APIv1 = require('../include/endpoints').APIv1;
let MODEL_EVENTS = require('../include/commons').MODEL_EVENTS;
let Management = require('../models/server/management');
/**
 * 数据库检查,包括所有表,连接状态。
 */
router.get(APIv1.Management.DbStatus, function (req, res) {
    let management = new Management();
    management.on(MODEL_EVENTS.OK, function (data) {
        res.status(200).send(data);
    });
    management.getDatabaseTables();
});
module.exports = router;

+ 0 - 108
src/server/endpoints/v1/users.endpoint.js

@ -1,108 +0,0 @@
/**
 * 用户端点。
 */
"use strict";
let express = require('express');
let router = express.Router();
let http = require('http');
let configFile = require('../include/commons').CONFIG_FILE;
let config = require('../resources/config/' + configFile);
let log = require("../util/log.js");
let ObjectUtil = require('../util/object.util');
let ControllerUtil = require('../util/controller.util');
let Users = require('../models/user/users');
const APIv1 = require('../include/endpoints').APIv1;
/**
 * 登录。用户登录后,更新数据库中的在线状态。
 *
 * 请求URL:
 *  /users/login?user_id=sand&token=0PFWlKmLBN9YzhCfFWVgYA&client_id=H6FYbDejks6VjMmW3uH7V6&platform=0
 *
 * 返回值:
 *  登录后的token.
 *
 * 参数说明:
 *  user_id:用户ID
 *  token:个推的token
 *  client_id:个推的client id
 *  platform:平台类型,0为iOS,1为Android
 */
router.get(APIv1.Users.Login, function (req, res) {
    let userId = req.query.user_id;
    let token = req.query.token;
    let clientId = req.query.client_id;
    let 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.'};
    }
    let users = new Users();
    ControllerUtil.regModelEventHandler(users, res);
    users.Login(userId, platform, token, clientId);
});
/**
 * 退出。
 *
 * 请求URL:
 *  /users/logout?user_id=sand
 *
 * 参数:
 *  user_id:用户ID
 */
router.get(APIv1.Users.Logout, function (req, res) {
    let userId = req.query.user_id;
    if (userId == null) {
        throw {httpStatus: 406, message: 'Logout Failed. Missing field: user_id.'};
    }
    let userStatus = new Users();
    ControllerUtil.regModelEventHandler(userStatus, res);
    userStatus.Logout(userId);
});
/**
 * 更新客户端状态。
 *
 * 请求URL:
 *  /users/:user_id/status
 *
 * POST参数格式:
 *  {status: 1}, app状态,0在后台,1在前台
 */
router.post(APIv1.Users.UserStatus, function (req, res) {
    let userId = req.param('user_id');
    let 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.'};
    }
    let userStatus = new Users();
    ControllerUtil.regModelEventHandler(userStatus, res);
    userStatus.UserStatus(userId, status.status);
});
module.exports = router;

+ 3 - 12
src/server/endpoints/v2/application.endpoint.js

@ -6,11 +6,11 @@ let router = express.Router();
const APIv2 = require('../../include/endpoints').APIv2;
const MODEL_EVENTS = require('../../include/commons').MODEL_EVENTS;
let StatsMessage = require("../../models/stats");
/**
 * 获取应用角标数。
 *
 * TODO: 角标数字为所有消息的总和
 *
 * /application/badge_no?user_id=sand
 *
 * 参数:
@ -19,16 +19,7 @@ let StatsMessage = require("../../models/stats");
router.get(APIv2.Application.BadgeNo, function (req, res) {
    let userId = req.query.user_id;
    let statsMsg = new StatsMessage();
    statsMsg.on(MODEL_EVENTS.OK, function (data) {
        res.status(200).send(data);
    });
    statsMsg.on(MODEL_EVENTS.Error, function (data) {
        res.status(500).send(data);
    });
    statsMsg.getBadgeNumber(userId);
    res.status(200).send({});
});
module.exports = router;

+ 78 - 60
src/server/endpoints/v2/session.endpoint.js

@ -16,63 +16,109 @@ let ControllerUtil = require('../../util/controller.util');
let Sessions = require('../../models/sessions/sessions');
let Participants = require('../../models/sessions/participants');
const SESSION_TYPES = require('../../include/commons').SESSION_TYPES;
const APIv2 = require('../../include/endpoints').APIv2;
/**
 * 获取用户的聊天列表
 * 请求URL /sessions?page=0&size=10&user=3121
 * 创建会话。对MUC会话而言,需要创建的是议题,如果是第一次创建议题,那应该附带创建会话,而不是直接创建会话。
 *
 * session_type:1表示MUC会话,2表示P2P,3表示群会话,4表示临时讨论组
 * users 讨论组包含的用户标示
 * sessionId 会话ID
 *
 * 请求URL: /sessions
 * 参数:
 * {
 *  session_id: 会话ID,非必须,仅对群消息使用。
 *  session_type: 会话类型,必须。
 *  session_name: 会话名称,
 *  participants: 此会话的成员列表,格式:["userId1:role", "userId2:role"],用户的ID及角色。
 * }
 */
router.post("/", function (req, res) {
    let payload = req.body;
    let testing = ObjectUtil.fieldsCheck(payload, 'participants', 'session_name', 'session_type');
    if (!testing.pass) {
        throw testing.message;
    }
    let participants = payload.participants;
    let sessionName = payload.session_name;
    let sessionType = payload.session_type;
    let sessionId = null;
    if (payload.session_type == SESSION_TYPES.GROUP) {
        if(!payload.hasOwnProperty('session_id') && payload.session_id.trim().length > 0){
            throw {httpStatus: 406, message: 'Field "session_id" is necessary and cannot be empty for GROUP session.'};
        }
        sessionId = payload.session_id;
    }
    let sessions = new Sessions();
    ControllerUtil.regModelEventHandler(sessions, res);
    sessions.createSession(sessionId, sessionName, sessionType, participants);
});
/**
 * 获取会话列表
 *
 * 请求URL /sessions?user_id=3121&page=0&size=10
 */
router.get("/", function (req, res) {
    let page = req.query.page;
    let size = req.query.size;
    let user = req.query.user;
    let userId = req.query.user_id;
    if (!page) {
        throw {httpStatus: 406, message: 'Missing page.'};
    }
    if (!size) {
        throw {httpStatus: 406, message: 'Missing size.'};
    }
    if (!user) {
    if (!userId) {
        throw {httpStatus: 406, message: 'Missing user.'};
    }
    
    let sessions = new Sessions();
    ControllerUtil.regModelEventHandler(sessions, res);
    sessions.getUserSessions(user, page, size);
    sessions.getUserSessions(userId, page, size);
});
/**
 * 创建会话
 * sessionType:1表示MUC会话,2表示P2P,3表示群会话,4表示临时讨论组
 * users 讨论组包含的用户标示
 * sessionId 会话ID
 * 请求URL /create?sessionId=0&users={10:1,20:1}&name=3121&sessionType=2
 * 发送消息
 *
 * 请求URL:
 *  /sessions/:session_id/messages
 *
 * 消息体:
 *  {
 *      sender_id: bea851fc,
 *      sender_name: 李毅,
 *      content_type: 1,
 *      content: 李医生,你好
 *  }
 */
router.post("/", function (req, res) {
    let sessionId = req.query.sessionId;
    let users = req.query.users;
    let name = req.query.name;
    let sessionType = req.query.sessionType;
    if (!sessionId) {
        throw {httpStatus: 406, message: 'Missing sessionId.'};
    }
    if (!users) {
        throw {httpStatus: 406, message: 'Missing users.'};
    }
    if (!name) {
        throw {httpStatus: 406, message: 'Missing name.'};
    }
    if (!sessionType) {
        throw {httpStatus: 406, message: 'Missing sessionType.'};
router.post(APIv2.Sessions.Messages, function (req, res) {
    let payload = req.body;
    let testing = ObjectUtil.fieldsCheck(payload, "sender_id", "sender_name", "content_type", "content");
    if (!testing.pass) {
        throw {httpStatus: 406, message: testing.message}
    }
    let sessions = new Sessions();
    ControllerUtil.regModelEventHandler(sessions, res);
    sessions.createSession(sessionId, name, sessionType, users);
    payload.timestamp = new Date();
    sessions.saveMessageBySession(req.params.session_id, payload);
});
/**
 * 获取用户的聊天信息列表
 * 请求URL /messages?page=0&size=10&user=3121&sessionId=testsessionmsg1
 * 获取消息
 *
 * 请求URL:
 *  /sessions/:session_id/messages?session_id=blabla&user_id=abc&start_message_id=100&end_message_id=20
 */
router.get(APIv2.Sessions.Messages, function (req, res) {
    let page = req.query.page;
@ -98,6 +144,7 @@ router.get(APIv2.Sessions.Messages, function (req, res) {
/**
 * 某个聊天记录置顶操作
 *
 * 请求URL /stick?user=3121&sessionId=testsessionmsg1
 */
router.post(APIv2.Sessions.SessionSticky, function (req, res) {
@ -132,36 +179,6 @@ router.delete(APIv2.Sessions.SessionSticky, function (req, res) {
    sessions.cancelStickSession(sessionId, user);
});
/**
 * 发送消息
 * message:消息发送对象
 * sessionId 会话ID
 */
router.post(APIv2.Sessions.Messages, function (req, res) {
    let message = req.body;
    if (!ObjectUtil.isJsonObject(message)) {
        throw {httpStatus: 406, message: 'Problems parsing JSON.'}
    }
    // 字段判断
    let testing = ObjectUtil.fieldsCheck(message, "senderId", "senderName", "contentType", "content");
    if (!testing.pass) {
        throw {httpStatus: 406, message: testing.message}
    }
    let sessionId = req.query.sessionId;
    if (!sessionId) {
        throw {httpStatus: 406, message: 'Missing sessionId.'};
    }
    let type = req.query.type;
    if (!type) {
        throw {httpStatus: 406, message: 'Missing type.'};
    }
    let sessions = new Sessions();
    ControllerUtil.regModelEventHandler(sessions, res);
    message.timestamp = new Date();
    sessions.saveMessageBySession(message, sessionId, type);
});
/**
 * 移除成员
 * user:移除的人员
@ -176,6 +193,7 @@ router.delete(APIv2.Sessions.Participant, function (req, res) {
    if (!sessionId) {
        throw {httpStatus: 406, message: 'Missing sessionId.'};
    }
    let participants = new Participants();
    ControllerUtil.regModelEventHandler(sessions, res);
    participants.deleteUser(sessionId, user);

+ 1 - 9
src/server/endpoints/v2/topic.endpoint.js

@ -8,10 +8,8 @@
let express = require('express');
let router = express.Router();
let http = require('http');
let log = require('../../util/log.js');
let ObjectUtil = require("../../util/object.util.js");
let ControllerUtil = require('../../util/controller.util');
let Topics = require('../../models/sessions/topics');
@ -34,12 +32,6 @@ router.post(APIv2.Sessions.Topics, function (req, res) {
    //    data.healthDoctor ="1";
    //    data.doctor ="2";
    //    data.message ={description:"咨询:猥琐大叔大",title:"测试咨询",img:"img/sada.jpg",patient:"topicpatient",patientName:"甘宁"};
    //if (!ObjectUtil.isJsonObject(data)) {
    //    throw {httpStatus: 406, message: 'Problems parsing JSON.'};
    //}
    //if (!ObjectUtil.isJsonObject(data)) {
    //    throw {httpStatus: 406, message: 'Problems parsing JSON.'};
    //}
    //let testing = ObjectUtil.fieldsCheck(data.message, "description", "title", "img", "patient","patientName");
    //if (!testing.description) {
    //    throw {httpStatus: 406, message: "miss message.description"};
@ -62,7 +54,7 @@ router.get(APIv2.Sessions.TopicEnded, function (req, res) {
    let data = req.body;
    let endUser = data.endUser;
    let endUserName = data.endUserName;
    let topicId = data.topicId
    let topicId = data.topicId;
    let topic = new Topics();
    ControllerUtil.regModelEventHandler(topic, res);
    topic.endTopic(topicId, endUser, endUserName);

+ 5 - 8
src/server/endpoints/v2/user.endpoint.js

@ -33,8 +33,8 @@ const APIv2 = require('../../include/endpoints').APIv2;
 *  platform:平台类型,0为iOS,1为Android
 */
router.post(APIv2.Users.Login, function (req, res) {
    let appStatus = req.body;
    let testing = ObjectUtil.fieldsCheck(appStatus, "user_id", "token", "client_id", "platform");
    let payload = req.body;
    let testing = ObjectUtil.fieldsCheck(payload, "user_id", "token", "client_id", "platform");
    if (!testing.pass) {
        throw {httpStatus: 406, message: testing.message}
    }
@ -42,7 +42,7 @@ router.post(APIv2.Users.Login, function (req, res) {
    let users = new Users();
    ControllerUtil.regModelEventHandler(users, res);
    users.login(appStatus.user_id, appStatus.platform, appStatus.token, appStatus.client_id);
    users.login(payload.user_id, payload.platform, payload.token, payload.client_id);
});
/**
@ -56,10 +56,7 @@ router.post(APIv2.Users.Login, function (req, res) {
 */
router.put(APIv2.Users.UserStatus, function (req, res) {
    let userId = req.params.user_id;
    let status = req.body;
    if (!ObjectUtil.isJsonObject(status)) {
        throw {httpStatus: 406, message: "Problems parsing json."};
    }
    let payload = req.body;
    if (userId === null) {
        throw {httpStatus: 406, message: 'Validation Failed. Missing field(s): user_id.'};
@ -68,7 +65,7 @@ router.put(APIv2.Users.UserStatus, function (req, res) {
    let users = new Users();
    ControllerUtil.regModelEventHandler(users, res);
    users.updateAppStatus(userId, status.app_in_bg);
    users.updateAppStatus(userId, payload.app_in_bg);
});
/**

+ 32 - 23
src/server/include/commons.js

@ -34,6 +34,17 @@ const SESSION_TYPES = {
exports.SESSION_TYPES = SESSION_TYPES;
/**
 * 会话参与者角色
 */
const PARTICIPANT_ROLES = {
    HOST: 0,
    REGULAR: 1,
    BYSTANDER: 10
};
exports.PARTICIPANT_ROLES = PARTICIPANT_ROLES;
/**
 *  消息内容类型。
 */
@ -98,10 +109,10 @@ const REDIS_KEY_REPLACER = "{id}";
exports.REDIS_KEY_REPLACER = REDIS_KEY_REPLACER;
exports.SESSION_USER_STATUS={
    "ONLINE":"0",
    "OTHER":"1"
}
exports.SESSION_USER_STATUS = {
    ONLINE: 0,
    OTHER: 1
};
exports.REDIS_KEYS = {
    Users: "users:",
@ -114,8 +125,8 @@ exports.REDIS_KEYS = {
    Sessions: "sessions:",
    Session: "sessions:" + REDIS_KEY_REPLACER,
    Participants: "participants:" + REDIS_KEY_REPLACER,
    ParticipantsRole: "participants:" + REDIS_KEY_REPLACER + ":role",
    SessionParticipants: "participants:" + REDIS_KEY_REPLACER,
    SessionParticipantsRole: "participants:" + REDIS_KEY_REPLACER + ":role",
    Topics: "sessions:" + REDIS_KEY_REPLACER + ":topics",
    Topic: "topics:" + REDIS_KEY_REPLACER,
@ -124,7 +135,7 @@ exports.REDIS_KEYS = {
    MessagesByTimestamp: "sessions:" + REDIS_KEY_REPLACER + ":messages_by_timestamp"
};
exports.DB_TABLES = {
const DB_TABLES = {
    P2pMessages: "p2p_messages",
    MucMessages: "muc_messages",
    GroupMessages: "group_messages",
@ -136,20 +147,18 @@ exports.DB_TABLES = {
    StickySessions: "sticky_sessions",
    sessionTypeToTableName: function (sessionType) {
        switch (sessionType) {
            case SESSION_TYPES.SYSTEM:
                return DB_TABLES.SystemMessages;
            case SESSION_TYPES.MUC:
                return DB_TABLES.MucMessages;
            case SESSION_TYPES.P2P:
                return DB_TABLES.P2pMessages;
            case SESSION_TYPES.GROUP:
                return DB_TABLES.GroupMessages;
            case SESSION_TYPES.DISCUSSION:
                return DB_TABLES.DiscussionMessages;
            default:
                throw {message: "Unknown session type"};
        }
        if (sessionType == SESSION_TYPES.SYSTEM)
            return DB_TABLES.SystemMessages;
        else if (sessionType == SESSION_TYPES.MUC)
            return DB_TABLES.MucMessages;
        else if (sessionType == SESSION_TYPES.P2P)
            return DB_TABLES.P2pMessages;
        else if (sessionType == SESSION_TYPES.GROUP)
            return DB_TABLES.GroupMessages;
        else if (sessionType == SESSION_TYPES.DISCUSSION)
            return DB_TABLES.DiscussionMessages;
        else throw {message: "Unknown session type"};
    }
};
};
exports.DB_TABLES = DB_TABLES;

+ 54 - 25
src/server/models/messages/messages.js

@ -6,6 +6,7 @@
let MessageRepo = require('../../repository/mysql/message.repo');
let RedisModel = require('./../redis.model.js');
let RedisClient = require('../../repository/redis/redis.client.js');
let Sessions = require('../sessions/sessions');
let redis = RedisClient.redisClient().connection;
let log = require('../../util/log.js');
@ -55,32 +56,60 @@ class Messages extends RedisModel {
    getMessageById(messageId) {
    }
    saveMessageToRedis(message_id, sessionId, message) {
        let self = this;
        let message_key = super.makeRedisKey(REDIS_KEYS.Messages, sessionId);
        let message_timestamp_key = super.makeRedisKey(REDIS_KEYS.MessagesByTimestamp, sessionId);
    /**
     * 将消息保存至REDIS,并更新会话的最后状态。也会清理会话的过期消息。
     *
     * @param messageId
     * @param sessionId
     * @param sessionType
     * @param message
     */
    saveMessageToRedis(sessionId, sessionType, messageId, message) {
        let sessionKey = RedisModel.makeRedisKey(REDIS_KEYS.Session, sessionId);
        let messageKey = RedisModel.makeRedisKey(REDIS_KEYS.Messages, sessionId);
        let messageTimestampKey = RedisModel.makeRedisKey(REDIS_KEYS.MessagesByTimestamp, sessionId);
        redis.multi()
            .hset(message_key, message_id, JSON.stringify(message))
            .zadd(message_timestamp_key, message.timestamp.getTime(), message_id)
            .execAsync().then(function (res) {
            log.info("Save redis message to session " + sessionId);
            // clean out range messages
            self.cleanOutRangeMessage(sessionId);
        });
            .hset(messageKey, messageId, JSON.stringify(message))
            .zadd(messageTimestampKey, message.timestamp.getTime(), messageId)
            .execAsync()
            .then(function (res) {
                Messages.updateLastContent(sessionKey, sessionType, null, message);
                Messages.cleanOutRangeMessage(sessionId); // clean out range messages
            });
    }
    /**
     * 保存Message 到mysql
     * @param messages 消息对象
     * @param type
     * @param message 消息对象
     * @param sessionType
     * @param messageId
     * @param sessionId
     * @type type 会话类型,1表示MUC会话,2表示P2P,3表示群会话,4表示临时讨论组
     */
    saveMessageToMysql(messages, type, messageId, sessionId) {
        MessageRepo.save(messages, type, messageId, sessionId);
    saveMessageToMysql(sessionId, sessionType, messageId, message, handler) {
        MessageRepo.save(message, sessionType, messageId, sessionId, handler);
    }
    /**
     * 更新会话最后一条消息
     *
     * @param sessionKey rediskey
     * @param sessionType
     * @param name 议题名称
     * @param message
     * @returns {*}
     */
    static updateLastContent(sessionKey, sessionType, name, message) {
        redis.hmsetAsync(sessionKey,
            "create_date", message.timestamp,
            "last_content", message.content,
            "last_content_type", message.content_type,
            "sender_id", message.sender_id,
            "sender_name", message.sender_name
        );
        if(name != null) redis.hsetAsync(sessionKey, "name", name);
        if(sessionType != null) redis.hsetAsync(sessionKey, "type", sessionType);
    }
    /**
@ -90,18 +119,18 @@ class Messages extends RedisModel {
     *
     * @param sessionId
     */
    cleanOutRangeMessage(sessionId) {
    static cleanOutRangeMessage(sessionId) {
        let maxMessageCacheCount = config.sessionConfig.maxMessageCount;
        let messageById = this.makeRedisKey(REDIS_KEYS.Messages, sessionId);
        let messagesByTimestampKey = this.makeRedisKey(REDIS_KEYS.MessagesByTimestamp, sessionId);
        redis.zcardAsync(messagesByTimestampKey).then(function (count) {
           if(count > maxMessageCacheCount){
               redis.zrevrangeAsync(messagesByTimestampKey, 0, count - maxMessageCacheCount).then(function (idList) {
                   redis.zremAsync(messagesByTimestampKey, idList).then(function (res) {
                       redis.hdel(messageById, idList);
                   })
               });
           }
            if (count > maxMessageCacheCount) {
                redis.zrevrangeAsync(messagesByTimestampKey, 0, count - maxMessageCacheCount).then(function (idList) {
                    redis.zremAsync(messagesByTimestampKey, idList).then(function (res) {
                        redis.hdel(messageById, idList);
                    })
                });
            }
        });
    }
}

+ 1 - 1
src/server/models/redis.model.js

@ -22,7 +22,7 @@ class RedisModel extends BaseModel {
     * @param redisKey
     * @param keyValue
     */
    makeRedisKey(redisKey, keyValue) {
    static makeRedisKey(redisKey, keyValue) {
        if (redisKey.indexOf(RedisKeyReplacer) >= 0) {
            return redisKey.replace(RedisKeyReplacer, keyValue);
        } else {

+ 50 - 44
src/server/models/sessions/participants.js

@ -24,7 +24,7 @@ class Participants extends RedisModel {
     * @param sessionId
     */
    getParticipants(sessionId, handler) {
        let participant_key = super.makeRedisKey(REDIS_KEYS.Participants, sessionId);
        let participant_key = RedisModel.makeRedisKey(REDIS_KEYS.SessionParticipants, sessionId);
        redis.existsAsync(participant_key).then(function (res) {
            if (res) {
                redis.zrangeAsync(participant_key, 0, -1).then(function (res) {
@ -44,21 +44,11 @@ class Participants extends RedisModel {
     * @param handler
     */
    existsParticipant(sessionId, userId, handler) {
        let participant_key = super.makeRedisKey(REDIS_KEYS.Participants, sessionId);
        redis.existsAsync(participant_key).then(function (res) {
            if (res) {
        let participantsRoleKey = RedisModel.makeRedisKey(REDIS_KEYS.SessionParticipantsRole, sessionId);
        redis.hgetAsync(participantsRoleKey, userId).then(function (res) {
            if (false) {
                // get from redis
                redis.zrangeAsync(participant_key, 0, -1).then(function (res) {
                    let exists = false;
                    for (var j in res) {
                        var value = res[j];
                        if (value == userId) {
                            exists = true;
                            break;
                        }
                    }
                    handler(null, exists);
                })
                handler(null, true);
            } else {
                // get from mysql
                ParticipantRepo.existsParticipant(sessionId, userId, handler);
@ -80,39 +70,54 @@ class Participants extends RedisModel {
    /**
     * 将成员写入redis
     *
     * @param session_id 会话ID
     * @param users 用户集合
     * @param sessionId 会话ID
     * @param participantsArray 会话参与者集合
     * @param createDate 创建日期
     * @param handler 回调
     */
    saveParticipantsToRedis(session_id, users, createDate, handler) {
        let participants_key = super.makeRedisKey(REDIS_KEYS.Participants, session_id);
        let participants_role_key = super.makeRedisKey(REDIS_KEYS.ParticipantsRole, session_id);
        let _super = super.makeRedisKey;
        redis.hmsetAsync(participants_role_key,users).then(function(res){
            log.info("create participants role!");
            for (var j in users) {
                let user_session_key = _super(REDIS_KEYS.UserSessions, j);
                redis.zaddAsync(participants_key, createDate.getTime(), j).then(function (res) {
                        return redis.zaddAsync(user_session_key, createDate.getTime(), session_id);
                    }
                ).catch(function (err) {
                    log.error("createParticipantsToRedis is fail error:" + err + ",session_id:" + session_id + ",users:" + JSON.stringify(users));
                    handler(false);
                });
            }
        })
        handler(true);
    static saveParticipantsToRedis(sessionId, participantsArray, createDate, handler) {
        // 构造会话,成员及成员角色zset, hash所需要的数据
        let userSessions = {};
        let sessionParticipants = [];
        let sessionParticipantsRoles = [];
        participantsArray.forEach(function (item) {
            let tokens = item.split(":");
            userSessions[RedisModel.makeRedisKey(REDIS_KEYS.UserSessions, tokens[0])] = [createDate.getTime(), sessionId];
            sessionParticipants.push(createDate.getTime());
            sessionParticipants.push(tokens[0]);
            sessionParticipantsRoles.push(tokens[0], tokens[1]);
        });
        // 向会话成员、会话成员角色集合中添加数据
        let sessionParticipantsKey = RedisModel.makeRedisKey(REDIS_KEYS.SessionParticipants, sessionId);
        let sessionParticipantsRoleKey = RedisModel.makeRedisKey(REDIS_KEYS.SessionParticipantsRole, sessionId);
        let multi = redis.multi()
            .zadd(sessionParticipantsKey, sessionParticipants)
            .hmset(sessionParticipantsRoleKey, sessionParticipantsRoles);
        // 更新用户参与的会话列表
        for(let key in userSessions){
            multi = multi.zadd(key, userSessions[key]);
        }
        multi.execAsync()
            .then(function (res) {
                handler(true);
            });
    }
    /**
     * mysql成员创建
     *
     * @param session_id
     * @param sessionId
     * @param users
     * @param handler
     */
    saveParticipantsToMysql(session_id, users) {
        return ParticipantRepo.saveParticipantsToMysql(session_id, users);
    static saveParticipantsToMysql(sessionId, users, handler) {
        return ParticipantRepo.saveParticipantsToMysql(sessionId, users, handler);
    }
@ -123,8 +128,8 @@ class Participants extends RedisModel {
     */
    deleteUser(sessionId, userId) {
        let self = this;
        let participants_key = super.makeRedisKey(REDIS_KEYS.Participants, sessionId);
        let user_session_key = super.makeRedisKey(REDIS_KEYS.UsersSessions, userId);
        let participants_key = RedisModel.makeRedisKey(REDIS_KEYS.SessionParticipants, sessionId);
        let user_session_key = RedisModel.makeRedisKey(REDIS_KEYS.UsersSessions, userId);
        //1.移除SESSION成员表中的成员信息
        redis.zremAsync(participants_key, userId).then(function (res) {
@ -151,12 +156,13 @@ class Participants extends RedisModel {
     * @param user 用户
     * @param role 变更状态
     */
    updateUser(sessionId,user,role){
        let participants_role_key = super.makeRedisKey(REDIS_KEYS.ParticipantsRole, sessionId);
        redis.hsetAsync(sessionId,user,role).then(function(res){
                ParticipantRepo.updateParticipant(sessionId,user,role);
    updateUser(sessionId, user, role) {
        let participants_role_key = RedisModel.makeRedisKey(REDIS_KEYS.SessionParticipantsRole, sessionId);
        redis.hsetAsync(sessionId, user, role).then(function (res) {
            ParticipantRepo.updateParticipant(sessionId, user, role);
        })
    }
    /**
     * 添加讨论组成员
     * @param sessionId

+ 1 - 1
src/server/models/sessions/session.cleaner.js

@ -63,7 +63,7 @@ class SessionCleaner {
                    // 清理会话、议题、参与者、消息
                    let toDeleteKeys = [REDIS_KEYS.Sessions, REDIS_KEYS.Session, REDIS_KEYS.Topics,
                        REDIS_KEYS.ParticipantsRole, REDIS_KEYS.Participants, REDIS_KEYS.Messages, REDIS_KEYS.MessagesByTimestamp];
                        REDIS_KEYS.SessionParticipantsRole, REDIS_KEYS.SessionParticipants, REDIS_KEYS.Messages, REDIS_KEYS.MessagesByTimestamp];
                    redis.del(toDeleteKeys.forEach(function (key) {
                        return RedisModel.makeRedisKey(key, sessionId);
                    }));

+ 187 - 178
src/server/models/sessions/sessions.js

@ -14,11 +14,12 @@ let ParticipantRepo = require('../../repository/mysql/participant.repo');
let configFile = require('../../include/commons').CONFIG_FILE;
let config = require('../../resources/config/' + configFile);
let redis = RedisClient.redisClient().connection;
let log = require('../../util/log.js');
let logger = require('../../util/log.js');
let mongoose = require('mongoose');
const RedisKeys = require('../../include/commons').REDIS_KEYS;
const Commons = require('../../include/commons');
const REDIS_KEYS = require('../../include/commons').REDIS_KEYS;
const SESSION_TYPES = require('../../include/commons').SESSION_TYPES;
const STICKY_SESSION_BASE_SCORE = require('../../include/commons').STICKY_SESSION_BASE_SCORE;
class Sessions extends RedisModel {
    constructor() {
@ -26,62 +27,78 @@ class Sessions extends RedisModel {
    }
    /**
     * 创建会话
     * 创建会话。会话的ID来源:
     * MUC:患者的ID
     * P2P:对成员的ID排序后,取hash值
     * GROUP:团队的ID
     *
     * type = 1 sessionid = md5(patientId); MUC
     * type  = 2 sessionId = hash(user1,user2); P2P
     * type = 3 sessionId = groupid; 团队群聊
     * @param sessionId 会话ID
     * @param sessionId
     * @param name 会话名称
     * @param type 会话类型
     * @param users 会话成员
     * @param participantArray 会话成员
     * @param handler 回调,仅MUC模式使用
     */
    createSession(sessionId, name, type, users,handler) {
    createSession(sessionId, name, type, participantArray, handler) {
        let self = this;
        let _super = super.makeRedisKey;
        users = eval("["+users+"]")[0];
        if (type == config.sessionConfig.P2P) {//P2P消息用hash校验
            var userArray=[];
            for(var key in users){
                userArray.push(key);
        if (type == SESSION_TYPES.P2P) {
            var participantIdArray = [];
            for (let i in participantArray) {
                participantIdArray.push(participantArray[i].split(":")[0]);
            }
            if(userArray.length>2){
                ModelUtil.emitOK(self.eventEmitter, {"status": -1, "msg": "会话人数超过2个无法创建P2P会话!"});
            if (participantIdArray.length != 2) {
                ModelUtil.emitDataNotFound(self.eventEmitter, {message: "P2P session only allow 2 participants."});
                return false;
            }
            ParticipantRepo.findSessionIdByParticipantIds(userArray[0],userArray[0],function(err,res){
            ParticipantRepo.findSessionIdByParticipantIds(participantIdArray[0], participantIdArray[1], function (err, res) {
                sessionId = res;
                callcreate(sessionId);
                callCreate(sessionId);
            });
        }else{
            callcreate();
        } else {
            callCreate(sessionId);
        }
        function  callcreate(){
            let createDate = new Date();
            let session_key = _super(RedisKeys.Session, sessionId);
            let participants = new Participants();
            // 将session加入redis
            participants.saveParticipantsToRedis(sessionId, users, createDate, function (res) {
                if (!res) {
                    ModelUtil.emitOK(self.eventEmitter, {"status": -1, "msg": res});
                } else {
                    let messages = {};
                    messages.senderId = "system";
                    messages.senderName = "系统消息";
                    messages.timestamp = createDate;
                    messages.content = "";
                    messages.contentType = "1";
                    self.updateLastContent(session_key, type, name, messages);
                    if(config.sessionConfig.MUC==type){
                        handler(true);
                    }else {
                        modelUtil.emitOK(self.eventEmitter, {"status": 200, "msg": "session create success!"});
                    }
                    self.saveSessionToMysql(sessionId, name, type, createDate);
                    participants.saveParticipantsToMysql(sessionId, users); //创建session成员到数据库
        function callCreate(sessionId) {
            SessionRepo.findOne(sessionId, function (err, res) {
                if (res.length > 0) {
                    let session = res[0];
                    ModelUtil.emitOK(self.eventEmitter, {
                        id: session.id,
                        name: session.name,
                        type: session.type,
                        create_date: session.create_date
                    });
                    return;
                }
            })
                let createDate = new Date();
                let sessionKey = RedisModel.makeRedisKey(REDIS_KEYS.Session, sessionId);
                // 保存会话及成员至MySQL中
                self.saveSessionToMysql(sessionId, name, type, createDate, function (err, res) {
                    Participants.saveParticipantsToMysql(sessionId, participantArray, function (err, res) {
                        // 保存会话及成员至Redis中,并更新会话的最后状态
                        let isMucSession = SESSION_TYPES.MUC == type;
                        let message = {
                            sender_id: "System",
                            sender_name: "System",
                            content_type: 1,
                            content: "",
                            timestamp: createDate
                        };
                        Messages.updateLastContent(sessionKey, type, name, message);
                        Participants.saveParticipantsToRedis(sessionId, participantArray, createDate, function (res) {
                            if (isMucSession) {
                                handler(true, sessionId);
                            } else {
                                ModelUtil.emitOK(self.eventEmitter, {id: sessionId});
                            }
                        });
                    });
                });
            });
        }
    }
@ -91,9 +108,10 @@ class Sessions extends RedisModel {
     * @param name
     * @param type
     * @param createDate
     * @param handler
     */
    saveSessionToMysql(sessionId, name, type, createDate) {
        SessionRepo.saveSession(sessionId, name, type, createDate);
    saveSessionToMysql(sessionId, name, type, createDate, handler) {
        SessionRepo.saveSession(sessionId, name, type, createDate, handler);
    }
    /**
@ -118,35 +136,36 @@ class Sessions extends RedisModel {
     * 根据用户ID获取用户的session列表
     * @param userId
     * @param page
     * @param pagesize
     * @param size
     */
    getUserSessions(userId, page, pagesize) {
        let user_session_key = super.makeRedisKey(RedisKeys.UserSessions, userId);
    getUserSessions(userId, page, size) {
        let userSessionKey = RedisModel.makeRedisKey(REDIS_KEYS.UserSessions, userId);
        let self = this;
        let _super = super.makeRedisKey;
        if (page > 0) {
            page = page * pagesize;
            pagesize = pagesize + page;
            page = page * size;
            size = size + page;
        }
        //倒序
        redis.zrevrangeAsync(user_session_key, page, pagesize).then(function (res) {
            let sessionlist = [];
        // 倒序获取
        redis.zrevrangeAsync(userSessionKey, page, size).then(function (res) {
            let sessionList = [];
            if (res.length == 0) {
                ModelUtil.emitOK(self.eventEmitter, {"status": 200, "data": res});
            } else {
                for (var j in res) {
                    calllist(res[j], j, res.length);
                }
                ModelUtil.emitOK(self.eventEmitter, []);
                return;
            }
            for (let i in res) {
                callGetSessions(res[i], i == res.length - 1);
            }
            function calllist(session, j, _len) {
                let session_key = _super(RedisKeys.Session, session);
                redis.hgetallAsync(session_key).then(function (res) {
                    let participants_key = _super(RedisKeys.Participants, session);
                    //当前用户最后一次登录改讨论组时间
                    redis.zscoreAsync(participants_key, userId).then(function (restimestamp) {
                        //时间差获取消息数量
                        callamount(res, j, _len, session, restimestamp);
            function callGetSessions(sessionId, lastOne) {
                let sessionKey = RedisModel.makeRedisKey(REDIS_KEYS.Session, sessionId);
                redis.hgetallAsync(sessionKey).then(function (session) {
                    let sessionParticipantsKey = RedisModel.makeRedisKey(REDIS_KEYS.SessionParticipants, sessionId);
                    // 对比当前用户最后一次此会话消息的时间与会话中最新的消息时间,以此判断未读消息数量
                    redis.zscoreAsync(sessionParticipantsKey, userId).then(function (lastFetchTime) {
                        callGetUnreadCount(session, sessionId, lastFetchTime, lastOne);
                    })
                }).catch(function (err) {
                    throw err;
@ -154,51 +173,58 @@ class Sessions extends RedisModel {
            }
            /**
             * 消息统计
             * @param res 返回的会话列表
             * @param j 当前会话列表的位置
             * @param _len 列表长度 用做返回前端操作
             * @param session 当前会话
             * @param restimestamp 当前会话当前用户的最后一次时间搓
             * 统计未读消息数。以当前时间为准。
             *
             * @param session 返回的会话列表
             * @param sessionId 当前会话ID
             * @param lastFetchTime 当前会话当前用户的最后一次时间搓
             * @param lastOne
             */
            function callamount(res, j, _len, session, restimestamp) {
                let message_time_key = _super(RedisKeys.MessagesByTimestamp, session);
                redis.zrangebyscoreAsync(message_time_key, restimestamp, (new Date().getTime())).then(function (messagetimelist) {
                    res.sessionId = session;
                    res.message = messagetimelist.length;
                    callrole(res, j, _len,session);
                }).catch(function (err) {
                    throw err;
                })
            function callGetUnreadCount(session, sessionId, lastFetchTime, lastOne) {
                let messagesByTimestampKey = RedisModel.makeRedisKey(REDIS_KEYS.MessagesByTimestamp, sessionId);
                redis.zrangebyscoreAsync(messagesByTimestampKey, lastFetchTime, (new Date().getTime()))
                    .then(function (messagetimelist) {
                        session.id = sessionId;
                        session.unread_count = messagetimelist.length;
                        callGetMyRole(session, sessionId, lastOne);
                    })
                    .catch(function (err) {
                        throw err;
                    });
            }
            /**
             * 用户角色
             * @param res要返回的JSON
             * @param j 第N调数据
             * @param _len 总数据长度
             * 获取用户在此会话中的角色。
             *
             * @param session 要返回的JSON
             * @param sessionId
             * @param lastOne
             */
            function callrole(res, j, _len,session){
                let participants_role_key = _super(RedisKeys.ParticipantsRole, session);
                redis.hgetAsync(participants_role_key, userId).then(function(role){
                    res.role=role;
                    callback(res, j, _len);
            function callGetMyRole(session, sessionId, lastOne) {
                let participantsRoleKey = RedisModel.makeRedisKey(REDIS_KEYS.SessionParticipantsRole, sessionId);
                redis.hgetAsync(participantsRoleKey, userId).then(function (role) {
                    session.my_role = role;
                    callback(session, lastOne);
                })
            }
            /**
             * 列表封装完毕后由此回调返回数据界面
             * @param res
             * @param j
             * @param _len
             *
             * @param session
             * @param lastOne
             */
            function callback(res, j, _len) {
                sessionlist.push(res);
                if (j == (_len - 1)) {
                    ModelUtil.emitOK(self.eventEmitter, {"status": 200, "data": sessionlist});
            function callback(session, lastOne) {
                sessionList.push(session);
                if (lastOne) {
                    ModelUtil.emitOK(self.eventEmitter, sessionList);
                }
            }
        }).catch(function (res) {
            ModelUtil.emitOK(self.eventEmitter, "get list error " + res + ",user:" + userId);
        }).catch(function (err) {
            ModelUtil.emitError(self.eventEmitter, {message: "Get sessions failed: " + err});
        })
    }
@ -212,9 +238,9 @@ class Sessions extends RedisModel {
     */
    getMessages(sessionId, user, page, pagesize) {
        let self = this;
        let message_timestamp_key = super.makeRedisKey(RedisKeys.MessagesByTimestamp, sessionId);
        let message_key = super.makeRedisKey(RedisKeys.Messages, sessionId);
        let participants_key = super.makeRedisKey(RedisKeys.Participants, sessionId);
        let message_timestamp_key = RedisModel.makeRedisKey(REDIS_KEYS.MessagesByTimestamp, sessionId);
        let message_key = RedisModel.makeRedisKey(REDIS_KEYS.Messages, sessionId);
        let participants_key = RedisModel.makeRedisKey(REDIS_KEYS.SessionParticipants, sessionId);
        //超过最大限制后从mysql获取数据
        if (page * pagesize >= config.sessionConfig.maxMessageCount) {
            self.getMessageFromMySQL(sessionId, page, pagesize, function (err, res) {
@ -262,119 +288,102 @@ class Sessions extends RedisModel {
    }
    /**
     * 更新会话最后一条消息
     * 保存消息。
     *
     * @param session_key rediskey
     * @param session_type
     * @param name 议题名称
     * @param message
     * @returns {*}
     */
    updateLastContent(session_key, session_type, name, message) {
        return redis.hmsetAsync(session_key,
            "create_date", message.timestamp,
            "last_content", message.content,
            "last_content_type", message.contentType,
            "type", session_type,
            "senderId", message.senderId,
            "senderName", message.senderName,
            "name", name
        );
    }
    /**
     * 保存消息
     * 也可以根据议题保存消息,但最终还是保存到与会话对象。
     *
     * see also: saveMessageByTopic
     *
     * @param message
     * @param sessionId
     */
    saveMessageBySession(message, sessionId) {
    saveMessageBySession(sessionId, message) {
        let self = this;
        let messages = new Messages();
        let participants = new Participants();
        let session_key = super.makeRedisKey(RedisKeys.Session, sessionId);
        let message_id = mongoose.Types.ObjectId().toString();
        let session_type = 0;
        let name = "";
        participants.existsParticipant(sessionId, message.senderId, function (res) {
            //校验发送成员是都在讨论组
        let sessionKey = RedisModel.makeRedisKey(REDIS_KEYS.Session, sessionId);
        let messageId = mongoose.Types.ObjectId().toString();
        // 检查会话中是否存在此成员
        participants.existsParticipant(sessionId, message.sender_id, function (err, res) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, "Check session paticipant failed: ", err);
                return;
            }
            if (res) {
                redis.hmgetAsync(session_key, ["type", "name"]).then(function (res) {
                    session_type = res[0];
                    name = res[1];
                    if (!session_type || !name) {
                        log.error("session is error for key " + session_key);
                        throw "session is not found";
                    }
                }).then(function (res) {
                    //更新消息相关
                    return messages.saveMessageToRedis(message_id, sessionId, message);
                }).then(function (res) {
                    //更新session的最后一条聊天记录
                    return self.updateLastContent(session_key, session_type, name, message);
                redis.hmgetAsync(sessionKey, ["type", "name"]).then(function (res) {
                    let sessionType = res[0];
                    messages.saveMessageToRedis(sessionId, sessionType, messageId, message);
                    messages.saveMessageToMysql(sessionId, sessionType, messageId, message, function (err, res) {
                        if (err) {
                            ModelUtil.emitError(self.eventEmitter, {message: "Failed to save message to mysql: " + err});
                        } else {
                            ModelUtil.emitOK(self.eventEmitter, {count: 1, messages: [message]});
                        }
                    });
                }).then(function (res) {
                    //操作mysql数据库
                    messages.saveMessageToMysql(message, session_type, message_id, sessionId);
                    //返回数据给前端。
                    ModelUtil.emitOK(self.eventEmitter, {"status": 200, "msg": "发送成功!"});
                    //消息推送
                }).catch(function (res) {
                    ModelUtil.emitOK(self.eventEmitter, {"status": -1, "msg": res});
                    // TODO: 消息推送
                }).catch(function (err) {
                    ModelUtil.emitError(self.eventEmitter, {message: "Error occurred while save message to session: " + err});
                })
            } else {
                ModelUtil.emitOK(self.eventEmitter, {"status": -1, "msg": "用户不在此会话当中!"});
                ModelUtil.emitDataNotFound(self.eventEmitter, {message: "当前会话找不到此发送者"});
            }
        })
        });
    }
    /**
     * 保存消息
     *
     * @param message
     * @param sessionId
     */
    saveMessageByTopic(message, sessionId,handler) {
    saveMessageByTopic(message, sessionId, handler) {
        let self = this;
        let messages = new Messages();
        let participants = new Participants();
        let session_key = super.makeRedisKey(RedisKeys.Session, sessionId);
        let message_id = mongoose.Types.ObjectId().toString();
        let session_type = 0;
        let session_key = RedisModel.makeRedisKey(REDIS_KEYS.Session, sessionId);
        let messageId = mongoose.Types.ObjectId().toString();
        let sessionType = 0;
        let name = "";
        participants.existsParticipant(sessionId, message.senderId, function (err,res) {
        participants.existsParticipant(sessionId, message.senderId, function (err, res) {
            //校验发送成员是都在讨论组
            if (res) {
                redis.hmgetAsync(session_key, ["type", "name"]).then(function (res) {
                    session_type = res[0];
                    sessionType = res[0];
                    name = res[1];
                    if (!session_type || !name) {
                        log.error("session is error for key " + session_key);
                    if (!sessionType || !name) {
                        logger.error("session is error for key " + session_key);
                        throw "session is not found";
                    }
                }).then(function (res) {
                    //更新消息相关
                    return messages.saveMessageForRedis(message_id, sessionId, message);
                    return messages.saveMessageForRedis(messageId, sessionId, message);
                }).then(function (res) {
                    //更新session的最后一条聊天记录
                    return self.updateLastContent(session_key, session_type, name, message);
                    return Messages.updateLastContent(session_key, sessionType, name, message);
                }).then(function (res) {
                    //操作mysql数据库
                    messages.saveMessageToMysql(message, session_type, message_id, sessionId);
                    messages.saveMessageToMysql(sessionId, sessionType, messageId, message);
                    //返回数据给前端。
                    handler(null,message_id)
                    handler(null, messageId)
                    //消息推送
                }).catch(function (res) {
                    handler(res,message_id)
                    handler(res, messageId)
                })
            } else {
                handler( "用户不在此会话当中!",message_id);
                handler("用户不在此会话当中!", messageId);
            }
        })
    }
    /**
     * 置顶操作
     */
    stickSession(sessionId, user) {
        let user_session_key = super.makeRedisKey(RedisKeys.UserSessions, user);
        let user_session_key = RedisModel.makeRedisKey(REDIS_KEYS.UserSessions, user);
        let self = this;
        //取出最大的session
        redis.zrevrangeAsync(user_session_key, 0, 0).then(function (res) {
@ -384,17 +393,17 @@ class Sessions extends RedisModel {
                //当前时间搓比redis的时间搓更早证明没有置顶过
                if (scoreres <= nowtime) {
                    //初始化置顶
                    redis.zaddAsync(user_session_key, Commons.STICKY_SESSION_BASE_SCORE, sessionId).then(function (res) {
                        log.info("stickSession:" + sessionId + ",res:" + res);
                    redis.zaddAsync(user_session_key, STICKY_SESSION_BASE_SCORE, sessionId).then(function (res) {
                        logger.info("stickSession:" + sessionId + ",res:" + res);
                        ModelUtil.emitOK(self.eventEmitter, {"status": 200, "msg": "置顶成功!"});
                    }).then(function () {
                        SessionRepo.saveStickySession(sessionId, user, Commons.STICKY_SESSION_BASE_SCORE);
                        SessionRepo.saveStickySession(sessionId, user, STICKY_SESSION_BASE_SCORE);
                    })
                } else {
                    //已有置顶的数据,取出来加1保存回去
                    scoreres = Number(scoreres) + 1;
                    redis.zaddAsync(user_session_key, scoreres, sessionId).then(function () {
                        log.info("stickSession:" + sessionId + ",res:" + res);
                        logger.info("stickSession:" + sessionId + ",res:" + res);
                        ModelUtil.emitOK(self.eventEmitter, {"status": 200, "msg": "置顶成功!"});
                    }).then(function () {
                        SessionRepo.saveStickySession(sessionId, user, scoreres);
@ -408,15 +417,15 @@ class Sessions extends RedisModel {
     * 取消会话置顶
     */
    cancelStickSession(sessionId, user) {
        let user_session_key = super.makeRedisKey(RedisKeys.UserSessions, user);
        let participants_key = super.makeRedisKey(RedisKeys.Participants, sessionId);
        let user_session_key = RedisModel.makeRedisKey(REDIS_KEYS.UserSessions, user);
        let participants_key = RedisModel.makeRedisKey(REDIS_KEYS.SessionParticipants, sessionId);
        let self = this;
        redis.zscoreAsync(participants_key, user).then(function (res) {
            if (!res) {
                res = new Date().getTime();
            }
            redis.zaddAsync(user_session_key, res, sessionId).then(function (res) {
                log.info("cancelStickSession:" + sessionId);
                logger.info("cancelStickSession:" + sessionId);
                ModelUtil.emitOK(self.eventEmitter, {"status": 200, "msg": "取消置顶成功!"});
            }).then(function () {
                SessionRepo.unstickSession(sessionId, user);

+ 86 - 77
src/server/models/sessions/topics.js

@ -4,19 +4,19 @@
"use strict";
let RedisClient = require('../../repository/redis/redis.client.js');
let redisClient = RedisClient.redisClient();
let redis = redisClient.connection;
let TopicsRepo = require('../../repository/mysql/topics.repo');
let RedisModel = require('./../redis.model.js');
let modelUtil = require('../../util/model.util');
let ModelUtil = require('../../util/model.util');
let Participants = require("./participants");
let Sessions = require("./sessions");
let log = require('../../util/log.js');
let TopicsRepo = require('../../repository/mysql/topics.repo');
let redis = RedisClient.redisClient().connection;
let configFile = require('../../include/commons').CONFIG_FILE;
let config = require('../../resources/config/' + configFile);
const RedisKey = require('../../include/commons').REDIS_KEYS;
const UserStatus = require('../../include/commons').SESSION_USER_STATUS;
const REDIS_KEYS = require('../../include/commons').REDIS_KEYS;
const SESSION_USER_STATUS = require('../../include/commons').SESSION_USER_STATUS;
class Topics extends RedisModel {
    constructor() {
@ -26,81 +26,90 @@ class Topics extends RedisModel {
    /**
     * 根据topicId获取对应的消息
     * @param topicId
     * @param page
     * @param size
     */
    getTopicMessages(topicId,page,pagesize) {
    getTopicMessages(topicId, page, size) {
        let self = this;
        let topic_key = super.makeRedisKey(RedisKey.Topic, topicId);
        let _super = super.makeRedisKey;
        let topic_key = RedisModel.makeRedisKey(REDIS_KEYS.Topic, topicId);
        let _super = RedisModel.makeRedisKey;
        redis.hgetallAsync(topic_key).then(function (topic) {
            let message_time_key = _super(RedisKey.MessagesByTimestamp, topic.session_id);
            let message_key = _super(RedisKey.Messages, topic.session_id);
            //倒序取出所有的消息ID
            let message_time_key = _super(REDIS_KEYS.MessagesByTimestamp, topic.session_id);
            let message_key = _super(REDIS_KEYS.Messages, topic.session_id);
            let create_time =topic.create_time;
            let end_time =topic.end_time;
            if(!end_time){
            //倒序取出所有的消息ID
            let create_time = topic.create_time;
            let end_time = topic.end_time;
            if (!end_time) {
                end_time = new Date().getTime();
            }
            redis.zrevrangebyscoreAsync(message_time_key,end_time,create_time).then(function (messages) {
            redis.zrevrangebyscoreAsync(message_time_key, end_time, create_time).then(function (messages) {
                //取出消息实例
                redis.hmgetAsync(message_key,messages).then(function(res){
                    modelUtil.emitOK(self.eventEmitter,res);
                })
                redis.hmgetAsync(message_key, messages).then(function (res) {
                    ModelUtil.emitOK(self.eventEmitter, res);
                });
            })
        })
    }
    /**
     *
     * @param topicName 发起议题的名称
     * @param topicId
     * @param patient 发起议题的患者
     * @param healthDoctor
     * @param doctor 参与的医生
     * @param messages 发送的消息对象{description:"",title:"",img:"",patient:"",patientName:""}图片多个用逗号隔开
     */
    createTopics(topicName,topicId,patient,healthDoctor,doctor,messages){
    createTopics(topicName, topicId, patient, healthDoctor, doctor, messages) {
        let self = this;
        
        //MUC模式中sessionID就是患者ID
        let topics_key  = super.makeRedisKey(RedisKey.Topics,patient);
        let topic_key  = super.makeRedisKey(RedisKey.Topic,topicId);
        let topics_key = RedisModel.makeRedisKey(REDIS_KEYS.Topics, patient);
        let topic_key = RedisModel.makeRedisKey(REDIS_KEYS.Topic, topicId);
        let sessions = new Sessions();
        let participants  = new Participants();
        let participants = new Participants();
        
        //从数据库中获取sessionId
        let date = new Date();
        redis.zaddAsync(topics_key, date.getTime(), topicId).then(function(res){
            redis.hmsetAsync(topic_key,"name",topicName,"end_by","","session_id",patient,"create_time",date.getTime(),"end_time","","description",messages.description).then(function(res){
                sessions.getSessions(patient,function(err,res){
        redis.zaddAsync(topics_key, date.getTime(), topicId).then(function (res) {
            redis.hmsetAsync(topic_key, "name", topicName, "end_by", "", "session_id", patient, "create_time", date.getTime(), "end_time", "", "description", messages.description).then(function (res) {
                sessions.getSessions(patient, function (err, res) {
                    //已经存在对应的会话更新全科为旁听
                    if(res&&res.length>0){
                        participants.updateUser(patient,doctor,UserStatus.OTHER);
                    if (res && res.length > 0) {
                        participants.updateUser(patient, doctor, SESSION_USER_STATUS.OTHER);
                        callbegin();
                    }else{//不存在创建SESSION
                        var users={};
                        users[patient]=UserStatus.ONLINE;
                        users[healthDoctor]=UserStatus.ONLINE;
                        users[doctor]=UserStatus.OTHER;
                        sessions.createSession(patient,messages.patientName,config.sessionConfig.MUC,JSON.stringify(users),function(res){
                            if(res){
                    } else {//不存在创建SESSION
                        var users = {};
                        users[patient] = SESSION_USER_STATUS.ONLINE;
                        users[healthDoctor] = SESSION_USER_STATUS.ONLINE;
                        users[doctor] = SESSION_USER_STATUS.OTHER;
                        sessions.createSession(patient, messages.patientName, config.sessionConfig.MUC, JSON.stringify(users), function (res) {
                            if (res) {
                                callbegin();
                            }
                        });
                    }
                })
            })
        })
        });
        
        /**
         * 开始消息发送
         */
        function callbegin(){
            let msg ={};
        function callbegin() {
            let msg = {};
            msg.senderId = messages.patient;
            msg.senderName = messages.patientName;
            msg.contentType = 6;
            msg.content ="开始咨询"
            msg.timestamp=date;
            sessions.saveMessageByTopic(msg,patient,function(err,msgId){
                if(err){
                    modelUtil.emitOK(self.eventEmitter,err);
                }else{
                    self.saveTopicsToSql(topicName,topicId,patient,msgId,date);
            msg.content = "开始咨询"
            msg.timestamp = date;
            sessions.saveMessageByTopic(msg, patient, function (err, msgId) {
                if (err) {
                    ModelUtil.emitOK(self.eventEmitter, err);
                } else {
                    self.saveTopicsToSql(topicName, topicId, patient, msgId, date);
                    callBeginMsg();
                }
            })
@ -109,36 +118,37 @@ class Topics extends RedisModel {
        /**
         * 发送求助内容
         */
        function callBeginMsg(){
            let msg ={};
        function callBeginMsg() {
            let msg = {};
            msg.senderId = messages.patient;
            msg.senderName = messages.patientName;
            msg.contentType = 1;
            msg.content =messages.description;
            msg.content = messages.description;
            msg.timestamp = new Date();
            sessions.saveMessageByTopic(msg,patient,function(err,msgId){
                log.info("begin send"+messages.description);
            })
            if(messages.img){
            sessions.saveMessageByTopic(msg, patient, function (err, msgId) {
                log.info("begin send" + messages.description);
            });
            
            if (messages.img) {
                let imgs = messages.img.split(",");
                for(var j in imgs){
                    let msgimg ={};
                for (var j in imgs) {
                    let msgimg = {};
                    msgimg.senderId = messages.patient;
                    msgimg.senderName = messages.patientName;
                    msgimg.contentType =2;
                    msgimg.content =imgs[j];
                    msgimg.contentType = 2;
                    msgimg.content = imgs[j];
                    msgimg.timestamp = new Date();
                    sessions.saveMessageByTopic(msgimg,patient,function(err,msgId){
                        log.info("begin send"+imgs[j]);
                    sessions.saveMessageByTopic(msgimg, patient, function (err, msgId) {
                        log.info("begin send" + imgs[j]);
                    })
                }
            }
            modelUtil.emitOK(self.eventEmitter,"创建成功!");
            ModelUtil.emitOK(self.eventEmitter, "创建成功!");
        }
    }
    saveTopicsToSql(topicName,topicId,sessionId,messageId,date){
        TopicsRepo.saveTopic(topicName,topicId,sessionId,messageId,date);
    saveTopicsToSql(topicName, topicId, sessionId, messageId, date) {
        TopicsRepo.saveTopic(topicName, topicId, sessionId, messageId, date);
    }
    /**
@ -146,38 +156,37 @@ class Topics extends RedisModel {
     * @param topicId
     * @param endUser
     */
    endTopic(topicId,endUser,endUserName){
    endTopic(topicId, endUser, endUserName) {
        let endDate = new Date();
        let self = this;
        let topic_key  = super.makeRedisKey(RedisKey.Topic,topicId);
        redis.hmsetAsync(topic_key,"end_time",endDate.getTime(),"end_by",endUser).then(function (res) {
            redis.hgetallAsync(topic_key).then(function(topic){
        let topic_key = RedisModel.makeRedisKey(REDIS_KEYS.Topic, topicId);
        redis.hmsetAsync(topic_key, "end_time", endDate.getTime(), "end_by", endUser).then(function (res) {
            redis.hgetallAsync(topic_key).then(function (topic) {
                callEnd(topic.session_id);
            })
        })
        });
        
        /**
         * 结束消息发送
         */
        function callEnd(sessionId){
            let msg ={};
        function callEnd(sessionId) {
            let msg = {};
            msg.senderId = endUser;
            msg.senderName = endUserName;
            msg.contentType = 7;
            msg.content =endUserName+"结束了咨询"
            msg.content = endUserName + "结束了咨询"
            msg.timestamp = new Date();
            let sessions = new Sessions();
            sessions.saveMessageByTopic(msg,sessionId,function(err,msgId){
                if(err){
                    modelUtil.emitOK(self.eventEmitter,err);
                }else{
                    modelUtil.emitOK(self.eventEmitter,"结束成功!");
                    TopicsRepo.endTopic(topicId,endUser,msg.date,msgId);
            sessions.saveMessageByTopic(msg, sessionId, function (err, msgId) {
                if (err) {
                    ModelUtil.emitOK(self.eventEmitter, err);
                } else {
                    ModelUtil.emitOK(self.eventEmitter, "结束成功!");
                    TopicsRepo.endTopic(topicId, endUser, msg.date, msgId);
                }
            })
        }
    }
}
// Expose class

+ 0 - 34
src/server/models/stats.js

@ -1,34 +0,0 @@
/**
 * 统计。
 */
"use strict";
let BaseModel = require('./base.model');
let statsRepo = require("../repository/mysql/stats.msg.repo.js");
let log = require("../util/log.js");
let modelUtil = require('../util/model.util');
class StatsMessage extends BaseModel{
    constructor(){
        super();
    }
    /**
     * 获取应用角标数,基于消息数量。
     * @param userId
     */
    getBadgeNumber(userId){
        let self = this;
        statsRepo.getBadgeNumber(userId, function (err, result) {
            if (err) {
                modelUtil.emitError(self.eventEmitter, "Get badge number failed: ", err);
                return;
            }
            let data = {userId: userId, badge: result};
            modelUtil.emitOK(self.eventEmitter, data);
        });
    }
}
module.exports = StatsMessage;

+ 1 - 1
src/server/models/user/patient.js

@ -165,7 +165,7 @@ class Patient extends RedisModel {
        }
        // 查询居民openid
        PatientRepo.getPatientOpenid(message.to, function (err, result) {
        PatientRepo.findPatientOpenId(message.to, function (err, result) {
            if (err) {
                ModelUtil.emitError(self.eventEmitter, "get patient openid failed", err);
                return;

+ 17 - 17
src/server/models/user/users.js

@ -77,7 +77,7 @@ class Users extends RedisModel {
     */
    getWechatStatus(userId) {
        let self = this;
        redisConn.hgetallAsync(self.makeRedisKey(REDIS_KEYS.UserWechatStatus, userId))
        redisConn.hgetallAsync(RedisModel.makeRedisKey(REDIS_KEYS.UserWechatStatus, userId))
            .then(function (res) {
                if (res) {
                    ModelUtil.emitOK(self, res);
@ -98,7 +98,7 @@ class Users extends RedisModel {
        async.waterfall([
            // get from redis
            function (callback) {
                let userStatusKey = self.makeRedisKey(REDIS_KEYS.UserStatus, userId);
                let userStatusKey = RedisModel.makeRedisKey(REDIS_KEYS.UserStatus, userId);
                redisConn.hgetallAsync(userStatusKey).then(function (res) {
                    if (res === null) {
                        callback(null);  // get from mysql
@ -134,7 +134,7 @@ class Users extends RedisModel {
     */
    updateAppStatus(userId, appInBg) {
        let self = this;
        let userStatusKey = self.makeRedisKey(REDIS_KEYS.UserAppStatus, userId);
        let userStatusKey = RedisModel.makeRedisKey(REDIS_KEYS.UserAppStatus, userId);
        redisConn.hgetAsync(userStatusKey, 'app_in_bg').then(function (res) {
            if (res !== null) {
                redisConn.hsetAsync(userStatusKey, 'app_in_bg', appInBg).then(function (res) {
@ -165,8 +165,8 @@ class Users extends RedisModel {
        let loginFromApp = platform !== PLATFORMS.Wechat;
        let usersKey = REDIS_KEYS.Users;
        let userKey = self.makeRedisKey(REDIS_KEYS.User, userId);
        let userStatusKey = self.makeRedisKey(loginFromApp ? REDIS_KEYS.UserAppStatus : REDIS_KEYS.UserWechatStatus, userId);
        let userKey = RedisModel.makeRedisKey(REDIS_KEYS.User, userId);
        let userStatusKey = RedisModel.makeRedisKey(loginFromApp ? REDIS_KEYS.UserAppStatus : REDIS_KEYS.UserWechatStatus, userId);
        let lastLoginTime = new Date();
        async.waterfall([
@ -184,7 +184,7 @@ class Users extends RedisModel {
                // cache user app/wechat status
                function (userInfo, callback) {
                    let multi = redisConn.multi()
                        .zadd(usersKey, lastLoginTime.getMilliseconds(), userId);
                        .zadd(usersKey, lastLoginTime.getTime(), userId);
                        //.hmset(userKey, 'avatar', userInfo.avatar ? userInfo.avatar : '', 'birthdate', userInfo.birthdate ? userInfo.birthdate : '',
                        //    'name', userInfo.name, 'role', loginFromApp ? 'doctor' : 'patient');
@ -213,19 +213,19 @@ class Users extends RedisModel {
                            (function (sessionId, userId) {
                                // cache sessions
                                redisConn.multi()
                                    .zadd(self.makeRedisKey(REDIS_KEYS.Sessions), lastLoginTime)                // 会话的最后活动时间设置为此用户的登录时间
                                    .zadd(self.makeRedisKey(REDIS_KEYS.UserSessions, userId), lastLoginTime)    // 会话的最后活动时间设置为此用户的登录时间
                                    .hmset(self.makeRedisKey(REDIS_KEYS.Session, sessionId, 'name', name, 'type', type, 'create_date', createDate))
                                    .zadd(RedisModel.makeRedisKey(REDIS_KEYS.Sessions), lastLoginTime)                // 会话的最后活动时间设置为此用户的登录时间
                                    .zadd(RedisModel.makeRedisKey(REDIS_KEYS.UserSessions, userId), lastLoginTime)    // 会话的最后活动时间设置为此用户的登录时间
                                    .hmset(RedisModel.makeRedisKey(REDIS_KEYS.Session, sessionId, 'name', name, 'type', type, 'create_date', createDate))
                                    .execAsync().then(function (res) {
                                    // cache participants
                                    let sessionParticipantsKey = self.makeRedisKey(REDIS_KEYS.Participants, sessionId);
                                    let sessionParticipantsRoleKey = self.makeRedisKey(REDIS_KEYS.ParticipantsRole, sessionId);
                                    let sessionParticipantsKey = RedisModel.makeRedisKey(REDIS_KEYS.SessionParticipants, sessionId);
                                    let sessionParticipantsRoleKey = RedisModel.makeRedisKey(REDIS_KEYS.SessionParticipantsRole, sessionId);
                                    ParticipantRepo.findParticipants(sessionId, function (err, participants) {
                                        for (let participant in participants) {
                                            let participantId = participant.participant_id;
                                            let participantRole = participant.participant_role;
                                            let score = new Date().getMilliseconds();
                                            let score = new Date().getTime();
                                            redisConn.multi()
                                                .zadd(sessionParticipantsKey, participantId, score)
@ -236,8 +236,8 @@ class Users extends RedisModel {
                                    });
                                    // cache messages
                                    let messagesKey = self.makeRedisKey(REDIS_KEYS.Messages, sessionId);
                                    let messagesByTimestampKey = self.makeRedisKey(REDIS_KEYS.MessagesByTimestamp, sessionId);
                                    let messagesKey = RedisModel.makeRedisKey(REDIS_KEYS.Messages, sessionId);
                                    let messagesByTimestampKey = RedisModel.makeRedisKey(REDIS_KEYS.MessagesByTimestamp, sessionId);
                                    MessageRepo.findBySessionId(sessionId, 0, config.sessionConfig.maxMessageCount, function (err, messages) {
                                        for (let message in messages) {
                                            let id = message.id;
@ -260,10 +260,10 @@ class Users extends RedisModel {
                                    });
                                    // cache topics for MUC
                                    let topicsKey = self.makeRedisKey(REDIS_KEYS.Topics, sessionId);
                                    let topicsKey = RedisModel.makeRedisKey(REDIS_KEYS.Topics, sessionId);
                                    TopicRepo.findAll(sessionId, function (err, topics) {
                                        for (let topic in topics) {
                                            let topicKey = self.makeRedisKey(REDIS_KEYS.Topic, topic.id);
                                            let topicKey = RedisModel.makeRedisKey(REDIS_KEYS.Topic, topic.id);
                                            let topicId = topic.id;
                                            let name = topic.name;
                                            let createTime = ObjectUtil.timestampToLong(topic.create_time);
@ -303,7 +303,7 @@ class Users extends RedisModel {
                },
                function (isPatient, callback) {
                    let usersKey = REDIS_KEYS.Users;
                    let userStatusKey = self.makeRedisKey(isPatient ? REDIS_KEYS.UserWechatStatus : REDIS_KEYS.UserAppStatus, userId);
                    let userStatusKey = RedisModel.makeRedisKey(isPatient ? REDIS_KEYS.UserWechatStatus : REDIS_KEYS.UserAppStatus, userId);
                    redisConn.multi()
                        .zrem(usersKey, userId)
                        .del(userStatusKey)

+ 4 - 12
src/server/repository/mysql/message.repo.js

@ -68,28 +68,20 @@ class MessageRepo {
    /**
     * 保存消息
     *
     * @param messages 消息对象
     * @param message 消息对象
     * @param sessionType 会话类型,1表示MUC会话,2表示P2P,3表示群会话,4表示临时讨论组
     * @param messageId
     * @param sessionId
     */
    static save(messages, sessionType, messageId, sessionId) {
    static save(message, sessionType, messageId, sessionId, handler) {
        var sql = "INSERT INTO " + DB_TABLES.sessionTypeToTableName(sessionType) +
            " (id, session_id, sender_id, sender_name,content_type, content, timestamp) VALUES (?,?,?,?,?,?,?)";
        ImDb.execQuery({
            "sql": sql,
            "args": [messageId, sessionId, messages.senderId, messages.senderName, messages.contentType, messages.content, messages.timestamp],
            "handler": function (err, res) {
                if (err) {
                    log.error("sql:" + sql + ",error:" + err + ",data:" + JSON.stringify(messages) + ",messageid:" + messageId + ",sessionId:" + sessionId);
                } else {
                    log.info("save message to mysql is success by session :" + sessionId);
                }
            }
            "args": [messageId, sessionId, message.sender_id, message.sender_name, message.content_type, message.content, message.timestamp],
            "handler": handler
        });
    }
}
module.exports = MessageRepo;

+ 22 - 44
src/server/repository/mysql/participant.repo.js

@ -40,16 +40,16 @@ class ParticipantRepo {
     * @param sessionId
     * @param handler
     */
    static updateParticipant(sessionId, participant_id,role) {
    static updateParticipant(sessionId, participant_id, role) {
        let sql = "update participants set participant_role = ? where session_id = ? and participant_id = ?";
        ImDb.execQuery({
            "sql": sql,
            "args": [role,sessionId,participant_id],
            "args": [role, sessionId, participant_id],
            "handler": function (err, res) {
                if (err) {
                    log.error("updateParticipant is fail error: " + err);
                }else{
                    log.info("updateParticipant is success" );
                } else {
                    log.info("updateParticipant is success");
                }
            }
        });
@ -65,21 +65,6 @@ class ParticipantRepo {
    static findSessionIdByParticipantIds(userId, anotherUserId, handler) {
        let sessionId = DbUtil.stringArrayHash([userId, anotherUserId]);
        handler(null, sessionId);
        /*let sql = "select session_id from " + DB_TABLES.Participants + " p1 ," + DB_TABLES.Participants + " p2 " +
            "where p1.session_id = p2.session_id and " +
            "((p1.participant_id = ? and p2.participant_id = ?) or (p1.participant_id = ? and p2.participant_id = ?))";
        ImDb.execQuery({
            "sql": sql,
            "args": [userId, anotherUserId, anotherUserId, userId],
            "handler": function (err, res) {
                if (err) {
                    log.error("getSessionIdByParticipants is fail error: " + err);
                }
                handler(err, res);
            }
        });*/
    }
    /**
@ -90,53 +75,46 @@ class ParticipantRepo {
     * @param handler
     */
    static existsParticipant(sessionId, userId, handler) {
        let sql = "select case when count(*) > 0 then True else False end as count from participants w where w.session_id =? and w.participant_id = ? ";
        let sql = "select case when count(*) > 0 then true else false end exist from participants w where w.session_id =? and w.participant_id = ? ";
        ImDb.execQuery({
            "sql": sql,
            "args": [sessionId, userId],
            "handler": function (err, res) {
                if (err) {
                    log.error("existsUser is fail error: " + err);
                }
                handler(err,res[0].count);
            }
            "handler": handler
        });
    }
    /**
     * mysql成员创建
     *
     * @param sessionId
     * @param users JSON
     * @param handler
     */
    static saveParticipantsToMysql(session_id, users) {
        let sql = "insert into " + DB_TABLES.Participants + " (session_id,participant_id,participant_role,receiving) VALUES "
    static saveParticipantsToMysql(sessionId, users, handler) {
        let sql = "insert into " + DB_TABLES.SessionParticipants + " (session_id,participant_id,participant_role) VALUES "
        let args = [];
        for (var j in users) {
            sql += "(?,?,?,?),";
            args.push(session_id);
            args.push(j);
            args.push(users[j]);
            args.push(0);
            sql += "(?,?,?)";
            let tokens = users[j].split(":");
            args.push(sessionId);
            args.push(tokens[0]);
            args.push(tokens.length > 1 ? tokens[1] : '0');
            if (j != users.length - 1) sql += ", ";
        }
        sql = sql.substring(0, sql.lastIndexOf(","));
        sql += " ON DUPLICATE KEY UPDATE participant_role = VALUES(participant_role)";
        ImDb.execQuery({
            "sql": sql,
            "args": args,
            "handler": function (err, res) {
                if (err) {
                    log.error("createParticipantsForMysql is fail error: " + err + ",session_id:" + session_id + ",users:" + JSON.stringify(users));
                } else {
                    return res;
                }
            }
            "handler": handler
        });
        return true;
    }
    static deleteUserFromMysql(sessionId, userId) {
        let sql = "delete from " + DB_TABLES.Participants + " where user_id=? and session_id=? ";
        let sql = "delete from " + DB_TABLES.SessionParticipants + " where user_id=? and session_id=? ";
        ImDb.execQuery({
            "sql": sql,
            "args": [userId, sessionId],

+ 2 - 2
src/server/repository/mysql/patient.repo.js

@ -20,8 +20,8 @@ class PatientRepo {
        });
    }
    static getPatientOpenid(code, handler) {
        var sql = "select openid from patients where code = ? ";
    static findPatientOpenId(code, handler) {
        var sql = "select openid from patients where id = ? ";
        ImDb.execQuery({
            "sql": sql,

+ 19 - 17
src/server/repository/mysql/session.repo.js

@ -19,7 +19,7 @@ class SessionRepo {
     * @param handler
     */
    static findOne(sessionId, handler) {
        let sessionSQL = "select id,name,type,create_date from " + DB_TABLES.Sessions + " s where s.id=?";
        let sessionSQL = "select id,name,type,create_date from " + DB_TABLES.Sessions + " s where s.id = ?";
        ImDb.execQuery({
            "sql": sessionSQL,
            "args": [sessionId],
@ -39,8 +39,9 @@ class SessionRepo {
     * @param userId
     * @param handler
     */
    static findAll(userId, handler) {
        let sql = "select session_id from " + DB_TABLES.Participants + " w where w.participant_id = ? group by w.session_id";
    static
    findAll(userId, handler) {
        let sql = "select session_id from " + DB_TABLES.SessionParticipants + " w where w.participant_id = ? group by w.session_id";
        let sessionSQL = "select id,name,type,create_date from " + DB_TABLES.Sessions + " s where s.id in(" + sql + ")";
        ImDb.execQuery({
            "sql": sessionSQL,
@ -63,8 +64,9 @@ class SessionRepo {
     * @param userId
     * @param handler
     */
    static findStickySessions(userId, handler) {
        let sql = "select session_id from " + DB_TABLES.Participants + " w where w.participant_id = ? group by w.session_id";
    static
    findStickySessions(userId, handler) {
        let sql = "select session_id from " + DB_TABLES.SessionParticipants + " w where w.participant_id = ? group by w.session_id";
        let sessionSQL = "select s.id,s.name,s.type,s.create_date from " + DB_TABLES.Sessions + " s," + DB_TABLES.StickySessions + " ss  where s.id = ss.session_id s.id in(" + sql + ")";
        ImDb.execQuery({
            "sql": sessionSQL,
@ -73,30 +75,28 @@ class SessionRepo {
                if (err) {
                    log.error("sql:" + sessionSQL + "data:userId:" + userId);
                }
                
                handler(err, res);
            }
        });
    }
    /**
     * 保存session。
     * 保存session。若会话重复创建,则更新会话名称。
     *
     * @param sessionId
     * @param name
     * @param type
     * @param createDate
     * @param handler
     */
    static saveSession(sessionId, name, type, createDate) {
        let sql = "insert into " + DB_TABLES.Sessions + " (id,name,type,create_date) VALUES (?,?,?,?) ";
    static saveSession(sessionId, name, type, createDate, handler) {
        let sql = "insert into " + DB_TABLES.Sessions + " (id, name, type, create_date) VALUES (?,?,?,?) " +
            "ON DUPLICATE KEY UPDATE name = ?";
        ImDb.execQuery({
            "sql": sql,
            "args": [sessionId, name, type, createDate],
            "handler": function (err, res) {
                if (err) {
                    log.error("sql:" + sql + "data:sessionId:" + sessionId + ",name:" + name + ",type:" + type + ",createDate:" + createDate);
                }
            }
            "args": [sessionId, name, type, createDate, name],
            "handler": handler
        });
    }
@ -107,7 +107,8 @@ class SessionRepo {
     * @param user
     * @param score
     */
    static saveStickySession(sessionId, user, score) {
    static
    saveStickySession(sessionId, user, score) {
        let sql = "insert into " + DB_TABLES.StickySessions + " (user_id,session_id,score) VALUES (?,?,?) ";
        ImDb.execQuery({
            "sql": sql,
@ -126,7 +127,8 @@ class SessionRepo {
     * @param sessionId
     * @param userId
     */
    static unstickSession(sessionId, userId) {
    static
    unstickSession(sessionId, userId) {
        let sql = "delete from " + DB_TABLES.StickySessions + " where user_id=? and session_id=? ";
        ImDb.execQuery({
            "sql": sql,

+ 1 - 5
src/server/resources/config/config.dev.js

@ -62,11 +62,7 @@ let sessionConfig = {
    maxMessageTimespan: 7 * 24 * 3600,      // 会话缓存的最大时间跨度
    expireTime: 3 * 60 * 60 * 1000,         // 会话过期时间,以毫秒计
    expireSessionCleanCount: 10,             // 每次清理多少个过期会话
    MUC:"1", //session模式配置
    P2P:"2",
    TEAM:"3"
    expireSessionCleanCount: 10              // 每次清理多少个过期会话
};
exports.app = 'IM.Server';

+ 1 - 2
src/server/resources/schema/ichat_1.2.8_table_schema.sql

@ -140,7 +140,6 @@ CREATE TABLE `participants`
	`session_id` VARCHAR(50) NOT NULL COMMENT '会话ID。ID结构:以患者ID+最大次数',
	`participant_id` VARCHAR(50) NOT NULL COMMENT '参与者ID',
	`participant_role` INTEGER COMMENT '参与者角色,MUC模式中的主持人/普通参与者',
	`receiving` TINYINT COMMENT '暂未使用',
	CONSTRAINT `PK_participants` PRIMARY KEY (`session_id`,`participant_id`)
) COMMENT='会话参与者'
;
@ -150,7 +149,7 @@ CREATE TABLE `sessions`
	`id` VARCHAR(50) NOT NULL COMMENT '会话标识。会话标识来源根据业务场景:1 医生间P2P会话使用随机生成的ID;2 医生间的群会话使用行政团队的ID;3 医生与患者间的咨询以患者的ID+当前咨询次数为ID',
	`name` VARCHAR(50) NOT NULL COMMENT '会话名称',
	`type` INTEGER NOT NULL COMMENT '会话类型,1表示MUC会话,2表示P2P,3表示群会话,4表示临时讨论组',
	`create_date` DATE NOT NULL COMMENT '创建时间',
	`create_date` DATETIME NOT NULL COMMENT '创建时间',
	CONSTRAINT `PK_sessions` PRIMARY KEY (`id`)
) COMMENT='会话'
;

+ 1 - 1
src/server/util/db.util.js

@ -73,7 +73,7 @@ class DbUtil {
     */
    static stringArrayHash(stringArray){
        let sortedArr = stringArray.sort();
        return crypto.createHash("sha256").update(sortedArr.join(","));
        return crypto.createHash("sha1").update(sortedArr.join(",")).digest('hex');
    }
}

+ 13 - 31
test/client/im.client.application.Test.js

@ -12,40 +12,22 @@ let imClient = require('../../src/client/im.client');
// 测试会话用的数据, test data
let TD = {
    MUC: {
        SessionId: "",
        Topics: [],
        DoctorA: "",
        DoctorB: "",
        Patient: ""
    },
    P2P: {
        SessionId: "",
        DoctorA: {
            id: "0de7295862dd11e69faffa163e8aee56",
            token: "0PFWlKmLBN9YzhCfFWVgYA",
            clientId: "H6FYbDejks6VjMmW3uH7V6",
            platform: 0
        },
        DoctorB: ""
    },
    Group: {
        SessionId: "",
        DoctorA: "",
        DoctorB: "",
        DoctorC: "",
        DoctorD: ""
    DoctorA: {
        id: "0de7295862dd11e69faffa163e8aee56"
    }
};
describe("IM SDK: Application Unit Test", function () {
    // Application API
    describe("Application API", function () {
        describe("get badge no", function () {
            it("return 200 when with valid user", function (done) {
// Application API
describe("Application API", function () {
    describe("getBadgeNo", function () {
        it("return success when with valid user", function (done) {
            imClient.Application.getBadgeNo(TD.DoctorA.id,
            function (data) {
                assert.ok(Object.keys(data).length > 0, "Do you forget finish the API?");
                done();
            },
            function (xhr, error, status) {
                assert.ok(false, xhr.responseJSON.message);
                done();
            });
        });

+ 0 - 31
test/client/im.client.health.Test.js

@ -1,31 +0,0 @@
/**
 * IM客户端单元测试。单元测试编写规则:至少对对每个接口执行正反例测试,若有条件可以增加接口的性能测试。
 *
 * @author sand
 * @since 2016/12/24
 */
"use strict";
var $ = require('jquery');
let assert = require('assert');
let imClient = require('../../src/client/im.client');
describe("IM SDK: health Unit Test", function () {
    // 健康API
    describe("Health API", function () {
        describe("health", function () {
            it("return 200 when system is all right", function (done) {
                done();
            });
        });
    });
    // 管理API
    describe("Management  API", function () {
        describe("db_status", function () {
            it("return 200 while database is ok", function (done) {
                done();
            });
        });
    });
});

+ 2 - 19
test/client/im.client.session.Test.js

@ -1,6 +1,8 @@
/**
 * IM客户端单元测试。单元测试编写规则:至少对对每个接口执行正反例测试,若有条件可以增加接口的性能测试。
 *
 * 此部分为群会话测试。
 *
 * @author sand
 * @since 2016/12/24
 */
@ -19,25 +21,6 @@ let TD = {
        DoctorA: "",
        DoctorB: "",
        Patient: ""
    },
    P2P: {
        SessionId: "",
        DoctorA: {
            id: "0de7295862dd11e69faffa163e8aee56",
            token: "0PFWlKmLBN9YzhCfFWVgYA",
            clientId: "H6FYbDejks6VjMmW3uH7V6",
            platform: 0
        },
        DoctorB: ""
    },
    Group: {
        SessionId: "",
        DoctorA: "",
        DoctorB: "",
        DoctorC: "",
        DoctorD: ""
    }
};

+ 36 - 0
test/client/im.client.session.muc.Test.js

@ -0,0 +1,36 @@
/**
 * IM客户端单元测试。单元测试编写规则:至少对对每个接口执行正反例测试,若有条件可以增加接口的性能测试。
 *
 * 此部分为MUC会话测试。
 *
 * @author sand
 * @since 2016/12/24
 */
"use strict";
var $ = require('jquery');
let assert = require('assert');
let imClient = require('../../src/client/im.client');
// 测试会话用的数据, test data
let TD = {
    MUC: {
        SessionId: "",
        Topics: [],
        DoctorA: "",
        DoctorB: "",
        Patient: ""
    }
};
describe("IM SDK Unit Test", function () {
    // 会话API
    describe("Session API", function () {
        describe("get sessions", function () {
            it("return 200 when user login yet.", function (done) {
                done();
            });
        });
    });
});

+ 165 - 0
test/client/im.client.session.p2p.Test.js

@ -0,0 +1,165 @@
/**
 * IM客户端单元测试。单元测试编写规则:至少对对每个接口执行正反例测试,若有条件可以增加接口的性能测试。
 *
 * 此部分为P2P会话测试。
 *
 * @author sand
 * @since 2016/12/24
 */
"use strict";
var $ = require('jquery');
let assert = require('assert');
let imClient = require('../../src/client/im.client');
// 测试会话用的数据, test data
let TD = {
    SessionId: 'b07e8c4eabcb0c6fe790843026424afb2fb64d80',
    UnreadMessageCount: 0,
    P2P: {
        DoctorA: {
            id: "cd92414c-5b06-11e6-8344-fa163e8aee56",
            name: "周美丽",
            token: "0PFWlKmLBN9YzhCfFWVgYA",
            clientId: "H6FYbDejks6VjMmW3uH7V6",
            platform: 0
        },
        DoctorB: {
            id: "cd919343-5b06-11e6-8344-fa163e8aee56",
            name: "李毅",
            token: "0PFWlKmLBN9YzhCfFWVgYA",
            clientId: "H6FYbDejks6VjMmW3uH7V6",
            platform: 0
        }
    }
};
/**
 * 会话API。测试逻辑:
 * A医生登录
 * B医生登录
 *
 * A医生向B医生发送3条消息
 * B医生获取会话列表
 * B医生获取与A医生的会话未读消息列表
 *
 * B医生向A医生发送5条消息
 * A医生获取会话列表
 * A医生获取与A医生的会话未读消息列表
 *
 * A医生退出
 * B医生退出
 */
describe("Session P2P", function () {
    // 登录
    describe("User login", function () {
        it("all user must be success", function (done) {
            imClient.Users.login(TD.P2P.DoctorA.id, TD.P2P.DoctorA.token, TD.P2P.DoctorA.clientId, TD.P2P.DoctorA.platform,
                function (data) {
                    assert.ok(Object.keys(data).length === 0, "Doctor A login failed.");
                },
                function (xhr, status, error) {
                    assert.ok(false, xhr.responseJSON.message);
                });
            imClient.Users.login(TD.P2P.DoctorB.id, TD.P2P.DoctorB.token, TD.P2P.DoctorB.clientId, TD.P2P.DoctorB.platform,
                function (data) {
                    assert.ok(Object.keys(data).length === 0, "Doctor B login failed.");
                    done();
                },
                function (xhr, status, error) {
                    assert.ok(false, xhr.responseJSON.message);
                    done();
                });
        });
    });
    // 发送消息: A -> B
    describe("Send message from A to B", function () {
        it("every message must be ok", function (done) {
            // 创建会话并发送消息
            imClient.Sessions.createP2pSession(TD.P2P.DoctorA.id, TD.P2P.DoctorB.id,
                function (session) {
                    assert.ok(session.id.length > 0, "Create session failed.");
                    TD.SessionId = session.id;
                    imClient.Sessions.sendMessage(session.id, TD.P2P.DoctorA.id, TD.P2P.DoctorA.name, "李医生,你好", 1,
                        function (data) {
                            assert.ok(Object.keys(data).length > 0, "Send message failed.");
                        }, function (xhr, status, error) {
                            assert.ok(false, xhr.responseJSON.message)
                        });
                    imClient.Sessions.sendMessage(session.id, TD.P2P.DoctorA.id, TD.P2P.DoctorA.name, "莲前社区糖尿病患者已进入随访跟踪状态", 1,
                        function (data) {
                            assert.ok(Object.keys(data).length > 0, "Send message failed.");
                        }, function (xhr, status, error) {
                            assert.ok(false, xhr.responseJSON.message)
                        });
                    imClient.Sessions.sendMessage(session.id, TD.P2P.DoctorA.id, TD.P2P.DoctorA.name, "但处方信息还需要您的确认,请尽快回复,谢谢!", 1,
                        function (data) {
                            assert.ok(Object.keys(data).length > 0, "Send message failed.");
                            done();
                        }, function (xhr, status, error) {
                            assert.ok(false, xhr.responseJSON.message)
                        });
                },
                function (xhr, status, error) {
                    assert.ok(false, xhr.responseJSON.message);
                    done();
                });
        });
    });
    // 获取并发送消息: B -> A
    describe("Send message from B to A", function () {
        it("every message must be ok", function (done) {
            imClient.Sessions.getSessionsWithDoctor(TD.P2P.DoctorB.id, 0, 10,
                function (sessions) {
                    let isPass = false;
                    for (let i in sessions) {
                        if (sessions[i].id == TD.SessionId) {
                            isPass = true;
                            TD.UnreadMessageCount = sessions[i].unread_count;
                            break;
                        }
                    }
                    assert.ok(isPass, "Get sessions with doctor failed.");
                    // 读取未读消息数
                    imClient.Sessions.getSessionUnreadMessageCount(TD.SessionId, TD.P2P.DoctorB,
                        function (data) {
                            assert.strictEqual(data.count, TD.UnreadMessageCount, "Get unread message count failed.");
                        },
                        function (xhr, status, error) {
                        });
                },
                function (xhr, status, error) {
                    assert.ok(false, xhr.responseJSON.message);
                    done();
                });
        });
    });
    // 退出
    describe("User logout", function () {
        it("all user must be success", function (done) {
            imClient.Users.logout(TD.P2P.DoctorA.id, function (data) {
                assert.ok(Object.keys(data).length === 0, "Doctor A logout failed.");
            }, function (xhr, status, error) {
                assert.ok(false, xhr.responseJSON.message);
            });
            imClient.Users.logout(TD.P2P.DoctorB.id, function (data) {
                assert.ok(Object.keys(data).length === 0, "Doctor B logout failed.");
                done();
            }, function (xhr, status, error) {
                assert.ok(false, xhr.responseJSON.message);
                done();
            });
        });
    });
});

+ 1 - 1
test/client/im.client.user.Test.js

@ -13,7 +13,7 @@ let imClient = require('../../src/client/im.client');
// 测试会话用的数据, test data
let TD = {
    DoctorA: {
        id: "0de7295862dd11e69faffa163e8aee56",
        id: "cd92414c-5b06-11e6-8344-fa163e8aee56", // 周美丽医生
        token: "0PFWlKmLBN9YzhCfFWVgYA",
        clientId: "H6FYbDejks6VjMmW3uH7V6",
        platform: 0